source: trunk/examples/new_rack_pbsmon.py.in @ 363

Last change on this file since 363 was 363, checked in by martijk, 6 years ago

use system python

  • Property svn:executable set to *
File size: 20.2 KB
Line 
1#! /usr/bin/env python
2#
3# This version of pbsmon is base on the new_rack_pbsmon.py
4#
5# Authors:
6#   Bas van der Vlies
7#   Dennis Stam
8#
9#      SVN Info:
10#              $Id: new_rack_pbsmon.py 361 2018-04-12 11:27:11Z martijk $
11#              $URL$
12#
13
14"""
15specifying hostnames:
16  To specify a range use the [] to indicate a range, a couple of examples:
17
18  The first five nodes of rack 16
19      - gb-r16n[1-5]
20
21  The first five nodes and node 12 and 18 of rack 16 to 20
22      - gb-r[16-20]n[1-5,12,18]
23
24  The first five nodes de in rack 16 with padding enabled
25      - gb-r[16]n[01-5]
26
27The ranges ([]) are not only limited to numbers, letters can also be used.
28"""
29from __future__ import print_function
30import sys
31import re
32import re
33import types
34
35import pbs
36from PBSQuery import PBSQuery
37from PBSQuery import PBSError
38
39## This RE pattern is used for the hostrange
40HOSTRANGE = r'\[([0-9az\-,]+)\]'
41
42# Remark: When both are True, extended view is being printed
43PRINT_TABLE = True
44PRINT_EXTENDED = False
45
46# Which nodes must be skipped
47EXCLUDE_NODES = [ 'login' ]
48
49# Some global OPTS
50OPT_SKIP_EMPTY_RACKS = True
51OPT_SERVERNAME = None
52
53## Begin: TABLE view opts
54
55# A node has the following syntax gb-r10n10
56#  r10 is rack name -> skip one char --> gives us rack number = 10
57#  n10 is node name -> skip one char --> gives us node number = 10
58# Then we have to set these variables to determine automatically the
59# number of nodes and racks
60#
61NODE_EXPR = "r(?P<racknr>[0-9]+)n(?P<nodenr>[0-9]+)" 
62
63START_RACK = 1 
64
65## End: TABLE view opts
66
67## Begin: EXTENDED view opts
68
69LENGTH_NODE  = 0
70LENGTH_STATE = 0
71
72EXTENDED_PATTERNS = {
73    'header' : ' %-*s | %-*s | %s', 
74    'row': ' %-*s | %-*s | %s',
75    'line': ' %s',
76    'line_char': '-',
77}
78
79## End: EXTENDED view opts
80
81pbs_ND_single           = 'job (single)'
82pbs_ND_total            = 'total'
83pbs_ND_free_serial      = 'free serial'
84pbs_ND_free_parallel    = 'free parallel'
85
86PBS_STATES = {
87    pbs.ND_free             : '_',
88    pbs.ND_down             : 'X',
89    pbs.ND_offline          : '.',
90    pbs.ND_reserve          : 'R',
91    pbs.ND_job_exclusive    : 'J',
92    pbs.ND_job_sharing      : 'S',
93    pbs.ND_busy             : '*',
94    pbs.ND_state_unknown    : '?',
95    pbs.ND_timeshared       : 'T',
96    pbs.ND_cluster          : 'C',
97    pbs_ND_single           : 'j',
98    pbs_ND_free_serial      : '_',
99    pbs_ND_free_parallel    : '_',
100    pbs_ND_total            : ' '
101}
102
103## Color support?
104import curses
105curses.setupterm()
106if curses.tigetnum("colors") > 2:
107    TERMINAL_COLOR=True
108else:
109    TERMINAL_COLOR=False
110
111####
112## Rewriting the print function, so it will work with all versions of Python
113def _print(*args, **kwargs):
114    '''A wrapper function to make the functionality for the print function the same for Python2.4 and higher'''
115    ## First try if we are running in Python3 and higher
116    try:
117        Print = eval('print')
118        Print(*args, **kwargs)
119    except SyntaxError:
120        ## Then Python2.6 and Python2.7
121        try:
122            D = dict()
123            exec('from __future__ import print_function\np=print', D)
124            D['p'](*args, **kwargs)
125            del D
126        ## Finally Python2.5 or lower
127        except SyntaxError:
128            del D
129            fout    = kwargs.get('file', sys.stdout)
130            write   = fout.write
131            if args:
132                write(str(args[0]))
133                sep = kwargs.get('sep', ' ')
134                for arg in args[1:]:
135                    write(sep)
136                    write(str(a))
137                write(kwargs.get('end', '\n'))
138
139class color:
140    PURPLE = '\033[95m'
141    CYAN = '\033[96m'
142    DARKCYAN = '\033[36m'
143    BLUE = '\033[94m'
144    GREEN = '\033[92m'
145    YELLOW = '\033[93m'
146    RED = '\033[91m'
147    BOLD = '\033[1m'
148    UNDERLINE = '\033[4m'
149    END = '\033[0m'
150
151def compare_lists(list_a, list_b):
152    return not any(x not in list_b for x in list_a)
153
154## Import argparse here, as I need the _print function
155try:
156    import argparse
157except ImportError:
158    _print('Cannot find argparse module', file=sys.stderr)
159    sys.exit(1)
160
161####
162## BEGIN functions for hostrange parsing
163def l_range(start, end):
164    '''The equivalent for the range function, but then with letters, uses the ord function'''
165    start = ord(start)
166    end   = ord(end)
167    rlist = list()
168
169    ## A ord number must be between 96 (a == 97) and 122 (z == 122)
170    if start < 96 or start > 122 and end < 96 or end > 122:
171        raise Exception('You can only use letters a to z')
172    ## If start is greater then end, then the range is invalid
173    elif start > end:
174        raise Exception('The first letter must be smaller then the second one')
175    ## Just revert the ord number to the char
176    for letter in range(start, end + 1):
177        rlist.append(chr(letter))
178    return rlist
179
180def return_range(string):
181    '''This function will return the possible values for the given ranges'''
182
183    ## First check if the first char is valid
184    if string.startswith(',') or string.startswith('-'):
185        raise Exception('Given pattern is invalid, you can\'t use , and - at the beginning')
186
187    numbers_chars        = list()
188    equal_width_length  = 0
189
190    ## First splitup the sections (divided by ,)
191    for section in string.split(','):
192        ## Within a section you can have a range, max two values
193        chars = section.split('-')
194        if len(chars) == 2:
195            ## When range is a digit, simply use the range function
196            if chars[0].isdigit() and chars[1].isdigit():
197                ## Owke, check for equal_width_length
198                if chars[0][0] == '0' or chars[1][0] == '0':
199                    if len(chars[0]) >= len(chars[1]):
200                        equal_width_length = len(chars[0])
201                    else:
202                        equal_width_length = len(chars[1])
203                ## Don't forget the +1
204                numbers_chars += range(int(chars[0]), int(chars[1])+1)
205            ## If one of the two is a digit, raise an exceptio
206            elif chars[0].isdigit() or chars[1].isdigit():
207                raise Exception('I can\'t combine integers with letters, change your range please')
208            ## Else use the l_range
209            else:
210                numbers_chars += l_range(chars[0], chars[1])
211        else:
212            ## If the value of the section is a integer value, check if it has a 0
213            if section.isdigit() and section[0] == '0':
214                if len(section) > equal_width_length:
215                    equal_width_length = len(section)
216            numbers_chars.append(section)
217
218        ## if the equal_width length is greater then 0, rebuild the list
219        ## 01, 02, 03, ... 10
220        if equal_width_length > 0:
221            tmp_list = list()
222            for number_char in numbers_chars:
223                if type(number_char) is types.IntType or number_char.isdigit():
224                    tmp_list.append('%0*d' % ( equal_width_length, int(number_char)))
225                else:
226                    tmp_list.append(number_char)
227            numbers_chars = tmp_list
228
229    return numbers_chars
230
231def product(*args, **kwargs):
232    '''Taken from the python docs, does the same as itertools.product,
233    but this also works for py2.5'''
234    pools = map(tuple, args) * kwargs.get('repeat', 1)
235    result = [[]]
236    for pool in pools:
237        result = [x+[y] for x in result for y in pool]
238    for prod in result:
239        yield tuple(prod)
240
241def parse_args(args):
242    rlist = list()
243    for arg in args:
244        parts = re.findall(HOSTRANGE, arg)
245        if parts:
246            ## create a formatter string, sub the matched patternn with %s
247            string_format = re.sub(HOSTRANGE, '%s', arg)
248            ranges = list()
249
250            ## detect the ranges in the parts
251            for part in parts:
252                ranges.append(return_range(part))
253           
254            ## produce the hostnames
255            for combination in product(*ranges):
256                rlist.append(string_format % combination)
257        else:
258            rlist.append(arg)
259    return rlist
260
261## END functions for hostrange parsing
262####
263
264def sanitize_jobs( jobs ):
265
266    ljobs = list()
267
268    for job in jobs:
269        ljobs.extend( re.findall( r'[0-9]+\/([0-9]+)\.*.', job ) )
270
271    return list( set( ljobs ) )
272
273def parse_nodename( nodename ):
274    global NODE_EXPR
275
276    parts = re.search( r'%s' % NODE_EXPR, nodename, re.VERBOSE )
277
278    try:
279        racknr = parts.group( 'racknr' )
280    except Exception:
281                racknr = 0
282
283    try:
284        nodenr = parts.group( 'nodenr' )
285    except Exception:
286        nodenr = 0
287
288    return int( racknr ), int( nodenr )
289
290def get_nodes( racknode=False, hosts=None ):
291    global LENGTH_NODE
292    global LENGTH_STATE
293    global OPT_SERVERNAME
294
295    nodes_dict = dict()
296
297    try:
298        if not OPT_SERVERNAME:
299            p = PBSQuery()
300        else:
301            p = PBSQuery( OPT_SERVERNAME )
302    except PBSError, reason:
303        _print('Error: %s' % reason)
304        sys.exit( -1 )
305
306    p.new_data_structure()
307
308    attr = [ 'state', 'jobs', 'properties', 'jobs' ]
309
310    try:
311        nodes = p.getnodes( attr )
312    except PBSError, reason:
313        _print('Error: %s' % reason)
314        sys.exit( -1 )
315
316    number_of_racks = 0
317    nodes_per_rack = 0
318    hosts_list = list()
319   
320    for node, attr in nodes.items():
321        if node in EXCLUDE_NODES:
322            continue
323
324        if hosts and node not in hosts:
325            continue
326
327        if pbs.ND_down in attr.state:
328            state = pbs.ND_down
329        else:
330            state = attr.state[ 0 ]
331
332        state_char = PBS_STATES[ state ]
333
334        if attr.is_free() and attr.has_job():
335            state = pbs.ND_busy
336            state_char = PBS_STATES[ pbs_ND_single ]
337
338        if not nodes_dict.has_key( node ):
339            nodes_dict[ node ] = dict()
340
341        # Setting the longest lenght
342        if len( node ) > LENGTH_NODE:
343            LENGTH_NODE = len( node )
344
345        if len( state ) > LENGTH_STATE:
346            LENGTH_STATE = len( state )
347
348        if racknode:
349            racknr, nodenr = parse_nodename( node )
350
351            if racknr > number_of_racks:
352                number_of_racks = racknr
353
354            if nodenr > nodes_per_rack:
355                nodes_per_rack = nodenr
356           
357            if not nodes_dict.has_key( racknr ):
358                nodes_dict[ racknr ] = dict()
359
360            if not nodes_dict[ racknr ].has_key( nodenr ):
361                nodes_dict[ racknr ][ nodenr ] = dict()
362
363            nodes_dict[ racknr ][ nodenr ][ 'state_char' ] = state_char
364            nodes_dict[ racknr ][ nodenr ][ 'state' ] = state
365       
366            if attr.has_key( 'jobs' ):
367                nodes_dict[ racknr ][ nodenr ][ 'jobs' ] = sanitize_jobs( attr.jobs )
368            else:
369                nodes_dict[ racknr ][ nodenr ][ 'jobs' ] = []
370
371            if attr.has_key( 'properties' ):
372                nodes_dict[ racknr ][ nodenr ][ 'properties' ] = attr.properties
373            else:
374                nodes_dict[ racknr ][ nodenr ][ 'properties' ] = []
375
376        else:
377            hosts_list.append( node )
378            nodes_dict[ node ][ 'state_char' ] = state_char
379            nodes_dict[ node ][ 'state' ] = state
380       
381            if attr.has_key( 'jobs' ):
382                nodes_dict[ node ][ 'jobs' ] = sanitize_jobs( attr.jobs )
383            else:
384                nodes_dict[ node ][ 'jobs' ] = []
385
386            if attr.has_key( 'properties' ):
387                nodes_dict[ node ][ 'properties' ] = attr.properties
388            else:
389                nodes_dict[ node ][ 'properties' ] = []
390
391    if not racknode:
392        return nodes_dict, hosts_list
393
394    return nodes_dict, number_of_racks, nodes_per_rack
395
396def _generate_index( str ):
397    index = []
398
399    def _append( fragment, alist=index ):
400        if fragment.isdigit():
401            fragment = int( fragment )
402        alist.append( fragment )
403
404    prev_isdigit = str[0].isdigit()
405    current_fragment = ''
406
407    for char in str:
408        curr_isdigit = char.isdigit()
409
410        if curr_isdigit == prev_isdigit:
411            current_fragment += char
412        else:
413            _append( current_fragment )
414            current_fragment = char
415            prev_isdigit = curr_isdigit
416
417    _append( current_fragment )
418
419    return tuple( index )
420
421def real_sort( inlist ):
422    indices = map(_generate_index, inlist )
423    decorated = zip( indices, inlist )
424    decorated.sort()
425
426    return [ item for index, item in decorated ]
427
428def print_table(properties=None, jobs=None):
429    global START_RACK
430    global OPT_SKIP_EMPTY_RACKS
431
432    nodes, racknr, nodenr = get_nodes( True )
433
434    ## Code herebelow has been taken from the new_rack_pbsmon.py
435    save_column = None
436   
437    _print()   
438    _print('  ', end=' ')
439    for rack in xrange( START_RACK, racknr + 1 ):
440       
441        if not ( rack % 10 ):
442            char = '%d' % ( rack / 10 )
443            save_column = char
444        else:
445            char = ' '
446
447        if OPT_SKIP_EMPTY_RACKS:
448            if nodes.has_key( rack ):
449                if save_column:
450                    char = save_column
451                    save_column = None
452                _print(char, end=' ')
453        else:
454            _print(char, end=' ')
455    _print()   
456
457    _print('  ', end=' ')
458    for rack in xrange( START_RACK, racknr + 1 ):
459       
460        char = rack % 10
461        if OPT_SKIP_EMPTY_RACKS:
462            if nodes.has_key( rack ):
463                _print(char, end=' ')
464        else:
465            _print(char, end=' ')
466    _print()
467
468    for node in xrange( 1, nodenr + 1 ):
469        _print('%2d' % node, end=' ')
470
471        for rack in xrange( START_RACK, racknr + 1 ):
472            if OPT_SKIP_EMPTY_RACKS:
473                if not nodes.has_key( rack ):
474                    continue
475            try:
476                if properties and compare_lists(properties,nodes[ rack ][ node ]['properties']):
477                    prop_color = True
478                else:
479                    prop_color = False
480
481                if jobs and compare_lists(jobs, nodes[ rack ][ node ]['jobs']):
482                    job_color = True
483                else:
484                    job_color = False
485
486                if prop_color or job_color:
487                    if TERMINAL_COLOR and prop_color and job_color:
488                        _print(color.GREEN + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
489                    elif TERMINAL_COLOR and  prop_color:
490                        _print(color.BLUE + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
491                    elif TERMINAL_COLOR and  job_color:
492                        _print(color.YELLOW + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
493                    else:
494                        _print('M', end=' ')
495                else:
496                    _print(nodes[ rack ][ node ][ 'state_char' ], end=' ')
497            except KeyError:
498                _print(' ', end=' ')
499        _print()
500    _print()
501
502def print_table_summary():
503    global PBS_STATES
504    global OPT_SERVERNAME
505
506    try:
507        if not OPT_SERVERNAME:
508            p = PBSQuery()
509        else:
510            p = PBSQuery( OPT_SERVERNAME )
511    except PBSError, reason:
512        _print('error: %s' % reason)
513        sys.exit(-1)
514
515    # get the state of the nodes
516    attr = [ 'state', 'jobs', 'properties' ]
517    try:
518        nodes = p.getnodes(attr)
519    except PBSError, reason:
520        _print('error: %s' % reason)
521        sys.exit(-1)
522
523    node_dict = {}
524
525    count_states = {}
526    for key in PBS_STATES.keys():
527        count_states[key] = 0
528
529    for nodename, node in nodes.items():
530
531        # Skip login nodes in status display
532        #
533        if not nodename.find('login'):
534            continue
535
536        state = node['state'][ 0 ]
537
538        state_char = PBS_STATES[state]
539        count_states[state] += 1
540        count_states[pbs_ND_total] += 1
541
542        if node.is_free():                          # can happen for single CPU jobs
543            if node.has_job():
544#               _print('TD: %s' % nodename, node)
545                state_char = PBS_STATES[pbs_ND_single]
546                count_states[pbs.ND_free] -=  1
547                count_states[pbs_ND_single] += 1
548            else:
549                if  'infiniband' in node['properties']:
550                    count_states[pbs_ND_free_parallel] +=  1 
551                elif  'ifiniband' in node['properties']:
552                    count_states[pbs_ND_free_serial] +=  1 
553                #else:
554                #   count_states[pbs_ND_free_serial] +=  1
555               
556#       print_('TD: %s %s' % (nodename, state_char))
557        dummy = nodename.split('-')
558        if len( dummy ) > 1:
559            node_dict[dummy[1]] = state_char
560        else:
561            node_dict[dummy[0]] = state_char
562
563    legend = PBS_STATES.keys()
564    legend.sort()
565
566    n = 0
567    for state in legend:
568        _print(%s  %-13s : %-5d' % (PBS_STATES[state], state, count_states[state]), end=' ')
569
570        n = n + 1
571        if not (n & 1):
572            _print()
573 
574    if TERMINAL_COLOR:
575        _print()
576        _print('Colors has been enabled for your terminal:')
577        _print(' - ' + color.YELLOW + 'Matched jobs' + color.END)
578        _print(' - ' + color.BLUE + 'Matched properties' + color.END)
579        _print(' - ' + color.GREEN + 'Matched jobs and properties' + color.END)
580
581def print_extended(hosts=None, properties=None, jobs=None):
582    global LENGTH_NODE
583    global LENGTH_STATE
584    global EXTENDED_PATTERNS
585   
586    nodes, ihosts = get_nodes( hosts=hosts )
587    row_header = EXTENDED_PATTERNS[ 'header' ] % ( ( LENGTH_NODE + 2 ), 'Node', ( LENGTH_STATE + 2 ), 'State', 'Jobs' )
588    LENGTH_ROW = len( row_header )
589
590    rows_str = list()
591    ihosts = real_sort( ihosts )
592    for node in ihosts:
593        attr = nodes[ node ]
594
595        if jobs and not compare_lists(jobs, attr['jobs']):
596            continue
597
598        if properties and not compare_lists(properties, attr['properties']):
599            continue
600
601        row_str = EXTENDED_PATTERNS[ 'row' ] % ( ( LENGTH_NODE + 2 ), node, ( LENGTH_STATE + 2 ), attr[ 'state' ], ','.join( attr[ 'jobs' ] ) )
602
603        if len( row_str ) > LENGTH_ROW:
604            LENGTH_ROW = len( row_str )
605
606        rows_str.append( row_str )
607
608    _print()
609    _print(row_header)
610    _print(EXTENDED_PATTERNS[ 'line' ] % ( EXTENDED_PATTERNS[ 'line_char' ] * LENGTH_ROW ))
611    _print('\n'.join( rows_str ))
612    _print()
613
614if __name__ == '__main__':
615   
616    parser = argparse.ArgumentParser(
617        formatter_class=argparse.RawDescriptionHelpFormatter,
618        description=__doc__,
619    )
620
621    parser.add_argument('nodes', metavar='NODES', nargs='*', type=str)
622    parser.add_argument("-t", "--table", dest="table", action="store_true", help="Show an table", default=PRINT_TABLE )
623    parser.add_argument("-l", "--list", dest="extended", action="store_true", help="Show node rows with state and jobinfo", default=PRINT_EXTENDED )
624    parser.add_argument("-s", "--summary", dest="summary", action="store_true", help="Display a short summary", default=False )
625    parser.add_argument("-a", "--all", dest="summary", action="store_true", help="Display a short summary" )
626    parser.add_argument("-w", "--wide", dest="wide", action="store_true", help="Wide display for node status ( only when -t is used )" )
627    parser.add_argument("-S", "--servername", dest="servername", help="Change the default servername", default=None )
628    parser.add_argument("-p", "--properties", dest="properties", help="Show nodes with property, you can use more than 1 property by using , (this is always een and) ie. -p infiniband,mem64gb", default=None)
629    parser.add_argument("-j", "--job", dest="jobs", help="Show which nodes are uses by a job", default=None)
630    parser.add_argument('--version', action='version', version=pbs.version)
631
632    args = parser.parse_args()
633
634    if args.nodes:
635        args.nodes = parse_args(args.nodes)
636
637    if args.servername:
638        OPT_SERVERNAME = args.servername
639
640    if args.wide:
641        OPT_SKIP_EMPTY_RACKS = False
642
643    if args.nodes:
644        args.extended = True
645
646    if args.extended and PRINT_TABLE:
647        args.table = False
648
649    if args.table and PRINT_EXTENDED:
650        args.extended = False
651
652    if args.properties:
653        args.properties = [ item.strip() for item in args.properties.split(',') ]
654
655    if args.jobs:
656        args.jobs = [ item.strip() for item in args.jobs.split(',') ]
657
658    if args.extended:
659        print_extended(args.nodes, args.properties, args.jobs) 
660    elif args.table:
661        print_table(args.properties, args.jobs)
662    else:
663        _print('Something is wrong, bye!', file=sys.stderr)
664        sys.exit( -1 )
665
666    if args.summary:
667        print_table_summary()
Note: See TracBrowser for help on using the repository browser.