Skip to content

Commit a063e5b

Browse files
authored
Merge pull request #9522 from graydon/more-stats-work
Minor adjustments to stats-output-dir and supporting script
2 parents 287d43c + e915081 commit a063e5b

File tree

2 files changed

+104
-27
lines changed

2 files changed

+104
-27
lines changed

lib/Basic/Statistic.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,31 @@ makeFileName(StringRef ProcessName) {
3636
return stream.str();
3737
}
3838

39+
// LLVM's statistics-reporting machinery is sensitive to filenames containing
40+
// YAML-quote-requiring characters, which occur surprisingly often in the wild;
41+
// we only need a recognizable and likely-unique name for a target here, not an
42+
// exact filename, so we go with a crude approximation.
43+
static std::string
44+
cleanTargetName(StringRef TargetName) {
45+
std::string tmp;
46+
for (auto c : TargetName) {
47+
if (('a' <= c && c <= 'z') ||
48+
('A' <= c && c <= 'Z') ||
49+
('0' <= c && c <= '9') ||
50+
(c == '-') || (c == '.') || (c == '/'))
51+
tmp += c;
52+
else
53+
tmp += '_';
54+
}
55+
return tmp;
56+
}
57+
3958
UnifiedStatsReporter::UnifiedStatsReporter(StringRef ProgramName,
4059
StringRef TargetName,
4160
StringRef Directory)
4261
: Filename(Directory),
43-
Timer(make_unique<NamedRegionTimer>(TargetName, "Building Target",
62+
Timer(make_unique<NamedRegionTimer>(cleanTargetName(TargetName),
63+
"Building Target",
4464
ProgramName, "Running Program"))
4565
{
4666
path::append(Filename, makeFileName(ProgramName));

utils/process-stats-dir.py

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# `swiftc -stats-output-dir` and emits summary data, traces etc. for analysis.
1717

1818
import argparse
19+
import csv
1920
import json
2021
import os
2122
import random
@@ -38,6 +39,9 @@ def __init__(self, jobkind, jobid, module, start_usec, dur_usec,
3839
def is_driver_job(self):
3940
return self.jobkind == 'driver'
4041

42+
def is_frontend_job(self):
43+
return self.jobkind == 'frontend'
44+
4145
def driver_jobs_ran(self):
4246
assert(self.is_driver_job())
4347
return self.stats.get("Driver.NumDriverJobsRun", 0)
@@ -72,7 +76,7 @@ def incrementality_percentage(self):
7276
assert(self.is_driver_job())
7377
ran = self.driver_jobs_ran()
7478
total = self.driver_jobs_total()
75-
return (float(ran) / float(total)) * 100.0
79+
return round((float(ran) / float(total)) * 100.0, 2)
7680

7781
# Return a JSON-formattable object of the form preferred by google chrome's
7882
# 'catapult' trace-viewer.
@@ -108,22 +112,26 @@ def load_stats_dir(path):
108112
patstr = (r"time\.swift-" + jobkind +
109113
r"\.(?P<module>[^\.]+)(?P<filename>.*)\.wall$")
110114
pat = re.compile(patstr)
115+
stats = dict()
111116
for (k, v) in j.items():
117+
if k.startswith("time."):
118+
v = int(1000000.0 * float(v))
119+
stats[k] = v
112120
tm = re.match(pat, k)
113121
if tm:
114122
tmg = tm.groupdict()
115-
dur_usec = int(1000000.0 * float(v))
123+
dur_usec = v
116124
module = tmg['module']
117125
if 'filename' in tmg:
118126
ff = tmg['filename']
119127
if ff.startswith('.'):
120128
ff = ff[1:]
121129
jobargs = [ff]
122-
break
130+
123131
e = JobStats(jobkind=jobkind, jobid=jobid,
124132
module=module, start_usec=start_usec,
125133
dur_usec=dur_usec, jobargs=jobargs,
126-
stats=j)
134+
stats=stats)
127135
jobstats.append(e)
128136
return jobstats
129137

@@ -168,39 +176,81 @@ def merge_all_jobstats(jobstats):
168176

169177

170178
def show_paired_incrementality(args):
179+
fieldnames = ["old_pct", "old_skip",
180+
"new_pct", "new_skip",
181+
"delta_pct", "delta_skip",
182+
"name"]
183+
out = csv.DictWriter(args.output, fieldnames, dialect='excel-tab')
184+
out.writeheader()
185+
171186
for (name, (oldstats, newstats)) in load_paired_stats_dirs(args):
172187
olddriver = merge_all_jobstats([x for x in oldstats
173188
if x.is_driver_job()])
174189
newdriver = merge_all_jobstats([x for x in newstats
175190
if x.is_driver_job()])
176191
if olddriver is None or newdriver is None:
177192
continue
178-
if args.csv:
179-
args.output.write("'%s',%d,%d\n" % (name,
180-
olddriver.driver_jobs_ran(),
181-
newdriver.driver_jobs_ran()))
182-
else:
183-
oldpct = olddriver.incrementality_percentage()
184-
newpct = newdriver.incrementality_percentage()
185-
deltapct = newpct - oldpct
186-
oldskip = olddriver.driver_jobs_skipped()
187-
newskip = newdriver.driver_jobs_skipped()
188-
deltaskip = newskip - oldskip
189-
fmt = "{}:\t" + "\t".join([lab + ":{:>3.0f}% ({} skipped)" for
190-
lab in ['old', 'new', 'delta']]) + "\n"
191-
args.output.write(fmt.format(name,
192-
oldpct, oldskip,
193-
newpct, newskip,
194-
deltapct, deltaskip))
193+
oldpct = olddriver.incrementality_percentage()
194+
newpct = newdriver.incrementality_percentage()
195+
deltapct = newpct - oldpct
196+
oldskip = olddriver.driver_jobs_skipped()
197+
newskip = newdriver.driver_jobs_skipped()
198+
deltaskip = newskip - oldskip
199+
out.writerow(dict(name=name,
200+
old_pct=oldpct, old_skip=oldskip,
201+
new_pct=newpct, new_skip=newskip,
202+
delta_pct=deltapct, delta_skip=deltaskip))
195203

196204

197205
def show_incrementality(args):
206+
fieldnames = ["incrementality", "name"]
207+
out = csv.DictWriter(args.output, fieldnames, dialect='excel-tab')
208+
out.writeheader()
209+
198210
for path in args.remainder:
199211
stats = load_stats_dir(path)
200212
for s in stats:
201213
if s.is_driver_job():
202214
pct = s.incrementality_percentage()
203-
args.output.write("%s: %f\n" % (os.path.basename(path), pct))
215+
out.writerow(dict(name=os.path.basename(path),
216+
incrementality=pct))
217+
218+
219+
def compare_frontend_stats(args):
220+
assert(len(args.remainder) == 2)
221+
(olddir, newdir) = args.remainder
222+
223+
regressions = 0
224+
fieldnames = ["old", "new", "delta_pct", "name"]
225+
out = csv.DictWriter(args.output, fieldnames, dialect='excel-tab')
226+
out.writeheader()
227+
228+
old_stats = load_stats_dir(olddir)
229+
new_stats = load_stats_dir(newdir)
230+
old_merged = merge_all_jobstats([x for x in old_stats
231+
if x.is_frontend_job()])
232+
new_merged = merge_all_jobstats([x for x in new_stats
233+
if x.is_frontend_job()])
234+
if old_merged is None or new_merged is None:
235+
return regressions
236+
for stat_name in sorted(old_merged.stats.keys()):
237+
if stat_name in new_merged.stats:
238+
old = old_merged.stats[stat_name]
239+
new = new_merged.stats.get(stat_name, 0)
240+
if old == 0 or new == 0:
241+
continue
242+
delta = (new - old)
243+
delta_pct = round((float(delta) / float(new)) * 100.0, 2)
244+
if (stat_name.startswith("time.") and
245+
abs(delta) < args.delta_usec_thresh):
246+
continue
247+
if abs(delta_pct) < args.delta_pct_thresh:
248+
continue
249+
out.writerow(dict(name=stat_name, old=old, new=new,
250+
delta_pct=delta_pct))
251+
if delta > 0:
252+
regressions += 1
253+
return regressions
204254

205255

206256
def main():
@@ -212,26 +262,33 @@ def main():
212262
help="Write output to file")
213263
parser.add_argument("--paired", action="store_true",
214264
help="Process two dirs-of-stats-dirs, pairwise")
215-
parser.add_argument("--csv", action="store_true",
216-
help="Write output as CSV")
265+
parser.add_argument("--delta-pct-thresh", type=float, default=0.01,
266+
help="Percentage change required to report")
267+
parser.add_argument("--delta-usec-thresh", type=int, default=100000,
268+
help="Absolute delta on times required to report")
217269
modes = parser.add_mutually_exclusive_group(required=True)
218270
modes.add_argument("--catapult", action="store_true",
219271
help="emit a 'catapult'-compatible trace of events")
220272
modes.add_argument("--incrementality", action="store_true",
221273
help="summarize the 'incrementality' of a build")
274+
modes.add_argument("--compare-frontend-stats", action="store_true",
275+
help="Compare frontend stats from two stats-dirs")
222276
parser.add_argument('remainder', nargs=argparse.REMAINDER,
223277
help="stats-dirs to process")
224278

225279
args = parser.parse_args()
226280
if len(args.remainder) == 0:
227281
parser.print_help()
228-
sys.exit(1)
282+
return 1
229283
if args.catapult:
230284
write_catapult_trace(args)
285+
elif args.compare_frontend_stats:
286+
return compare_frontend_stats(args)
231287
elif args.incrementality:
232288
if args.paired:
233289
show_paired_incrementality(args)
234290
else:
235291
show_incrementality(args)
292+
return None
236293

237-
main()
294+
sys.exit(main())

0 commit comments

Comments
 (0)