| #!/usr/bin/env vpython3 |
| # |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| '''Implements Chrome-Fuchsia package binary size differ.''' |
| |
| import argparse |
| import json |
| import os |
| import sys |
| import traceback |
| |
| from binary_sizes import ReadPackageSizesJson |
| from binary_sizes import PACKAGES_SIZES_FILE |
| |
| # Eng is not responsible for changes that cause "reasonable growth" if the |
| # uncompressed binary size does not grow. |
| # First-warning will fail the test if the uncompressed and compressed size |
| # grow, while always-fail will fail the test regardless of uncompressed growth |
| # (solely based on compressed growth). |
| _FIRST_WARNING_DELTA_BYTES = 12 * 1024 # 12 KiB |
| _ALWAYS_FAIL_DELTA_BYTES = 100 * 1024 # 100 KiB |
| _TRYBOT_DOC = 'https://chromium.googlesource.com/chromium/src/+/main/docs/speed/binary_size/fuchsia_binary_size_trybot.md' |
| |
| SIZE_FAILURE = 1 |
| ROLLER_SIZE_WARNING = 2 |
| SUCCESS = 0 |
| |
| |
| def ComputePackageDiffs(before_sizes_file, after_sizes_file, author=None): |
| '''Computes difference between after and before diff, for each package.''' |
| before_sizes = ReadPackageSizesJson(before_sizes_file) |
| after_sizes = ReadPackageSizesJson(after_sizes_file) |
| |
| assert before_sizes.keys() == after_sizes.keys(), ( |
| 'Package files cannot' |
| ' be compared with different packages: ' |
| '{} vs {}'.format(before_sizes.keys(), after_sizes.keys())) |
| |
| growth = {'compressed': {}, 'uncompressed': {}} |
| status_code = SUCCESS |
| summary = '' |
| for package_name in before_sizes: |
| growth['compressed'][package_name] = (after_sizes[package_name].compressed - |
| before_sizes[package_name].compressed) |
| growth['uncompressed'][package_name] = ( |
| after_sizes[package_name].uncompressed - |
| before_sizes[package_name].uncompressed) |
| # Developers are only responsible if uncompressed increases. |
| if ((growth['compressed'][package_name] >= _FIRST_WARNING_DELTA_BYTES |
| and growth['uncompressed'][package_name] > 0) |
| # However, if compressed growth is unusually large, fail always. |
| or growth['compressed'][package_name] >= _ALWAYS_FAIL_DELTA_BYTES): |
| if not summary: |
| summary = ('Size check failed! The following package(s) are affected:' |
| '<br>') |
| status_code = SIZE_FAILURE |
| summary += (('- {} (compressed) grew by {} bytes (uncompressed growth:' |
| ' {} bytes).<br>').format( |
| package_name, growth['compressed'][package_name], |
| growth['uncompressed'][package_name])) |
| summary += ('Note that this bot compares growth against trunk, and is ' |
| 'not aware of CL chaining.<br>') |
| |
| # Allow rollers to pass even with size increases. See crbug.com/1355914. |
| if author and '-autoroll' in author and status_code == SIZE_FAILURE: |
| summary = summary.replace('Size check failed! ', '') |
| summary = ( |
| 'The following growth by an autoroller will be ignored:<br><br>' + |
| summary) |
| status_code = ROLLER_SIZE_WARNING |
| growth['status_code'] = status_code |
| summary += ('<br>See the following document for more information about' |
| ' this trybot:<br>{}'.format(_TRYBOT_DOC)) |
| growth['summary'] = summary |
| |
| # TODO(crbug.com/1266085): Investigate using these fields. |
| growth['archive_filenames'] = [] |
| growth['links'] = [] |
| return growth |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--before-dir', |
| type=os.path.realpath, |
| required=True, |
| help='Location of the build without the patch', |
| ) |
| parser.add_argument( |
| '--after-dir', |
| type=os.path.realpath, |
| required=True, |
| help='Location of the build with the patch', |
| ) |
| parser.add_argument('--author', help='Author of change') |
| parser.add_argument( |
| '--results-path', |
| type=os.path.realpath, |
| required=True, |
| help='Output path for the trybot result .json file', |
| ) |
| parser.add_argument('--verbose', |
| '-v', |
| action='store_true', |
| help='Enable verbose output') |
| args = parser.parse_args() |
| |
| if args.verbose: |
| print('Fuchsia binary sizes') |
| print('Working directory', os.getcwd()) |
| print('Args:') |
| for var in vars(args): |
| print(' {}: {}'.format(var, getattr(args, var) or '')) |
| |
| if not os.path.isdir(args.before_dir) or not os.path.isdir(args.after_dir): |
| raise Exception( |
| 'Could not find build output directory "{}" or "{}".'.format( |
| args.before_dir, args.after_dir)) |
| |
| test_name = 'sizes' |
| before_sizes_file = os.path.join(args.before_dir, test_name, |
| PACKAGES_SIZES_FILE) |
| after_sizes_file = os.path.join(args.after_dir, test_name, |
| PACKAGES_SIZES_FILE) |
| if not os.path.isfile(before_sizes_file): |
| raise Exception( |
| 'Could not find before sizes file: "{}"'.format(before_sizes_file)) |
| |
| if not os.path.isfile(after_sizes_file): |
| raise Exception( |
| 'Could not find after sizes file: "{}"'.format(after_sizes_file)) |
| |
| test_completed = False |
| try: |
| growth = ComputePackageDiffs(before_sizes_file, |
| after_sizes_file, |
| author=args.author) |
| test_completed = True |
| with open(args.results_path, 'wt') as results_file: |
| json.dump(growth, results_file) |
| except: |
| _, value, trace = sys.exc_info() |
| traceback.print_tb(trace) |
| print(str(value)) |
| finally: |
| return 0 if test_completed else 1 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |