1#!/usr/bin/env python3 2# 3# Copyright 2017 gRPC authors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import csv 19import glob 20import math 21import multiprocessing 22import os 23import pathlib 24import shutil 25import subprocess 26import sys 27 28sys.path.append( 29 os.path.join( 30 os.path.dirname(sys.argv[0]), "..", "..", "run_tests", "python_utils" 31 ) 32) 33import check_on_pr 34 35argp = argparse.ArgumentParser(description="Perform diff on microbenchmarks") 36 37argp.add_argument( 38 "-d", 39 "--diff_base", 40 type=str, 41 help="Commit or branch to compare the current one to", 42) 43 44argp.add_argument("-j", "--jobs", type=int, default=multiprocessing.cpu_count()) 45 46args = argp.parse_args() 47 48# the libraries for which check bloat difference is calculated 49LIBS = [ 50 "libgrpc.so", 51 "libgrpc++.so", 52] 53 54 55def _build(output_dir): 56 """Perform the cmake build under the output_dir.""" 57 shutil.rmtree(output_dir, ignore_errors=True) 58 subprocess.check_call("mkdir -p %s" % output_dir, shell=True, cwd=".") 59 subprocess.check_call( 60 [ 61 "cmake", 62 "-DgRPC_BUILD_TESTS=OFF", 63 "-DCMAKE_CXX_STANDARD=17", 64 "-DBUILD_SHARED_LIBS=ON", 65 "-DCMAKE_BUILD_TYPE=RelWithDebInfo", 66 '-DCMAKE_C_FLAGS="-gsplit-dwarf"', 67 '-DCMAKE_CXX_FLAGS="-gsplit-dwarf"', 68 "..", 69 ], 70 cwd=output_dir, 71 ) 72 subprocess.check_call("make -j%d" % args.jobs, shell=True, cwd=output_dir) 73 74 75def _rank_diff_bytes(diff_bytes): 76 """Determine how significant diff_bytes is, and return a simple integer representing that""" 77 mul = 1 78 if diff_bytes < 0: 79 mul = -1 80 diff_bytes = -diff_bytes 81 if diff_bytes < 2 * 1024: 82 return 0 83 if diff_bytes < 16 * 1024: 84 return 1 * mul 85 if diff_bytes < 128 * 1024: 86 return 2 * mul 87 return 3 * mul 88 89 90_build("bloat_diff_new") 91 92if args.diff_base: 93 where_am_i = ( 94 subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) 95 .decode() 96 .strip() 97 ) 98 # checkout the diff base (="old") 99 subprocess.check_call(["git", "checkout", args.diff_base]) 100 subprocess.check_call(["git", "submodule", "update"]) 101 try: 102 _build("bloat_diff_old") 103 finally: 104 # restore the original revision (="new") 105 subprocess.check_call(["git", "checkout", where_am_i]) 106 subprocess.check_call(["git", "submodule", "update"]) 107 108pathlib.Path("bloaty-build").mkdir(exist_ok=True) 109subprocess.check_call( 110 ["cmake", "-G", "Unix Makefiles", "../third_party/bloaty"], 111 cwd="bloaty-build", 112) 113subprocess.check_call("make -j%d" % args.jobs, shell=True, cwd="bloaty-build") 114 115text = "" 116diff_size = 0 117for lib in LIBS: 118 text += ( 119 "****************************************************************\n\n" 120 ) 121 text += lib + "\n\n" 122 old_version = glob.glob("bloat_diff_old/%s" % lib) 123 new_version = glob.glob("bloat_diff_new/%s" % lib) 124 for filename in [old_version, new_version]: 125 if filename: 126 subprocess.check_call( 127 "strip %s -o %s.stripped" % (filename[0], filename[0]), 128 shell=True, 129 ) 130 assert len(new_version) == 1 131 cmd = "bloaty-build/bloaty -d compileunits,symbols" 132 if old_version: 133 assert len(old_version) == 1 134 text += subprocess.check_output( 135 "%s -n 0 --debug-file=%s --debug-file=%s %s.stripped -- %s.stripped" 136 % ( 137 cmd, 138 new_version[0], 139 old_version[0], 140 new_version[0], 141 old_version[0], 142 ), 143 shell=True, 144 ).decode() 145 sections = [ 146 x 147 for x in csv.reader( 148 subprocess.check_output( 149 "bloaty-build/bloaty -n 0 --csv %s -- %s" 150 % (new_version[0], old_version[0]), 151 shell=True, 152 ) 153 .decode() 154 .splitlines() 155 ) 156 ] 157 print(sections) 158 for section in sections[1:]: 159 # skip debug sections for bloat severity calculation 160 if section[0].startswith(".debug"): 161 continue 162 # skip dynamic loader sections too 163 if section[0].startswith(".dyn"): 164 continue 165 diff_size += int(section[2]) 166 else: 167 text += subprocess.check_output( 168 "%s %s.stripped -n 0 --debug-file=%s" 169 % (cmd, new_version[0], new_version[0]), 170 shell=True, 171 ).decode() 172 text += "\n\n" 173 174severity = _rank_diff_bytes(diff_size) 175print("SEVERITY: %d" % severity) 176 177print(text) 178check_on_pr.check_on_pr("Bloat Difference", "```\n%s\n```" % text) 179check_on_pr.label_significance_on_pr("bloat", severity) 180