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