1#!/usr/bin/env python3 2# 3# Copyright 2022 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 re 25import shutil 26import subprocess 27import sys 28 29sys.path.append( 30 os.path.join( 31 os.path.dirname(sys.argv[0]), "..", "..", "run_tests", "python_utils" 32 ) 33) 34import check_on_pr 35 36argp = argparse.ArgumentParser(description="Perform diff on memory benchmarks") 37 38argp.add_argument( 39 "-d", 40 "--diff_base", 41 type=str, 42 help="Commit or branch to compare the current one to", 43) 44 45argp.add_argument("-j", "--jobs", type=int, default=multiprocessing.cpu_count()) 46 47args = argp.parse_args() 48 49_INTERESTING = { 50 "call/client": ( 51 rb"client call memory usage: ([0-9\.]+) bytes per call", 52 float, 53 ), 54 "call/server": ( 55 rb"server call memory usage: ([0-9\.]+) bytes per call", 56 float, 57 ), 58 "channel/client": ( 59 rb"client channel memory usage: ([0-9\.]+) bytes per channel", 60 float, 61 ), 62 "channel/server": ( 63 rb"server channel memory usage: ([0-9\.]+) bytes per channel", 64 float, 65 ), 66 "call/xds_client": ( 67 rb"xds client call memory usage: ([0-9\.]+) bytes per call", 68 float, 69 ), 70 "call/xds_server": ( 71 rb"xds server call memory usage: ([0-9\.]+) bytes per call", 72 float, 73 ), 74 "channel/xds_client": ( 75 rb"xds client channel memory usage: ([0-9\.]+) bytes per channel", 76 float, 77 ), 78 "channel/xds_server": ( 79 rb"xds server channel memory usage: ([0-9\.]+) bytes per channel", 80 float, 81 ), 82 "channel_multi_address/xds_client": ( 83 rb"xds multi_address client channel memory usage: ([0-9\.]+) bytes per channel", 84 float, 85 ), 86} 87 88_SCENARIOS = { 89 "default": [], 90 "minstack": ["--scenario_config=minstack"], 91} 92 93_BENCHMARKS = { 94 "call": ["--benchmark_names=call", "--size=50000"], 95 "channel": ["--benchmark_names=channel", "--size=10000"], 96 "channel_multi_address": [ 97 "--benchmark_names=channel_multi_address", 98 "--size=10000", 99 ], 100} 101 102 103def _run(): 104 """Build with Bazel, then run, and extract interesting lines from the output.""" 105 subprocess.check_call( 106 [ 107 "tools/bazel", 108 "build", 109 "-c", 110 "opt", 111 "test/core/memory_usage/memory_usage_test", 112 ] 113 ) 114 ret = {} 115 for name, benchmark_args in _BENCHMARKS.items(): 116 for use_xds in (False, True): 117 for scenario, extra_args in _SCENARIOS.items(): 118 # TODO(chenancy) Remove when minstack is implemented for channel 119 if name == "channel" and scenario == "minstack": 120 continue 121 if name == "channel_multi_address" and not use_xds: 122 continue 123 argv = ( 124 ["bazel-bin/test/core/memory_usage/memory_usage_test"] 125 + benchmark_args 126 + extra_args 127 ) 128 if use_xds: 129 argv.append("--use_xds") 130 try: 131 output = subprocess.check_output(argv) 132 except subprocess.CalledProcessError as e: 133 print("Error running benchmark:", e) 134 continue 135 for line in output.splitlines(): 136 for key, (pattern, conversion) in _INTERESTING.items(): 137 m = re.match(pattern, line) 138 if m: 139 ret[scenario + ": " + key] = conversion(m.group(1)) 140 return ret 141 142 143cur = _run() 144old = None 145 146if args.diff_base: 147 where_am_i = ( 148 subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) 149 .decode() 150 .strip() 151 ) 152 # checkout the diff base (="old") 153 subprocess.check_call(["git", "checkout", args.diff_base]) 154 try: 155 old = _run() 156 finally: 157 # restore the original revision (="cur") 158 subprocess.check_call(["git", "checkout", where_am_i]) 159 160text = "" 161if old is None: 162 print(cur) 163 for key, value in sorted(cur.items()): 164 text += "{}: {}\n".format(key, value) 165else: 166 print(cur, old) 167 call_diff_size = 0 168 channel_diff_size = 0 169 for scenario in _SCENARIOS.keys(): 170 for key, value in sorted(_INTERESTING.items()): 171 key = scenario + ": " + key 172 if key in cur: 173 if key not in old: 174 text += "{}: {}\n".format(key, cur[key]) 175 else: 176 text += "{}: {} -> {}\n".format(key, old[key], cur[key]) 177 if "call" in key: 178 call_diff_size += cur[key] - old[key] 179 else: 180 channel_diff_size += cur[key] - old[key] 181 182 print("CALL_DIFF_SIZE: %f" % call_diff_size) 183 print("CHANNEL_DIFF_SIZE: %f" % channel_diff_size) 184 check_on_pr.label_increase_decrease_on_pr( 185 "per-call-memory", call_diff_size, 64 186 ) 187 check_on_pr.label_increase_decrease_on_pr( 188 "per-channel-memory", channel_diff_size, 1000 189 ) 190 # TODO(chennancy)Change significant value when minstack also runs for channel 191 192print(text) 193check_on_pr.check_on_pr("Memory Difference", "```\n%s\n```" % text) 194