source: trunk/examples/new_rack_pbsmon.py @ 321

Last change on this file since 321 was 321, checked in by dennis, 10 years ago

Added a new feature which allows you to specify jobnumbers to see the status of the nodes

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 20.1 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 321 2014-09-29 13:12:50Z dennis $
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"""
29
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.