source: trunk/examples/new_rack_pbsmon.py @ 294

Last change on this file since 294 was 294, checked in by dennis, 11 years ago

The following has changed for new_rack_pbsmon.py and sara_nodes.py

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