xref: /aosp_15_r20/external/skia/tools/calmbench/ab.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/python
2*c8dee2aaSAndroid Build Coastguard Worker# encoding: utf-8
3*c8dee2aaSAndroid Build Coastguard Worker
4*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2017 Google Inc.
5*c8dee2aaSAndroid Build Coastguard Worker#
6*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be found
7*c8dee2aaSAndroid Build Coastguard Worker# in the LICENSE file.
8*c8dee2aaSAndroid Build Coastguard Worker#
9*c8dee2aaSAndroid Build Coastguard Worker# This is an A/B test utility script used by calmbench.py
10*c8dee2aaSAndroid Build Coastguard Worker#
11*c8dee2aaSAndroid Build Coastguard Worker# For each bench, we get a distribution of min_ms measurements from nanobench.
12*c8dee2aaSAndroid Build Coastguard Worker# From that, we try to recover the 1/3 and 2/3 quantiles of the distribution.
13*c8dee2aaSAndroid Build Coastguard Worker# If range (1/3 quantile, 2/3 quantile) is completely disjoint between A and B,
14*c8dee2aaSAndroid Build Coastguard Worker# we report that as a regression.
15*c8dee2aaSAndroid Build Coastguard Worker#
16*c8dee2aaSAndroid Build Coastguard Worker# The more measurements we have for a bench, the more accurate our quantiles
17*c8dee2aaSAndroid Build Coastguard Worker# are. However, taking more measurements is time consuming. Hence we'll prune
18*c8dee2aaSAndroid Build Coastguard Worker# out benches and only take more measurements for benches whose current quantile
19*c8dee2aaSAndroid Build Coastguard Worker# ranges are disjoint.
20*c8dee2aaSAndroid Build Coastguard Worker#
21*c8dee2aaSAndroid Build Coastguard Worker# P.S. The current script is brute forcely translated from a ruby script. So it
22*c8dee2aaSAndroid Build Coastguard Worker# may be ugly...
23*c8dee2aaSAndroid Build Coastguard Worker
24*c8dee2aaSAndroid Build Coastguard Worker
25*c8dee2aaSAndroid Build Coastguard Workerfrom __future__ import print_function
26*c8dee2aaSAndroid Build Coastguard Workerimport re
27*c8dee2aaSAndroid Build Coastguard Workerimport os
28*c8dee2aaSAndroid Build Coastguard Workerimport sys
29*c8dee2aaSAndroid Build Coastguard Workerimport time
30*c8dee2aaSAndroid Build Coastguard Workerimport json
31*c8dee2aaSAndroid Build Coastguard Workerimport subprocess
32*c8dee2aaSAndroid Build Coastguard Workerimport shlex
33*c8dee2aaSAndroid Build Coastguard Workerimport multiprocessing
34*c8dee2aaSAndroid Build Coastguard Workerimport traceback
35*c8dee2aaSAndroid Build Coastguard Workerfrom argparse import ArgumentParser
36*c8dee2aaSAndroid Build Coastguard Workerfrom multiprocessing import Process
37*c8dee2aaSAndroid Build Coastguard Workerfrom threading import Thread
38*c8dee2aaSAndroid Build Coastguard Workerfrom threading import Lock
39*c8dee2aaSAndroid Build Coastguard Workerfrom pdb import set_trace
40*c8dee2aaSAndroid Build Coastguard Worker
41*c8dee2aaSAndroid Build Coastguard Worker
42*c8dee2aaSAndroid Build Coastguard WorkerHELP = """
43*c8dee2aaSAndroid Build Coastguard Worker\033[31mPlease call calmbench.py to drive this script if you're not doing so.
44*c8dee2aaSAndroid Build Coastguard WorkerThis script is not supposed to be used by itself. (At least, it's not easy to
45*c8dee2aaSAndroid Build Coastguard Workeruse by itself. The calmbench bots may use this script directly.)
46*c8dee2aaSAndroid Build Coastguard Worker\033[0m
47*c8dee2aaSAndroid Build Coastguard Worker"""
48*c8dee2aaSAndroid Build Coastguard Worker
49*c8dee2aaSAndroid Build Coastguard WorkerFACTOR  = 3     # lower/upper quantile factor
50*c8dee2aaSAndroid Build Coastguard WorkerDIFF_T  = 0.99  # different enough threshold
51*c8dee2aaSAndroid Build Coastguard WorkerTERM    = 10    # terminate after this no. of iterations without suspect changes
52*c8dee2aaSAndroid Build Coastguard WorkerMAXTRY  = 30    # max number of nanobench tries to narrow down suspects
53*c8dee2aaSAndroid Build Coastguard Worker
54*c8dee2aaSAndroid Build Coastguard WorkerUNITS   = "ns µs ms s".split()
55*c8dee2aaSAndroid Build Coastguard Worker
56*c8dee2aaSAndroid Build Coastguard Worker
57*c8dee2aaSAndroid Build Coastguard WorkertimesLock = Lock()
58*c8dee2aaSAndroid Build Coastguard WorkertimesA  = {}
59*c8dee2aaSAndroid Build Coastguard WorkertimesB  = {}
60*c8dee2aaSAndroid Build Coastguard Worker
61*c8dee2aaSAndroid Build Coastguard Worker
62*c8dee2aaSAndroid Build Coastguard Workerdef parse_args():
63*c8dee2aaSAndroid Build Coastguard Worker  parser = ArgumentParser(description=HELP)
64*c8dee2aaSAndroid Build Coastguard Worker
65*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('outdir', type=str, help="output directory")
66*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('a', type=str, help="name of A")
67*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('b', type=str, help="name of B")
68*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('nano_a', type=str, help="path to A's nanobench binary")
69*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('nano_b', type=str, help="path to B's nanobench binary")
70*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('arg_a', type=str, help="args for A's nanobench run")
71*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('arg_b', type=str, help="args for B's nanobench run")
72*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('repeat', type=int, help="number of initial runs")
73*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('skip_b', type=str, help=("whether to skip running B"
74*c8dee2aaSAndroid Build Coastguard Worker                                                " ('true' or 'false')"))
75*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('config', type=str, help="nanobenh config")
76*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('threads', type=int, help="number of threads to run")
77*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('noinit', type=str, help=("whether to skip running B"
78*c8dee2aaSAndroid Build Coastguard Worker                                                " ('true' or 'false')"))
79*c8dee2aaSAndroid Build Coastguard Worker
80*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('--concise', dest='concise', action="store_true",
81*c8dee2aaSAndroid Build Coastguard Worker      help="If set, no verbose thread info will be printed.")
82*c8dee2aaSAndroid Build Coastguard Worker  parser.set_defaults(concise=False)
83*c8dee2aaSAndroid Build Coastguard Worker
84*c8dee2aaSAndroid Build Coastguard Worker  # Additional args for bots
85*c8dee2aaSAndroid Build Coastguard Worker  BHELP = "bot specific options"
86*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('--githash', type=str, default="", help=BHELP)
87*c8dee2aaSAndroid Build Coastguard Worker  parser.add_argument('--keys', type=str, default=[], nargs='+', help=BHELP)
88*c8dee2aaSAndroid Build Coastguard Worker
89*c8dee2aaSAndroid Build Coastguard Worker  args = parser.parse_args()
90*c8dee2aaSAndroid Build Coastguard Worker  args.skip_b = args.skip_b == "true"
91*c8dee2aaSAndroid Build Coastguard Worker  args.noinit = args.noinit == "true"
92*c8dee2aaSAndroid Build Coastguard Worker
93*c8dee2aaSAndroid Build Coastguard Worker  if args.threads == -1:
94*c8dee2aaSAndroid Build Coastguard Worker    args.threads = 1
95*c8dee2aaSAndroid Build Coastguard Worker    if args.config in ["8888", "565"]: # multi-thread for CPU only
96*c8dee2aaSAndroid Build Coastguard Worker        args.threads = max(1, multiprocessing.cpu_count() / 2)
97*c8dee2aaSAndroid Build Coastguard Worker
98*c8dee2aaSAndroid Build Coastguard Worker  return args
99*c8dee2aaSAndroid Build Coastguard Worker
100*c8dee2aaSAndroid Build Coastguard Workerdef append_dict_sorted_array(dict_array, key, value):
101*c8dee2aaSAndroid Build Coastguard Worker  if key not in dict_array:
102*c8dee2aaSAndroid Build Coastguard Worker    dict_array[key] = []
103*c8dee2aaSAndroid Build Coastguard Worker  dict_array[key].append(value)
104*c8dee2aaSAndroid Build Coastguard Worker  dict_array[key].sort()
105*c8dee2aaSAndroid Build Coastguard Worker
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Workerdef add_time(args, name, bench, t, unit):
108*c8dee2aaSAndroid Build Coastguard Worker  normalized_t = t * 1000 ** UNITS.index(unit);
109*c8dee2aaSAndroid Build Coastguard Worker  if name.startswith(args.a):
110*c8dee2aaSAndroid Build Coastguard Worker    append_dict_sorted_array(timesA, bench, normalized_t)
111*c8dee2aaSAndroid Build Coastguard Worker  else:
112*c8dee2aaSAndroid Build Coastguard Worker    append_dict_sorted_array(timesB, bench, normalized_t)
113*c8dee2aaSAndroid Build Coastguard Worker
114*c8dee2aaSAndroid Build Coastguard Worker
115*c8dee2aaSAndroid Build Coastguard Workerdef append_times_from_file(args, name, filename):
116*c8dee2aaSAndroid Build Coastguard Worker  with open(filename) as f:
117*c8dee2aaSAndroid Build Coastguard Worker    lines = f.readlines()
118*c8dee2aaSAndroid Build Coastguard Worker  for line in lines:
119*c8dee2aaSAndroid Build Coastguard Worker    items = line.split()
120*c8dee2aaSAndroid Build Coastguard Worker    if len(items) > 10:
121*c8dee2aaSAndroid Build Coastguard Worker      bench = items[10]
122*c8dee2aaSAndroid Build Coastguard Worker      matches = re.search("([+-]?\d*.?\d+)(s|ms|µs|ns)", items[3])
123*c8dee2aaSAndroid Build Coastguard Worker      if (not matches or items[9] != args.config):
124*c8dee2aaSAndroid Build Coastguard Worker        continue
125*c8dee2aaSAndroid Build Coastguard Worker      time_num = matches.group(1)
126*c8dee2aaSAndroid Build Coastguard Worker      time_unit = matches.group(2)
127*c8dee2aaSAndroid Build Coastguard Worker      add_time(args, name, bench, float(time_num), time_unit)
128*c8dee2aaSAndroid Build Coastguard Worker
129*c8dee2aaSAndroid Build Coastguard Worker
130*c8dee2aaSAndroid Build Coastguard Workerclass ThreadWithException(Thread):
131*c8dee2aaSAndroid Build Coastguard Worker  def __init__(self, target):
132*c8dee2aaSAndroid Build Coastguard Worker    super(ThreadWithException, self).__init__(target = target)
133*c8dee2aaSAndroid Build Coastguard Worker    self.exception = None
134*c8dee2aaSAndroid Build Coastguard Worker
135*c8dee2aaSAndroid Build Coastguard Worker  def run(self):
136*c8dee2aaSAndroid Build Coastguard Worker    try:
137*c8dee2aaSAndroid Build Coastguard Worker      self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
138*c8dee2aaSAndroid Build Coastguard Worker    except BaseException as e:
139*c8dee2aaSAndroid Build Coastguard Worker      self.exception = e
140*c8dee2aaSAndroid Build Coastguard Worker
141*c8dee2aaSAndroid Build Coastguard Worker  def join(self, timeout=None):
142*c8dee2aaSAndroid Build Coastguard Worker    super(ThreadWithException, self).join(timeout)
143*c8dee2aaSAndroid Build Coastguard Worker
144*c8dee2aaSAndroid Build Coastguard Worker
145*c8dee2aaSAndroid Build Coastguard Workerclass ThreadRunner:
146*c8dee2aaSAndroid Build Coastguard Worker  """Simplest and stupidiest threaded executer."""
147*c8dee2aaSAndroid Build Coastguard Worker  def __init__(self, args):
148*c8dee2aaSAndroid Build Coastguard Worker    self.concise = args.concise
149*c8dee2aaSAndroid Build Coastguard Worker    self.threads = []
150*c8dee2aaSAndroid Build Coastguard Worker
151*c8dee2aaSAndroid Build Coastguard Worker  def add(self, args, fn):
152*c8dee2aaSAndroid Build Coastguard Worker    if len(self.threads) >= args.threads:
153*c8dee2aaSAndroid Build Coastguard Worker      self.wait()
154*c8dee2aaSAndroid Build Coastguard Worker    t = ThreadWithException(target = fn)
155*c8dee2aaSAndroid Build Coastguard Worker    t.daemon = True
156*c8dee2aaSAndroid Build Coastguard Worker    self.threads.append(t)
157*c8dee2aaSAndroid Build Coastguard Worker    t.start()
158*c8dee2aaSAndroid Build Coastguard Worker
159*c8dee2aaSAndroid Build Coastguard Worker  def wait(self):
160*c8dee2aaSAndroid Build Coastguard Worker    def spin():
161*c8dee2aaSAndroid Build Coastguard Worker      i = 0
162*c8dee2aaSAndroid Build Coastguard Worker      spinners = [".  ", ".. ", "..."]
163*c8dee2aaSAndroid Build Coastguard Worker      while len(self.threads) > 0:
164*c8dee2aaSAndroid Build Coastguard Worker        timesLock.acquire()
165*c8dee2aaSAndroid Build Coastguard Worker        sys.stderr.write(
166*c8dee2aaSAndroid Build Coastguard Worker            "\r" + spinners[i % len(spinners)] +
167*c8dee2aaSAndroid Build Coastguard Worker            " (%d threads running)" % len(self.threads) +
168*c8dee2aaSAndroid Build Coastguard Worker            "           \r" # spaces for erasing characters
169*c8dee2aaSAndroid Build Coastguard Worker        )
170*c8dee2aaSAndroid Build Coastguard Worker        timesLock.release()
171*c8dee2aaSAndroid Build Coastguard Worker        time.sleep(0.5)
172*c8dee2aaSAndroid Build Coastguard Worker        i += 1
173*c8dee2aaSAndroid Build Coastguard Worker
174*c8dee2aaSAndroid Build Coastguard Worker    if not self.concise:
175*c8dee2aaSAndroid Build Coastguard Worker      ts = Thread(target = spin);
176*c8dee2aaSAndroid Build Coastguard Worker      ts.start()
177*c8dee2aaSAndroid Build Coastguard Worker
178*c8dee2aaSAndroid Build Coastguard Worker    for t in self.threads:
179*c8dee2aaSAndroid Build Coastguard Worker      t.join()
180*c8dee2aaSAndroid Build Coastguard Worker
181*c8dee2aaSAndroid Build Coastguard Worker    exceptions = []
182*c8dee2aaSAndroid Build Coastguard Worker    for t in self.threads:
183*c8dee2aaSAndroid Build Coastguard Worker      if t.exception:
184*c8dee2aaSAndroid Build Coastguard Worker        exceptions.append(t.exception)
185*c8dee2aaSAndroid Build Coastguard Worker
186*c8dee2aaSAndroid Build Coastguard Worker    self.threads = []
187*c8dee2aaSAndroid Build Coastguard Worker
188*c8dee2aaSAndroid Build Coastguard Worker    if not self.concise:
189*c8dee2aaSAndroid Build Coastguard Worker      ts.join()
190*c8dee2aaSAndroid Build Coastguard Worker
191*c8dee2aaSAndroid Build Coastguard Worker    if len(exceptions):
192*c8dee2aaSAndroid Build Coastguard Worker      for exc in exceptions:
193*c8dee2aaSAndroid Build Coastguard Worker        print(exc)
194*c8dee2aaSAndroid Build Coastguard Worker      raise exceptions[0]
195*c8dee2aaSAndroid Build Coastguard Worker
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Workerdef split_arg(arg):
198*c8dee2aaSAndroid Build Coastguard Worker  raw = shlex.split(arg)
199*c8dee2aaSAndroid Build Coastguard Worker  result = []
200*c8dee2aaSAndroid Build Coastguard Worker  for r in raw:
201*c8dee2aaSAndroid Build Coastguard Worker    if '~' in r:
202*c8dee2aaSAndroid Build Coastguard Worker      result.append(os.path.expanduser(r))
203*c8dee2aaSAndroid Build Coastguard Worker    else:
204*c8dee2aaSAndroid Build Coastguard Worker      result.append(r)
205*c8dee2aaSAndroid Build Coastguard Worker  return result
206*c8dee2aaSAndroid Build Coastguard Worker
207*c8dee2aaSAndroid Build Coastguard Worker
208*c8dee2aaSAndroid Build Coastguard Workerdef run(args, threadRunner, name, nano, arg, i):
209*c8dee2aaSAndroid Build Coastguard Worker  def task():
210*c8dee2aaSAndroid Build Coastguard Worker    file_i = "%s/%s.out%d" % (args.outdir, name, i)
211*c8dee2aaSAndroid Build Coastguard Worker
212*c8dee2aaSAndroid Build Coastguard Worker    should_run = not args.noinit and not (name == args.b and args.skip_b)
213*c8dee2aaSAndroid Build Coastguard Worker    if i <= 0:
214*c8dee2aaSAndroid Build Coastguard Worker      should_run = True # always run for suspects
215*c8dee2aaSAndroid Build Coastguard Worker
216*c8dee2aaSAndroid Build Coastguard Worker    if should_run:
217*c8dee2aaSAndroid Build Coastguard Worker      if i > 0:
218*c8dee2aaSAndroid Build Coastguard Worker        timesLock.acquire()
219*c8dee2aaSAndroid Build Coastguard Worker        print("Init run %d for %s..." % (i, name))
220*c8dee2aaSAndroid Build Coastguard Worker        timesLock.release()
221*c8dee2aaSAndroid Build Coastguard Worker      subprocess.check_call(["touch", file_i])
222*c8dee2aaSAndroid Build Coastguard Worker      with open(file_i, 'w') as f:
223*c8dee2aaSAndroid Build Coastguard Worker        subprocess.check_call([nano] + split_arg(arg) +
224*c8dee2aaSAndroid Build Coastguard Worker                              ["--config", args.config], stderr=f, stdout=f)
225*c8dee2aaSAndroid Build Coastguard Worker
226*c8dee2aaSAndroid Build Coastguard Worker    timesLock.acquire()
227*c8dee2aaSAndroid Build Coastguard Worker    append_times_from_file(args, name, file_i)
228*c8dee2aaSAndroid Build Coastguard Worker    timesLock.release()
229*c8dee2aaSAndroid Build Coastguard Worker
230*c8dee2aaSAndroid Build Coastguard Worker  threadRunner.add(args, task)
231*c8dee2aaSAndroid Build Coastguard Worker
232*c8dee2aaSAndroid Build Coastguard Worker
233*c8dee2aaSAndroid Build Coastguard Workerdef init_run(args):
234*c8dee2aaSAndroid Build Coastguard Worker  threadRunner = ThreadRunner(args)
235*c8dee2aaSAndroid Build Coastguard Worker  for i in range(1, max(args.repeat, args.threads / 2) + 1):
236*c8dee2aaSAndroid Build Coastguard Worker    run(args, threadRunner, args.a, args.nano_a, args.arg_a, i)
237*c8dee2aaSAndroid Build Coastguard Worker    run(args, threadRunner, args.b, args.nano_b, args.arg_b, i)
238*c8dee2aaSAndroid Build Coastguard Worker  threadRunner.wait()
239*c8dee2aaSAndroid Build Coastguard Worker
240*c8dee2aaSAndroid Build Coastguard Worker
241*c8dee2aaSAndroid Build Coastguard Workerdef get_lower_upper(values):
242*c8dee2aaSAndroid Build Coastguard Worker  i = max(0, (len(values) - 1) / FACTOR)
243*c8dee2aaSAndroid Build Coastguard Worker  return values[i], values[-i - 1]
244*c8dee2aaSAndroid Build Coastguard Worker
245*c8dee2aaSAndroid Build Coastguard Worker
246*c8dee2aaSAndroid Build Coastguard Workerdef different_enough(lower1, upper2):
247*c8dee2aaSAndroid Build Coastguard Worker  return upper2 < DIFF_T * lower1
248*c8dee2aaSAndroid Build Coastguard Worker
249*c8dee2aaSAndroid Build Coastguard Worker
250*c8dee2aaSAndroid Build Coastguard Worker# TODO(liyuqian): we used this hacky criteria mainly because that I didn't have
251*c8dee2aaSAndroid Build Coastguard Worker# time to study more rigorous statistical tests. We should adopt a more rigorous
252*c8dee2aaSAndroid Build Coastguard Worker# test in the future.
253*c8dee2aaSAndroid Build Coastguard Workerdef get_suspects():
254*c8dee2aaSAndroid Build Coastguard Worker  suspects = []
255*c8dee2aaSAndroid Build Coastguard Worker  for bench in timesA.keys():
256*c8dee2aaSAndroid Build Coastguard Worker    if bench not in timesB:
257*c8dee2aaSAndroid Build Coastguard Worker      continue
258*c8dee2aaSAndroid Build Coastguard Worker    lowerA, upperA = get_lower_upper(timesA[bench])
259*c8dee2aaSAndroid Build Coastguard Worker    lowerB, upperB = get_lower_upper(timesB[bench])
260*c8dee2aaSAndroid Build Coastguard Worker    if different_enough(lowerA, upperB) or different_enough(lowerB, upperA):
261*c8dee2aaSAndroid Build Coastguard Worker      suspects.append(bench)
262*c8dee2aaSAndroid Build Coastguard Worker  return suspects
263*c8dee2aaSAndroid Build Coastguard Worker
264*c8dee2aaSAndroid Build Coastguard Worker
265*c8dee2aaSAndroid Build Coastguard Workerdef process_bench_pattern(s):
266*c8dee2aaSAndroid Build Coastguard Worker  if ".skp" in s: # skp bench won't match their exact names...
267*c8dee2aaSAndroid Build Coastguard Worker    return "^\"" + s[0:(s.index(".skp") + 3)] + "\""
268*c8dee2aaSAndroid Build Coastguard Worker  else:
269*c8dee2aaSAndroid Build Coastguard Worker    return "^\"" + s + "\"$"
270*c8dee2aaSAndroid Build Coastguard Worker
271*c8dee2aaSAndroid Build Coastguard Worker
272*c8dee2aaSAndroid Build Coastguard Workerdef suspects_arg(suspects):
273*c8dee2aaSAndroid Build Coastguard Worker  patterns = map(process_bench_pattern, suspects)
274*c8dee2aaSAndroid Build Coastguard Worker  return " --match " + (" ".join(patterns))
275*c8dee2aaSAndroid Build Coastguard Worker
276*c8dee2aaSAndroid Build Coastguard Worker
277*c8dee2aaSAndroid Build Coastguard Workerdef median(array):
278*c8dee2aaSAndroid Build Coastguard Worker  return array[len(array) / 2]
279*c8dee2aaSAndroid Build Coastguard Worker
280*c8dee2aaSAndroid Build Coastguard Worker
281*c8dee2aaSAndroid Build Coastguard Workerdef regression(bench):
282*c8dee2aaSAndroid Build Coastguard Worker  a = median(timesA[bench])
283*c8dee2aaSAndroid Build Coastguard Worker  b = median(timesB[bench])
284*c8dee2aaSAndroid Build Coastguard Worker  if (a == 0): # bad bench, just return no regression
285*c8dee2aaSAndroid Build Coastguard Worker    return 1
286*c8dee2aaSAndroid Build Coastguard Worker  return b / a
287*c8dee2aaSAndroid Build Coastguard Worker
288*c8dee2aaSAndroid Build Coastguard Worker
289*c8dee2aaSAndroid Build Coastguard Workerdef percentage(x):
290*c8dee2aaSAndroid Build Coastguard Worker  return (x - 1) * 100
291*c8dee2aaSAndroid Build Coastguard Worker
292*c8dee2aaSAndroid Build Coastguard Worker
293*c8dee2aaSAndroid Build Coastguard Workerdef format_r(r):
294*c8dee2aaSAndroid Build Coastguard Worker  return ('%6.2f' % percentage(r)) + "%"
295*c8dee2aaSAndroid Build Coastguard Worker
296*c8dee2aaSAndroid Build Coastguard Worker
297*c8dee2aaSAndroid Build Coastguard Workerdef normalize_r(r):
298*c8dee2aaSAndroid Build Coastguard Worker  if r > 1.0:
299*c8dee2aaSAndroid Build Coastguard Worker    return r - 1.0
300*c8dee2aaSAndroid Build Coastguard Worker  else:
301*c8dee2aaSAndroid Build Coastguard Worker    return 1.0 - 1/r
302*c8dee2aaSAndroid Build Coastguard Worker
303*c8dee2aaSAndroid Build Coastguard Worker
304*c8dee2aaSAndroid Build Coastguard Workerdef test():
305*c8dee2aaSAndroid Build Coastguard Worker  args = parse_args()
306*c8dee2aaSAndroid Build Coastguard Worker
307*c8dee2aaSAndroid Build Coastguard Worker  init_run(args)
308*c8dee2aaSAndroid Build Coastguard Worker  last_unchanged_iter = 0
309*c8dee2aaSAndroid Build Coastguard Worker  last_suspect_number = -1
310*c8dee2aaSAndroid Build Coastguard Worker  tryCnt = 0
311*c8dee2aaSAndroid Build Coastguard Worker  it = 0
312*c8dee2aaSAndroid Build Coastguard Worker  while tryCnt < MAXTRY:
313*c8dee2aaSAndroid Build Coastguard Worker    it += 1
314*c8dee2aaSAndroid Build Coastguard Worker    suspects = get_suspects()
315*c8dee2aaSAndroid Build Coastguard Worker    if len(suspects) != last_suspect_number:
316*c8dee2aaSAndroid Build Coastguard Worker      last_suspect_number = len(suspects)
317*c8dee2aaSAndroid Build Coastguard Worker      last_unchanged_iter = it
318*c8dee2aaSAndroid Build Coastguard Worker    if (len(suspects) == 0 or it - last_unchanged_iter >= TERM):
319*c8dee2aaSAndroid Build Coastguard Worker      break
320*c8dee2aaSAndroid Build Coastguard Worker
321*c8dee2aaSAndroid Build Coastguard Worker    print("Number of suspects at iteration %d: %d" % (it, len(suspects)))
322*c8dee2aaSAndroid Build Coastguard Worker    threadRunner = ThreadRunner(args)
323*c8dee2aaSAndroid Build Coastguard Worker    for j in range(1, max(1, args.threads / 2) + 1):
324*c8dee2aaSAndroid Build Coastguard Worker      run(args, threadRunner, args.a, args.nano_a,
325*c8dee2aaSAndroid Build Coastguard Worker          args.arg_a + suspects_arg(suspects), -j)
326*c8dee2aaSAndroid Build Coastguard Worker      run(args, threadRunner, args.b, args.nano_b,
327*c8dee2aaSAndroid Build Coastguard Worker          args.arg_b + suspects_arg(suspects), -j)
328*c8dee2aaSAndroid Build Coastguard Worker      tryCnt += 1
329*c8dee2aaSAndroid Build Coastguard Worker    threadRunner.wait()
330*c8dee2aaSAndroid Build Coastguard Worker
331*c8dee2aaSAndroid Build Coastguard Worker  suspects = get_suspects()
332*c8dee2aaSAndroid Build Coastguard Worker  if len(suspects) == 0:
333*c8dee2aaSAndroid Build Coastguard Worker    print(("%s and %s does not seem to have significant " + \
334*c8dee2aaSAndroid Build Coastguard Worker           "performance differences.") % (args.a, args.b))
335*c8dee2aaSAndroid Build Coastguard Worker  else:
336*c8dee2aaSAndroid Build Coastguard Worker    suspects.sort(key = regression)
337*c8dee2aaSAndroid Build Coastguard Worker    print("%s (compared to %s) is likely" % (args.a, args.b))
338*c8dee2aaSAndroid Build Coastguard Worker    for suspect in suspects:
339*c8dee2aaSAndroid Build Coastguard Worker      r = regression(suspect)
340*c8dee2aaSAndroid Build Coastguard Worker      if r < 1:
341*c8dee2aaSAndroid Build Coastguard Worker        print("\033[31m  %s slower in %s\033[0m" % (format_r(1/r), suspect))
342*c8dee2aaSAndroid Build Coastguard Worker      else:
343*c8dee2aaSAndroid Build Coastguard Worker        print("\033[32m  %s faster in %s\033[0m" % (format_r(r), suspect))
344*c8dee2aaSAndroid Build Coastguard Worker
345*c8dee2aaSAndroid Build Coastguard Worker  with open("%s/bench_%s_%s.json" % (args.outdir, args.a, args.b), 'w') as f:
346*c8dee2aaSAndroid Build Coastguard Worker    results = {}
347*c8dee2aaSAndroid Build Coastguard Worker    for bench in timesA:
348*c8dee2aaSAndroid Build Coastguard Worker      r = regression(bench) if bench in suspects else 1.0
349*c8dee2aaSAndroid Build Coastguard Worker      results[bench] = {
350*c8dee2aaSAndroid Build Coastguard Worker        args.config: {
351*c8dee2aaSAndroid Build Coastguard Worker          "signed_regression": normalize_r(r),
352*c8dee2aaSAndroid Build Coastguard Worker          "lower_quantile_ms": get_lower_upper(timesA[bench])[0] * 1e-6,
353*c8dee2aaSAndroid Build Coastguard Worker          "upper_quantile_ms": get_lower_upper(timesA[bench])[1] * 1e-6,
354*c8dee2aaSAndroid Build Coastguard Worker          "options": {
355*c8dee2aaSAndroid Build Coastguard Worker            # TODO(liyuqian): let ab.py call nanobench with --outResultsFile so
356*c8dee2aaSAndroid Build Coastguard Worker            # nanobench could generate the json for us that's exactly the same
357*c8dee2aaSAndroid Build Coastguard Worker            # as that being used by perf bots. Currently, we cannot guarantee
358*c8dee2aaSAndroid Build Coastguard Worker            # that bench is the name (e.g., bench may have additional resolution
359*c8dee2aaSAndroid Build Coastguard Worker            # information appended after name).
360*c8dee2aaSAndroid Build Coastguard Worker            "name": bench
361*c8dee2aaSAndroid Build Coastguard Worker          }
362*c8dee2aaSAndroid Build Coastguard Worker        }
363*c8dee2aaSAndroid Build Coastguard Worker      }
364*c8dee2aaSAndroid Build Coastguard Worker
365*c8dee2aaSAndroid Build Coastguard Worker    output = {"results": results}
366*c8dee2aaSAndroid Build Coastguard Worker    if args.githash:
367*c8dee2aaSAndroid Build Coastguard Worker      output["gitHash"] = args.githash
368*c8dee2aaSAndroid Build Coastguard Worker    if args.keys:
369*c8dee2aaSAndroid Build Coastguard Worker      keys = {}
370*c8dee2aaSAndroid Build Coastguard Worker      for i in range(len(args.keys) / 2):
371*c8dee2aaSAndroid Build Coastguard Worker        keys[args.keys[i * 2]] = args.keys[i * 2 + 1]
372*c8dee2aaSAndroid Build Coastguard Worker      output["key"] = keys
373*c8dee2aaSAndroid Build Coastguard Worker    f.write(json.dumps(output, indent=4))
374*c8dee2aaSAndroid Build Coastguard Worker    print(("\033[36mJSON results available in %s\033[0m" % f.name))
375*c8dee2aaSAndroid Build Coastguard Worker
376*c8dee2aaSAndroid Build Coastguard Worker  with open("%s/bench_%s_%s.csv" % (args.outdir, args.a, args.b), 'w') as out:
377*c8dee2aaSAndroid Build Coastguard Worker    out.write(("bench, significant?, raw regresion, " +
378*c8dee2aaSAndroid Build Coastguard Worker                   "%(A)s quantile (ns), %(B)s quantile (ns), " +
379*c8dee2aaSAndroid Build Coastguard Worker                   "%(A)s (ns), %(B)s (ns)\n") % {'A': args.a, 'B': args.b})
380*c8dee2aaSAndroid Build Coastguard Worker    for bench in suspects + timesA.keys():
381*c8dee2aaSAndroid Build Coastguard Worker      if (bench not in timesA or bench not in timesB):
382*c8dee2aaSAndroid Build Coastguard Worker        continue
383*c8dee2aaSAndroid Build Coastguard Worker      ta = timesA[bench]
384*c8dee2aaSAndroid Build Coastguard Worker      tb = timesB[bench]
385*c8dee2aaSAndroid Build Coastguard Worker      out.write(
386*c8dee2aaSAndroid Build Coastguard Worker          "%s, %s, %f, " % (bench, bench in suspects, regression(bench)) +
387*c8dee2aaSAndroid Build Coastguard Worker          ' '.join(map(str, get_lower_upper(ta))) + ", " +
388*c8dee2aaSAndroid Build Coastguard Worker          ' '.join(map(str, get_lower_upper(tb))) + ", " +
389*c8dee2aaSAndroid Build Coastguard Worker          ("%s, %s\n" % (' '.join(map(str, ta)), ' '.join(map(str, tb))))
390*c8dee2aaSAndroid Build Coastguard Worker      )
391*c8dee2aaSAndroid Build Coastguard Worker    print(("\033[36m" +
392*c8dee2aaSAndroid Build Coastguard Worker           "Compared %d benches. " +
393*c8dee2aaSAndroid Build Coastguard Worker           "%d of them seem to be significantly differrent." +
394*c8dee2aaSAndroid Build Coastguard Worker           "\033[0m") %
395*c8dee2aaSAndroid Build Coastguard Worker           (len([x for x in timesA if x in timesB]), len(suspects)))
396*c8dee2aaSAndroid Build Coastguard Worker    print("\033[36mPlease see detailed bench results in %s\033[0m" % out.name)
397*c8dee2aaSAndroid Build Coastguard Worker
398*c8dee2aaSAndroid Build Coastguard Worker
399*c8dee2aaSAndroid Build Coastguard Workerif __name__ == "__main__":
400*c8dee2aaSAndroid Build Coastguard Worker  try:
401*c8dee2aaSAndroid Build Coastguard Worker    test()
402*c8dee2aaSAndroid Build Coastguard Worker  except Exception as e:
403*c8dee2aaSAndroid Build Coastguard Worker    print(e)
404*c8dee2aaSAndroid Build Coastguard Worker    print(HELP)
405*c8dee2aaSAndroid Build Coastguard Worker    traceback.print_exc()
406*c8dee2aaSAndroid Build Coastguard Worker    raise e
407