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