1#!/usr/bin/env python3 2# Copyright 2017 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Runs resource_sizes.py on two apks and outputs the diff.""" 7 8 9import argparse 10import json 11import logging 12import os 13import subprocess 14import sys 15 16from pylib.constants import host_paths 17 18with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): 19 import perf_tests_results_helper # pylint: disable=import-error 20 21with host_paths.SysPath(host_paths.TRACING_PATH): 22 from tracing.value import convert_chart_json # pylint: disable=import-error 23 24_ANDROID_DIR = os.path.dirname(os.path.abspath(__file__)) 25with host_paths.SysPath(os.path.join(_ANDROID_DIR, 'gyp')): 26 from util import build_utils # pylint: disable=import-error 27 28 29_BASE_CHART = { 30 'format_version': '0.1', 31 'benchmark_name': 'resource_sizes_diff', 32 'benchmark_description': 'APK resource size diff information', 33 'trace_rerun_options': [], 34 'charts': {}, 35} 36 37_CHARTJSON_FILENAME = 'results-chart.json' 38_HISTOGRAMS_FILENAME = 'perf_results.json' 39 40 41def DiffResults(chartjson, base_results, diff_results): 42 """Reports the diff between the two given results. 43 44 Args: 45 chartjson: A dictionary that chartjson results will be placed in, or None 46 to only print results. 47 base_results: The chartjson-formatted size results of the base APK. 48 diff_results: The chartjson-formatted size results of the diff APK. 49 """ 50 for graph_title, graph in base_results['charts'].items(): 51 for trace_title, trace in graph.items(): 52 perf_tests_results_helper.ReportPerfResult( 53 chartjson, graph_title, trace_title, 54 diff_results['charts'][graph_title][trace_title]['value'] 55 - trace['value'], 56 trace['units'], trace['improvement_direction'], 57 trace['important']) 58 59 60def AddIntermediateResults(chartjson, base_results, diff_results): 61 """Copies the intermediate size results into the output chartjson. 62 63 Args: 64 chartjson: A dictionary that chartjson results will be placed in. 65 base_results: The chartjson-formatted size results of the base APK. 66 diff_results: The chartjson-formatted size results of the diff APK. 67 """ 68 for graph_title, graph in base_results['charts'].items(): 69 for trace_title, trace in graph.items(): 70 perf_tests_results_helper.ReportPerfResult( 71 chartjson, graph_title + '_base_apk', trace_title, 72 trace['value'], trace['units'], trace['improvement_direction'], 73 trace['important']) 74 75 # Both base_results and diff_results should have the same charts/traces, but 76 # loop over them separately in case they don't 77 for graph_title, graph in diff_results['charts'].items(): 78 for trace_title, trace in graph.items(): 79 perf_tests_results_helper.ReportPerfResult( 80 chartjson, graph_title + '_diff_apk', trace_title, 81 trace['value'], trace['units'], trace['improvement_direction'], 82 trace['important']) 83 84 85def _CreateArgparser(): 86 def chromium_path(arg): 87 if arg.startswith('//'): 88 return os.path.join(host_paths.DIR_SOURCE_ROOT, arg[2:]) 89 return arg 90 91 argparser = argparse.ArgumentParser( 92 description='Diff resource sizes of two APKs. Arguments not listed here ' 93 'will be passed on to both invocations of resource_sizes.py.') 94 argparser.add_argument('--chromium-output-directory-base', 95 dest='out_dir_base', 96 type=chromium_path, 97 help='Location of the build artifacts for the base ' 98 'APK, i.e. what the size increase/decrease will ' 99 'be measured from.') 100 argparser.add_argument('--chromium-output-directory-diff', 101 dest='out_dir_diff', 102 type=chromium_path, 103 help='Location of the build artifacts for the diff ' 104 'APK.') 105 argparser.add_argument('--chartjson', 106 action='store_true', 107 help='DEPRECATED. Use --output-format=chartjson ' 108 'instead.') 109 argparser.add_argument('--output-format', 110 choices=['chartjson', 'histograms'], 111 help='Output the results to a file in the given ' 112 'format instead of printing the results.') 113 argparser.add_argument('--include-intermediate-results', 114 action='store_true', 115 help='Include the results from the resource_sizes.py ' 116 'runs in the chartjson output.') 117 argparser.add_argument('--output-dir', 118 default='.', 119 type=chromium_path, 120 help='Directory to save chartjson to.') 121 argparser.add_argument('--base-apk', 122 required=True, 123 type=chromium_path, 124 help='Path to the base APK, i.e. what the size ' 125 'increase/decrease will be measured from.') 126 argparser.add_argument('--diff-apk', 127 required=True, 128 type=chromium_path, 129 help='Path to the diff APK, i.e. the APK whose size ' 130 'increase/decrease will be measured against the ' 131 'base APK.') 132 return argparser 133 134 135def main(): 136 args, unknown_args = _CreateArgparser().parse_known_args() 137 # TODO(bsheedy): Remove this once all uses of --chartjson are removed. 138 if args.chartjson: 139 args.output_format = 'chartjson' 140 141 chartjson = _BASE_CHART.copy() if args.output_format else None 142 143 with build_utils.TempDir() as base_dir, build_utils.TempDir() as diff_dir: 144 # Run resource_sizes.py on the two APKs 145 resource_sizes_path = os.path.join(_ANDROID_DIR, 'resource_sizes.py') 146 shared_args = (['python', resource_sizes_path, '--output-format=chartjson'] 147 + unknown_args) 148 149 base_args = shared_args + ['--output-dir', base_dir, args.base_apk] 150 if args.out_dir_base: 151 base_args += ['--chromium-output-directory', args.out_dir_base] 152 try: 153 subprocess.check_output(base_args, stderr=subprocess.STDOUT) 154 except subprocess.CalledProcessError as e: 155 print(e.output) 156 raise 157 158 diff_args = shared_args + ['--output-dir', diff_dir, args.diff_apk] 159 if args.out_dir_diff: 160 diff_args += ['--chromium-output-directory', args.out_dir_diff] 161 try: 162 subprocess.check_output(diff_args, stderr=subprocess.STDOUT) 163 except subprocess.CalledProcessError as e: 164 print(e.output) 165 raise 166 167 # Combine the separate results 168 with open(os.path.join(base_dir, _CHARTJSON_FILENAME)) as base_file: 169 base_results = json.load(base_file) 170 with open(os.path.join(diff_dir, _CHARTJSON_FILENAME)) as diff_file: 171 diff_results = json.load(diff_file) 172 DiffResults(chartjson, base_results, diff_results) 173 if args.include_intermediate_results: 174 AddIntermediateResults(chartjson, base_results, diff_results) 175 176 if args.output_format: 177 chartjson_path = os.path.join(os.path.abspath(args.output_dir), 178 _CHARTJSON_FILENAME) 179 logging.critical('Dumping diff chartjson to %s', chartjson_path) 180 with open(chartjson_path, 'w') as outfile: 181 json.dump(chartjson, outfile) 182 183 if args.output_format == 'histograms': 184 histogram_result = convert_chart_json.ConvertChartJson(chartjson_path) 185 if histogram_result.returncode != 0: 186 logging.error('chartjson conversion failed with error: %s', 187 histogram_result.stdout) 188 return 1 189 190 histogram_path = os.path.join(os.path.abspath(args.output_dir), 191 'perf_results.json') 192 logging.critical('Dumping diff histograms to %s', histogram_path) 193 with open(histogram_path, 'w') as json_file: 194 json_file.write(histogram_result.stdout) 195 return 0 196 197 198if __name__ == '__main__': 199 sys.exit(main()) 200