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

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

removed rewritten print function

  • Property svn:executable set to *
File size: 19.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
111class color:
112    PURPLE = '\033[95m'
113    CYAN = '\033[96m'
114    DARKCYAN = '\033[36m'
115    BLUE = '\033[94m'
116    GREEN = '\033[92m'
117    YELLOW = '\033[93m'
118    RED = '\033[91m'
119    BOLD = '\033[1m'
120    UNDERLINE = '\033[4m'
121    END = '\033[0m'
122
123def compare_lists(list_a, list_b):
124    return not any(x not in list_b for x in list_a)
125
126## Import argparse here, as I need the print function
127try:
128    import argparse
129except ImportError:
130    print('Cannot find argparse module', file=sys.stderr)
131    sys.exit(1)
132
133####
134## BEGIN functions for hostrange parsing
135def l_range(start, end):
136    '''The equivalent for the range function, but then with letters, uses the ord function'''
137    start = ord(start)
138    end   = ord(end)
139    rlist = list()
140
141    ## A ord number must be between 96 (a == 97) and 122 (z == 122)
142    if start < 96 or start > 122 and end < 96 or end > 122:
143        raise Exception('You can only use letters a to z')
144    ## If start is greater then end, then the range is invalid
145    elif start > end:
146        raise Exception('The first letter must be smaller then the second one')
147    ## Just revert the ord number to the char
148    for letter in range(start, end + 1):
149        rlist.append(chr(letter))
150    return rlist
151
152def return_range(string):
153    '''This function will return the possible values for the given ranges'''
154
155    ## First check if the first char is valid
156    if string.startswith(',') or string.startswith('-'):
157        raise Exception('Given pattern is invalid, you can\'t use , and - at the beginning')
158
159    numbers_chars        = list()
160    equal_width_length  = 0
161
162    ## First splitup the sections (divided by ,)
163    for section in string.split(','):
164        ## Within a section you can have a range, max two values
165        chars = section.split('-')
166        if len(chars) == 2:
167            ## When range is a digit, simply use the range function
168            if chars[0].isdigit() and chars[1].isdigit():
169                ## Owke, check for equal_width_length
170                if chars[0][0] == '0' or chars[1][0] == '0':
171                    if len(chars[0]) >= len(chars[1]):
172                        equal_width_length = len(chars[0])
173                    else:
174                        equal_width_length = len(chars[1])
175                ## Don't forget the +1
176                numbers_chars += range(int(chars[0]), int(chars[1])+1)
177            ## If one of the two is a digit, raise an exceptio
178            elif chars[0].isdigit() or chars[1].isdigit():
179                raise Exception('I can\'t combine integers with letters, change your range please')
180            ## Else use the l_range
181            else:
182                numbers_chars += l_range(chars[0], chars[1])
183        else:
184            ## If the value of the section is a integer value, check if it has a 0
185            if section.isdigit() and section[0] == '0':
186                if len(section) > equal_width_length:
187                    equal_width_length = len(section)
188            numbers_chars.append(section)
189
190        ## if the equal_width length is greater then 0, rebuild the list
191        ## 01, 02, 03, ... 10
192        if equal_width_length > 0:
193            tmp_list = list()
194            for number_char in numbers_chars:
195                if type(number_char) is types.IntType or number_char.isdigit():
196                    tmp_list.append('%0*d' % ( equal_width_length, int(number_char)))
197                else:
198                    tmp_list.append(number_char)
199            numbers_chars = tmp_list
200
201    return numbers_chars
202
203def product(*args, **kwargs):
204    '''Taken from the python docs, does the same as itertools.product,
205    but this also works for py2.5'''
206    pools = map(tuple, args) * kwargs.get('repeat', 1)
207    result = [[]]
208    for pool in pools:
209        result = [x+[y] for x in result for y in pool]
210    for prod in result:
211        yield tuple(prod)
212
213def parse_args(args):
214    rlist = list()
215    for arg in args:
216        parts = re.findall(HOSTRANGE, arg)
217        if parts:
218            ## create a formatter string, sub the matched patternn with %s
219            string_format = re.sub(HOSTRANGE, '%s', arg)
220            ranges = list()
221
222            ## detect the ranges in the parts
223            for part in parts:
224                ranges.append(return_range(part))
225           
226            ## produce the hostnames
227            for combination in product(*ranges):
228                rlist.append(string_format % combination)
229        else:
230            rlist.append(arg)
231    return rlist
232
233## END functions for hostrange parsing
234####
235
236def sanitize_jobs( jobs ):
237
238    ljobs = list()
239
240    for job in jobs:
241        ljobs.extend( re.findall( r'[0-9]+\/([0-9]+)\.*.', job ) )
242
243    return list( set( ljobs ) )
244
245def parse_nodename( nodename ):
246    global NODE_EXPR
247
248    parts = re.search( r'%s' % NODE_EXPR, nodename, re.VERBOSE )
249
250    try:
251        racknr = parts.group( 'racknr' )
252    except Exception:
253                racknr = 0
254
255    try:
256        nodenr = parts.group( 'nodenr' )
257    except Exception:
258        nodenr = 0
259
260    return int( racknr ), int( nodenr )
261
262def get_nodes( racknode=False, hosts=None ):
263    global LENGTH_NODE
264    global LENGTH_STATE
265    global OPT_SERVERNAME
266
267    nodes_dict = dict()
268
269    try:
270        if not OPT_SERVERNAME:
271            p = PBSQuery()
272        else:
273            p = PBSQuery( OPT_SERVERNAME )
274    except PBSError, reason:
275        print('Error: %s' % reason)
276        sys.exit( -1 )
277
278    p.new_data_structure()
279
280    attr = [ 'state', 'jobs', 'properties', 'jobs' ]
281
282    try:
283        nodes = p.getnodes( attr )
284    except PBSError, reason:
285        print('Error: %s' % reason)
286        sys.exit( -1 )
287
288    number_of_racks = 0
289    nodes_per_rack = 0
290    hosts_list = list()
291   
292    for node, attr in nodes.items():
293        if node in EXCLUDE_NODES:
294            continue
295
296        if hosts and node not in hosts:
297            continue
298
299        if pbs.ND_down in attr.state:
300            state = pbs.ND_down
301        else:
302            state = attr.state[ 0 ]
303
304        state_char = PBS_STATES[ state ]
305
306        if attr.is_free() and attr.has_job():
307            state = pbs.ND_busy
308            state_char = PBS_STATES[ pbs_ND_single ]
309
310        if not nodes_dict.has_key( node ):
311            nodes_dict[ node ] = dict()
312
313        # Setting the longest lenght
314        if len( node ) > LENGTH_NODE:
315            LENGTH_NODE = len( node )
316
317        if len( state ) > LENGTH_STATE:
318            LENGTH_STATE = len( state )
319
320        if racknode:
321            racknr, nodenr = parse_nodename( node )
322
323            if racknr > number_of_racks:
324                number_of_racks = racknr
325
326            if nodenr > nodes_per_rack:
327                nodes_per_rack = nodenr
328           
329            if not nodes_dict.has_key( racknr ):
330                nodes_dict[ racknr ] = dict()
331
332            if not nodes_dict[ racknr ].has_key( nodenr ):
333                nodes_dict[ racknr ][ nodenr ] = dict()
334
335            nodes_dict[ racknr ][ nodenr ][ 'state_char' ] = state_char
336            nodes_dict[ racknr ][ nodenr ][ 'state' ] = state
337       
338            if attr.has_key( 'jobs' ):
339                nodes_dict[ racknr ][ nodenr ][ 'jobs' ] = sanitize_jobs( attr.jobs )
340            else:
341                nodes_dict[ racknr ][ nodenr ][ 'jobs' ] = []
342
343            if attr.has_key( 'properties' ):
344                nodes_dict[ racknr ][ nodenr ][ 'properties' ] = attr.properties
345            else:
346                nodes_dict[ racknr ][ nodenr ][ 'properties' ] = []
347
348        else:
349            hosts_list.append( node )
350            nodes_dict[ node ][ 'state_char' ] = state_char
351            nodes_dict[ node ][ 'state' ] = state
352       
353            if attr.has_key( 'jobs' ):
354                nodes_dict[ node ][ 'jobs' ] = sanitize_jobs( attr.jobs )
355            else:
356                nodes_dict[ node ][ 'jobs' ] = []
357
358            if attr.has_key( 'properties' ):
359                nodes_dict[ node ][ 'properties' ] = attr.properties
360            else:
361                nodes_dict[ node ][ 'properties' ] = []
362
363    if not racknode:
364        return nodes_dict, hosts_list
365
366    return nodes_dict, number_of_racks, nodes_per_rack
367
368def _generate_index( str ):
369    index = []
370
371    def _append( fragment, alist=index ):
372        if fragment.isdigit():
373            fragment = int( fragment )
374        alist.append( fragment )
375
376    prev_isdigit = str[0].isdigit()
377    current_fragment = ''
378
379    for char in str:
380        curr_isdigit = char.isdigit()
381
382        if curr_isdigit == prev_isdigit:
383            current_fragment += char
384        else:
385            _append( current_fragment )
386            current_fragment = char
387            prev_isdigit = curr_isdigit
388
389    _append( current_fragment )
390
391    return tuple( index )
392
393def real_sort( inlist ):
394    indices = map(_generate_index, inlist )
395    decorated = zip( indices, inlist )
396    decorated.sort()
397
398    return [ item for index, item in decorated ]
399
400def print_table(properties=None, jobs=None):
401    global START_RACK
402    global OPT_SKIP_EMPTY_RACKS
403
404    nodes, racknr, nodenr = get_nodes( True )
405
406    ## Code herebelow has been taken from the new_rack_pbsmon.py
407    save_column = None
408   
409    print()   
410    print('  ', end=' ')
411    for rack in xrange( START_RACK, racknr + 1 ):
412       
413        if not ( rack % 10 ):
414            char = '%d' % ( rack / 10 )
415            save_column = char
416        else:
417            char = ' '
418
419        if OPT_SKIP_EMPTY_RACKS:
420            if nodes.has_key( rack ):
421                if save_column:
422                    char = save_column
423                    save_column = None
424                print(char, end=' ')
425        else:
426            print(char, end=' ')
427    print()   
428
429    print('  ', end=' ')
430    for rack in xrange( START_RACK, racknr + 1 ):
431       
432        char = rack % 10
433        if OPT_SKIP_EMPTY_RACKS:
434            if nodes.has_key( rack ):
435                print(char, end=' ')
436        else:
437            print(char, end=' ')
438    print()
439
440    for node in xrange( 1, nodenr + 1 ):
441        print('%2d' % node, end=' ')
442
443        for rack in xrange( START_RACK, racknr + 1 ):
444            if OPT_SKIP_EMPTY_RACKS:
445                if not nodes.has_key( rack ):
446                    continue
447            try:
448                if properties and compare_lists(properties,nodes[ rack ][ node ]['properties']):
449                    prop_color = True
450                else:
451                    prop_color = False
452
453                if jobs and compare_lists(jobs, nodes[ rack ][ node ]['jobs']):
454                    job_color = True
455                else:
456                    job_color = False
457
458                if prop_color or job_color:
459                    if TERMINAL_COLOR and prop_color and job_color:
460                        print(color.GREEN + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
461                    elif TERMINAL_COLOR and  prop_color:
462                        print(color.BLUE + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
463                    elif TERMINAL_COLOR and  job_color:
464                        print(color.YELLOW + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
465                    else:
466                        print('M', end=' ')
467                else:
468                    print(nodes[ rack ][ node ][ 'state_char' ], end=' ')
469            except KeyError:
470                print(' ', end=' ')
471        print()
472    print()
473
474def print_table_summary():
475    global PBS_STATES
476    global OPT_SERVERNAME
477
478    try:
479        if not OPT_SERVERNAME:
480            p = PBSQuery()
481        else:
482            p = PBSQuery( OPT_SERVERNAME )
483    except PBSError, reason:
484        print('error: %s' % reason)
485        sys.exit(-1)
486
487    # get the state of the nodes
488    attr = [ 'state', 'jobs', 'properties' ]
489    try:
490        nodes = p.getnodes(attr)
491    except PBSError, reason:
492        print('error: %s' % reason)
493        sys.exit(-1)
494
495    node_dict = {}
496
497    count_states = {}
498    for key in PBS_STATES.keys():
499        count_states[key] = 0
500
501    for nodename, node in nodes.items():
502
503        # Skip login nodes in status display
504        #
505        if not nodename.find('login'):
506            continue
507
508        state = node['state'][ 0 ]
509
510        state_char = PBS_STATES[state]
511        count_states[state] += 1
512        count_states[pbs_ND_total] += 1
513
514        if node.is_free():                          # can happen for single CPU jobs
515            if node.has_job():
516#               print('TD: %s' % nodename, node)
517                state_char = PBS_STATES[pbs_ND_single]
518                count_states[pbs.ND_free] -=  1
519                count_states[pbs_ND_single] += 1
520            else:
521                if  'infiniband' in node['properties']:
522                    count_states[pbs_ND_free_parallel] +=  1 
523                elif  'ifiniband' in node['properties']:
524                    count_states[pbs_ND_free_serial] +=  1 
525                #else:
526                #   count_states[pbs_ND_free_serial] +=  1
527               
528#       print_('TD: %s %s' % (nodename, state_char))
529        dummy = nodename.split('-')
530        if len( dummy ) > 1:
531            node_dict[dummy[1]] = state_char
532        else:
533            node_dict[dummy[0]] = state_char
534
535    legend = PBS_STATES.keys()
536    legend.sort()
537
538    n = 0
539    for state in legend:
540        print(%s  %-13s : %-5d' % (PBS_STATES[state], state, count_states[state]), end=' ')
541
542        n = n + 1
543        if not (n & 1):
544            print()
545 
546    if TERMINAL_COLOR:
547        print()
548        print('Colors has been enabled for your terminal:')
549        print(' - ' + color.YELLOW + 'Matched jobs' + color.END)
550        print(' - ' + color.BLUE + 'Matched properties' + color.END)
551        print(' - ' + color.GREEN + 'Matched jobs and properties' + color.END)
552
553def print_extended(hosts=None, properties=None, jobs=None):
554    global LENGTH_NODE
555    global LENGTH_STATE
556    global EXTENDED_PATTERNS
557   
558    nodes, ihosts = get_nodes( hosts=hosts )
559    row_header = EXTENDED_PATTERNS[ 'header' ] % ( ( LENGTH_NODE + 2 ), 'Node', ( LENGTH_STATE + 2 ), 'State', 'Jobs' )
560    LENGTH_ROW = len( row_header )
561
562    rows_str = list()
563    ihosts = real_sort( ihosts )
564    for node in ihosts:
565        attr = nodes[ node ]
566
567        if jobs and not compare_lists(jobs, attr['jobs']):
568            continue
569
570        if properties and not compare_lists(properties, attr['properties']):
571            continue
572
573        row_str = EXTENDED_PATTERNS[ 'row' ] % ( ( LENGTH_NODE + 2 ), node, ( LENGTH_STATE + 2 ), attr[ 'state' ], ','.join( attr[ 'jobs' ] ) )
574
575        if len( row_str ) > LENGTH_ROW:
576            LENGTH_ROW = len( row_str )
577
578        rows_str.append( row_str )
579
580    print()
581    print(row_header)
582    print(EXTENDED_PATTERNS[ 'line' ] % ( EXTENDED_PATTERNS[ 'line_char' ] * LENGTH_ROW ))
583    print('\n'.join( rows_str ))
584    print()
585
586if __name__ == '__main__':
587   
588    parser = argparse.ArgumentParser(
589        formatter_class=argparse.RawDescriptionHelpFormatter,
590        description=__doc__,
591    )
592
593    parser.add_argument('nodes', metavar='NODES', nargs='*', type=str)
594    parser.add_argument("-t", "--table", dest="table", action="store_true", help="Show an table", default=PRINT_TABLE )
595    parser.add_argument("-l", "--list", dest="extended", action="store_true", help="Show node rows with state and jobinfo", default=PRINT_EXTENDED )
596    parser.add_argument("-s", "--summary", dest="summary", action="store_true", help="Display a short summary", default=False )
597    parser.add_argument("-a", "--all", dest="summary", action="store_true", help="Display a short summary" )
598    parser.add_argument("-w", "--wide", dest="wide", action="store_true", help="Wide display for node status ( only when -t is used )" )
599    parser.add_argument("-S", "--servername", dest="servername", help="Change the default servername", default=None )
600    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)
601    parser.add_argument("-j", "--job", dest="jobs", help="Show which nodes are uses by a job", default=None)
602    parser.add_argument('--version', action='version', version=pbs.version)
603
604    args = parser.parse_args()
605
606    if args.nodes:
607        args.nodes = parse_args(args.nodes)
608
609    if args.servername:
610        OPT_SERVERNAME = args.servername
611
612    if args.wide:
613        OPT_SKIP_EMPTY_RACKS = False
614
615    if args.nodes:
616        args.extended = True
617
618    if args.extended and PRINT_TABLE:
619        args.table = False
620
621    if args.table and PRINT_EXTENDED:
622        args.extended = False
623
624    if args.properties:
625        args.properties = [ item.strip() for item in args.properties.split(',') ]
626
627    if args.jobs:
628        args.jobs = [ item.strip() for item in args.jobs.split(',') ]
629
630    if args.extended:
631        print_extended(args.nodes, args.properties, args.jobs) 
632    elif args.table:
633        print_table(args.properties, args.jobs)
634    else:
635        print('Something is wrong, bye!', file=sys.stderr)
636        sys.exit( -1 )
637
638    if args.summary:
639        print_table_summary()
Note: See TracBrowser for help on using the repository browser.