@@ -554,6 +554,86 @@ def influx(args):
554554 return make_for_influxDB (arrange_into_categories (metrics_map ), metrics_map ["tags" ], args .table_base , args .output )
555555
556556
557+ def resources (args ):
558+
559+ # Collect all metrics we got
560+ metrics = []
561+ for m in args .metrics :
562+ with open (m , "r" ) as f :
563+ metrics .append (json .load (f ))
564+
565+ # We will finally use the intersection of task names
566+ intersection = [m for m in metrics [0 ]["metrics" ]]
567+ # union is built as a cross check, TODO, could be used to identify very fast tasks as well
568+ union = [m for m in metrics [0 ]["metrics" ]]
569+ # collect number of timeframes for each metrics file
570+ ntfs = [metrics [- 1 ]["tags" ]["ntfs" ]]
571+
572+ for met in metrics [1 :]:
573+ intersection = list (set (intersection ) & set ([m for m in met ["metrics" ]]))
574+ union = list (set (intersection ) | set ([m for m in met ["metrics" ]]))
575+ ntfs .append (met ["tags" ]["ntfs" ])
576+
577+ if len (intersection ) != len (union ):
578+ print ("WARNING: Input metrics seem to be different, union and intersection do not have the same length, using intersection. This can however happen when some tasks finish super fast" )
579+
580+ # quick helper to remove TF suffices
581+ def unique_names_wo_tf_suffix (name , tasks_per_tf_ , tasks_no_tf_ ):
582+ name_split = name .split ("_" )
583+ try :
584+ # assume "_<int>" to reflect a TF suffix
585+ tf = int (name_split [- 1 ])
586+ name = "_" .join (name_split [:- 1 ])
587+ tasks_per_tf_ .append (name )
588+ except ValueError :
589+ tasks_no_tf_ .append (name )
590+
591+ tasks_per_tf = []
592+ tasks_no_tf = []
593+
594+ for name in intersection :
595+ unique_names_wo_tf_suffix (name , tasks_per_tf , tasks_no_tf )
596+ # We treat every tf the same, none of those is special, so strip TF suffices and get unique list of names
597+ tasks_per_tf = list (set (tasks_per_tf ))
598+
599+ # what to do in case there were multiple metrics files given as input
600+ derive_func = {"average" : lambda l : sum (l ) / len (l ),
601+ "min" : min ,
602+ "max" : max }[args .take ]
603+ # Collect here
604+ resources_map = {t : {} for t in tasks_per_tf + tasks_no_tf }
605+ # now let's only take what we are interested in
606+ metrics = [m ["metrics" ] for m in metrics ]
607+ # for convenience
608+ scaling_map = {"mem" : lambda x : int (x ), "cpu" : lambda x : ceil (x * 0.01 )}
609+ # for the workflows we specify mem and cpu, in the metrics we have pss/uss and cpu
610+ metrics_name_map = {"mem" : "uss" , "cpu" : "cpu" }
611+
612+ for w in args .which :
613+ met_ind = MET_TO_IND [metrics_name_map [w ]]
614+ scale = scaling_map [w ]
615+ for tptf in tasks_per_tf :
616+ values = []
617+ for met , n in zip (metrics , ntfs ):
618+ this_value = 0
619+ for i in range (1 , n + 1 ):
620+ key = f"{ tptf } _{ i } "
621+ # It could happen that a task is missing in a certain TF, e.g. when it went through fast enough to not leave a trace in pipeline iterations
622+ if key not in met :
623+ continue
624+ # now do per TF in current metrics, here we always take the max for now ==> conservative
625+ this_value = max (met [key ][met_ind ], this_value )
626+ values .append (this_value )
627+ resources_map [tptf ][w ] = scale (derive_func (values ))
628+
629+ for tntf in tasks_no_tf :
630+ resources_map [tntf ][w ] = scale (derive_func ([met [tntf ][met_ind ] for met in metrics ]))
631+
632+ # finally save to JSON
633+ with open (args .output , "w" ) as f :
634+ json .dump (resources_map , f , indent = 2 )
635+
636+
557637def main ():
558638
559639 parser = argparse .ArgumentParser (description = "Metrics evaluation of O2 simulation workflow" )
@@ -580,6 +660,13 @@ def main():
580660 influx_parser .add_argument ("--table-base" , dest = "table_base" , help = "base name of InfluxDB table name" , default = "O2DPG_MC" )
581661 influx_parser .add_argument ("--output" , "-o" , help = "pmetric JSON file to prepare for InfluxDB" , default = "metrics_influxDB.dat" )
582662
663+ resource_parser = sub_parsers .add_parser ("resources" , help = "Derive resource estimate from metrics to be passed to workflow runner" )
664+ resource_parser .set_defaults (func = resources )
665+ resource_parser .add_argument ("--metrics" , nargs = "+" , help = "metric JSON files" )
666+ resource_parser .add_argument ("--which" , help = "which resources to estimate" , nargs = "*" , choices = ["mem" , "cpu" ], default = ["mem" , "cpu" ])
667+ resource_parser .add_argument ("--take" , help = "how to treat multiple input metric files" , default = "average" , choices = ["average" , "max" , "min" ])
668+ resource_parser .add_argument ("--output" , "-o" , help = "pmetric JSON file to prepare for InfluxDB" , default = "metrics_influxDB.dat" )
669+
583670 args = parser .parse_args ()
584671 return args .func (args )
585672
0 commit comments