binary_size_differ.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/usr/bin/env vpython3
  2. #
  3. # Copyright 2021 The Chromium Authors. All rights reserved.
  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. import argparse
  8. import collections
  9. import copy
  10. import json
  11. import logging
  12. import math
  13. import os
  14. import re
  15. import shutil
  16. import subprocess
  17. import sys
  18. import tempfile
  19. import time
  20. import traceback
  21. import uuid
  22. from common import GetHostToolPathFromPlatform, GetHostArchFromPlatform
  23. from common import SDK_ROOT, DIR_SOURCE_ROOT
  24. from binary_sizes import ReadPackageSizesJson
  25. from binary_sizes import PACKAGES_SIZES_FILE
  26. # Eng is not responsible for changes that cause "reasonable growth" if the
  27. # uncompressed binary size does not grow.
  28. # First-warning will fail the test if the uncompressed and compressed size
  29. # grow, while always-fail will fail the test regardless of uncompressed growth
  30. # (solely based on compressed growth).
  31. _FIRST_WARNING_DELTA_BYTES = 12 * 1024 # 12 KiB
  32. _ALWAYS_FAIL_DELTA_BYTES = 100 * 1024 # 100 KiB
  33. _TRYBOT_DOC = 'https://chromium.googlesource.com/chromium/src/+/main/docs/speed/binary_size/fuchsia_binary_size_trybot.md'
  34. def ComputePackageDiffs(before_sizes_file, after_sizes_file):
  35. '''Computes difference between after and before diff, for each package.'''
  36. before_sizes = ReadPackageSizesJson(before_sizes_file)
  37. after_sizes = ReadPackageSizesJson(after_sizes_file)
  38. assert before_sizes.keys() == after_sizes.keys(), (
  39. 'Package files cannot'
  40. ' be compared with different packages: '
  41. '{} vs {}'.format(before_sizes.keys(), after_sizes.keys()))
  42. growth = {'compressed': {}, 'uncompressed': {}}
  43. status_code = 0
  44. summary = ''
  45. for package_name in before_sizes:
  46. growth['compressed'][package_name] = (after_sizes[package_name].compressed -
  47. before_sizes[package_name].compressed)
  48. growth['uncompressed'][package_name] = (
  49. after_sizes[package_name].uncompressed -
  50. before_sizes[package_name].uncompressed)
  51. # Developers are only responsible if uncompressed increases.
  52. if ((growth['compressed'][package_name] >= _FIRST_WARNING_DELTA_BYTES
  53. and growth['uncompressed'][package_name] > 0)
  54. # However, if compressed growth is unusually large, fail always.
  55. or growth['compressed'][package_name] >= _ALWAYS_FAIL_DELTA_BYTES):
  56. if not summary:
  57. summary = ('Size check failed! The following package(s) are affected:'
  58. '<br>')
  59. status_code = 1
  60. summary += (('- {} (compressed) grew by {} bytes (uncompressed growth:'
  61. ' {} bytes).<br>').format(
  62. package_name, growth['compressed'][package_name],
  63. growth['uncompressed'][package_name]))
  64. growth['status_code'] = status_code
  65. summary += ('<br>See the following document for more information about'
  66. ' this trybot:<br>{}'.format(_TRYBOT_DOC))
  67. growth['summary'] = summary
  68. # TODO(crbug.com/1266085): Investigate using these fields.
  69. growth['archive_filenames'] = []
  70. growth['links'] = []
  71. return growth
  72. def main():
  73. parser = argparse.ArgumentParser()
  74. parser.add_argument(
  75. '--before-dir',
  76. type=os.path.realpath,
  77. required=True,
  78. help='Location of the build without the patch',
  79. )
  80. parser.add_argument(
  81. '--after-dir',
  82. type=os.path.realpath,
  83. required=True,
  84. help='Location of the build with the patch',
  85. )
  86. parser.add_argument(
  87. '--results-path',
  88. type=os.path.realpath,
  89. required=True,
  90. help='Output path for the trybot result .json file',
  91. )
  92. parser.add_argument('--verbose',
  93. '-v',
  94. action='store_true',
  95. help='Enable verbose output')
  96. args = parser.parse_args()
  97. if args.verbose:
  98. print('Fuchsia binary sizes')
  99. print('Working directory', os.getcwd())
  100. print('Args:')
  101. for var in vars(args):
  102. print(' {}: {}'.format(var, getattr(args, var) or ''))
  103. if not os.path.isdir(args.before_dir) or not os.path.isdir(args.after_dir):
  104. raise Exception(
  105. 'Could not find build output directory "{}" or "{}".'.format(
  106. args.before_dir, args.after_dir))
  107. test_name = 'sizes'
  108. before_sizes_file = os.path.join(args.before_dir, test_name,
  109. PACKAGES_SIZES_FILE)
  110. after_sizes_file = os.path.join(args.after_dir, test_name,
  111. PACKAGES_SIZES_FILE)
  112. if not os.path.isfile(before_sizes_file):
  113. raise Exception(
  114. 'Could not find before sizes file: "{}"'.format(before_sizes_file))
  115. if not os.path.isfile(after_sizes_file):
  116. raise Exception(
  117. 'Could not find after sizes file: "{}"'.format(after_sizes_file))
  118. test_completed = False
  119. try:
  120. growth = ComputePackageDiffs(before_sizes_file, after_sizes_file)
  121. test_completed = True
  122. with open(args.results_path, 'wt') as results_file:
  123. json.dump(growth, results_file)
  124. except:
  125. _, value, trace = sys.exc_info()
  126. traceback.print_tb(trace)
  127. print(str(value))
  128. finally:
  129. return 0 if test_completed else 1
  130. if __name__ == '__main__':
  131. sys.exit(main())