source: trunk/examples/new_rack_pbsmon.py @ 316

Last change on this file since 316 was 316, checked in by bas, 10 years ago

Dennis Stam:

  • fixed docdir error, closes #39
  • added a new option to examples/new_rack_pbsmon.py -p/--property
  • improved the autoconf setup
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 18.8 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 316 2014-06-04 16:24:52Z bas $
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' ]
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):
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                    if TERMINAL_COLOR:
478                        _print(color.GREEN + nodes[ rack ][ node ][ 'state_char' ] + color.END, end=' ')
479                    else:
480                        _print('M', end=' ')
481                else:
482                    _print(nodes[ rack ][ node ][ 'state_char' ], end=' ')
483            except KeyError:
484                _print(' ', end=' ')
485        _print()
486    _print()
487
488def print_table_summary():
489    global PBS_STATES
490    global OPT_SERVERNAME
491
492    try:
493        if not OPT_SERVERNAME:
494            p = PBSQuery()
495        else:
496            p = PBSQuery( OPT_SERVERNAME )
497    except PBSError, reason:
498        _print('error: %s' % reason)
499        sys.exit(-1)
500
501    # get the state of the nodes
502    attr = [ 'state', 'jobs', 'properties' ]
503    try:
504        nodes = p.getnodes(attr)
505    except PBSError, reason:
506        _print('error: %s' % reason)
507        sys.exit(-1)
508
509    node_dict = {}
510
511    count_states = {}
512    for key in PBS_STATES.keys():
513        count_states[key] = 0
514
515    for nodename, node in nodes.items():
516
517        # Skip login nodes in status display
518        #
519        if not nodename.find('login'):
520            continue
521
522        state = node['state'][ 0 ]
523
524        state_char = PBS_STATES[state]
525        count_states[state] += 1
526        count_states[pbs_ND_total] += 1
527
528        if node.is_free():                          # can happen for single CPU jobs
529            if node.has_job():
530#               _print('TD: %s' % nodename, node)
531                state_char = PBS_STATES[pbs_ND_single]
532                count_states[pbs.ND_free] -=  1
533                count_states[pbs_ND_single] += 1
534            else:
535                if  'infiniband' in node['properties']:
536                    count_states[pbs_ND_free_parallel] +=  1 
537                elif  'ifiniband' in node['properties']:
538                    count_states[pbs_ND_free_serial] +=  1 
539                #else:
540                #   count_states[pbs_ND_free_serial] +=  1
541               
542#       print_('TD: %s %s' % (nodename, state_char))
543        dummy = nodename.split('-')
544        if len( dummy ) > 1:
545            node_dict[dummy[1]] = state_char
546        else:
547            node_dict[dummy[0]] = state_char
548
549    legend = PBS_STATES.keys()
550    legend.sort()
551
552    n = 0
553    for state in legend:
554        _print(%s  %-13s : %-5d' % (PBS_STATES[state], state, count_states[state]), end=' ')
555
556        n = n + 1
557        if not (n & 1):
558            _print()
559
560def print_extended(hosts=None, properties=None):
561    global LENGTH_NODE
562    global LENGTH_STATE
563    global EXTENDED_PATTERNS
564   
565    nodes, ihosts = get_nodes( hosts=hosts )
566    row_header = EXTENDED_PATTERNS[ 'header' ] % ( ( LENGTH_NODE + 2 ), 'Node', ( LENGTH_STATE + 2 ), 'State', 'Jobs' )
567    LENGTH_ROW = len( row_header )
568
569    rows_str = list()
570    ihosts = real_sort( ihosts )
571
572    for node in ihosts:
573        attr = nodes[ node ]
574
575        if properties and not compare_lists(properties, attr['properties']):
576            continue
577
578        row_str = EXTENDED_PATTERNS[ 'row' ] % ( ( LENGTH_NODE + 2 ), node, ( LENGTH_STATE + 2 ), attr[ 'state' ], ','.join( attr[ 'jobs' ] ) )
579
580        if len( row_str ) > LENGTH_ROW:
581            LENGTH_ROW = len( row_str )
582
583        rows_str.append( row_str )
584
585    _print()
586    _print(row_header)
587    _print(EXTENDED_PATTERNS[ 'line' ] % ( EXTENDED_PATTERNS[ 'line_char' ] * LENGTH_ROW ))
588    _print('\n'.join( rows_str ))
589    _print()
590
591if __name__ == '__main__':
592   
593    parser = argparse.ArgumentParser(
594        formatter_class=argparse.RawDescriptionHelpFormatter,
595        description=__doc__,
596    )
597
598    parser.add_argument('nodes', metavar='NODES', nargs='*', type=str)
599    parser.add_argument( "-t", "--table", dest="table", action="store_true", help="Show an table", default=PRINT_TABLE )
600    parser.add_argument( "-l", "--list", dest="extended", action="store_true", help="Show node rows with state and jobinfo", default=PRINT_EXTENDED )
601    parser.add_argument( "-s", "--summary", dest="summary", action="store_true", help="Display a short summary", default=False )
602    parser.add_argument( "-a", "--all", dest="summary", action="store_true", help="Display a short summary" )
603    parser.add_argument( "-w", "--wide", dest="wide", action="store_true", help="Wide display for node status ( only when -t is used )" )
604    parser.add_argument( "-S", "--servername", dest="servername", help="Change the default servername", default=None )
605    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)
606    parser.add_argument('--version', action='version', version=pbs.version)
607
608    args = parser.parse_args()
609
610    if args.nodes:
611        args.nodes = parse_args(args.nodes)
612
613    if args.servername:
614        OPT_SERVERNAME = args.servername
615
616    if args.wide:
617        OPT_SKIP_EMPTY_RACKS = False
618
619    if args.nodes:
620        args.extended = True
621
622    if args.extended and PRINT_TABLE:
623        args.table = False
624
625    if args.table and PRINT_EXTENDED:
626        args.extended = False
627
628    if args.properties:
629        args.properties = [ item.strip() for item in args.properties.split(',') ]
630
631    if args.extended:
632        print_extended(args.nodes, args.properties) 
633    elif args.table:
634        print_table(args.properties)
635    else:
636        _print('Something is wrong, bye!', file=sys.stderr)
637        sys.exit( -1 )
638
639    if args.summary:
640        print_table_summary()
Note: See TracBrowser for help on using the repository browser.