Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(194)

Unified Diff: build/android/resource_sizes.py

Issue 2524033004: Report more apk size metrics in resource_sizes.py (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/android/resource_sizes.py
diff --git a/build/android/resource_sizes.py b/build/android/resource_sizes.py
index 6c77ec3e61dfafeeb3506e4050015b021f7d60f7..56e5a4969f6043df9f2f1f5b05e21c91dbc6471b 100755
--- a/build/android/resource_sizes.py
+++ b/build/android/resource_sizes.py
@@ -168,38 +168,64 @@ def PrintResourceSizes(files, chartjson=None):
os.path.getsize(f), 'bytes')
+class _FileGroup(object):
+ """Represents a category that apk files can fall into."""
+
+ def __init__(self, name):
+ self.name = name
+ self._zip_infos = []
+ self._extracted = []
+
+ def AddZipInfo(self, zip_info, extracted=False):
+ self._zip_infos.append(zip_info)
+ self._extracted.append(extracted)
+
+ def GetNumEntries(self):
+ return len(self._zip_infos)
+
+ def FindByPattern(self, pattern):
+ return next(i for i in self._zip_infos if re.match(pattern, i.filename))
+
+ def FindLargest(self):
+ return max(self._zip_infos, key=lambda i: i.file_size)
+
+ def ComputeZippedSize(self):
+ return sum(i.compress_size for i in self._zip_infos)
+
+ def ComputeUncompressedSize(self):
+ return sum(i.file_size for i in self._zip_infos)
+
+ def ComputeExtractedSize(self):
+ ret = 0
+ for zi, extracted in zip(self._zip_infos, self._extracted):
+ if extracted:
+ ret += zi.file_size
+ return ret
+
+ def ComputeInstallSize(self):
+ return self.ComputeExtractedSize() + self.ComputeZippedSize()
+
+
def PrintApkAnalysis(apk_filename, chartjson=None):
"""Analyse APK to determine size contributions of different file classes."""
- # Define a named tuple type for file grouping.
- # name: Human readable name for this file group
- # regex: Regular expression to match filename
- # extracted: Function that takes a file name and returns whether the file is
- # extracted from the apk at install/runtime.
- FileGroup = collections.namedtuple('FileGroup',
- ['name', 'regex', 'extracted'])
-
- # File groups are checked in sequence, so more specific regexes should be
- # earlier in the list.
- YES = lambda _: True
- NO = lambda _: False
- FILE_GROUPS = (
- FileGroup('Native code', r'\.so$', lambda f: 'crazy' not in f),
- FileGroup('Java code', r'\.dex$', YES),
- FileGroup('Native resources (no l10n)',
- r'^assets/.*(resources|percent)\.pak$', NO),
- # For locale paks, assume only english paks are extracted.
- # Handles locale paks as bother resources or assets (.lpak or .pak).
- FileGroup('Native resources (l10n)',
- r'\.lpak$|^assets/.*(?!resources|percent)\.pak$',
- lambda f: 'en_' in f or 'en-' in f),
- FileGroup('ICU (i18n library) data', r'^assets/icudtl\.dat$', NO),
- FileGroup('V8 Snapshots', r'^assets/.*\.bin$', NO),
- FileGroup('PNG drawables', r'\.png$', NO),
- FileGroup('Non-compiled Android resources', r'^res/', NO),
- FileGroup('Compiled Android resources', r'\.arsc$', NO),
- FileGroup('Package metadata', r'^(META-INF/|AndroidManifest\.xml$)', NO),
- FileGroup('Unknown files', r'.', NO),
- )
+ 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)')
+ 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')
apk = zipfile.ZipFile(apk_filename, 'r')
try:
@@ -210,49 +236,99 @@ def PrintApkAnalysis(apk_filename, chartjson=None):
total_apk_size = os.path.getsize(apk_filename)
apk_basename = os.path.basename(apk_filename)
- found_files = {}
- for group in FILE_GROUPS:
- found_files[group] = []
-
for member in apk_contents:
- for group in FILE_GROUPS:
- if re.search(group.regex, member.filename):
- found_files[group].append(member)
- break
+ filename = member.filename
+ if filename.endswith('/'):
+ continue
+
+ if filename.endswith('.so'):
+ native_code.AddZipInfo(member, 'crazy' not in filename)
+ elif filename.endswith('.dex'):
+ java_code.AddZipInfo(member, True)
+ elif re.search(r'^assets/.*(resources|percent)\.pak$', filename):
+ native_resources_no_translations.AddZipInfo(member)
+ elif re.search(r'\.lpak$|^assets/.*(?!resources|percent)\.pak$', filename):
+ translations.AddZipInfo(member, 'en_' in filename or 'en-' in filename)
+ 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)
else:
- raise KeyError('No group found for file "%s"' % member.filename)
+ unknown.AddZipInfo(member)
total_install_size = total_apk_size
- for group in FILE_GROUPS:
- uncompressed_size = 0
- packed_size = 0
- extracted_size = 0
- for member in found_files[group]:
- uncompressed_size += member.file_size
- packed_size += member.compress_size
- # Assume that if a file is not compressed, then it is not extracted.
- is_compressed = member.compress_type != zipfile.ZIP_STORED
- if is_compressed and group.extracted(member.filename):
- extracted_size += member.file_size
- install_size = packed_size + extracted_size
- total_install_size += extracted_size
+ for group in file_groups:
+ install_size = group.ComputeInstallSize()
+ total_install_size += group.ComputeExtractedSize()
ReportPerfResult(chartjson, apk_basename + '_Breakdown',
- group.name + ' size', packed_size, 'bytes')
+ group.name + ' size', group.ComputeZippedSize(), 'bytes')
ReportPerfResult(chartjson, apk_basename + '_InstallBreakdown',
group.name + ' size', install_size, 'bytes')
ReportPerfResult(chartjson, apk_basename + '_Uncompressed',
- group.name + ' size', uncompressed_size, 'bytes')
+ group.name + ' size', group.ComputeUncompressedSize(),
+ 'bytes')
- transfer_size = _CalculateCompressedSize(apk_filename)
- ReportPerfResult(chartjson, apk_basename + '_InstallSize',
- 'Estimated installed size', total_install_size, 'bytes')
ReportPerfResult(chartjson, apk_basename + '_InstallSize', 'APK size',
total_apk_size, 'bytes')
+ ReportPerfResult(chartjson, apk_basename + '_InstallSize',
+ 'Estimated installed size', total_install_size, 'bytes')
+ transfer_size = _CalculateCompressedSize(apk_filename)
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
+ ReportPerfResult(chartjson, apk_basename + '_Specifics',
+ 'main dex size', main_dex_size, 'bytes')
+ secondary_size = java_code.ComputeUncompressedSize() - main_dex_size
+ 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
+ ReportPerfResult(chartjson, apk_basename + '_Specifics',
+ 'main lib size', main_lib_size, 'bytes')
+ secondary_size = native_code.ComputeUncompressedSize() - main_lib_size
+ ReportPerfResult(chartjson, apk_basename + '_Specifics',
+ 'other lib size', secondary_size, 'bytes')
+
+ # Main metric that we want to monitor for jumps.
+ normalized_apk_size = total_apk_size
+ # Always look at uncompressed .dex & .so.
+ normalized_apk_size -= java_code.ComputeZippedSize()
+ normalized_apk_size += java_code.ComputeUncompressedSize()
+ normalized_apk_size -= native_code.ComputeZippedSize()
+ normalized_apk_size += native_code.ComputeUncompressedSize()
+ # Avoid noise caused when strings change and translations haven't yet been
+ # updated.
+ english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak')
+ if english_pak:
+ normalized_apk_size -= translations.ComputeZippedSize()
+ # 1.17 found by looking at Chrome.apk and seeing how much smaller en-US.pak
+ # is relative to the average locale .pak.
+ normalized_apk_size += int(
+ english_pak.compress_size * translations.GetNumEntries() * 1.17)
+
+ ReportPerfResult(chartjson, apk_basename + '_Specifics',
+ 'normalized apk size', normalized_apk_size, 'bytes')
+
+ ReportPerfResult(chartjson, apk_basename + '_Specifics',
+ 'file count', len(apk_contents), 'zip entries')
+
def IsPakFileName(file_name):
"""Returns whether the given file name ends with .pak or .lpak."""
@@ -466,10 +542,13 @@ Pass any number of files to graph their sizes. Any files with the extension
option_parser.add_option('--chromium-output-directory',
help='Location of the build artifacts. '
'Takes precidence over --build_type.')
- option_parser.add_option('--chartjson', action="store_true",
+ option_parser.add_option('--chartjson', action='store_true',
help='Sets output mode to chartjson.')
option_parser.add_option('--output-dir', default='.',
help='Directory to save chartjson to.')
+ option_parser.add_option('--no-output-dir', action='store_true',
+ help='Skip all measurements that rely on having '
+ 'output-dir')
option_parser.add_option('-d', '--device',
help='Dummy option for perf runner.')
options, args = option_parser.parse_args(argv)
@@ -479,7 +558,9 @@ Pass any number of files to graph their sizes. Any files with the extension
constants.SetBuildType(options.build_type)
if options.chromium_output_directory:
constants.SetOutputDirectory(options.chromium_output_directory)
- constants.CheckOutputDirectory()
+ if not options.no_output_dir:
+ constants.CheckOutputDirectory()
+ devil_chromium.Initialize()
# For backward compatibilty with buildbot scripts, treat --so-path as just
# another file to print the size of. We don't need it for anything special any
@@ -490,8 +571,6 @@ Pass any number of files to graph their sizes. Any files with the extension
if not files:
option_parser.error('Must specify a file')
- devil_chromium.Initialize()
-
if options.so_with_symbols_path:
si_count = _PrintStaticInitializersCount(options.so_with_symbols_path)
ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count,
@@ -502,10 +581,11 @@ Pass any number of files to graph their sizes. Any files with the extension
for f in files:
if f.endswith('.apk'):
PrintApkAnalysis(f, chartjson=chartjson)
- PrintPakAnalysis(f, options.min_pak_resource_size)
_PrintDexAnalysis(f, chartjson=chartjson)
- if not options.so_with_symbols_path:
- _PrintStaticInitializersCountFromApk(f, chartjson=chartjson)
+ if not options.no_output_dir:
+ PrintPakAnalysis(f, options.min_pak_resource_size)
+ if not options.so_with_symbols_path:
+ _PrintStaticInitializersCountFromApk(f, chartjson=chartjson)
if chartjson:
results_path = os.path.join(options.output_dir, 'results-chart.json')
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698