From 7957b28a05aa1182139c78b91b22bd13c6ef7488 Mon Sep 17 00:00:00 2001 From: Wirawan Purwanto Date: Fri, 9 Sep 2016 16:39:28 -0400 Subject: [PATCH] * show-node-status.py: Initial tool to replace node-slot-status.sh. This initial edition contains only "--raw" command. --- sge/show-node-status.py | 164 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100755 sge/show-node-status.py diff --git a/sge/show-node-status.py b/sge/show-node-status.py new file mode 100755 index 0000000..d8bddd1 --- /dev/null +++ b/sge/show-node-status.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# +# Created: 20160830 +# Wirawan Purwanto + +""" +show-node-status.py +--------------------- + +Various tools to investigate node status in an SGE cluster. +This tool is a replacement and upgrade of the shell version of the tool +`node-slot-status.sh`. + +Usage: + +""" + +import os +import re +import subprocess +import sys + +#----------------------- UNDER CONSTRUCTION ----------------------- +#Nothing was done yet + +def node_slot_stats_raw(qstat_f, show_disabled_nodes=True): + """Prints the node stats from `qstat -f' in raw format: + - not printing disabled nodes + - not showing the computational jobs that are running on these nodes + """ + FNR = 0 + for L in qstat_f: + FNR += 1 + FLDS = L.split() + status_flags = FLDS[5] if (len(FLDS) > 5) else "" + + if FNR == 1 and FLDS[0] == "queuename": + print(L) + continue + + # Valid host status field + if re.search(r'^[A-Za-z]', L) and len(FLDS) in (5,6) \ + and (show_disabled_nodes or ("d" not in status_flags)): + print(L) + + + + +def help(): + msg = """\ +show-node-status.py - Showing node status from SGE information + +The information is mainly drawn from `qstat -f` output. + +Usage: one of the following: + +--raw +raw + Shows the raw queue/node status + +--stats +stats +(no argument) + Shows the statistic summary per node type +""" + + +def main_default(argv, save_qstat=True): + """Main default function: + - By default we invoke qstat -f and prints the analysis. + - If argv[1] is given, then we read in the file and + use that for the analysis. + """ + from time import localtime, strftime + + dtime = localtime() + dtimestr = strftime("%Y%m%d-%H%M", dtime) + + # Read the command first--what do we want to do + if len(argv) < 2: + cmd = "stats" + elif argv[1] in ('--raw', 'raw'): + cmd = "raw" + elif argv[1] in ('--stats', 'stats'): + cmd = "stats" + else: + raise ValueError, "Unknown action: "+argv[1] + + # Skip program name and first command: + cmdargs = argv[2:] + + # Default options + show_disabled_nodes = False + + if len(cmdargs) > 0: + qstat_f_current = open(cmdargs[0], "r").read().splitlines() + else: + qstat_f_current = pipe_out(('qstat', '-f'), split=True) + if save_qstat: + with open("qstat-f-%s.txt" % dtimestr, "w") as F: + F.write("\n".join(qstat_f_current)) + F.write("\n") + + if cmd == "raw": + node_slot_stats_raw(qstat_f_current, + show_disabled_nodes=show_disabled_nodes, + ) + elif cmd == "stats": + node_slots_stats_per_node_type(qstat_f_current, + show_disabled_nodes=show_disabled_nodes, + ) + else: + raise "Missing support for command: "+cmd + + + +# --------------------------------------------------------------------------- +# Support tools below +# --------------------------------------------------------------------------- + +def pipe_out(args, split=False, shell=False): + """Executes a shell command, piping out the stdout to python for parsing. + This is my customary shortcut for backtick operator. + The result is either a single string (if split==False) or a list of strings + with EOLs removed (if split==True).""" + retval = subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell).communicate()[0] + if not split: + return retval + else: + return retval.splitlines() + + +# Internal variable: don't mess! +_str_fmt_heading_rx = None +def str_fmt_heading(fmt): + """Replaces a printf-style formatting with one suitable for table heading: + all non-string conversions are replaced with string conversions, + preserving the minimum widths.""" + # Originally from: $PWQMC77/scripts/cost.py and later Cr2_analysis_cbs.py . + # + #_str_fmt_heading_rx = None # only for development purposes + import re + global _str_fmt_heading_rx + if _str_fmt_heading_rx is None: + # Because of complicated regex, I verbosely write it out here: + _str_fmt_heading_rx = re.compile(r""" + ( + % # % sign + (?:\([^)]+\))? # optional '(keyname)' mapping key + [-+#0 hlL]* # optional conversion flag + [0-9*]* # optional minimum field width + ) + ((?:\.[0-9]*)?) # optional precision + [^-+#*0 hlL0-9.%s] # not conv flag, dimensions, nor literal '%', + # nor 's' conversion specifiers + """, re.VERBOSE) + return _str_fmt_heading_rx.sub(r'\1s', fmt) + + + +# stub main code + +if __name__ == "__main__" and not "get_ipython" in globals(): + main_default(sys.argv)