• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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