Exemple #1
0
def _PrintDexAnalysis(apk_filename, chartjson=None):
  sizes = method_count.ExtractSizesFromZip(apk_filename)

  graph_title = os.path.basename(apk_filename) + '_Dex'
  dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE
  for key, label in dex_metrics.iteritems():
    perf_tests_results_helper.ReportPerfResult(chartjson, graph_title, label,
                                               sizes[key], 'entries')

  graph_title = '%sCache' % graph_title
  perf_tests_results_helper.ReportPerfResult(chartjson, graph_title, 'DexCache',
                                             sizes['dex_cache_size'], 'bytes')
Exemple #2
0
    def __call__(self, graph_title, trace_title, value, units):
        super(_ChartJsonReporter, self).__call__(graph_title, trace_title,
                                                 value, units)

        perf_tests_results_helper.ReportPerfResult(
            self._chartjson, graph_title,
            self.trace_title_prefix + trace_title, value, units)
Exemple #3
0
def _PrintDexAnalysis(apk_filename, chartjson=None):
  sizes, total_size = method_count.ExtractSizesFromZip(apk_filename)

  graph_title = os.path.basename(apk_filename) + '_Dex'
  dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE
  cumulative_sizes = collections.defaultdict(int)
  for classes_dex_sizes in sizes.values():
    for key in dex_metrics:
      cumulative_sizes[key] += classes_dex_sizes[key]
  for key, label in dex_metrics.iteritems():
    perf_tests_results_helper.ReportPerfResult(chartjson, graph_title, label,
                                               cumulative_sizes[key], 'entries')

  graph_title = '%sCache' % graph_title
  perf_tests_results_helper.ReportPerfResult(chartjson, graph_title, 'DexCache',
                                             total_size, 'bytes')
Exemple #4
0
 def SynthesizeTotals(self, unique_method_count):
     for tup, value in sorted(self._combined_metrics.items()):
         graph_title, trace_title, units = tup
         if trace_title == 'unique methods':
             value = unique_method_count
         perf_tests_results_helper.ReportPerfResult(
             self._chartjson, graph_title, 'Combined_' + trace_title, value,
             units)
Exemple #5
0
def PrintApkAnalysis(apk_filename, tool_prefix, out_dir, chartjson=None):
    """Calls GenerateApkAnalysis() and report the value."""
    def PrintUnknown(info):
        print('Unknown entry: %s %d' % (info.filename, info.compress_size))

    title_prefix = os.path.basename(apk_filename) + '_'
    for data in GenerateApkAnalysis(apk_filename, tool_prefix, out_dir,
                                    PrintUnknown):
        title = title_prefix + data[0]
        perf_tests_results_helper.ReportPerfResult(chartjson, title, *data[1:])
Exemple #6
0
def _PrintPatchSizeEstimate(new_apk, builder, bucket, chartjson=None):
  apk_name = os.path.basename(new_apk)
  title = apk_name + '_PatchSizeEstimate'
  # Reference APK paths have spaces replaced by underscores.
  builder = builder.replace(' ', '_')
  old_apk = apk_downloader.MaybeDownloadApk(
      builder, apk_downloader.CURRENT_MILESTONE, apk_name,
      apk_downloader.DEFAULT_DOWNLOAD_PATH, bucket)
  if old_apk:
    # Use a temp dir in case patch size functions fail to clean up temp files.
    with build_utils.TempDir() as tmp:
      tmp_name = os.path.join(tmp, 'patch.tmp')
      bsdiff = apk_patch_size_estimator.calculate_bsdiff(
          old_apk, new_apk, None, tmp_name)
      perf_tests_results_helper.ReportPerfResult(chartjson, title,
                                'BSDiff (gzipped)', bsdiff, 'bytes')
      fbf = apk_patch_size_estimator.calculate_filebyfile(
          old_apk, new_apk, None, tmp_name)
      perf_tests_results_helper.ReportPerfResult(chartjson, title,
                                'FileByFile (gzipped)', fbf, 'bytes')
