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

Side by Side Diff: build/android/resource_sizes.py

Issue 2930313002: resource_sizes.py: Better pakfile and dex normalization. (Closed)
Patch Set: Review comments Created 3 years, 6 months 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Prints the size of each given file and optionally computes the size of 6 """Prints the size of each given file and optionally computes the size of
7 libchrome.so without the dependencies added for building with android NDK. 7 libchrome.so without the dependencies added for building with android NDK.
8 Also breaks down the contents of the APK to determine the installed size 8 Also breaks down the contents of the APK to determine the installed size
9 and assign size contributions to different classes of file. 9 and assign size contributions to different classes of file.
10 """ 10 """
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
104 'benchmark_name': 'resource_sizes', 104 'benchmark_name': 'resource_sizes',
105 'benchmark_description': 'APK resource size information.', 105 'benchmark_description': 'APK resource size information.',
106 'trace_rerun_options': [], 106 'trace_rerun_options': [],
107 'charts': {} 107 'charts': {}
108 } 108 }
109 _DUMP_STATIC_INITIALIZERS_PATH = os.path.join( 109 _DUMP_STATIC_INITIALIZERS_PATH = os.path.join(
110 host_paths.DIR_SOURCE_ROOT, 'tools', 'linux', 'dump-static-initializers.py') 110 host_paths.DIR_SOURCE_ROOT, 'tools', 'linux', 'dump-static-initializers.py')
111 # Pragma exists when enable_resource_whitelist_generation=true. 111 # Pragma exists when enable_resource_whitelist_generation=true.
112 _RC_HEADER_RE = re.compile( 112 _RC_HEADER_RE = re.compile(
113 r'^#define (?P<name>\w+) (?:_Pragma\(.*?\) )?(?P<id>\d+)$') 113 r'^#define (?P<name>\w+) (?:_Pragma\(.*?\) )?(?P<id>\d+)$')
114 _RE_NON_LANGUAGE_PAK = re.compile(r'^assets/.*(resources|percent)\.pak$')
115 _RE_COMPRESSED_LANGUAGE_PAK = re.compile(
116 r'\.lpak$|^assets/(?!stored-locales/).*(?!resources|percent)\.pak$')
117 _RE_STORED_LANGUAGE_PAK = re.compile(r'^assets/stored-locales/.*\.pak$')
114 _READELF_SIZES_METRICS = { 118 _READELF_SIZES_METRICS = {
115 'text': ['.text'], 119 'text': ['.text'],
116 'data': ['.data', '.rodata', '.data.rel.ro', '.data.rel.ro.local'], 120 'data': ['.data', '.rodata', '.data.rel.ro', '.data.rel.ro.local'],
117 'relocations': ['.rel.dyn', '.rel.plt', '.rela.dyn', '.rela.plt'], 121 'relocations': ['.rel.dyn', '.rel.plt', '.rela.dyn', '.rela.plt'],
118 'unwind': ['.ARM.extab', '.ARM.exidx', '.eh_frame', '.eh_frame_hdr',], 122 'unwind': ['.ARM.extab', '.ARM.exidx', '.eh_frame', '.eh_frame_hdr',],
119 'symbols': ['.dynsym', '.dynstr', '.dynamic', '.shstrtab', '.got', '.plt', 123 'symbols': ['.dynsym', '.dynstr', '.dynamic', '.shstrtab', '.got', '.plt',
120 '.got.plt', '.hash'], 124 '.got.plt', '.hash'],
121 'bss': ['.bss'], 125 'bss': ['.bss'],
122 'other': ['.init_array', '.fini_array', '.comment', '.note.gnu.gold-version', 126 'other': ['.init_array', '.fini_array', '.comment', '.note.gnu.gold-version',
123 '.ARM.attributes', '.note.gnu.build-id', '.gnu.version', 127 '.ARM.attributes', '.note.gnu.build-id', '.gnu.version',
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
158 return section_sizes 162 return section_sizes
159 163
160 164
161 def _ParseLibBuildId(so_path, tools_prefix): 165 def _ParseLibBuildId(so_path, tools_prefix):
162 """Returns the Build ID of the given native library.""" 166 """Returns the Build ID of the given native library."""
163 stdout = _RunReadelf(so_path, ['-n'], tools_prefix) 167 stdout = _RunReadelf(so_path, ['-n'], tools_prefix)
164 match = re.search(r'Build ID: (\w+)', stdout) 168 match = re.search(r'Build ID: (\w+)', stdout)
165 return match.group(1) if match else None 169 return match.group(1) if match else None
166 170
167 171
172 def _ParseManifestAttributes(apk_path):
173 # Check if the manifest specifies whether or not to extract native libs.
174 skip_extract_lib = False
175 output = cmd_helper.GetCmdOutput([
176 _AAPT_PATH.read(), 'd', 'xmltree', apk_path, 'AndroidManifest.xml'])
177 m = re.search(r'extractNativeLibs\(.*\)=\(.*\)(\w)', output)
178 if m:
179 skip_extract_lib = not bool(int(m.group(1)))
180
181 m = re.search(r'android:minSdkVersion\(\w+\)=\(type \w+\)(\w+)\n', output)
182 sdk_version = int(m.group(1), 16)
183 # Dex decompression overhead varies by Android version.
184 if sdk_version < 21:
185 dex_multiplier = 1
186 elif sdk_version < 24:
187 dex_multiplier = 3
188 elif 'monochrome' in apk_path.lower():
189 # Monochrome consists of WebView and Chrome packaged into a single APK, so
agrieve 2017/06/16 01:37:16 It's not that both apps extract the dex, it's that
estevenson 2017/06/16 14:56:19 Ahhh yes that's what it is, couldn't remember. Fix
190 # we need to account for both apps extracting the dex file.
191 dex_multiplier = 2
192 else:
193 dex_multiplier = 1
194
195 return dex_multiplier, skip_extract_lib
196
197
168 def CountStaticInitializers(so_path, tools_prefix): 198 def CountStaticInitializers(so_path, tools_prefix):
169 # Static initializers expected in official builds. Note that this list is 199 # Static initializers expected in official builds. Note that this list is
170 # built using 'nm' on libchrome.so which results from a GCC official build 200 # built using 'nm' on libchrome.so which results from a GCC official build
171 # (i.e. Clang is not supported currently). 201 # (i.e. Clang is not supported currently).
172 def get_elf_section_size(readelf_stdout, section_name): 202 def get_elf_section_size(readelf_stdout, section_name):
173 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8 203 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8
174 match = re.search(r'\.%s.*$' % re.escape(section_name), 204 match = re.search(r'\.%s.*$' % re.escape(section_name),
175 readelf_stdout, re.MULTILINE) 205 readelf_stdout, re.MULTILINE)
176 if not match: 206 if not match:
177 return (False, -1) 207 return (False, -1)
(...skipping 22 matching lines...) Expand all
200 return si_count 230 return si_count
201 231
202 232
203 def GetStaticInitializers(so_path, tools_prefix): 233 def GetStaticInitializers(so_path, tools_prefix):
204 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d', 234 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d',
205 so_path, '-t', tools_prefix]) 235 so_path, '-t', tools_prefix])
206 summary = re.search(r'Found \d+ static initializers in (\d+) files.', output) 236 summary = re.search(r'Found \d+ static initializers in (\d+) files.', output)
207 return output.splitlines()[:-1], int(summary.group(1)) 237 return output.splitlines()[:-1], int(summary.group(1))
208 238
209 239
240 def _NormalizeLanguagePaks(translations, normalized_apk_size, factor):
241 english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak')
242 num_translations = translations.GetNumEntries()
243 if english_pak:
244 normalized_apk_size -= translations.ComputeZippedSize()
245 normalized_apk_size += int(
246 english_pak.compress_size * num_translations * factor)
247 return normalized_apk_size
248
249
210 def _NormalizeResourcesArsc(apk_path): 250 def _NormalizeResourcesArsc(apk_path):
211 """Estimates the expected overhead of untranslated strings in resources.arsc. 251 """Estimates the expected overhead of untranslated strings in resources.arsc.
212 252
213 See http://crbug.com/677966 for why this is necessary. 253 See http://crbug.com/677966 for why this is necessary.
214 """ 254 """
215 aapt_output = _RunAaptDumpResources(apk_path) 255 aapt_output = _RunAaptDumpResources(apk_path)
216 256
217 # en-rUS is in the default config and may be cluttered with non-translatable 257 # en-rUS is in the default config and may be cluttered with non-translatable
218 # strings, so en-rGB is a better baseline for finding missing translations. 258 # strings, so en-rGB is a better baseline for finding missing translations.
219 en_strings = _CreateResourceIdValueMap(aapt_output, 'en-rGB') 259 en_strings = _CreateResourceIdValueMap(aapt_output, 'en-rGB')
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 perf_tests_results_helper.PrintPerfResult( 315 perf_tests_results_helper.PrintPerfResult(
276 graph_title, trace_title, [value], units) 316 graph_title, trace_title, [value], units)
277 317
278 318
279 class _FileGroup(object): 319 class _FileGroup(object):
280 """Represents a category that apk files can fall into.""" 320 """Represents a category that apk files can fall into."""
281 321
282 def __init__(self, name): 322 def __init__(self, name):
283 self.name = name 323 self.name = name
284 self._zip_infos = [] 324 self._zip_infos = []
285 self._extracted = [] 325 self._extracted_multipliers = []
286 326
287 def AddZipInfo(self, zip_info, extracted=False): 327 def AddZipInfo(self, zip_info, extracted_multiplier=0):
288 self._zip_infos.append(zip_info) 328 self._zip_infos.append(zip_info)
289 self._extracted.append(extracted) 329 self._extracted_multipliers.append(extracted_multiplier)
290 330
291 def AllEntries(self): 331 def AllEntries(self):
292 return iter(self._zip_infos) 332 return iter(self._zip_infos)
293 333
294 def GetNumEntries(self): 334 def GetNumEntries(self):
295 return len(self._zip_infos) 335 return len(self._zip_infos)
296 336
297 def FindByPattern(self, pattern): 337 def FindByPattern(self, pattern):
298 return next((i for i in self._zip_infos if re.match(pattern, i.filename)), 338 return next((i for i in self._zip_infos if re.match(pattern, i.filename)),
299 None) 339 None)
300 340
301 def FindLargest(self): 341 def FindLargest(self):
302 if not self._zip_infos: 342 if not self._zip_infos:
303 return None 343 return None
304 return max(self._zip_infos, key=lambda i: i.file_size) 344 return max(self._zip_infos, key=lambda i: i.file_size)
305 345
306 def ComputeZippedSize(self): 346 def ComputeZippedSize(self):
307 return sum(i.compress_size for i in self._zip_infos) 347 return sum(i.compress_size for i in self._zip_infos)
308 348
309 def ComputeUncompressedSize(self): 349 def ComputeUncompressedSize(self):
310 return sum(i.file_size for i in self._zip_infos) 350 return sum(i.file_size for i in self._zip_infos)
311 351
312 def ComputeExtractedSize(self): 352 def ComputeExtractedSize(self):
313 ret = 0 353 ret = 0
314 for zi, extracted in zip(self._zip_infos, self._extracted): 354 for zi, multiplier in zip(self._zip_infos, self._extracted_multipliers):
315 if extracted: 355 ret += zi.file_size * multiplier
316 ret += zi.file_size
317 return ret 356 return ret
318 357
319 def ComputeInstallSize(self): 358 def ComputeInstallSize(self):
320 return self.ComputeExtractedSize() + self.ComputeZippedSize() 359 return self.ComputeExtractedSize() + self.ComputeZippedSize()
321 360
322 361
323 def PrintApkAnalysis(apk_filename, tools_prefix, chartjson=None): 362 def PrintApkAnalysis(apk_filename, tools_prefix, chartjson=None):
324 """Analyse APK to determine size contributions of different file classes.""" 363 """Analyse APK to determine size contributions of different file classes."""
325 file_groups = [] 364 file_groups = []
326 365
327 def make_group(name): 366 def make_group(name):
328 group = _FileGroup(name) 367 group = _FileGroup(name)
329 file_groups.append(group) 368 file_groups.append(group)
330 return group 369 return group
331 370
332 native_code = make_group('Native code') 371 native_code = make_group('Native code')
333 java_code = make_group('Java code') 372 java_code = make_group('Java code')
334 native_resources_no_translations = make_group('Native resources (no l10n)') 373 native_resources_no_translations = make_group('Native resources (no l10n)')
335 translations = make_group('Native resources (l10n)') 374 translations = make_group('Native resources (l10n)')
375 stored_translations = make_group('Native resources stored (l10n)')
336 icu_data = make_group('ICU (i18n library) data') 376 icu_data = make_group('ICU (i18n library) data')
337 v8_snapshots = make_group('V8 Snapshots') 377 v8_snapshots = make_group('V8 Snapshots')
338 png_drawables = make_group('PNG drawables') 378 png_drawables = make_group('PNG drawables')
339 res_directory = make_group('Non-compiled Android resources') 379 res_directory = make_group('Non-compiled Android resources')
340 arsc = make_group('Compiled Android resources') 380 arsc = make_group('Compiled Android resources')
341 metadata = make_group('Package metadata') 381 metadata = make_group('Package metadata')
342 unknown = make_group('Unknown files') 382 unknown = make_group('Unknown files')
343 notices = make_group('licenses.notice file') 383 notices = make_group('licenses.notice file')
344 384
345 apk = zipfile.ZipFile(apk_filename, 'r') 385 apk = zipfile.ZipFile(apk_filename, 'r')
346 try: 386 try:
347 apk_contents = apk.infolist() 387 apk_contents = apk.infolist()
348 finally: 388 finally:
349 apk.close() 389 apk.close()
350 390
391 dex_multiplier, skip_extract_lib = _ParseManifestAttributes(apk_filename)
351 total_apk_size = os.path.getsize(apk_filename) 392 total_apk_size = os.path.getsize(apk_filename)
352 apk_basename = os.path.basename(apk_filename) 393 apk_basename = os.path.basename(apk_filename)
353
354 for member in apk_contents: 394 for member in apk_contents:
355 filename = member.filename 395 filename = member.filename
356 if filename.endswith('/'): 396 if filename.endswith('/'):
357 continue 397 continue
358
359 if filename.endswith('.so'): 398 if filename.endswith('.so'):
360 native_code.AddZipInfo(member, 'crazy' not in filename) 399 should_extract_lib = not (skip_extract_lib or 'crazy' in filename)
400 native_code.AddZipInfo(member, int(should_extract_lib))
361 elif filename.endswith('.dex'): 401 elif filename.endswith('.dex'):
362 java_code.AddZipInfo(member, True) 402 java_code.AddZipInfo(member, dex_multiplier)
363 elif re.search(r'^assets/.*(resources|percent)\.pak$', filename): 403 elif re.search(_RE_NON_LANGUAGE_PAK, filename):
364 native_resources_no_translations.AddZipInfo(member) 404 native_resources_no_translations.AddZipInfo(member)
365 elif re.search(r'\.lpak$|^assets/.*(?!resources|percent)\.pak$', filename): 405 elif re.search(_RE_COMPRESSED_LANGUAGE_PAK, filename):
366 translations.AddZipInfo(member, 'en_' in filename or 'en-' in filename) 406 translations.AddZipInfo(member)
407 elif re.search(_RE_STORED_LANGUAGE_PAK, filename):
408 stored_translations.AddZipInfo(
409 member, 'en_' in filename or 'en-' in filename)
agrieve 2017/06/16 01:37:16 add int()
estevenson 2017/06/16 14:56:19 Oops, must have mixed these up. Added int() to the
367 elif filename == 'assets/icudtl.dat': 410 elif filename == 'assets/icudtl.dat':
368 icu_data.AddZipInfo(member) 411 icu_data.AddZipInfo(member)
369 elif filename.endswith('.bin'): 412 elif filename.endswith('.bin'):
370 v8_snapshots.AddZipInfo(member) 413 v8_snapshots.AddZipInfo(member)
371 elif filename.endswith('.png') or filename.endswith('.webp'): 414 elif filename.endswith('.png') or filename.endswith('.webp'):
372 png_drawables.AddZipInfo(member) 415 png_drawables.AddZipInfo(member)
373 elif filename.startswith('res/'): 416 elif filename.startswith('res/'):
374 res_directory.AddZipInfo(member) 417 res_directory.AddZipInfo(member)
375 elif filename.endswith('.arsc'): 418 elif filename.endswith('.arsc'):
376 arsc.AddZipInfo(member) 419 arsc.AddZipInfo(member)
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
431 474
432 # Main metric that we want to monitor for jumps. 475 # Main metric that we want to monitor for jumps.
433 normalized_apk_size = total_apk_size 476 normalized_apk_size = total_apk_size
434 # Always look at uncompressed .dex & .so. 477 # Always look at uncompressed .dex & .so.
435 normalized_apk_size -= java_code.ComputeZippedSize() 478 normalized_apk_size -= java_code.ComputeZippedSize()
436 normalized_apk_size += java_code.ComputeUncompressedSize() 479 normalized_apk_size += java_code.ComputeUncompressedSize()
437 normalized_apk_size -= native_code.ComputeZippedSize() 480 normalized_apk_size -= native_code.ComputeZippedSize()
438 normalized_apk_size += native_code.ComputeUncompressedSize() 481 normalized_apk_size += native_code.ComputeUncompressedSize()
439 # Avoid noise caused when strings change and translations haven't yet been 482 # Avoid noise caused when strings change and translations haven't yet been
440 # updated. 483 # updated.
441 english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak')
442 num_translations = translations.GetNumEntries() 484 num_translations = translations.GetNumEntries()
443 if english_pak and num_translations > 1: 485 if num_translations > 1:
444 normalized_apk_size -= translations.ComputeZippedSize() 486 # Multipliers found by looking at MonochromePublic.apk and seeing how much
445 # 1.17 found by looking at Chrome.apk and seeing how much smaller en-US.pak 487 # smaller en-US.pak is relative to the average locale.pak.
446 # is relative to the average locale .pak. 488 normalized_apk_size = _NormalizeLanguagePaks(
447 normalized_apk_size += int( 489 translations, normalized_apk_size, 1.17)
448 english_pak.compress_size * num_translations * 1.17) 490 normalized_apk_size = _NormalizeLanguagePaks(
491 stored_translations, normalized_apk_size, 1.43)
449 normalized_apk_size += int(_NormalizeResourcesArsc(apk_filename)) 492 normalized_apk_size += int(_NormalizeResourcesArsc(apk_filename))
450 493
451 ReportPerfResult(chartjson, apk_basename + '_Specifics', 494 ReportPerfResult(chartjson, apk_basename + '_Specifics',
452 'normalized apk size', normalized_apk_size, 'bytes') 495 'normalized apk size', normalized_apk_size, 'bytes')
453 496
454 ReportPerfResult(chartjson, apk_basename + '_Specifics', 497 ReportPerfResult(chartjson, apk_basename + '_Specifics',
455 'file count', len(apk_contents), 'zip entries') 498 'file count', len(apk_contents), 'zip entries')
456 499
457 for info in unknown.AllEntries(): 500 for info in unknown.AllEntries():
458 print 'Unknown entry:', info.filename, info.compress_size 501 print 'Unknown entry:', info.filename, info.compress_size
(...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after
755 default=apk_downloader.DEFAULT_BUILDER, 798 default=apk_downloader.DEFAULT_BUILDER,
756 help='Builder name to use for reference APK for patch ' 799 help='Builder name to use for reference APK for patch '
757 'size estimates.') 800 'size estimates.')
758 argparser.add_argument('--reference-apk-bucket', 801 argparser.add_argument('--reference-apk-bucket',
759 default=apk_downloader.DEFAULT_BUCKET, 802 default=apk_downloader.DEFAULT_BUCKET,
760 help='Storage bucket holding reference APKs.') 803 help='Storage bucket holding reference APKs.')
761 argparser.add_argument('apk', help='APK file path.') 804 argparser.add_argument('apk', help='APK file path.')
762 args = argparser.parse_args() 805 args = argparser.parse_args()
763 806
764 chartjson = _BASE_CHART.copy() if args.chartjson else None 807 chartjson = _BASE_CHART.copy() if args.chartjson else None
765
766 if args.chromium_output_directory: 808 if args.chromium_output_directory:
767 constants.SetOutputDirectory(args.chromium_output_directory) 809 constants.SetOutputDirectory(args.chromium_output_directory)
768 if not args.no_output_dir: 810 if not args.no_output_dir:
769 constants.CheckOutputDirectory() 811 constants.CheckOutputDirectory()
770 devil_chromium.Initialize() 812 devil_chromium.Initialize()
771 build_vars = _ReadBuildVars(constants.GetOutDirectory()) 813 build_vars = _ReadBuildVars(constants.GetOutDirectory())
772 tools_prefix = os.path.join(constants.GetOutDirectory(), 814 tools_prefix = os.path.join(constants.GetOutDirectory(),
773 build_vars['android_tool_prefix']) 815 build_vars['android_tool_prefix'])
774 else: 816 else:
775 tools_prefix = '' 817 tools_prefix = ''
(...skipping 10 matching lines...) Expand all
786 chartjson=chartjson) 828 chartjson=chartjson)
787 if chartjson: 829 if chartjson:
788 results_path = os.path.join(args.output_dir, 'results-chart.json') 830 results_path = os.path.join(args.output_dir, 'results-chart.json')
789 logging.critical('Dumping json to %s', results_path) 831 logging.critical('Dumping json to %s', results_path)
790 with open(results_path, 'w') as json_file: 832 with open(results_path, 'w') as json_file:
791 json.dump(chartjson, json_file) 833 json.dump(chartjson, json_file)
792 834
793 835
794 if __name__ == '__main__': 836 if __name__ == '__main__':
795 sys.exit(main()) 837 sys.exit(main())
OLDNEW
« 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