xref: /aosp_15_r20/external/grpc-grpc/tools/profiling/bloat/bloat_diff.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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            "-DBUILD_SHARED_LIBS=ON",
64            "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
65            '-DCMAKE_C_FLAGS="-gsplit-dwarf"',
66            '-DCMAKE_CXX_FLAGS="-gsplit-dwarf"',
67            "..",
68        ],
69        cwd=output_dir,
70    )
71    subprocess.check_call("make -j%d" % args.jobs, shell=True, cwd=output_dir)
72
73
74def _rank_diff_bytes(diff_bytes):
75    """Determine how significant diff_bytes is, and return a simple integer representing that"""
76    mul = 1
77    if diff_bytes < 0:
78        mul = -1
79        diff_bytes = -diff_bytes
80    if diff_bytes < 2 * 1024:
81        return 0
82    if diff_bytes < 16 * 1024:
83        return 1 * mul
84    if diff_bytes < 128 * 1024:
85        return 2 * mul
86    return 3 * mul
87
88
89_build("bloat_diff_new")
90
91if args.diff_base:
92    where_am_i = (
93        subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
94        .decode()
95        .strip()
96    )
97    # checkout the diff base (="old")
98    subprocess.check_call(["git", "checkout", args.diff_base])
99    subprocess.check_call(["git", "submodule", "update"])
100    try:
101        _build("bloat_diff_old")
102    finally:
103        # restore the original revision (="new")
104        subprocess.check_call(["git", "checkout", where_am_i])
105        subprocess.check_call(["git", "submodule", "update"])
106
107pathlib.Path("bloaty-build").mkdir(exist_ok=True)
108subprocess.check_call(
109    ["cmake", "-G", "Unix Makefiles", "../third_party/bloaty"],
110    cwd="bloaty-build",
111)
112subprocess.check_call("make -j%d" % args.jobs, shell=True, cwd="bloaty-build")
113
114text = ""
115diff_size = 0
116for lib in LIBS:
117    text += (
118        "****************************************************************\n\n"
119    )
120    text += lib + "\n\n"
121    old_version = glob.glob("bloat_diff_old/%s" % lib)
122    new_version = glob.glob("bloat_diff_new/%s" % lib)
123    for filename in [old_version, new_version]:
124        if filename:
125            subprocess.check_call(
126                "strip %s -o %s.stripped" % (filename[0], filename[0]),
127                shell=True,
128            )
129    assert len(new_version) == 1
130    cmd = "bloaty-build/bloaty -d compileunits,symbols"
131    if old_version:
132        assert len(old_version) == 1
133        text += subprocess.check_output(
134            "%s -n 0 --debug-file=%s --debug-file=%s %s.stripped -- %s.stripped"
135            % (
136                cmd,
137                new_version[0],
138                old_version[0],
139                new_version[0],
140                old_version[0],
141            ),
142            shell=True,
143        ).decode()
144        sections = [
145            x
146            for x in csv.reader(
147                subprocess.check_output(
148                    "bloaty-build/bloaty -n 0 --csv %s -- %s"
149                    % (new_version[0], old_version[0]),
150                    shell=True,
151                )
152                .decode()
153                .splitlines()
154            )
155        ]
156        print(sections)
157        for section in sections[1:]:
158            # skip debug sections for bloat severity calculation
159            if section[0].startswith(".debug"):
160                continue
161            # skip dynamic loader sections too
162            if section[0].startswith(".dyn"):
163                continue
164            diff_size += int(section[2])
165    else:
166        text += subprocess.check_output(
167            "%s %s.stripped -n 0 --debug-file=%s"
168            % (cmd, new_version[0], new_version[0]),
169            shell=True,
170        ).decode()
171    text += "\n\n"
172
173severity = _rank_diff_bytes(diff_size)
174print("SEVERITY: %d" % severity)
175
176print(text)
177check_on_pr.check_on_pr("Bloat Difference", "```\n%s\n```" % text)
178check_on_pr.label_significance_on_pr("bloat", severity)
179