def AddIntermediateResults(chartjson, base_results, diff_results):
    """Copies the intermediate size results into the output chartjson.

  Args:
    chartjson: A dictionary that chartjson results will be placed in.
    base_results: The chartjson-formatted size results of the base APK.
    diff_results: The chartjson-formatted size results of the diff APK.
  """
    for graph_title, graph in base_results['charts'].iteritems():
        for trace_title, trace in graph.iteritems():
            perf_tests_results_helper.ReportPerfResult(
                chartjson, graph_title + '_base_apk', trace_title,
                trace['value'], trace['units'], trace['improvement_direction'],
                trace['important'])

    # Both base_results and diff_results should have the same charts/traces, but
    # loop over them separately in case they don't
    for graph_title, graph in diff_results['charts'].iteritems():
        for trace_title, trace in graph.iteritems():
            perf_tests_results_helper.ReportPerfResult(
                chartjson, graph_title + '_diff_apk', trace_title,
                trace['value'], trace['units'], trace['improvement_direction'],
                trace['important'])
def DiffResults(chartjson, base_results, diff_results):
    """Reports the diff between the two given results.

  Args:
    chartjson: A dictionary that chartjson results will be placed in, or None
        to only print results.
    base_results: The chartjson-formatted size results of the base APK.
    diff_results: The chartjson-formatted size results of the diff APK.
  """
    for graph_title, graph in base_results['charts'].iteritems():
        for trace_title, trace in graph.iteritems():
            perf_tests_results_helper.ReportPerfResult(
                chartjson, graph_title, trace_title,
                diff_results['charts'][graph_title][trace_title]['value'] -
                trace['value'], trace['units'], trace['improvement_direction'],
                trace['important'])
Exemple #9
0
 def SynthesizeTotals(self):
     for tup, value in sorted(self._combined_metrics.iteritems()):
         graph_title, trace_title, units = tup
         perf_tests_results_helper.ReportPerfResult(
             self._chartjson, graph_title, 'Combined_' + trace_title, value,
             units)
Exemple #10
0
    def __call__(self, graph_title, trace_title, value, units):
        self._combined_metrics[(graph_title, trace_title, units)] += value

        perf_tests_results_helper.ReportPerfResult(
            self._chartjson, graph_title,
            self.trace_title_prefix + trace_title, value, units)
 def report_func(*args):
     # Do not add any new metrics without also documenting them in:
     # //docs/speed/binary_size/metrics.md.
     perf_tests_results_helper.ReportPerfResult(chartjson, *args)
def main():
    argparser = argparse.ArgumentParser(description='Print APK size metrics.')
    argparser.add_argument(
        '--min-pak-resource-size',
        type=int,
        default=20 * 1024,
        help='Minimum byte size of displayed pak resources.')
    argparser.add_argument('--chromium-output-directory',
                           dest='out_dir',
                           help='Location of the build artifacts.')
    argparser.add_argument('--chartjson',
                           action='store_true',
                           help='Sets output mode to chartjson.')
    argparser.add_argument('--output-dir',
                           default='.',
                           help='Directory to save chartjson to.')
    argparser.add_argument(
        '--dump-static-initializers',
        action='store_true',
        dest='dump_sis',
        help='Run dump-static-initializers.py to get the list'
        'of static initializers (slow).')
    argparser.add_argument(
        '--estimate-patch-size',
        action='store_true',
        help='Include patch size estimates. Useful for perf '
        'builders where a reference APK is available but adds '
        '~3 mins to run time.')
    argparser.add_argument(
        '--reference-apk-builder',
        default=apk_downloader.DEFAULT_BUILDER,
        help='Builder name to use for reference APK for patch '
        'size estimates.')
    argparser.add_argument('--reference-apk-bucket',
                           default=apk_downloader.DEFAULT_BUCKET,
                           help='Storage bucket holding reference APKs.')
    argparser.add_argument('apk', help='APK file path.')
    args = argparser.parse_args()

    chartjson = _BASE_CHART.copy() if args.chartjson else None
    out_dir, tool_prefix = _ConfigOutDirAndToolsPrefix(args.out_dir)
    if args.dump_sis and not out_dir:
        argparser.error(
            '--dump-static-initializers requires --chromium-output-directory')

    # Do not add any new metrics without also documenting them in:
    # //docs/speed/binary_size/metrics.md.

    PrintApkAnalysis(args.apk, tool_prefix, out_dir, chartjson=chartjson)
    _PrintDexAnalysis(args.apk, chartjson=chartjson)

    si_count = AnalyzeStaticInitializers(args.apk, tool_prefix, args.dump_sis,
                                         out_dir, [])
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               'StaticInitializersCount',
                                               'count', si_count, 'count')

    if args.estimate_patch_size:
        _PrintPatchSizeEstimate(args.apk,
                                args.reference_apk_builder,
                                args.reference_apk_bucket,
                                chartjson=chartjson)
    if chartjson:
        results_path = os.path.join(args.output_dir, 'results-chart.json')
        logging.critical('Dumping json to %s', results_path)
        with open(results_path, 'w') as json_file:
            json.dump(chartjson, json_file)
