1*cc02d7e2SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*cc02d7e2SAndroid Build Coastguard Worker# 3*cc02d7e2SAndroid Build Coastguard Worker# Copyright 2017 gRPC authors. 4*cc02d7e2SAndroid Build Coastguard Worker# 5*cc02d7e2SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*cc02d7e2SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*cc02d7e2SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*cc02d7e2SAndroid Build Coastguard Worker# 9*cc02d7e2SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*cc02d7e2SAndroid Build Coastguard Worker# 11*cc02d7e2SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*cc02d7e2SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*cc02d7e2SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*cc02d7e2SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*cc02d7e2SAndroid Build Coastguard Worker# limitations under the License. 16*cc02d7e2SAndroid Build Coastguard Worker""" Computes the diff between two qps runs and outputs significant results """ 17*cc02d7e2SAndroid Build Coastguard Worker 18*cc02d7e2SAndroid Build Coastguard Workerimport argparse 19*cc02d7e2SAndroid Build Coastguard Workerimport json 20*cc02d7e2SAndroid Build Coastguard Workerimport multiprocessing 21*cc02d7e2SAndroid Build Coastguard Workerimport os 22*cc02d7e2SAndroid Build Coastguard Workerimport shutil 23*cc02d7e2SAndroid Build Coastguard Workerimport subprocess 24*cc02d7e2SAndroid Build Coastguard Workerimport sys 25*cc02d7e2SAndroid Build Coastguard Worker 26*cc02d7e2SAndroid Build Coastguard Workerimport qps_scenarios 27*cc02d7e2SAndroid Build Coastguard Workerimport tabulate 28*cc02d7e2SAndroid Build Coastguard Worker 29*cc02d7e2SAndroid Build Coastguard Workersys.path.append( 30*cc02d7e2SAndroid Build Coastguard Worker os.path.join( 31*cc02d7e2SAndroid Build Coastguard Worker os.path.dirname(sys.argv[0]), "..", "microbenchmarks", "bm_diff" 32*cc02d7e2SAndroid Build Coastguard Worker ) 33*cc02d7e2SAndroid Build Coastguard Worker) 34*cc02d7e2SAndroid Build Coastguard Workerimport bm_speedup 35*cc02d7e2SAndroid Build Coastguard Worker 36*cc02d7e2SAndroid Build Coastguard Workersys.path.append( 37*cc02d7e2SAndroid Build Coastguard Worker os.path.join( 38*cc02d7e2SAndroid Build Coastguard Worker os.path.dirname(sys.argv[0]), "..", "..", "run_tests", "python_utils" 39*cc02d7e2SAndroid Build Coastguard Worker ) 40*cc02d7e2SAndroid Build Coastguard Worker) 41*cc02d7e2SAndroid Build Coastguard Workerimport check_on_pr 42*cc02d7e2SAndroid Build Coastguard Worker 43*cc02d7e2SAndroid Build Coastguard Worker 44*cc02d7e2SAndroid Build Coastguard Workerdef _args(): 45*cc02d7e2SAndroid Build Coastguard Worker argp = argparse.ArgumentParser(description="Perform diff on QPS Driver") 46*cc02d7e2SAndroid Build Coastguard Worker argp.add_argument( 47*cc02d7e2SAndroid Build Coastguard Worker "-d", 48*cc02d7e2SAndroid Build Coastguard Worker "--diff_base", 49*cc02d7e2SAndroid Build Coastguard Worker type=str, 50*cc02d7e2SAndroid Build Coastguard Worker help="Commit or branch to compare the current one to", 51*cc02d7e2SAndroid Build Coastguard Worker ) 52*cc02d7e2SAndroid Build Coastguard Worker argp.add_argument( 53*cc02d7e2SAndroid Build Coastguard Worker "-l", 54*cc02d7e2SAndroid Build Coastguard Worker "--loops", 55*cc02d7e2SAndroid Build Coastguard Worker type=int, 56*cc02d7e2SAndroid Build Coastguard Worker default=4, 57*cc02d7e2SAndroid Build Coastguard Worker help=( 58*cc02d7e2SAndroid Build Coastguard Worker "Number of loops for each benchmark. More loops cuts down on noise" 59*cc02d7e2SAndroid Build Coastguard Worker ), 60*cc02d7e2SAndroid Build Coastguard Worker ) 61*cc02d7e2SAndroid Build Coastguard Worker argp.add_argument( 62*cc02d7e2SAndroid Build Coastguard Worker "-j", 63*cc02d7e2SAndroid Build Coastguard Worker "--jobs", 64*cc02d7e2SAndroid Build Coastguard Worker type=int, 65*cc02d7e2SAndroid Build Coastguard Worker default=multiprocessing.cpu_count(), 66*cc02d7e2SAndroid Build Coastguard Worker help="Number of CPUs to use", 67*cc02d7e2SAndroid Build Coastguard Worker ) 68*cc02d7e2SAndroid Build Coastguard Worker args = argp.parse_args() 69*cc02d7e2SAndroid Build Coastguard Worker assert args.diff_base, "diff_base must be set" 70*cc02d7e2SAndroid Build Coastguard Worker return args 71*cc02d7e2SAndroid Build Coastguard Worker 72*cc02d7e2SAndroid Build Coastguard Worker 73*cc02d7e2SAndroid Build Coastguard Workerdef _make_cmd(jobs): 74*cc02d7e2SAndroid Build Coastguard Worker return ["make", "-j", "%d" % jobs, "qps_json_driver", "qps_worker"] 75*cc02d7e2SAndroid Build Coastguard Worker 76*cc02d7e2SAndroid Build Coastguard Worker 77*cc02d7e2SAndroid Build Coastguard Workerdef build(name, jobs): 78*cc02d7e2SAndroid Build Coastguard Worker shutil.rmtree("qps_diff_%s" % name, ignore_errors=True) 79*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(["git", "submodule", "update"]) 80*cc02d7e2SAndroid Build Coastguard Worker try: 81*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(_make_cmd(jobs)) 82*cc02d7e2SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 83*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(["make", "clean"]) 84*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(_make_cmd(jobs)) 85*cc02d7e2SAndroid Build Coastguard Worker os.rename("bins", "qps_diff_%s" % name) 86*cc02d7e2SAndroid Build Coastguard Worker 87*cc02d7e2SAndroid Build Coastguard Worker 88*cc02d7e2SAndroid Build Coastguard Workerdef _run_cmd(name, scenario, fname): 89*cc02d7e2SAndroid Build Coastguard Worker return [ 90*cc02d7e2SAndroid Build Coastguard Worker "qps_diff_%s/opt/qps_json_driver" % name, 91*cc02d7e2SAndroid Build Coastguard Worker "--scenarios_json", 92*cc02d7e2SAndroid Build Coastguard Worker scenario, 93*cc02d7e2SAndroid Build Coastguard Worker "--json_file_out", 94*cc02d7e2SAndroid Build Coastguard Worker fname, 95*cc02d7e2SAndroid Build Coastguard Worker ] 96*cc02d7e2SAndroid Build Coastguard Worker 97*cc02d7e2SAndroid Build Coastguard Worker 98*cc02d7e2SAndroid Build Coastguard Workerdef run(name, scenarios, loops): 99*cc02d7e2SAndroid Build Coastguard Worker for sn in scenarios: 100*cc02d7e2SAndroid Build Coastguard Worker for i in range(0, loops): 101*cc02d7e2SAndroid Build Coastguard Worker fname = "%s.%s.%d.json" % (sn, name, i) 102*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(_run_cmd(name, scenarios[sn], fname)) 103*cc02d7e2SAndroid Build Coastguard Worker 104*cc02d7e2SAndroid Build Coastguard Worker 105*cc02d7e2SAndroid Build Coastguard Workerdef _load_qps(fname): 106*cc02d7e2SAndroid Build Coastguard Worker try: 107*cc02d7e2SAndroid Build Coastguard Worker with open(fname) as f: 108*cc02d7e2SAndroid Build Coastguard Worker return json.loads(f.read())["qps"] 109*cc02d7e2SAndroid Build Coastguard Worker except IOError as e: 110*cc02d7e2SAndroid Build Coastguard Worker print(("IOError occurred reading file: %s" % fname)) 111*cc02d7e2SAndroid Build Coastguard Worker return None 112*cc02d7e2SAndroid Build Coastguard Worker except ValueError as e: 113*cc02d7e2SAndroid Build Coastguard Worker print(("ValueError occurred reading file: %s" % fname)) 114*cc02d7e2SAndroid Build Coastguard Worker return None 115*cc02d7e2SAndroid Build Coastguard Worker 116*cc02d7e2SAndroid Build Coastguard Worker 117*cc02d7e2SAndroid Build Coastguard Workerdef _median(ary): 118*cc02d7e2SAndroid Build Coastguard Worker assert len(ary) 119*cc02d7e2SAndroid Build Coastguard Worker ary = sorted(ary) 120*cc02d7e2SAndroid Build Coastguard Worker n = len(ary) 121*cc02d7e2SAndroid Build Coastguard Worker if n % 2 == 0: 122*cc02d7e2SAndroid Build Coastguard Worker return (ary[(n - 1) / 2] + ary[(n - 1) / 2 + 1]) / 2.0 123*cc02d7e2SAndroid Build Coastguard Worker else: 124*cc02d7e2SAndroid Build Coastguard Worker return ary[n / 2] 125*cc02d7e2SAndroid Build Coastguard Worker 126*cc02d7e2SAndroid Build Coastguard Worker 127*cc02d7e2SAndroid Build Coastguard Workerdef diff(scenarios, loops, old, new): 128*cc02d7e2SAndroid Build Coastguard Worker old_data = {} 129*cc02d7e2SAndroid Build Coastguard Worker new_data = {} 130*cc02d7e2SAndroid Build Coastguard Worker 131*cc02d7e2SAndroid Build Coastguard Worker # collect data 132*cc02d7e2SAndroid Build Coastguard Worker for sn in scenarios: 133*cc02d7e2SAndroid Build Coastguard Worker old_data[sn] = [] 134*cc02d7e2SAndroid Build Coastguard Worker new_data[sn] = [] 135*cc02d7e2SAndroid Build Coastguard Worker for i in range(loops): 136*cc02d7e2SAndroid Build Coastguard Worker old_data[sn].append(_load_qps("%s.%s.%d.json" % (sn, old, i))) 137*cc02d7e2SAndroid Build Coastguard Worker new_data[sn].append(_load_qps("%s.%s.%d.json" % (sn, new, i))) 138*cc02d7e2SAndroid Build Coastguard Worker 139*cc02d7e2SAndroid Build Coastguard Worker # crunch data 140*cc02d7e2SAndroid Build Coastguard Worker headers = ["Benchmark", "qps"] 141*cc02d7e2SAndroid Build Coastguard Worker rows = [] 142*cc02d7e2SAndroid Build Coastguard Worker for sn in scenarios: 143*cc02d7e2SAndroid Build Coastguard Worker mdn_diff = abs(_median(new_data[sn]) - _median(old_data[sn])) 144*cc02d7e2SAndroid Build Coastguard Worker print( 145*cc02d7e2SAndroid Build Coastguard Worker "%s: %s=%r %s=%r mdn_diff=%r" 146*cc02d7e2SAndroid Build Coastguard Worker % (sn, new, new_data[sn], old, old_data[sn], mdn_diff) 147*cc02d7e2SAndroid Build Coastguard Worker ) 148*cc02d7e2SAndroid Build Coastguard Worker s = bm_speedup.speedup(new_data[sn], old_data[sn], 10e-5) 149*cc02d7e2SAndroid Build Coastguard Worker if abs(s) > 3 and mdn_diff > 0.5: 150*cc02d7e2SAndroid Build Coastguard Worker rows.append([sn, "%+d%%" % s]) 151*cc02d7e2SAndroid Build Coastguard Worker 152*cc02d7e2SAndroid Build Coastguard Worker if rows: 153*cc02d7e2SAndroid Build Coastguard Worker return tabulate.tabulate(rows, headers=headers, floatfmt="+.2f") 154*cc02d7e2SAndroid Build Coastguard Worker else: 155*cc02d7e2SAndroid Build Coastguard Worker return None 156*cc02d7e2SAndroid Build Coastguard Worker 157*cc02d7e2SAndroid Build Coastguard Worker 158*cc02d7e2SAndroid Build Coastguard Workerdef main(args): 159*cc02d7e2SAndroid Build Coastguard Worker build("new", args.jobs) 160*cc02d7e2SAndroid Build Coastguard Worker 161*cc02d7e2SAndroid Build Coastguard Worker if args.diff_base: 162*cc02d7e2SAndroid Build Coastguard Worker where_am_i = ( 163*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_output( 164*cc02d7e2SAndroid Build Coastguard Worker ["git", "rev-parse", "--abbrev-ref", "HEAD"] 165*cc02d7e2SAndroid Build Coastguard Worker ) 166*cc02d7e2SAndroid Build Coastguard Worker .decode() 167*cc02d7e2SAndroid Build Coastguard Worker .strip() 168*cc02d7e2SAndroid Build Coastguard Worker ) 169*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(["git", "checkout", args.diff_base]) 170*cc02d7e2SAndroid Build Coastguard Worker try: 171*cc02d7e2SAndroid Build Coastguard Worker build("old", args.jobs) 172*cc02d7e2SAndroid Build Coastguard Worker finally: 173*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(["git", "checkout", where_am_i]) 174*cc02d7e2SAndroid Build Coastguard Worker subprocess.check_call(["git", "submodule", "update"]) 175*cc02d7e2SAndroid Build Coastguard Worker 176*cc02d7e2SAndroid Build Coastguard Worker run("new", qps_scenarios._SCENARIOS, args.loops) 177*cc02d7e2SAndroid Build Coastguard Worker run("old", qps_scenarios._SCENARIOS, args.loops) 178*cc02d7e2SAndroid Build Coastguard Worker 179*cc02d7e2SAndroid Build Coastguard Worker diff_output = diff(qps_scenarios._SCENARIOS, args.loops, "old", "new") 180*cc02d7e2SAndroid Build Coastguard Worker 181*cc02d7e2SAndroid Build Coastguard Worker if diff_output: 182*cc02d7e2SAndroid Build Coastguard Worker text = "[qps] Performance differences noted:\n%s" % diff_output 183*cc02d7e2SAndroid Build Coastguard Worker else: 184*cc02d7e2SAndroid Build Coastguard Worker text = "[qps] No significant performance differences" 185*cc02d7e2SAndroid Build Coastguard Worker print(("%s" % text)) 186*cc02d7e2SAndroid Build Coastguard Worker check_on_pr.check_on_pr("QPS", "```\n%s\n```" % text) 187*cc02d7e2SAndroid Build Coastguard Worker 188*cc02d7e2SAndroid Build Coastguard Worker 189*cc02d7e2SAndroid Build Coastguard Workerif __name__ == "__main__": 190*cc02d7e2SAndroid Build Coastguard Worker args = _args() 191*cc02d7e2SAndroid Build Coastguard Worker main(args) 192