| # 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. |
| |
| # Recipe which analyzes a compiled binary for information (e.g. file size) |
| |
| import ast |
| import json |
| |
| PYTHON_VERSION_COMPATIBILITY = "PY3" |
| |
| DEPS = [ |
| 'checkout', |
| 'env', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/path', |
| 'recipe_engine/properties', |
| 'recipe_engine/python', |
| 'recipe_engine/raw_io', |
| 'recipe_engine/step', |
| 'run', |
| 'vars', |
| ] |
| |
| |
| MAGIC_SEPERATOR = '#$%^&*' |
| TOTAL_SIZE_BYTES_KEY = "total_size_bytes" |
| |
| |
| def add_binary_size_output_property(result, source, binary_size): |
| result.presentation.properties['binary_size_%s' % source] = binary_size |
| |
| |
| def RunSteps(api): |
| api.vars.setup() |
| |
| checkout_root = api.checkout.default_checkout_root |
| api.checkout.bot_update(checkout_root=checkout_root) |
| |
| out_dir = api.vars.swarming_out_dir |
| # Any binaries to scan should be here. |
| bin_dir = api.vars.build_dir |
| |
| api.file.ensure_directory('mkdirs out_dir', out_dir, mode=0o777) |
| |
| analyzed = 0 |
| with api.context(cwd=bin_dir): |
| files = api.file.glob_paths( |
| 'find WASM binaries', |
| bin_dir, |
| '*.wasm', |
| test_data=['pathkit.wasm']) |
| analyzed += len(files) |
| if files: |
| analyze_wasm_file(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find JS files', |
| bin_dir, |
| '*.js', |
| test_data=['pathkit.js']) |
| analyzed += len(files) |
| if files: |
| analyze_web_file(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find JS mem files', |
| bin_dir, |
| '*.js.mem', |
| test_data=['pathkit.js.mem']) |
| analyzed += len(files) |
| if files: |
| analyze_web_file(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find flutter library', |
| bin_dir, |
| 'libflutter.so', |
| test_data=['libflutter.so']) |
| analyzed += len(files) |
| if files: |
| analyze_flutter_lib(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find skia library', |
| bin_dir, |
| 'libskia.so', |
| test_data=['libskia.so']) |
| analyzed += len(files) |
| if files: |
| analyze_cpp_lib(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find skottie_tool', |
| bin_dir, |
| 'skottie_tool', |
| test_data=['skottie_tool']) |
| analyzed += len(files) |
| if files: |
| make_treemap(api, checkout_root, out_dir, files) |
| |
| files = api.file.glob_paths( |
| 'find dm', |
| bin_dir, |
| 'dm', |
| test_data=['dm']) |
| analyzed += len(files) |
| if files: |
| make_treemap(api, checkout_root, out_dir, files) |
| |
| if not analyzed: # pragma: nocover |
| raise Exception('No files were analyzed!') |
| |
| |
| def keys_and_props(api): |
| keys = [] |
| for k in sorted(api.vars.builder_cfg.keys()): |
| if not k in ['role']: |
| keys.extend([k, api.vars.builder_cfg[k]]) |
| keystr = ' '.join(keys) |
| |
| props = [ |
| 'gitHash', api.properties['revision'], |
| 'swarming_bot_id', api.vars.swarming_bot_id, |
| 'swarming_task_id', api.vars.swarming_task_id, |
| ] |
| |
| if api.vars.is_trybot: |
| props.extend([ |
| 'issue', api.vars.issue, |
| 'patchset', api.vars.patchset, |
| 'patch_storage', api.vars.patch_storage, |
| ]) |
| propstr = ' '.join(str(prop) for prop in props) |
| return (keystr, propstr) |
| |
| |
| # Get the raw and gzipped size of the given file |
| def analyze_web_file(api, checkout_root, out_dir, files): |
| (keystr, propstr) = keys_and_props(api) |
| |
| for f in files: |
| skia_dir = checkout_root.join('skia') |
| with api.context(cwd=skia_dir): |
| script = skia_dir.join('infra', 'bots', 'buildstats', |
| 'buildstats_web.py') |
| step_data = api.run(api.python, 'Analyze %s' % f, script=script, |
| args=[f, out_dir, keystr, propstr, TOTAL_SIZE_BYTES_KEY, |
| MAGIC_SEPERATOR], |
| stdout=api.raw_io.output()) |
| if step_data and step_data.stdout: |
| sections = step_data.stdout.decode('utf-8').split(MAGIC_SEPERATOR) |
| result = api.step.active_result |
| logs = result.presentation.logs |
| logs['perf_json'] = sections[1].split('\n') |
| |
| add_binary_size_output_property(result, api.path.basename(f), ( |
| ast.literal_eval(sections[1]) |
| .get('results', {}) |
| .get(api.path.basename(f), {}) |
| .get('default', {}) |
| .get(TOTAL_SIZE_BYTES_KEY, {}))) |
| |
| |
| # Get the raw size and a few metrics from bloaty |
| def analyze_cpp_lib(api, checkout_root, out_dir, files): |
| (keystr, propstr) = keys_and_props(api) |
| bloaty_exe = api.path['start_dir'].join('bloaty', 'bloaty') |
| |
| for f in files: |
| skia_dir = checkout_root.join('skia') |
| with api.context(cwd=skia_dir): |
| script = skia_dir.join('infra', 'bots', 'buildstats', |
| 'buildstats_cpp.py') |
| step_data = api.run(api.python, 'Analyze %s' % f, script=script, |
| args=[f, out_dir, keystr, propstr, bloaty_exe, TOTAL_SIZE_BYTES_KEY, |
| MAGIC_SEPERATOR], |
| stdout=api.raw_io.output()) |
| if step_data and step_data.stdout: |
| sections = step_data.stdout.decode('utf-8').split(MAGIC_SEPERATOR) |
| result = api.step.active_result |
| logs = result.presentation.logs |
| logs['perf_json'] = sections[2].split('\n') |
| |
| add_binary_size_output_property(result, api.path.basename(f), ( |
| ast.literal_eval(sections[2]) |
| .get('results', {}) |
| .get(api.path.basename(f), {}) |
| .get('default', {}) |
| .get(TOTAL_SIZE_BYTES_KEY, {}))) |
| |
| |
| # Get the size of skia in flutter and a few metrics from bloaty |
| def analyze_flutter_lib(api, checkout_root, out_dir, files): |
| (keystr, propstr) = keys_and_props(api) |
| bloaty_exe = api.path['start_dir'].join('bloaty', 'bloaty') |
| |
| for f in files: |
| |
| skia_dir = checkout_root.join('skia') |
| with api.context(cwd=skia_dir): |
| stripped = api.vars.build_dir.join('libflutter_stripped.so') |
| script = skia_dir.join('infra', 'bots', 'buildstats', |
| 'buildstats_flutter.py') |
| config = "skia_in_flutter" |
| lib_name = "libflutter.so" |
| step_data = api.run(api.python, 'Analyze flutter', script=script, |
| args=[stripped, out_dir, keystr, propstr, bloaty_exe, |
| f, config, TOTAL_SIZE_BYTES_KEY, lib_name, |
| MAGIC_SEPERATOR], |
| stdout=api.raw_io.output()) |
| if step_data and step_data.stdout: |
| sections = step_data.stdout.decode('utf-8').split(MAGIC_SEPERATOR) |
| result = api.step.active_result |
| logs = result.presentation.logs |
| # Skip section 0 because it's everything before first print, |
| # which is probably the empty string. |
| logs['bloaty_file_symbol_short'] = sections[1].split('\n') |
| logs['bloaty_file_symbol_full'] = sections[2].split('\n') |
| logs['bloaty_symbol_file_short'] = sections[3].split('\n') |
| logs['bloaty_symbol_file_full'] = sections[4].split('\n') |
| logs['perf_json'] = sections[5].split('\n') |
| |
| add_binary_size_output_property(result, lib_name, ( |
| ast.literal_eval(sections[5]) |
| .get('results', {}) |
| .get(lib_name, {}) |
| .get(config, {}) |
| .get(TOTAL_SIZE_BYTES_KEY, {}))) |
| |
| |
| # Get the size of skia in flutter and a few metrics from bloaty |
| def analyze_wasm_file(api, checkout_root, out_dir, files): |
| (keystr, propstr) = keys_and_props(api) |
| bloaty_exe = api.path['start_dir'].join('bloaty', 'bloaty') |
| |
| for f in files: |
| |
| skia_dir = checkout_root.join('skia') |
| with api.context(cwd=skia_dir): |
| script = skia_dir.join('infra', 'bots', 'buildstats', |
| 'buildstats_wasm.py') |
| step_data = api.run(api.python, 'Analyze wasm', script=script, |
| args=[f, out_dir, keystr, propstr, bloaty_exe, |
| TOTAL_SIZE_BYTES_KEY, MAGIC_SEPERATOR], |
| stdout=api.raw_io.output()) |
| if step_data and step_data.stdout: |
| sections = step_data.stdout.decode('utf-8').split(MAGIC_SEPERATOR) |
| result = api.step.active_result |
| logs = result.presentation.logs |
| # Skip section 0 because it's everything before first print, |
| # which is probably the empty string. |
| logs['bloaty_symbol_short'] = sections[1].split('\n') |
| logs['bloaty_symbol_full'] = sections[2].split('\n') |
| logs['perf_json'] = sections[3].split('\n') |
| add_binary_size_output_property(result, api.path.basename(f), ( |
| ast.literal_eval(str(sections[3])) |
| .get('results', {}) |
| .get(api.path.basename(f), {}) |
| .get('default', {}) |
| .get(TOTAL_SIZE_BYTES_KEY, {}))) |
| |
| |
| # make a zip file containing an HTML treemap of the files |
| def make_treemap(api, checkout_root, out_dir, files): |
| for f in files: |
| env = {'DOCKER_CONFIG': '/home/chrome-bot/.docker'} |
| with api.env(env): |
| skia_dir = checkout_root.join('skia') |
| with api.context(cwd=skia_dir): |
| script = skia_dir.join('infra', 'bots', 'buildstats', |
| 'make_treemap.py') |
| api.run(api.python, 'Make code size treemap %s' % f, |
| script=script, |
| args=[f, out_dir], |
| stdout=api.raw_io.output()) |
| |
| |
| def GenTests(api): |
| builder = 'BuildStats-Debian10-EMCC-wasm-Release-PathKit' |
| yield ( |
| api.test('normal_bot') + |
| api.properties(buildername=builder, |
| repository='https://skia.googlesource.com/skia.git', |
| revision='abc123', |
| swarm_out_dir='[SWARM_OUT_DIR]', |
| path_config='kitchen') + |
| api.step_data('get swarming bot id', |
| stdout=api.raw_io.output('skia-bot-123')) + |
| api.step_data('get swarming task id', |
| stdout=api.raw_io.output('123456abc')) + |
| api.step_data('Analyze [START_DIR]/build/pathkit.js.mem', |
| stdout=api.raw_io.output(sample_web)) + |
| api.step_data('Analyze [START_DIR]/build/libskia.so', |
| stdout=api.raw_io.output(sample_cpp)) + |
| api.step_data('Analyze wasm', |
| stdout=api.raw_io.output(sample_wasm)) + |
| api.step_data('Analyze flutter', |
| stdout=api.raw_io.output(sample_flutter)) |
| ) |
| |
| yield ( |
| api.test('trybot') + |
| api.properties(buildername=builder, |
| repository='https://skia.googlesource.com/skia.git', |
| revision='abc123', |
| swarm_out_dir='[SWARM_OUT_DIR]', |
| patch_repo='https://skia.googlesource.com/skia.git', |
| path_config='kitchen') + |
| api.step_data('get swarming bot id', |
| stdout=api.raw_io.output('skia-bot-123')) + |
| api.step_data('get swarming task id', |
| stdout=api.raw_io.output('123456abc')) + |
| api.properties(patch_storage='gerrit') + |
| api.properties.tryserver( |
| buildername=builder, |
| gerrit_project='skia', |
| gerrit_url='https://skia-review.googlesource.com/', |
| ) + |
| api.step_data('Analyze [START_DIR]/build/pathkit.js.mem', |
| stdout=api.raw_io.output(sample_web)) + |
| api.step_data('Analyze [START_DIR]/build/libskia.so', |
| stdout=api.raw_io.output(sample_cpp)) + |
| api.step_data('Analyze wasm', |
| stdout=api.raw_io.output(sample_wasm)) + |
| api.step_data('Analyze flutter', |
| stdout=api.raw_io.output(sample_flutter)) |
| ) |
| |
| sample_web = """ |
| Report A |
| Total size: 50 bytes |
| #$%^&* |
| { |
| "some": "json", |
| "results": { |
| "pathkit.js.mem": { |
| "default": { |
| "total_size_bytes": 7391117, |
| "gzip_size_bytes": 2884841 |
| } |
| } |
| } |
| } |
| """ |
| |
| sample_cpp = """ |
| #$%^&* |
| Report A |
| Total size: 50 bytes |
| #$%^&* |
| { |
| "some": "json", |
| "results": { |
| "libskia.so": { |
| "default": { |
| "total_size_bytes": 7391117, |
| "gzip_size_bytes": 2884841 |
| } |
| } |
| } |
| } |
| """ |
| |
| sample_wasm = """ |
| #$%^&* |
| Report A |
| Total size: 50 bytes |
| #$%^&* |
| Report B |
| Total size: 60 bytes |
| #$%^&* |
| { |
| "some": "json", |
| "results": { |
| "pathkit.wasm": { |
| "default": { |
| "total_size_bytes": 7391117, |
| "gzip_size_bytes": 2884841 |
| } |
| } |
| } |
| } |
| """ |
| |
| sample_flutter = """ |
| #$%^&* |
| Report A |
| Total size: 50 bytes |
| #$%^&* |
| Report B |
| Total size: 60 bytes |
| #$%^&* |
| Report C |
| Total size: 70 bytes |
| #$%^&* |
| Report D |
| Total size: 80 bytes |
| #$%^&* |
| { |
| "some": "json", |
| "results": { |
| "libflutter.so": { |
| "skia_in_flutter": { |
| "total_size_bytes": 1256676 |
| } |
| } |
| } |
| } |
| """ |