def PrintApkAnalysis(apk_filename, tool_prefix, out_dir, chartjson=None):
    """Analyse APK to determine size contributions of different file classes."""
    file_groups = []

    def make_group(name):
        group = _FileGroup(name)
        file_groups.append(group)
        return group

    native_code = make_group('Native code')
    java_code = make_group('Java code')
    native_resources_no_translations = make_group('Native resources (no l10n)')
    translations = make_group('Native resources (l10n)')
    stored_translations = make_group('Native resources stored (l10n)')
    icu_data = make_group('ICU (i18n library) data')
    v8_snapshots = make_group('V8 Snapshots')
    png_drawables = make_group('PNG drawables')
    res_directory = make_group('Non-compiled Android resources')
    arsc = make_group('Compiled Android resources')
    metadata = make_group('Package metadata')
    unknown = make_group('Unknown files')
    notices = make_group('licenses.notice file')
    unwind_cfi = make_group('unwind_cfi (dev and canary only)')

    apk = zipfile.ZipFile(apk_filename, 'r')
    try:
        apk_contents = apk.infolist()
    finally:
        apk.close()

    dex_multiplier, skip_extract_lib = _ParseManifestAttributes(apk_filename)
    total_apk_size = os.path.getsize(apk_filename)
    apk_basename = os.path.basename(apk_filename)
    for member in apk_contents:
        filename = member.filename
        if filename.endswith('/'):
            continue
        if filename.endswith('.so'):
            should_extract_lib = not (skip_extract_lib or 'crazy' in filename)
            native_code.AddZipInfo(
                member, extracted_multiplier=int(should_extract_lib))
        elif filename.endswith('.dex'):
            java_code.AddZipInfo(member, extracted_multiplier=dex_multiplier)
        elif re.search(_RE_NON_LANGUAGE_PAK, filename):
            native_resources_no_translations.AddZipInfo(member)
        elif re.search(_RE_COMPRESSED_LANGUAGE_PAK, filename):
            translations.AddZipInfo(
                member,
                extracted_multiplier=int('en_' in filename
                                         or 'en-' in filename))
        elif re.search(_RE_STORED_LANGUAGE_PAK, filename):
            stored_translations.AddZipInfo(member)
        elif filename == 'assets/icudtl.dat':
            icu_data.AddZipInfo(member)
        elif filename.endswith('.bin'):
            v8_snapshots.AddZipInfo(member)
        elif filename.endswith('.png') or filename.endswith('.webp'):
            png_drawables.AddZipInfo(member)
        elif filename.startswith('res/'):
            res_directory.AddZipInfo(member)
        elif filename.endswith('.arsc'):
            arsc.AddZipInfo(member)
        elif filename.startswith(
                'META-INF') or filename == 'AndroidManifest.xml':
            metadata.AddZipInfo(member)
        elif filename.endswith('.notice'):
            notices.AddZipInfo(member)
        elif filename.startswith('assets/unwind_cfi'):
            unwind_cfi.AddZipInfo(member)
        else:
            unknown.AddZipInfo(member)

    total_install_size = total_apk_size
    zip_overhead = total_apk_size

    for group in file_groups:
        actual_size = group.ComputeZippedSize()
        install_size = group.ComputeInstallSize()
        uncompressed_size = group.ComputeUncompressedSize()

        total_install_size += group.ComputeExtractedSize()
        zip_overhead -= actual_size

        perf_tests_results_helper.ReportPerfResult(chartjson,
                                                   apk_basename + '_Breakdown',
                                                   group.name + ' size',
                                                   actual_size, 'bytes')
        perf_tests_results_helper.ReportPerfResult(
            chartjson, apk_basename + '_InstallBreakdown',
            group.name + ' size', install_size, 'bytes')
        # Only a few metrics are compressed in the first place.
        # To avoid over-reporting, track uncompressed size only for compressed
        # entries.
        if uncompressed_size != actual_size:
            perf_tests_results_helper.ReportPerfResult(
                chartjson, apk_basename + '_Uncompressed',
                group.name + ' size', uncompressed_size, 'bytes')

    # Per-file zip overhead is caused by:
    # * 30 byte entry header + len(file name)
    # * 46 byte central directory entry + len(file name)
    # * 0-3 bytes for zipalign.
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_Breakdown',
                                               'Zip Overhead', zip_overhead,
                                               'bytes')
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_InstallSize',
                                               'APK size', total_apk_size,
                                               'bytes')
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_InstallSize',
                                               'Estimated installed size',
                                               total_install_size, 'bytes')
    transfer_size = _CalculateCompressedSize(apk_filename)
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_TransferSize',
                                               'Transfer size (deflate)',
                                               transfer_size, 'bytes')

    # Size of main dex vs remaining.
    main_dex_info = java_code.FindByPattern('classes.dex')
    if main_dex_info:
        main_dex_size = main_dex_info.file_size
        perf_tests_results_helper.ReportPerfResult(chartjson,
                                                   apk_basename + '_Specifics',
                                                   'main dex size',
                                                   main_dex_size, 'bytes')
        secondary_size = java_code.ComputeUncompressedSize() - main_dex_size
        perf_tests_results_helper.ReportPerfResult(chartjson,
                                                   apk_basename + '_Specifics',
                                                   'secondary dex size',
                                                   secondary_size, 'bytes')

    # Size of main .so vs remaining.
    main_lib_info = native_code.FindLargest()
    if main_lib_info:
        main_lib_size = main_lib_info.file_size
        perf_tests_results_helper.ReportPerfResult(chartjson,
                                                   apk_basename + '_Specifics',
                                                   'main lib size',
                                                   main_lib_size, 'bytes')
        secondary_size = native_code.ComputeUncompressedSize() - main_lib_size
        perf_tests_results_helper.ReportPerfResult(chartjson,
                                                   apk_basename + '_Specifics',
                                                   'other lib size',
                                                   secondary_size, 'bytes')

        main_lib_section_sizes = _ExtractMainLibSectionSizesFromApk(
            apk_filename, main_lib_info.filename, tool_prefix)
        for metric_name, size in main_lib_section_sizes.iteritems():
            perf_tests_results_helper.ReportPerfResult(
                chartjson, apk_basename + '_MainLibInfo', metric_name, size,
                'bytes')

    # Main metric that we want to monitor for jumps.
    normalized_apk_size = total_apk_size
    # unwind_cfi exists only in dev, canary, and non-channel builds.
    normalized_apk_size -= unwind_cfi.ComputeZippedSize()
    # Always look at uncompressed .so.
    normalized_apk_size -= native_code.ComputeZippedSize()
    normalized_apk_size += native_code.ComputeUncompressedSize()
    # TODO(agrieve): Once we have better tooling (which can tell you where dex
    #     size came from), change this to "ComputeExtractedSize()".
    normalized_apk_size += java_code.ComputeUncompressedSize()
    # Avoid noise caused when strings change and translations haven't yet been
    # updated.
    num_translations = translations.GetNumEntries()
    num_stored_translations = stored_translations.GetNumEntries()

    if num_translations > 1:
        # Multipliers found by looking at MonochromePublic.apk and seeing how much
        # smaller en-US.pak is relative to the average locale.pak.
        normalized_apk_size = _NormalizeLanguagePaks(translations,
                                                     normalized_apk_size, 1.17)
    if num_stored_translations > 1:
        normalized_apk_size = _NormalizeLanguagePaks(stored_translations,
                                                     normalized_apk_size, 1.43)
    if num_translations + num_stored_translations > 1:
        if num_translations == 0:
            # WebView stores all locale paks uncompressed.
            num_arsc_translations = num_stored_translations
        else:
            # Monochrome has more configurations than Chrome since it includes
            # WebView (which supports more locales), but these should mostly be empty
            # so ignore them here.
            num_arsc_translations = num_translations
        normalized_apk_size += int(
            _NormalizeResourcesArsc(apk_filename, arsc.GetNumEntries(),
                                    num_arsc_translations, out_dir))

    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_Specifics',
                                               'normalized apk size',
                                               normalized_apk_size, 'bytes')

    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               apk_basename + '_Specifics',
                                               'file count', len(apk_contents),
                                               'zip entries')

    for info in unknown.AllEntries():
        print 'Unknown entry:', info.filename, info.compress_size
