diff --git a/Sausage b/Sausage index c28323f..874cff6 100755 --- a/Sausage +++ b/Sausage @@ -1,273 +1,277 @@ #!/usr/bin/python3 # © All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, # Switzerland # SCITAS - Scientific IT and Application Support, 2021 # See the LICENSE.txt file for more details. import configparser import requests import json import argparse import getpass from datetime import date from datetime import datetime def valid_date(date): try: validate = datetime.strptime(date, "%Y-%m-%d") return validate except ValueError: msg = "Not a valid date: '{0}', YYYY-MM-DD expected.".format(date) raise argparse.ArgumentTypeError(msg) class AppArgs(object): def __init__(self): self.response = {} self.parser = argparse.ArgumentParser(prog = 'Sausage', description = 'SCITAS Account Usage.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) self.add_args() def add_args(self): self.parser.add_argument('-u','--user', help='If not provided whoami is considered') self.parser.add_argument('-a','--all', help='all users from an account are printed', action='store_true') self.parser.add_argument('-A','--account', help='Prints account consumption per cluster') self.parser.add_argument('-s','--start', help='Start date - format YYYY-MM-DD', type=valid_date) self.parser.add_argument('-e','--end', help='End date - format YYYY-MM-DD', type=valid_date) self.parser.add_argument('-c','--co2', help='Prints the co2 footprint per cluster', action='store_true') args = self.parser.parse_args() if args.start and args.end is None: self.parser.error("range requires both dates (--start and --end)") if args.end: if args.start is None: self.parser.error("range requires both dates (--start and --end)") if args.end < args.start: self.parser.error("start date must be earlier than end date") if args.all and args.account is None: self.parser.error("the option --all requires a valid account (--all and --account)") if args.all and args.user: self.parser.error("--all option is not compatible with --user option") self.response = { "user": args.user, "account": args.account, "all": args.all, "start": args.start, "end": args.end, "co2": args.co2 } class GetData(object): def __init__(self,conf,args): self.cfg_parser = configparser.ConfigParser() self.cfg_parser.read(conf) self.server = self.cfg_parser.get("server", "url") + ":" + self.cfg_parser.get("server", "port") self.unit = self.cfg_parser.get("cluster", "calc_unit") self.co2 = args["co2"] self.message = [] self.vseparator = '|' self.hseparator = '-' * 57 if args["start"]: self.firstday = str(args["start"].date()) else: self.firstday = str(date.today().replace(day=1)) if args["end"]: self.lastday = str(args["end"].date()) else: self.lastday = str(date.today()) self.request(args) def formatheader(self,a,b,c,d): self.message.append(f"{a:^19}" + self.vseparator + f"{b:^12}" + self.vseparator + f"{c:^12}" + self.vseparator + f"{d:^11}") def formatline(self,a,b,c,d): self.message.append(f" {a:<18}" + self.vseparator + f" {b:<11}" + self.vseparator + f"{c:>11} " + self.vseparator + f"{d:>10} ") def formatfooter(self,a,b,c): self.message.append(f" {a:<28}" + f"{b:>13} " + f"{c:<12}") def printbox(self): # Init internal variables data = self.response.json() element = data["name"] carbon = 0 money = 0 walltime = 0 # begin print headers # Default values title = "ACCOUNT: " head_b = "Cluster" # By case values if self.format == 1: head_a = "" elif self.format == 2: head_a = "Account" title = "USER: " elif self.format == 3: head_a = "Username" head_c = self.unit + "-hrs" if self.co2: head_d = "kg.eCO²" else: head_d = "CHF" # Append headers self.message.append(self.hseparator) self.message.append(title + element) self.message.append("Global usage from " + self.firstday + " to " + self.lastday) self.message.append(self.hseparator) self.formatheader(head_a,head_b,head_c,head_d) self.message.append(self.hseparator) # end print headers # Begin print body for key, value in sorted(data.items()): if isinstance(value, dict): head_a = key # Format 1 used in cases 3 and 6 if self.format == 1: # if money is less than 0.01 CHF, print '-' if value['chf'] > 0.00999: chf = "{:.2f}".format(value['chf']) money += value['chf'] else: chf = "-" # if time is less than 1 second, print '-' if value['time'] > 0.00999: time = "{:.2f}".format(value['time']) else: time = "-" # Convert co2 in ekg if value['co2'] > 9.999: co2 = "{:.2f}".format(value['co2'] / 1000) else: co2 = "-" carbon += value['co2'] money += value['chf'] walltime += value['time'] head_c = str(time) if self.co2: head_d = str(co2) else: head_d = str(chf) self.formatline(head_a,head_b,head_c,head_d) # Format 2 used in cases 1, 2, 4 and 6 # Format 3 used in case 5 elif self.format == 2 or self.format == 3: for k,v in value.items(): head_b = k # Convert co2 in ekg if v['co2'] > 9.999: co2 = "{:.2f}".format(v['co2'] / 1000) else: co2 = "-" # if money is less than 0.01 CHF, print '-' if v['chf'] > 0.00999: chf = "{:.2f}".format(v['chf']) money += v['chf'] else: chf = "-" # if time is less than 1 second, print '-' if v['time'] > 0.00999: time = "{:.2f}".format(v['time']) else: time = "-" carbon += v['co2'] walltime += v['time'] head_c = str(time) if self.co2: head_d = str(co2) else: head_d = str(chf) self.formatline(head_a,head_b,head_c,head_d) # End print body # Begin print footer carbon = "{:.2f}".format(carbon / 1000) money = "{:.2f}".format(money) walltime = "{:.2f}".format(walltime) self.message.append(self.hseparator) self.formatfooter("Total costs:",str(money), "CHF") self.formatfooter("Total walltime:", str(walltime), self.unit + "-hrs") self.formatfooter("Estimated carbon footprint:", str(carbon), "kg. eCO²") self.message.append(self.hseparator) # End print footer def request(self,args): # First case : without arguments if all(v == None for v in [args["user"], args["account"], args["start"], args["end"]]) and not args["all"]: self.response = requests.get(self.server + '/user/' + getpass.getuser()) self.format = 2 # Second case : only with user elif args["user"] and all(v == None for v in [args["account"], args["start"], args["end"]]) and not args["all"]: self.response = requests.get(self.server + '/user/' + args["user"]) self.format = 2 # Third case : only with account elif args["account"] and all(v == None for v in [args["user"], args["start"], args["end"]]) and not args["all"]: self.response = requests.get(self.server + '/account/' + args["account"]) self.format = 1 # Fourth case : with user and account (without range) elif all(v != None for v in [args["user"], args["account"]]) and all(v == None for v in [args["start"], args["end"]]) and not args["all"]: self.response = requests.get(self.server + '/account/' + args["account"] + '/' + args["user"]) self.format = 2 # Fifth case : with account and all elif args["account"] and all(v == None for v in [args["user"], args["start"], args["end"]]) and args["all"]: self.response = requests.get(self.server + '/account/' + args["account"] + '/' + "all") self.format = 3 # Sixth case : with range elif all(v != None for v in [args["start"], args["end"]]) and (args["user"] or args["account"]): if args["account"]: - self.response = requests.get(self.server + '/range/account/' + self.firstday + '/' + self.lastday + '/' + args["account"]) - self.format = 1 + if args["all"]: + self.response = requests.get(self.server + '/range/all/' + self.firstday + '/' + self.lastday + '/' + args["account"]) + self.format = 3 + else: + self.response = requests.get(self.server + '/range/account/' + self.firstday + '/' + self.lastday + '/' + args["account"]) + self.format = 1 elif args["user"]: self.response = requests.get(self.server + '/range/user/' + self.firstday + '/' + self.lastday + '/' + args["user"]) self.format = 2 if self.response.status_code == 200: self.printbox() conf_file = "/etc/sausage/sausage.cfg" options = AppArgs() getdata = GetData(conf_file,options.response) for item in getdata.message: print("#" + "{0:^57}".format(item) + "#")