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(os.path.dirname(sys.argv[0]), '..', '..', 'run_tests',
30                 'python_utils'))
31import check_on_pr
32
33argp = argparse.ArgumentParser(description='Perform diff on microbenchmarks')
34
35argp.add_argument('-d',
36                  '--diff_base',
37                  type=str,
38                  help='Commit or branch to compare the current one to')
39
40argp.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count())
41
42args = argp.parse_args()
43
44# the libraries for which check bloat difference is calculated
45LIBS = [
46    'libgrpc.so',
47    'libgrpc++.so',
48]
49
50
51def _build(output_dir):
52    """Perform the cmake build under the output_dir."""
53    shutil.rmtree(output_dir, ignore_errors=True)
54    subprocess.check_call('mkdir -p %s' % output_dir, shell=True, cwd='.')
55    subprocess.check_call([
56        'cmake', '-DgRPC_BUILD_TESTS=OFF', '-DBUILD_SHARED_LIBS=ON',
57        '-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DCMAKE_C_FLAGS="-gsplit-dwarf"',
58        '-DCMAKE_CXX_FLAGS="-gsplit-dwarf"', '..'
59    ],
60                          cwd=output_dir)
61    subprocess.check_call('make -j%d' % args.jobs, shell=True, cwd=output_dir)
62
63
64def _rank_diff_bytes(diff_bytes):
65    """Determine how significant diff_bytes is, and return a simple integer representing that"""
66    mul = 1
67    if diff_bytes < 0:
68        mul = -1
69        diff_bytes = -diff_bytes
70    if diff_bytes < 2 * 1024:
71        return 0
72    if diff_bytes < 16 * 1024:
73        return 1 * mul
74    if diff_bytes < 128 * 1024:
75        return 2 * mul
76    return 3 * mul
77
78
79_build('bloat_diff_new')
80
81if args.diff_base:
82    where_am_i = subprocess.check_output(
83        ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode().strip()
84    # checkout the diff base (="old")
85    subprocess.check_call(['git', 'checkout', args.diff_base])
86    subprocess.check_call(['git', 'submodule', 'update'])
87    try:
88        _build('bloat_diff_old')
89    finally:
90        # restore the original revision (="new")
91        subprocess.check_call(['git', 'checkout', where_am_i])
92        subprocess.check_call(['git', 'submodule', 'update'])
93
94pathlib.Path('bloaty-build').mkdir(exist_ok=True)
95subprocess.check_call(
96    ['cmake', '-G', 'Unix Makefiles', '../third_party/bloaty'],
97    cwd='bloaty-build')
98subprocess.check_call('make -j%d' % args.jobs, shell=True, cwd='bloaty-build')
99
100text = ''
101diff_size = 0
102for lib in LIBS:
103    text += '****************************************************************\n\n'
104    text += lib + '\n\n'
105    old_version = glob.glob('bloat_diff_old/%s' % lib)
106    new_version = glob.glob('bloat_diff_new/%s' % lib)
107    for filename in [old_version, new_version]:
108        if filename:
109            subprocess.check_call('strip %s -o %s.stripped' %
110                                  (filename[0], filename[0]),
111                                  shell=True)
112    assert len(new_version) == 1
113    cmd = 'bloaty-build/bloaty -d compileunits,symbols'
114    if old_version:
115        assert len(old_version) == 1
116        text += subprocess.check_output(
117            '%s -n 0 --debug-file=%s --debug-file=%s %s.stripped -- %s.stripped'
118            % (cmd, new_version[0], old_version[0], new_version[0],
119               old_version[0]),
120            shell=True).decode()
121        sections = [
122            x for x in csv.reader(
123                subprocess.check_output(
124                    'bloaty-build/bloaty -n 0 --csv %s -- %s' %
125                    (new_version[0], old_version[0]),
126                    shell=True).decode().splitlines())
127        ]
128        print(sections)
129        for section in sections[1:]:
130            # skip debug sections for bloat severity calculation
131            if section[0].startswith(".debug"):
132                continue
133            # skip dynamic loader sections too
134            if section[0].startswith(".dyn"):
135                continue
136            diff_size += int(section[2])
137    else:
138        text += subprocess.check_output('%s %s.stripped -n 0 --debug-file=%s' %
139                                        (cmd, new_version[0], new_version[0]),
140                                        shell=True).decode()
141    text += '\n\n'
142
143severity = _rank_diff_bytes(diff_size)
144print("SEVERITY: %d" % severity)
145
146print(text)
147check_on_pr.check_on_pr('Bloat Difference', '```\n%s\n```' % text)
148check_on_pr.label_significance_on_pr('bloat', severity)
149