Exemple #14
0
def main():
    argparser = argparse.ArgumentParser(description='Print APK size metrics.')
    argparser.add_argument(
        '--min-pak-resource-size',
        type=int,
        default=20 * 1024,
        help='Minimum byte size of displayed pak resources.')
    argparser.add_argument('--chromium-output-directory',
                           dest='out_dir',
                           help='Location of the build artifacts.')
    argparser.add_argument('--chartjson',
                           action='store_true',
                           help='DEPRECATED. Use --output-format=chartjson '
                           'instead.')
    argparser.add_argument('--output-format',
                           choices=['chartjson', 'histograms'],
                           help='Output the results to a file in the given '
                           'format instead of printing the results.')
    argparser.add_argument('--output-dir',
                           default='.',
                           help='Directory to save chartjson to.')
    argparser.add_argument(
        '--dump-static-initializers',
        action='store_true',
        dest='dump_sis',
        help='Run dump-static-initializers.py to get the list'
        'of static initializers (slow).')
    argparser.add_argument('--loadable_module',
                           action='append',
                           help='Use for libraries added via loadable_modules')
    argparser.add_argument(
        '--estimate-patch-size',
        action='store_true',
        help='Include patch size estimates. Useful for perf '
        'builders where a reference APK is available but adds '
        '~3 mins to run time.')
    argparser.add_argument(
        '--reference-apk-builder',
        default=apk_downloader.DEFAULT_BUILDER,
        help='Builder name to use for reference APK for patch '
        'size estimates.')
    argparser.add_argument('--reference-apk-bucket',
                           default=apk_downloader.DEFAULT_BUCKET,
                           help='Storage bucket holding reference APKs.')
    argparser.add_argument('apk', help='APK file path.')
    args = argparser.parse_args()

    # TODO(bsheedy): Remove this once uses of --chartjson have been removed.
    if args.chartjson:
        args.output_format = 'chartjson'

    chartjson = _BASE_CHART.copy() if args.output_format else None
    out_dir, tool_prefix = _ConfigOutDirAndToolsPrefix(args.out_dir)
    if args.dump_sis and not out_dir:
        argparser.error(
            '--dump-static-initializers requires --chromium-output-directory')

    # Do not add any new metrics without also documenting them in:
    # //docs/speed/binary_size/metrics.md.

    PrintApkAnalysis(args.apk, tool_prefix, out_dir, chartjson=chartjson)
    _PrintDexAnalysis(args.apk, chartjson=chartjson)

    ignored_libs = args.loadable_module if args.loadable_module else []

    si_count = AnalyzeStaticInitializers(args.apk, tool_prefix, args.dump_sis,
                                         out_dir, ignored_libs)
    perf_tests_results_helper.ReportPerfResult(chartjson,
                                               'StaticInitializersCount',
                                               'count', si_count, 'count')

    if args.estimate_patch_size:
        _PrintPatchSizeEstimate(args.apk,
                                args.reference_apk_builder,
                                args.reference_apk_bucket,
                                chartjson=chartjson)

    if chartjson:
        results_path = os.path.join(args.output_dir, 'results-chart.json')
        logging.critical('Dumping chartjson to %s', results_path)
        with open(results_path, 'w') as json_file:
            json.dump(chartjson, json_file)

        # We would ideally generate a histogram set directly instead of generating
        # chartjson then converting. However, perf_tests_results_helper is in
        # //build, which doesn't seem to have any precedent for depending on
        # anything in Catapult. This can probably be fixed, but since this doesn't
        # need to be super fast or anything, converting is a good enough solution
        # for the time being.
        if args.output_format == 'histograms':
            histogram_result = convert_chart_json.ConvertChartJson(
                results_path)
            if histogram_result.returncode != 0:
                logging.error('chartjson conversion failed with error: %s',
                              histogram_result.stdout)
                return 1

            histogram_path = os.path.join(args.output_dir, 'perf_results.json')
            logging.critical('Dumping histograms to %s', histogram_path)
            with open(histogram_path, 'w') as json_file:
                json_file.write(histogram_result.stdout)
Exemple #15
0
def _PrintDexAnalysis(apk_filename, chartjson=None):
    title_prefix = os.path.basename(apk_filename) + '_'
    for data in GenerateDexAnalysis(apk_filename):
        title = title_prefix + data[0]
        perf_tests_results_helper.ReportPerfResult(chartjson, title, *data[1:])