| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from recipe_engine import post_process |
| |
| DEPS = [ |
| 'chromium', |
| 'chromium_android', |
| 'chromium_checkout', |
| 'chromium_tests', |
| 'depot_tools/bot_update', |
| 'depot_tools/gclient', |
| 'depot_tools/gerrit', |
| 'depot_tools/gsutil', |
| 'depot_tools/tryserver', |
| 'filter', |
| 'recipe_engine/buildbucket', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/json', |
| 'recipe_engine/path', |
| 'recipe_engine/platform', |
| 'recipe_engine/properties', |
| 'recipe_engine/python', |
| 'recipe_engine/step', |
| 'recipe_engine/time', |
| ] |
| |
| _ANALYZE_TARGETS = [ |
| '//chrome/android:monochrome_public_apk', |
| '//tools/binary_size:binary_size_trybot_py', |
| ] |
| _COMPILE_TARGETS = [ |
| 'monochrome_public_apk', |
| 'monochrome_static_initializers', |
| ] |
| _APK_NAME = 'MonochromePublic.apk' |
| _PATCH_FIXED_BUILD_STEP_NAME = ( |
| 'Not measuring binary size because build is broken without patch.') |
| _FOOTER_PRESENT_STEP_NAME = ( |
| 'Not measuring binary size because Binary-Size justification was provided.') |
| _NDJSON_GS_BUCKET = 'chromium-binary-size-trybot-results' |
| _HTML_REPORT_BASE_URL = ( |
| 'https://storage.googleapis.com/chrome-supersize/viewer.html?load_url=' |
| 'https://storage.googleapis.com/' + _NDJSON_GS_BUCKET + '/') |
| |
| _TEST_TIME = 1454371200 |
| _TEST_BUILDNUMBER = '200' |
| _TEST_TIME_FMT = '2016/02/02' |
| |
| |
| def RunSteps(api): |
| assert api.tryserver.is_tryserver |
| |
| with api.chromium.chromium_layout(): |
| api.gclient.set_config('chromium') |
| api.gclient.apply_config('android') |
| api.chromium.set_config('chromium') |
| api.chromium.apply_config('mb') |
| api.chromium_android.set_config('base_config') |
| |
| revision_info = api.gerrit.get_revision_info( |
| api.properties['patch_gerrit_url'], |
| api.properties['patch_issue'], |
| api.properties['patch_set']) |
| author = revision_info['commit']['author']['email'] |
| # get_footer returns a list of footer values. |
| size_footers = api.tryserver.get_footer( |
| 'Binary-Size', patch_text=revision_info['commit']['message']) |
| # Short-circuit early so that the bot is fast when disabled via header. |
| # Although the bot is also meant to test compiles of official builds, |
| # headers are generally only added when a previous job compiles fine and |
| # fails with the "You need to add the header" message. |
| if size_footers: |
| api.python.succeeding_step(_FOOTER_PRESENT_STEP_NAME, '') |
| return |
| |
| suffix = ' (with patch)' |
| bot_config = {} |
| checkout_dir = api.chromium_checkout.get_checkout_dir(bot_config) |
| with api.context(cwd=checkout_dir): |
| bot_update_step = api.chromium_checkout.ensure_checkout(bot_config) |
| api.chromium.runhooks(name='runhooks' + suffix) |
| |
| affected_files = api.chromium_checkout.get_files_affected_by_patch() |
| if not api.filter.analyze(affected_files, _ANALYZE_TARGETS, None, |
| 'trybot_analyze_config.json')[0]: |
| return |
| |
| api.chromium.ensure_goma() |
| with_results_dir = _BuildAndMeasure(api, True) |
| |
| with api.context(cwd=api.chromium_checkout.working_dir): |
| api.bot_update.deapply_patch(bot_update_step) |
| |
| with api.context(cwd=api.path['checkout']): |
| suffix = ' (without patch)' |
| try: |
| api.chromium.runhooks(name='runhooks' + suffix) |
| without_results_dir = _BuildAndMeasure(api, False) |
| except api.step.StepFailure: |
| api.python.succeeding_step(_PATCH_FIXED_BUILD_STEP_NAME, '') |
| return |
| |
| # Re-apply patch so that the diff scripts can be tested via tryjobs. |
| # We could build without-patch first to avoid having to apply the patch |
| # twice, but it's nicer to fail fast when the patch does not compile. |
| suffix = ' (with patch again)' |
| with api.context(cwd=checkout_dir): |
| bot_update_step = api.bot_update.ensure_checkout(suffix=suffix, |
| patch=True) |
| api.chromium.runhooks(name='runhooks' + suffix) |
| |
| with api.context(cwd=api.path['checkout']): |
| output_dir = api.chromium.output_dir |
| resource_sizes_diff_path = output_dir.join('resource_sizes_diff.txt') |
| dex_method_count_diff_path = output_dir.join('dex_method_counts_diff.txt') |
| supersize_diff_path = output_dir.join('supersize_diff.txt') |
| ndjson_path = output_dir.join('report.ndjson') |
| results_path = output_dir.join('results.json') |
| |
| _CreateDiffs(api, author, without_results_dir, with_results_dir, |
| resource_sizes_diff_path, supersize_diff_path, |
| dex_method_count_diff_path, ndjson_path, results_path) |
| |
| _UploadNdJson(api, ndjson_path) |
| |
| _DisplayDiffResults(api, 'Resource Sizes', resource_sizes_diff_path, |
| '(Look here for high-level metrics)') |
| _DisplayDiffResults(api, 'Supersize', supersize_diff_path, |
| '(Look here for detailed breakdown)') |
| _DisplayDiffResults(api, 'Dex Method Count', dex_method_count_diff_path, |
| '(Look here for added/removed Java methods)') |
| |
| _CheckForUndocumentedIncrease(api, results_path) |
| |
| |
| def _BuildAndMeasure(api, with_patch): |
| suffix = ' (with patch)' if with_patch else ' (without patch)' |
| results_basename = 'with_patch' if with_patch else 'without_patch' |
| |
| api.chromium_tests.run_mb_and_compile(_COMPILE_TARGETS, None, suffix) |
| |
| results_dir = api.chromium.output_dir.join(results_basename) |
| api.file.ensure_directory('mkdir ' + results_basename, results_dir) |
| |
| apk_path = api.chromium_android.apk_path(_APK_NAME) |
| # Can't use api.chromium_android.resource_sizes() without it trying to upload |
| # the results. |
| api.python( |
| 'resource_sizes ({}){}'.format(api.path.basename(apk_path), suffix), |
| api.chromium_android.c.resource_sizes, [ |
| str(apk_path), |
| '--chartjson', |
| '--output-dir', results_dir, |
| '--chromium-output-directory', api.chromium.output_dir, |
| ]) |
| api.json.read( |
| 'resource_sizes result{}'.format(suffix), |
| results_dir.join('results-chart.json')) |
| |
| size_path = results_dir.join(_APK_NAME + '.size') |
| api.chromium_android.supersize_archive( |
| apk_path, size_path, step_suffix=suffix) |
| return results_dir |
| |
| |
| def _CheckForUndocumentedIncrease(api, results_path): |
| step_result = api.json.read( |
| 'Check for undocumented increase', results_path, |
| step_test_data=lambda: api.json.test_api.output({ |
| 'details': 'Binary size checks passed.', |
| 'normalized_apk_size': 1024, |
| 'status_code': 0, |
| })) |
| result_json = step_result.json.output |
| presentation = step_result.presentation |
| |
| try: |
| presentation.logs['Size delta summary'] = ( |
| result_json['details'].splitlines()) |
| presentation.step_text = 'Normalized apk size delta: {} bytes'.format( |
| result_json['normalized_apk_size']) |
| if result_json['status_code'] != 0: |
| presentation.status = api.step.FAILURE |
| raise api.step.StepFailure('Undocumented size increase detected') |
| except KeyError: |
| presentation.status = api.step.FAILURE |
| raise api.step.StepFailure('Malformed results JSON detected') |
| |
| |
| def _CreateDiffs(api, author, before_dir, after_dir, resource_sizes_diff_path, |
| supersize_diff_path, dex_method_count_diff_path, |
| ndjson_path, results_path): |
| checker_script = api.path['checkout'].join( |
| 'tools', 'binary_size', 'trybot_commit_size_checker.py') |
| |
| api.python('Generate diffs', checker_script, [ |
| '--author', author, |
| '--apk-name', _APK_NAME, |
| '--before-dir', before_dir, |
| '--after-dir', after_dir, |
| '--resource-sizes-diff-path', resource_sizes_diff_path, |
| '--supersize-diff-path', supersize_diff_path, |
| '--dex-method-count-diff-path', dex_method_count_diff_path, |
| '--ndjson-path', ndjson_path, |
| "--results-path", results_path |
| ]) |
| |
| |
| def _DisplayDiffResults(api, name, path, description): |
| diff_text = api.file.read_text('Show {} Diff'.format(name), path, |
| test_data='Test output data') |
| read_step_result = api.step.active_result |
| read_step_result.presentation.step_text = description |
| read_step_result.presentation.logs['>>> View {} Diff <<<'.format(name)] = ( |
| diff_text.splitlines()) |
| |
| |
| def _UploadNdJson(api, ndjson_path): |
| today = api.time.utcnow().date() |
| gs_dest = '{}/{}/{}.ndjson'.format( |
| api.buildbucket.builder_name, |
| today.strftime('%Y/%m/%d'), |
| api.buildbucket.build.number) |
| upload_result = api.gsutil.upload( |
| source=ndjson_path, |
| bucket=_NDJSON_GS_BUCKET, |
| dest=gs_dest, |
| name='upload Supersize HTML report', |
| link_name='Supersize HTML Report', |
| unauthenticated_url=True) |
| report_link_text = '>>> View Supersize HTML Report <<<' |
| upload_result.presentation.links[report_link_text] = ( |
| _HTML_REPORT_BASE_URL + gs_dest) |
| |
| |
| def GenTests(api): |
| def props(name, size_footer=False, **kwargs): |
| kwargs.setdefault('path_config', 'kitchen') |
| kwargs['revision'] = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' |
| revision_info = { |
| '_number': 1, |
| 'commit': { |
| 'author': { |
| 'email': 'foo@bar.com', |
| }, |
| 'message': 'message', |
| } |
| } |
| footer_json = {} |
| if size_footer: |
| footer_json['Binary-Size'] = ['Totally worth it.'] |
| return ( |
| api.test(name) + |
| api.properties.tryserver( |
| build_config='Release', |
| mastername='tryserver.chromium.android', |
| buildername='android_binary_size', |
| buildnumber=_TEST_BUILDNUMBER, |
| patch_set=1, |
| **kwargs) + |
| api.platform('linux', 64) + |
| api.override_step_data( |
| 'gerrit changes', |
| api.json.output([{ |
| 'revisions': { |
| kwargs['revision']: revision_info |
| } |
| }])) + |
| api.override_step_data('parse description', |
| api.json.output(footer_json)) + |
| api.time.seed(_TEST_TIME) |
| ) |
| |
| |
| def override_analyze(no_changes=False): |
| """Overrides analyze step data so that targets get compiled.""" |
| return api.override_step_data( |
| 'analyze', |
| api.json.output({ |
| 'status': 'Found dependency', |
| 'compile_targets': _ANALYZE_TARGETS, |
| 'test_targets': [] if no_changes else _COMPILE_TARGETS})) |
| |
| yield ( |
| props('noop_because_of_size_footer', size_footer=True) + |
| api.post_process(post_process.MustRun, _FOOTER_PRESENT_STEP_NAME) + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('noop_because_of_analyze') + |
| override_analyze(no_changes=True) + |
| api.post_process(post_process.MustRun, 'analyze') + |
| api.post_process(post_process.DoesNotRunRE, r'.*build') + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('patch_fixes_build') + |
| override_analyze() + |
| api.override_step_data('compile (without patch)', retcode=1) + |
| api.post_process(post_process.MustRun, _PATCH_FIXED_BUILD_STEP_NAME) + |
| api.post_process(post_process.DropExpectation) |
| ) |
| yield ( |
| props('normal_build') + |
| override_analyze() + |
| api.post_process( |
| post_process.AnnotationContains, |
| 'gsutil upload Supersize HTML report', |
| ['{}android_binary_size/{}/{}.ndjson'.format( |
| _HTML_REPORT_BASE_URL, _TEST_TIME_FMT, _TEST_BUILDNUMBER)]) |
| ) |
| yield ( |
| props('unexpected_increase') + |
| override_analyze() + |
| api.override_step_data( |
| 'Check for undocumented increase', |
| api.json.output({ |
| 'details': 'Failed', |
| 'normalized_apk_size': 1024 * 17, |
| 'status_code': 1 |
| })) |
| ) |
| yield( |
| props('malformed_results_json') + |
| override_analyze() + |
| api.override_step_data( |
| 'Check for undocumented increase', |
| api.json.output({ |
| 'details': 'Failed', |
| 'normalized_apk_size': 1024 * 17, |
| 'error_code': 1 |
| })) |
| ) |