Skip to content

Commit 93a832d

Browse files
committed
[stats] Move jobstats support code to its own module.
1 parent acfb511 commit 93a832d

File tree

3 files changed

+228
-171
lines changed

3 files changed

+228
-171
lines changed

utils/jobstats/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/python
2+
#
3+
# ==-- jobstats - support for reading the contents of stats dirs --==#
4+
#
5+
# This source file is part of the Swift.org open source project
6+
#
7+
# Copyright (c) 2014-2017 Apple Inc. and the Swift project authors
8+
# Licensed under Apache License v2.0 with Runtime Library Exception
9+
#
10+
# See https://swift.org/LICENSE.txt for license information
11+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
12+
#
13+
# ==------------------------------------------------------------------------==#
14+
#
15+
# This module contains subroutines for loading object-representations of one or
16+
# more directories generated by `swiftc -stats-output-dir`.
17+
18+
__author__ = 'Graydon Hoare'
19+
__email__ = '[email protected]'
20+
__versioninfo__ = (0, 1, 0)
21+
__version__ = '.'.join(str(v) for v in __versioninfo__)
22+
23+
from .jobstats import JobStats, load_stats_dir, merge_all_jobstats # noqa

utils/jobstats/jobstats.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#!/usr/bin/python
2+
#
3+
# ==-- jobstats - support for reading the contents of stats dirs --==#
4+
#
5+
# This source file is part of the Swift.org open source project
6+
#
7+
# Copyright (c) 2014-2017 Apple Inc. and the Swift project authors
8+
# Licensed under Apache License v2.0 with Runtime Library Exception
9+
#
10+
# See https://swift.org/LICENSE.txt for license information
11+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
12+
#
13+
# ==------------------------------------------------------------------------==#
14+
#
15+
# This file contains subroutines for loading object-representations of one or
16+
# more directories generated by `swiftc -stats-output-dir`.
17+
18+
import datetime
19+
import json
20+
import os
21+
import random
22+
import re
23+
24+
25+
class JobStats:
26+
"""Object holding the stats of a single job run during a compilation,
27+
corresponding to a single JSON file produced by a single job process
28+
passed -stats-output-dir."""
29+
30+
def __init__(self, jobkind, jobid, module, start_usec, dur_usec,
31+
jobargs, stats):
32+
self.jobkind = jobkind
33+
self.jobid = jobid
34+
self.module = module
35+
self.start_usec = start_usec
36+
self.dur_usec = dur_usec
37+
self.jobargs = jobargs
38+
self.stats = stats
39+
40+
def is_driver_job(self):
41+
"""Return true iff self measures a driver job"""
42+
return self.jobkind == 'driver'
43+
44+
def is_frontend_job(self):
45+
"""Return true iff self measures a frontend job"""
46+
return self.jobkind == 'frontend'
47+
48+
def driver_jobs_ran(self):
49+
"""Return the count of a driver job's ran sub-jobs"""
50+
assert(self.is_driver_job())
51+
return self.stats.get("Driver.NumDriverJobsRun", 0)
52+
53+
def driver_jobs_skipped(self):
54+
"""Return the count of a driver job's skipped sub-jobs"""
55+
assert(self.is_driver_job())
56+
return self.stats.get("Driver.NumDriverJobsSkipped", 0)
57+
58+
def driver_jobs_total(self):
59+
"""Return the total count of a driver job's ran + skipped sub-jobs"""
60+
assert(self.is_driver_job())
61+
return self.driver_jobs_ran() + self.driver_jobs_skipped()
62+
63+
def merged_with(self, other):
64+
"""Return a new JobStats, holding the merger of self and other"""
65+
merged_stats = {}
66+
for k, v in self.stats.items() + other.stats.items():
67+
merged_stats[k] = v + merged_stats.get(k, 0.0)
68+
merged_kind = self.jobkind
69+
if other.jobkind != merged_kind:
70+
merged_kind = "<merged>"
71+
merged_module = self.module
72+
if other.module != merged_module:
73+
merged_module = "<merged>"
74+
merged_start = min(self.start_usec, other.start_usec)
75+
merged_end = max(self.start_usec + self.dur_usec,
76+
other.start_usec + other.dur_usec)
77+
merged_dur = merged_end - merged_start
78+
return JobStats(merged_kind, random.randint(0, 1000000000),
79+
merged_module, merged_start, merged_dur,
80+
self.jobargs + other.jobargs, merged_stats)
81+
82+
def incrementality_percentage(self):
83+
"""Assuming the job is a driver job, return the amount of
84+
jobs that actually ran, as a percentage of the total number."""
85+
assert(self.is_driver_job())
86+
ran = self.driver_jobs_ran()
87+
total = self.driver_jobs_total()
88+
return round((float(ran) / float(total)) * 100.0, 2)
89+
90+
def to_catapult_trace_obj(self):
91+
"""Return a JSON-formattable object fitting chrome's
92+
'catapult' trace format"""
93+
return {"name": self.module,
94+
"cat": self.jobkind,
95+
"ph": "X", # "X" == "complete event"
96+
"pid": self.jobid,
97+
"tid": 1,
98+
"ts": self.start_usec,
99+
"dur": self.dur_usec,
100+
"args": self.jobargs}
101+
102+
def start_timestr(self):
103+
"""Return a formatted timestamp of the job's start-time"""
104+
t = datetime.datetime.fromtimestamp(self.start_usec / 1000000.0)
105+
return t.strftime("%Y-%m-%d %H:%M:%S")
106+
107+
def end_timestr(self):
108+
"""Return a formatted timestamp of the job's end-time"""
109+
t = datetime.datetime.fromtimestamp((self.start_usec +
110+
self.dur_usec) / 1000000.0)
111+
return t.strftime("%Y-%m-%d %H:%M:%S")
112+
113+
def pick_lnt_metric_suffix(self, metric_name):
114+
"""Guess an appropriate LNT metric type for a given metric name"""
115+
if "BytesOutput" in metric_name:
116+
return "code_size"
117+
if "RSS" in metric_name or "BytesAllocated" in metric_name:
118+
return "mem"
119+
return "compile"
120+
121+
def to_lnt_test_obj(self, args):
122+
"""Return a JSON-formattable object fitting LNT's 'submit' format"""
123+
run_info = {
124+
"run_order": str(args.lnt_order),
125+
"tag": str(args.lnt_tag),
126+
}
127+
run_info.update(dict(args.lnt_run_info))
128+
stats = self.stats
129+
return {
130+
"Machine":
131+
{
132+
"Name": args.lnt_machine,
133+
"Info": dict(args.lnt_machine_info)
134+
},
135+
"Run":
136+
{
137+
"Start Time": self.start_timestr(),
138+
"End Time": self.end_timestr(),
139+
"Info": run_info
140+
},
141+
"Tests":
142+
[
143+
{
144+
"Data": [v],
145+
"Info": {},
146+
"Name": "%s.%s.%s.%s" % (args.lnt_tag, self.module,
147+
k, self.pick_lnt_metric_suffix(k))
148+
}
149+
for (k, v) in stats.items()
150+
]
151+
}
152+
153+
154+
def load_stats_dir(path):
155+
"""Loads all stats-files found in path into a list of JobStats objects"""
156+
jobstats = []
157+
auxpat = (r"(?P<module>[^-]+)-(?P<input>[^-]+)-(?P<triple>[^-]+)" +
158+
r"-(?P<out>[^-]+)-(?P<opt>[^-]+)")
159+
fpat = (r"^stats-(?P<start>\d+)-swift-(?P<kind>\w+)-" +
160+
auxpat +
161+
r"-(?P<pid>\d+)(-.*)?.json$")
162+
for root, dirs, files in os.walk(path):
163+
for f in files:
164+
m = re.match(fpat, f)
165+
if m:
166+
# NB: "pid" in fpat is a random number, not unix pid.
167+
mg = m.groupdict()
168+
jobkind = mg['kind']
169+
jobid = int(mg['pid'])
170+
start_usec = int(mg['start'])
171+
module = mg["module"]
172+
jobargs = [mg["input"], mg["triple"], mg["out"], mg["opt"]]
173+
174+
j = json.load(open(os.path.join(root, f)))
175+
dur_usec = 1
176+
patstr = (r"time\.swift-" + jobkind + r"\." + auxpat +
177+
r"\.wall$")
178+
pat = re.compile(patstr)
179+
stats = dict()
180+
for (k, v) in j.items():
181+
if k.startswith("time."):
182+
v = int(1000000.0 * float(v))
183+
stats[k] = v
184+
tm = re.match(pat, k)
185+
if tm:
186+
dur_usec = v
187+
188+
e = JobStats(jobkind=jobkind, jobid=jobid,
189+
module=module, start_usec=start_usec,
190+
dur_usec=dur_usec, jobargs=jobargs,
191+
stats=stats)
192+
jobstats.append(e)
193+
return jobstats
194+
195+
196+
def merge_all_jobstats(jobstats):
197+
"""Does a pairwise merge of the elements of list of jobs"""
198+
m = None
199+
for j in jobstats:
200+
if m is None:
201+
m = j
202+
else:
203+
m = m.merged_with(j)
204+
return m

0 commit comments

Comments
 (0)