xref: /aosp_15_r20/external/cronet/build/fuchsia/binary_size_differ.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env vpython3
2#
3# Copyright 2021 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6'''Implements Chrome-Fuchsia package binary size differ.'''
7
8import argparse
9import json
10import os
11import sys
12import traceback
13
14from binary_sizes import ReadPackageSizesJson
15from binary_sizes import PACKAGES_SIZES_FILE
16
17# Eng is not responsible for changes that cause "reasonable growth" if the
18# uncompressed binary size does not grow.
19# First-warning will fail the test if the uncompressed and compressed size
20# grow, while always-fail will fail the test regardless of uncompressed growth
21# (solely based on compressed growth).
22_FIRST_WARNING_DELTA_BYTES = 12 * 1024  # 12 KiB
23_ALWAYS_FAIL_DELTA_BYTES = 100 * 1024  # 100 KiB
24_TRYBOT_DOC = 'https://chromium.googlesource.com/chromium/src/+/main/docs/speed/binary_size/fuchsia_binary_size_trybot.md'
25
26SIZE_FAILURE = 1
27ROLLER_SIZE_WARNING = 2
28SUCCESS = 0
29
30
31def ComputePackageDiffs(before_sizes_file, after_sizes_file, author=None):
32  '''Computes difference between after and before diff, for each package.'''
33  before_sizes = ReadPackageSizesJson(before_sizes_file)
34  after_sizes = ReadPackageSizesJson(after_sizes_file)
35
36  assert before_sizes.keys() == after_sizes.keys(), (
37      'Package files cannot'
38      ' be compared with different packages: '
39      '{} vs {}'.format(before_sizes.keys(), after_sizes.keys()))
40
41  growth = {'compressed': {}, 'uncompressed': {}}
42  status_code = SUCCESS
43  summary = ''
44  for package_name in before_sizes:
45    growth['compressed'][package_name] = (after_sizes[package_name].compressed -
46                                          before_sizes[package_name].compressed)
47    growth['uncompressed'][package_name] = (
48        after_sizes[package_name].uncompressed -
49        before_sizes[package_name].uncompressed)
50    # Developers are only responsible if uncompressed increases.
51    if ((growth['compressed'][package_name] >= _FIRST_WARNING_DELTA_BYTES
52         and growth['uncompressed'][package_name] > 0)
53        # However, if compressed growth is unusually large, fail always.
54        or growth['compressed'][package_name] >= _ALWAYS_FAIL_DELTA_BYTES):
55      if not summary:
56        summary = ('Size check failed! The following package(s) are affected:'
57                   '<br>')
58      status_code = SIZE_FAILURE
59      summary += (('- {} (compressed) grew by {} bytes (uncompressed growth:'
60                   ' {} bytes).<br>').format(
61                       package_name, growth['compressed'][package_name],
62                       growth['uncompressed'][package_name]))
63      summary += ('Note that this bot compares growth against trunk, and is '
64                  'not aware of CL chaining.<br>')
65
66  # Allow rollers to pass even with size increases. See crbug.com/1355914.
67  if author and '-autoroll' in author and status_code == SIZE_FAILURE:
68    summary = summary.replace('Size check failed! ', '')
69    summary = (
70        'The following growth by an autoroller will be ignored:<br><br>' +
71        summary)
72    status_code = ROLLER_SIZE_WARNING
73  growth['status_code'] = status_code
74  summary += ('<br>See the following document for more information about'
75              ' this trybot:<br>{}'.format(_TRYBOT_DOC))
76  growth['summary'] = summary
77
78  # TODO(crbug.com/1266085): Investigate using these fields.
79  growth['archive_filenames'] = []
80  growth['links'] = []
81  return growth
82
83
84def main():
85  parser = argparse.ArgumentParser()
86  parser.add_argument(
87      '--before-dir',
88      type=os.path.realpath,
89      required=True,
90      help='Location of the build without the patch',
91  )
92  parser.add_argument(
93      '--after-dir',
94      type=os.path.realpath,
95      required=True,
96      help='Location of the build with the patch',
97  )
98  parser.add_argument('--author', help='Author of change')
99  parser.add_argument(
100      '--results-path',
101      type=os.path.realpath,
102      required=True,
103      help='Output path for the trybot result .json file',
104  )
105  parser.add_argument('--verbose',
106                      '-v',
107                      action='store_true',
108                      help='Enable verbose output')
109  args = parser.parse_args()
110
111  if args.verbose:
112    print('Fuchsia binary sizes')
113    print('Working directory', os.getcwd())
114    print('Args:')
115    for var in vars(args):
116      print('  {}: {}'.format(var, getattr(args, var) or ''))
117
118  if not os.path.isdir(args.before_dir) or not os.path.isdir(args.after_dir):
119    raise Exception(
120        'Could not find build output directory "{}" or "{}".'.format(
121            args.before_dir, args.after_dir))
122
123  test_name = 'sizes'
124  before_sizes_file = os.path.join(args.before_dir, test_name,
125                                   PACKAGES_SIZES_FILE)
126  after_sizes_file = os.path.join(args.after_dir, test_name,
127                                  PACKAGES_SIZES_FILE)
128  if not os.path.isfile(before_sizes_file):
129    raise Exception(
130        'Could not find before sizes file: "{}"'.format(before_sizes_file))
131
132  if not os.path.isfile(after_sizes_file):
133    raise Exception(
134        'Could not find after sizes file: "{}"'.format(after_sizes_file))
135
136  test_completed = False
137  try:
138    growth = ComputePackageDiffs(before_sizes_file,
139                                 after_sizes_file,
140                                 author=args.author)
141    test_completed = True
142    with open(args.results_path, 'wt') as results_file:
143      json.dump(growth, results_file)
144  except:
145    _, value, trace = sys.exc_info()
146    traceback.print_tb(trace)
147    print(str(value))
148  finally:
149    return 0 if test_completed else 1
150
151
152if __name__ == '__main__':
153  sys.exit(main())
154