blob: f1b6ef1ab94b775831aa3e4ce5b8002be12de112 [file] [log] [blame]
Craig Tillercc89f782017-04-03 08:08:25 -07001#!/usr/bin/env python2.7
Matt Kwongf90d4c82017-03-09 16:35:21 -08002# Copyright 2017, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Craig Tiller76fc48b2017-03-07 17:21:16 -080030
31import sys
32import json
33import bm_json
Craig Tiller47c56a62017-03-08 13:45:53 -080034import tabulate
ncteisenff023ef2017-03-10 11:43:48 -080035import argparse
Craig Tiller42749252017-03-27 22:20:37 -070036from scipy import stats
Craig Tillerec8dee22017-03-27 21:26:47 -070037import subprocess
Craig Tillera0af68d2017-03-27 21:27:37 -070038import multiprocessing
Craig Tiller8aeb8982017-03-27 21:53:04 -070039import collections
Craig Tiller06ac1c72017-03-28 08:25:35 -070040import pipes
Craig Tillerebb7e822017-03-29 08:17:11 -070041import os
42sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..', '..', 'run_tests', 'python_utils'))
43import comment_on_pr
Craig Tiller691eefb2017-04-03 08:06:32 -070044import jobset
Craig Tiller2fbbcd12017-04-03 08:29:08 -070045import itertools
Craig Tiller125b04f2017-04-04 21:31:44 -070046import speedup
Craig Tiller233ad392017-04-14 08:25:53 -070047import random
Craig Tiller0553b9c2017-04-14 08:31:17 -070048import shutil
Craig Tiller349907b2017-04-14 13:00:47 -070049import errno
Craig Tiller76fc48b2017-03-07 17:21:16 -080050
Craig Tiller7281d192017-04-05 15:04:55 -070051_INTERESTING = (
52 'cpu_time',
53 'real_time',
54 'locks_per_iteration',
55 'allocs_per_iteration',
56 'writes_per_iteration',
57 'atm_cas_per_iteration',
58 'atm_add_per_iteration',
59)
60
ncteisenff023ef2017-03-10 11:43:48 -080061def changed_ratio(n, o):
62 if float(o) <= .0001: o = 0
63 if float(n) <= .0001: n = 0
64 if o == 0 and n == 0: return 0
65 if o == 0: return 100
66 return (float(n)-float(o))/float(o)
67
Craig Tiller23e6a8a2017-03-30 07:24:01 -070068def median(ary):
69 ary = sorted(ary)
70 n = len(ary)
71 if n%2 == 0:
72 return (ary[n/2] + ary[n/2+1]) / 2.0
73 else:
74 return ary[n/2]
75
ncteisenff023ef2017-03-10 11:43:48 -080076def min_change(pct):
77 return lambda n, o: abs(changed_ratio(n,o)) > pct/100.0
78
Craig Tillerd23826e2017-03-27 21:25:15 -070079_AVAILABLE_BENCHMARK_TESTS = ['bm_fullstack_unary_ping_pong',
80 'bm_fullstack_streaming_ping_pong',
81 'bm_fullstack_streaming_pump',
82 'bm_closure',
83 'bm_cq',
84 'bm_call_create',
85 'bm_error',
86 'bm_chttp2_hpack',
87 'bm_chttp2_transport',
88 'bm_pollset',
89 'bm_metadata',
90 'bm_fullstack_trickle']
ncteisenff023ef2017-03-10 11:43:48 -080091
92argp = argparse.ArgumentParser(description='Perform diff on microbenchmarks')
93argp.add_argument('-t', '--track',
Craig Tiller7281d192017-04-05 15:04:55 -070094 choices=sorted(_INTERESTING),
ncteisenff023ef2017-03-10 11:43:48 -080095 nargs='+',
Craig Tiller7281d192017-04-05 15:04:55 -070096 default=sorted(_INTERESTING),
ncteisenff023ef2017-03-10 11:43:48 -080097 help='Which metrics to track')
Craig Tillerc523f5d2017-03-27 21:37:19 -070098argp.add_argument('-b', '--benchmarks', nargs='+', choices=_AVAILABLE_BENCHMARK_TESTS, default=['bm_cq'])
Craig Tillerd23826e2017-03-27 21:25:15 -070099argp.add_argument('-d', '--diff_base', type=str)
Craig Tillerd71ba832017-04-14 08:36:11 -0700100argp.add_argument('-r', '--repetitions', type=int, default=1)
Craig Tiller97e40da2017-04-14 09:04:07 -0700101argp.add_argument('-l', '--loops', type=int, default=12)
Craig Tiller691eefb2017-04-03 08:06:32 -0700102argp.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count())
ncteisenff023ef2017-03-10 11:43:48 -0800103args = argp.parse_args()
104
Craig Tillerd23826e2017-03-27 21:25:15 -0700105assert args.diff_base
Craig Tiller76fc48b2017-03-07 17:21:16 -0800106
Craig Tillered2c2922017-03-27 23:23:45 -0700107def avg(lst):
Craig Tiller23e6a8a2017-03-30 07:24:01 -0700108 sum = 0.0
109 n = 0.0
Craig Tillered2c2922017-03-27 23:23:45 -0700110 for el in lst:
111 sum += el
112 n += 1
113 return sum / n
114
Craig Tillerd1d055a2017-03-29 01:52:28 -0700115def make_cmd(cfg):
Craig Tillerfda22622017-03-29 01:54:16 -0700116 return ['make'] + args.benchmarks + [
Craig Tiller691eefb2017-04-03 08:06:32 -0700117 'CONFIG=%s' % cfg, '-j', '%d' % args.jobs]
Craig Tillerd1d055a2017-03-29 01:52:28 -0700118
Craig Tiller233ad392017-04-14 08:25:53 -0700119def build(dest):
Craig Tiller0553b9c2017-04-14 08:31:17 -0700120 shutil.rmtree('bm_diff_%s' % dest, ignore_errors=True)
Craig Tillerd1d055a2017-03-29 01:52:28 -0700121 subprocess.check_call(['git', 'submodule', 'update'])
Craig Tillerba32fee2017-03-29 01:49:02 -0700122 try:
Craig Tillerd1d055a2017-03-29 01:52:28 -0700123 subprocess.check_call(make_cmd('opt'))
124 subprocess.check_call(make_cmd('counters'))
Craig Tillerba32fee2017-03-29 01:49:02 -0700125 except subprocess.CalledProcessError, e:
126 subprocess.check_call(['make', 'clean'])
Craig Tillerd1d055a2017-03-29 01:52:28 -0700127 subprocess.check_call(make_cmd('opt'))
128 subprocess.check_call(make_cmd('counters'))
Craig Tiller0553b9c2017-04-14 08:31:17 -0700129 os.rename('bins', 'bm_diff_%s' % dest)
Craig Tillerba32fee2017-03-29 01:49:02 -0700130
Craig Tiller67dd8552017-04-12 16:42:30 -0700131def collect1(bm, cfg, ver, idx):
Craig Tiller0553b9c2017-04-14 08:31:17 -0700132 cmd = ['bm_diff_%s/%s/%s' % (ver, cfg, bm),
Craig Tiller67dd8552017-04-12 16:42:30 -0700133 '--benchmark_out=%s.%s.%s.%d.json' % (bm, cfg, ver, idx),
Craig Tillerd23826e2017-03-27 21:25:15 -0700134 '--benchmark_out_format=json',
135 '--benchmark_repetitions=%d' % (args.repetitions)
136 ]
Craig Tiller233ad392017-04-14 08:25:53 -0700137 return jobset.JobSpec(cmd, shortname='%s %s %s %d/%d' % (bm, cfg, ver, idx+1, args.loops),
Craig Tiller7e313852017-04-03 15:14:08 -0700138 verbose_success=True, timeout_seconds=None)
Craig Tiller76fc48b2017-03-07 17:21:16 -0800139
Craig Tiller233ad392017-04-14 08:25:53 -0700140build('new')
141
142where_am_i = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).strip()
143subprocess.check_call(['git', 'checkout', args.diff_base])
Craig Tiller233ad392017-04-14 08:25:53 -0700144try:
145 build('old')
146finally:
147 subprocess.check_call(['git', 'checkout', where_am_i])
148 subprocess.check_call(['git', 'submodule', 'update'])
149
Craig Tillerbc442e12017-04-14 08:33:55 -0700150jobs = []
Craig Tiller0ebff8b2017-04-14 08:32:38 -0700151for loop in range(0, args.loops):
Craig Tiller233ad392017-04-14 08:25:53 -0700152 jobs.extend(x for x in itertools.chain(
Craig Tiller67dd8552017-04-12 16:42:30 -0700153 (collect1(bm, 'opt', 'new', loop) for bm in args.benchmarks),
154 (collect1(bm, 'counters', 'new', loop) for bm in args.benchmarks),
Craig Tiller233ad392017-04-14 08:25:53 -0700155 (collect1(bm, 'opt', 'old', loop) for bm in args.benchmarks),
156 (collect1(bm, 'counters', 'old', loop) for bm in args.benchmarks),
157 ))
Craig Tiller97e40da2017-04-14 09:04:07 -0700158random.shuffle(jobs, random.SystemRandom().random)
Craig Tiller67dd8552017-04-12 16:42:30 -0700159
Craig Tiller233ad392017-04-14 08:25:53 -0700160jobset.run(jobs, maxjobs=args.jobs)
Craig Tillerd23826e2017-03-27 21:25:15 -0700161
162class Benchmark:
163
164 def __init__(self):
165 self.samples = {
166 True: collections.defaultdict(list),
167 False: collections.defaultdict(list)
168 }
169 self.final = {}
170
171 def add_sample(self, data, new):
Craig Tiller9a212df2017-03-30 13:19:45 -0700172 for f in args.track:
Craig Tillerd23826e2017-03-27 21:25:15 -0700173 if f in data:
Craig Tiller4efb5e12017-03-27 22:37:00 -0700174 self.samples[new][f].append(float(data[f]))
Craig Tillerd23826e2017-03-27 21:25:15 -0700175
176 def process(self):
Craig Tiller9298a922017-03-31 06:51:48 -0700177 for f in sorted(args.track):
Craig Tillerd23826e2017-03-27 21:25:15 -0700178 new = self.samples[True][f]
179 old = self.samples[False][f]
180 if not new or not old: continue
Craig Tillerb1c02ca2017-04-05 14:11:28 -0700181 mdn_diff = abs(median(new) - median(old))
182 print '%s: new=%r old=%r mdn_diff=%r' % (f, new, old, mdn_diff)
Craig Tiller125b04f2017-04-04 21:31:44 -0700183 s = speedup.speedup(new, old)
Craig Tiller96464772017-04-13 22:06:25 -0700184 if abs(s) > 3 and mdn_diff > 0.5:
Craig Tillere5f18282017-04-05 09:08:37 -0700185 self.final[f] = '%+d%%' % s
Craig Tillerd23826e2017-03-27 21:25:15 -0700186 return self.final.keys()
187
Craig Tillera87b1382017-03-27 23:06:10 -0700188 def skip(self):
189 return not self.final
190
Craig Tillerd23826e2017-03-27 21:25:15 -0700191 def row(self, flds):
192 return [self.final[f] if f in self.final else '' for f in flds]
193
194
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700195def eintr_be_gone(fn):
196 """Run fn until it doesn't stop because of EINTR"""
Craig Tiller349907b2017-04-14 13:00:47 -0700197 while True:
198 try:
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700199 return fn()
Craig Tiller349907b2017-04-14 13:00:47 -0700200 except IOError, e:
201 if e.errno != errno.EINTR:
202 raise
203
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700204
Craig Tiller349907b2017-04-14 13:00:47 -0700205def read_json(filename):
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700206 with open(filename) as f: return json.loads(f.read())
Craig Tiller349907b2017-04-14 13:00:47 -0700207
Craig Tillerd23826e2017-03-27 21:25:15 -0700208
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700209def finalize():
210 benchmarks = collections.defaultdict(Benchmark)
Craig Tillerd23826e2017-03-27 21:25:15 -0700211
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700212 for bm in args.benchmarks:
213 for loop in range(0, args.loops):
214 js_new_ctr = read_json('%s.counters.new.%d.json' % (bm, loop))
215 js_new_opt = read_json('%s.opt.new.%d.json' % (bm, loop))
216 js_old_ctr = read_json('%s.counters.old.%d.json' % (bm, loop))
217 js_old_opt = read_json('%s.opt.old.%d.json' % (bm, loop))
Craig Tillerd23826e2017-03-27 21:25:15 -0700218
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700219 for row in bm_json.expand_json(js_new_ctr, js_new_opt):
220 print row
221 name = row['cpp_name']
222 if name.endswith('_mean') or name.endswith('_stddev'): continue
223 benchmarks[name].add_sample(row, True)
224 for row in bm_json.expand_json(js_old_ctr, js_old_opt):
225 print row
226 name = row['cpp_name']
227 if name.endswith('_mean') or name.endswith('_stddev'): continue
228 benchmarks[name].add_sample(row, False)
Craig Tillerd23826e2017-03-27 21:25:15 -0700229
Craig Tiller25e3c6d2017-04-18 13:57:38 -0700230 really_interesting = set()
231 for name, bm in benchmarks.items():
232 print name
233 really_interesting.update(bm.process())
234 fields = [f for f in args.track if f in really_interesting]
235
236 headers = ['Benchmark'] + fields
237 rows = []
238 for name in sorted(benchmarks.keys()):
239 if benchmarks[name].skip(): continue
240 rows.append([name] + benchmarks[name].row(fields))
241 if rows:
242 text = 'Performance differences noted:\n' + tabulate.tabulate(rows, headers=headers, floatfmt='+.2f')
243 else:
244 text = 'No significant performance differences'
245 comment_on_pr.comment_on_pr('```\n%s\n```' % text)
246 print text
247
248
249eintr_be_gone(finalize)
250