Skip to content

Commit 0f50638

Browse files
committed
add a script to determine resource estimates from jobutils output
1 parent 11a77db commit 0f50638

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from os.path import dirname
5+
import argparse
6+
import re
7+
from glob import glob
8+
import json
9+
import math
10+
11+
################################################################
12+
# #
13+
# Script to exctract CPU/MEM resource estimates from #
14+
# the log_time files left from the individual tasks #
15+
# #
16+
# Outputs a json to be fed to the workflow runnner for dynamic #
17+
# scheduling decisions. #
18+
################################################################
19+
20+
# helper function to find files
21+
def find_files(path, search, depth=0):
22+
files = []
23+
for d in range(depth + 1):
24+
wildcards = "/*" * d
25+
path_search = path + wildcards + f"/{search}"
26+
files.extend(glob(path_search))
27+
return files
28+
29+
# this functions extracts the important metrics from a single resource file (left by O2 jobutils/taskwrapper)
30+
def extract_time_single(path):
31+
r = {}
32+
with open(path, "r") as f:
33+
for l in f:
34+
if "walltime" in l:
35+
r["walltime"] = float(l.strip().split()[-1])
36+
elif "CPU" in l:
37+
r["cpu"] = float(l.strip().split()[-1].split('%')[0])
38+
elif "mem" in l:
39+
r["mem"] = float(l.strip().split()[-1])
40+
return r
41+
42+
def process(args):
43+
pipeline_dir = dirname(args.path)
44+
files = find_files(pipeline_dir, "*.log_time", 1)
45+
if not files:
46+
print(f"WARNING: Cannot find time logs in {pipeline_dir}.")
47+
return
48+
49+
resource_accum = {} # accumulates resources per task name
50+
for f in files:
51+
# name from time log file
52+
name = f.split("/")[-1]
53+
name = re.sub("\.log_time$", "", name)
54+
name_notf = name.split("_")[0]
55+
resources = extract_time_single(f)
56+
resources["name"] = name
57+
resource_accum[name_notf] = resource_accum.get(name_notf,[])
58+
resource_accum[name_notf].append(resources)
59+
60+
61+
# finalizes metrics; average for CPU; max for MEM; average for walltime
62+
# returns final metric result suitable for output to json
63+
def finalize(resource_list):
64+
"""
65+
input is map of lists of resource dictionaries
66+
output is map of final resource estimates
67+
"""
68+
result = {}
69+
for task in resource_list:
70+
finalr = {"walltime": 0, "cpu" : 0, "mem" : 0}
71+
for r in resource_list[task]: # r is a resource estimate
72+
finalr["walltime"] += r["walltime"]
73+
finalr["mem"] = max(r["mem"], finalr["mem"])
74+
finalr["cpu"] += r["cpu"]
75+
finalr["walltime"] /= len(resource_list[task])
76+
finalr["cpu"] /= 100*len(resource_list[task]) # we take the number of CPUs not the percent
77+
finalr["mem"] /= 1024 # we'd like to have it in MB
78+
finalr["mem"] = math.ceil(finalr["mem"])
79+
result[task] = finalr
80+
return result
81+
82+
estimate=finalize(resource_accum)
83+
print (estimate)
84+
85+
# finally save to JSON
86+
with open(args.output, "w") as f:
87+
json.dump(estimate, f, indent=2)
88+
89+
90+
def main():
91+
parser = argparse.ArgumentParser(description="Produce a O2DPG workflow resource file from existing time logs")
92+
parser.add_argument('-o','--output', help='Filename of output metric json file', default='learned_O2DPG_resource_metrics.json')
93+
parser.add_argument('-p','--path', help='Path to O2DPG workspace', default="./")
94+
args = parser.parse_args()
95+
process(args)
96+
97+
if __name__ == "__main__":
98+
sys.exit(main())

0 commit comments

Comments
 (0)