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