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

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

Issue 2524033004: Report more apk size metrics in resource_sizes.py (Closed)
Patch Set: Created 4 years 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 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 """Prints the sizes of each given file. 161 """Prints the sizes of each given file.
162 162
163 Args: 163 Args:
164 files: List of files to print sizes for. 164 files: List of files to print sizes for.
165 """ 165 """
166 for f in files: 166 for f in files:
167 ReportPerfResult(chartjson, 'ResourceSizes', os.path.basename(f) + ' size', 167 ReportPerfResult(chartjson, 'ResourceSizes', os.path.basename(f) + ' size',
168 os.path.getsize(f), 'bytes') 168 os.path.getsize(f), 'bytes')
169 169
170 170
171 class _FileGroup(object):
172 """Represents a category that apk files can fall into."""
173
174 def __init__(self, name):
175 self.name = name
176 self._zip_infos = []
177 self._extracted = []
178
179 def AddZipInfo(self, zip_info, extracted=False):
180 self._zip_infos.append(zip_info)
181 self._extracted.append(extracted)
182
183 def GetNumEntries(self):
184 return len(self._zip_infos)
185
186 def FindByPattern(self, pattern):
187 return next(i for i in self._zip_infos if re.match(pattern, i.filename))
188
189 def FindLargest(self):
190 return max(self._zip_infos, key=lambda i: i.file_size)
191
192 def ComputeZippedSize(self):
193 return sum(i.compress_size for i in self._zip_infos)
194
195 def ComputeUncompressedSize(self):
196 return sum(i.file_size for i in self._zip_infos)
197
198 def ComputeExtractedSize(self):
199 ret = 0
200 for zi, extracted in zip(self._zip_infos, self._extracted):
201 if extracted:
202 ret += zi.file_size
203 return ret
204
205 def ComputeInstallSize(self):
206 return self.ComputeExtractedSize() + self.ComputeZippedSize()
207
208
171 def PrintApkAnalysis(apk_filename, chartjson=None): 209 def PrintApkAnalysis(apk_filename, chartjson=None):
172 """Analyse APK to determine size contributions of different file classes.""" 210 """Analyse APK to determine size contributions of different file classes."""
173 # Define a named tuple type for file grouping. 211 file_groups = []
174 # name: Human readable name for this file group
175 # regex: Regular expression to match filename
176 # extracted: Function that takes a file name and returns whether the file is
177 # extracted from the apk at install/runtime.
178 FileGroup = collections.namedtuple('FileGroup',
179 ['name', 'regex', 'extracted'])
180 212
181 # File groups are checked in sequence, so more specific regexes should be 213 def make_group(name):
182 # earlier in the list. 214 group = _FileGroup(name)
183 YES = lambda _: True 215 file_groups.append(group)
184 NO = lambda _: False 216 return group
185 FILE_GROUPS = ( 217
186 FileGroup('Native code', r'\.so$', lambda f: 'crazy' not in f), 218 native_code = make_group('Native code')
187 FileGroup('Java code', r'\.dex$', YES), 219 java_code = make_group('Java code')
188 FileGroup('Native resources (no l10n)', 220 native_resources_no_translations = make_group('Native resources (no l10n)')
189 r'^assets/.*(resources|percent)\.pak$', NO), 221 translations = make_group('Native resources (l10n)')
190 # For locale paks, assume only english paks are extracted. 222 icu_data = make_group('ICU (i18n library) data')
191 # Handles locale paks as bother resources or assets (.lpak or .pak). 223 v8_snapshots = make_group('V8 Snapshots')
192 FileGroup('Native resources (l10n)', 224 png_drawables = make_group('PNG drawables')
193 r'\.lpak$|^assets/.*(?!resources|percent)\.pak$', 225 res_directory = make_group('Non-compiled Android resources')
194 lambda f: 'en_' in f or 'en-' in f), 226 arsc = make_group('Compiled Android resources')
195 FileGroup('ICU (i18n library) data', r'^assets/icudtl\.dat$', NO), 227 metadata = make_group('Package metadata')
196 FileGroup('V8 Snapshots', r'^assets/.*\.bin$', NO), 228 unknown = make_group('Unknown files')
197 FileGroup('PNG drawables', r'\.png$', NO),
198 FileGroup('Non-compiled Android resources', r'^res/', NO),
199 FileGroup('Compiled Android resources', r'\.arsc$', NO),
200 FileGroup('Package metadata', r'^(META-INF/|AndroidManifest\.xml$)', NO),
201 FileGroup('Unknown files', r'.', NO),
202 )
203 229
204 apk = zipfile.ZipFile(apk_filename, 'r') 230 apk = zipfile.ZipFile(apk_filename, 'r')
205 try: 231 try:
206 apk_contents = apk.infolist() 232 apk_contents = apk.infolist()
207 finally: 233 finally:
208 apk.close() 234 apk.close()
209 235
210 total_apk_size = os.path.getsize(apk_filename) 236 total_apk_size = os.path.getsize(apk_filename)
211 apk_basename = os.path.basename(apk_filename) 237 apk_basename = os.path.basename(apk_filename)
212 238
213 found_files = {} 239 for member in apk_contents:
214 for group in FILE_GROUPS: 240 filename = member.filename
215 found_files[group] = [] 241 if filename.endswith('/'):
242 continue
216 243
217 for member in apk_contents: 244 if filename.endswith('.so'):
218 for group in FILE_GROUPS: 245 native_code.AddZipInfo(member, 'crazy' not in filename)
219 if re.search(group.regex, member.filename): 246 elif filename.endswith('.dex'):
220 found_files[group].append(member) 247 java_code.AddZipInfo(member, True)
221 break 248 elif re.search(r'^assets/.*(resources|percent)\.pak$', filename):
249 native_resources_no_translations.AddZipInfo(member)
250 elif re.search(r'\.lpak$|^assets/.*(?!resources|percent)\.pak$', filename):
251 translations.AddZipInfo(member, 'en_' in filename or 'en-' in filename)
252 elif filename == 'assets/icudtl.dat':
253 icu_data.AddZipInfo(member)
254 elif filename.endswith('.bin'):
255 v8_snapshots.AddZipInfo(member)
256 elif filename.endswith('.png') or filename.endswith('.webp'):
257 png_drawables.AddZipInfo(member)
258 elif filename.startswith('res/'):
259 res_directory.AddZipInfo(member)
260 elif filename.endswith('.arsc'):
261 arsc.AddZipInfo(member)
262 elif filename.startswith('META-INF') or filename == 'AndroidManifest.xml':
263 metadata.AddZipInfo(member)
222 else: 264 else:
223 raise KeyError('No group found for file "%s"' % member.filename) 265 unknown.AddZipInfo(member)
224 266
225 total_install_size = total_apk_size 267 total_install_size = total_apk_size
226 268
227 for group in FILE_GROUPS: 269 for group in file_groups:
228 uncompressed_size = 0 270 install_size = group.ComputeInstallSize()
229 packed_size = 0 271 total_install_size += group.ComputeExtractedSize()
230 extracted_size = 0
231 for member in found_files[group]:
232 uncompressed_size += member.file_size
233 packed_size += member.compress_size
234 # Assume that if a file is not compressed, then it is not extracted.
235 is_compressed = member.compress_type != zipfile.ZIP_STORED
236 if is_compressed and group.extracted(member.filename):
237 extracted_size += member.file_size
238 install_size = packed_size + extracted_size
239 total_install_size += extracted_size
240 272
241 ReportPerfResult(chartjson, apk_basename + '_Breakdown', 273 ReportPerfResult(chartjson, apk_basename + '_Breakdown',
242 group.name + ' size', packed_size, 'bytes') 274 group.name + ' size', group.ComputeZippedSize(), 'bytes')
243 ReportPerfResult(chartjson, apk_basename + '_InstallBreakdown', 275 ReportPerfResult(chartjson, apk_basename + '_InstallBreakdown',
244 group.name + ' size', install_size, 'bytes') 276 group.name + ' size', install_size, 'bytes')
245 ReportPerfResult(chartjson, apk_basename + '_Uncompressed', 277 ReportPerfResult(chartjson, apk_basename + '_Uncompressed',
246 group.name + ' size', uncompressed_size, 'bytes') 278 group.name + ' size', group.ComputeUncompressedSize(),
279 'bytes')
247 280
248 transfer_size = _CalculateCompressedSize(apk_filename) 281 ReportPerfResult(chartjson, apk_basename + '_InstallSize', 'APK size',
282 total_apk_size, 'bytes')
249 ReportPerfResult(chartjson, apk_basename + '_InstallSize', 283 ReportPerfResult(chartjson, apk_basename + '_InstallSize',
250 'Estimated installed size', total_install_size, 'bytes') 284 'Estimated installed size', total_install_size, 'bytes')
251 ReportPerfResult(chartjson, apk_basename + '_InstallSize', 'APK size', 285 transfer_size = _CalculateCompressedSize(apk_filename)
252 total_apk_size, 'bytes')
253 ReportPerfResult(chartjson, apk_basename + '_TransferSize', 286 ReportPerfResult(chartjson, apk_basename + '_TransferSize',
254 'Transfer size (deflate)', transfer_size, 'bytes') 287 'Transfer size (deflate)', transfer_size, 'bytes')
255 288
289 # Size of main dex vs remaining.
290 main_dex_info = java_code.FindByPattern('classes.dex')
291 if main_dex_info:
292 main_dex_size = main_dex_info.file_size
293 ReportPerfResult(chartjson, apk_basename + '_Specifics',
294 'main dex size', main_dex_size, 'bytes')
295 secondary_size = java_code.ComputeUncompressedSize() - main_dex_size
296 ReportPerfResult(chartjson, apk_basename + '_Specifics',
297 'secondary dex size', secondary_size, 'bytes')
298
299 # Size of main .so vs remaining.
300 main_lib_info = native_code.FindLargest()
301 if main_lib_info:
302 main_lib_size = main_lib_info.file_size
303 ReportPerfResult(chartjson, apk_basename + '_Specifics',
304 'main lib size', main_lib_size, 'bytes')
305 secondary_size = native_code.ComputeUncompressedSize() - main_lib_size
306 ReportPerfResult(chartjson, apk_basename + '_Specifics',
307 'other lib size', secondary_size, 'bytes')
308
309 # Main metric that we want to monitor for jumps.
310 normalized_apk_size = total_apk_size
311 # Always look at uncompressed .dex & .so.
312 normalized_apk_size -= java_code.ComputeZippedSize()
313 normalized_apk_size += java_code.ComputeUncompressedSize()
314 normalized_apk_size -= native_code.ComputeZippedSize()
315 normalized_apk_size += native_code.ComputeUncompressedSize()
316 # Avoid noise caused when strings change and translations haven't yet been
317 # updated.
318 english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak')
319 if english_pak:
320 normalized_apk_size -= translations.ComputeZippedSize()
321 # 1.17 found by looking at Chrome.apk and seeing how much smaller en-US.pak
322 # is relative to the average locale .pak.
323 normalized_apk_size += int(
324 english_pak.compress_size * translations.GetNumEntries() * 1.17)
325
326 ReportPerfResult(chartjson, apk_basename + '_Specifics',
327 'normalized apk size', normalized_apk_size, 'bytes')
328
329 ReportPerfResult(chartjson, apk_basename + '_Specifics',
330 'file count', len(apk_contents), 'zip entries')
331
256 332
257 def IsPakFileName(file_name): 333 def IsPakFileName(file_name):
258 """Returns whether the given file name ends with .pak or .lpak.""" 334 """Returns whether the given file name ends with .pak or .lpak."""
259 return file_name.endswith('.pak') or file_name.endswith('.lpak') 335 return file_name.endswith('.pak') or file_name.endswith('.lpak')
260 336
261 337
262 def PrintPakAnalysis(apk_filename, min_pak_resource_size): 338 def PrintPakAnalysis(apk_filename, min_pak_resource_size):
263 """Print sizes of all resources in all pak files in |apk_filename|.""" 339 """Print sizes of all resources in all pak files in |apk_filename|."""
264 print 340 print
265 print 'Analyzing pak files in %s...' % apk_filename 341 print 'Analyzing pak files in %s...' % apk_filename
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after
459 option_parser.add_option('--so-with-symbols-path', 535 option_parser.add_option('--so-with-symbols-path',
460 help='Mostly obsolete. Use .so within .apk instead.') 536 help='Mostly obsolete. Use .so within .apk instead.')
461 option_parser.add_option('--min-pak-resource-size', type='int', 537 option_parser.add_option('--min-pak-resource-size', type='int',
462 default=20*1024, 538 default=20*1024,
463 help='Minimum byte size of displayed pak resources.') 539 help='Minimum byte size of displayed pak resources.')
464 option_parser.add_option('--build_type', dest='build_type', default='Debug', 540 option_parser.add_option('--build_type', dest='build_type', default='Debug',
465 help='Obsoleted by --chromium-output-directory.') 541 help='Obsoleted by --chromium-output-directory.')
466 option_parser.add_option('--chromium-output-directory', 542 option_parser.add_option('--chromium-output-directory',
467 help='Location of the build artifacts. ' 543 help='Location of the build artifacts. '
468 'Takes precidence over --build_type.') 544 'Takes precidence over --build_type.')
469 option_parser.add_option('--chartjson', action="store_true", 545 option_parser.add_option('--chartjson', action='store_true',
470 help='Sets output mode to chartjson.') 546 help='Sets output mode to chartjson.')
471 option_parser.add_option('--output-dir', default='.', 547 option_parser.add_option('--output-dir', default='.',
472 help='Directory to save chartjson to.') 548 help='Directory to save chartjson to.')
549 option_parser.add_option('--no-output-dir', action='store_true',
550 help='Skip all measurements that rely on having '
551 'output-dir')
473 option_parser.add_option('-d', '--device', 552 option_parser.add_option('-d', '--device',
474 help='Dummy option for perf runner.') 553 help='Dummy option for perf runner.')
475 options, args = option_parser.parse_args(argv) 554 options, args = option_parser.parse_args(argv)
476 files = args[1:] 555 files = args[1:]
477 chartjson = _BASE_CHART.copy() if options.chartjson else None 556 chartjson = _BASE_CHART.copy() if options.chartjson else None
478 557
479 constants.SetBuildType(options.build_type) 558 constants.SetBuildType(options.build_type)
480 if options.chromium_output_directory: 559 if options.chromium_output_directory:
481 constants.SetOutputDirectory(options.chromium_output_directory) 560 constants.SetOutputDirectory(options.chromium_output_directory)
482 constants.CheckOutputDirectory() 561 if not options.no_output_dir:
562 constants.CheckOutputDirectory()
563 devil_chromium.Initialize()
483 564
484 # For backward compatibilty with buildbot scripts, treat --so-path as just 565 # For backward compatibilty with buildbot scripts, treat --so-path as just
485 # another file to print the size of. We don't need it for anything special any 566 # another file to print the size of. We don't need it for anything special any
486 # more. 567 # more.
487 if options.so_path: 568 if options.so_path:
488 files.append(options.so_path) 569 files.append(options.so_path)
489 570
490 if not files: 571 if not files:
491 option_parser.error('Must specify a file') 572 option_parser.error('Must specify a file')
492 573
493 devil_chromium.Initialize()
494
495 if options.so_with_symbols_path: 574 if options.so_with_symbols_path:
496 si_count = _PrintStaticInitializersCount(options.so_with_symbols_path) 575 si_count = _PrintStaticInitializersCount(options.so_with_symbols_path)
497 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, 576 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count,
498 'count') 577 'count')
499 578
500 PrintResourceSizes(files, chartjson=chartjson) 579 PrintResourceSizes(files, chartjson=chartjson)
501 580
502 for f in files: 581 for f in files:
503 if f.endswith('.apk'): 582 if f.endswith('.apk'):
504 PrintApkAnalysis(f, chartjson=chartjson) 583 PrintApkAnalysis(f, chartjson=chartjson)
505 PrintPakAnalysis(f, options.min_pak_resource_size)
506 _PrintDexAnalysis(f, chartjson=chartjson) 584 _PrintDexAnalysis(f, chartjson=chartjson)
507 if not options.so_with_symbols_path: 585 if not options.no_output_dir:
508 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson) 586 PrintPakAnalysis(f, options.min_pak_resource_size)
587 if not options.so_with_symbols_path:
588 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson)
509 589
510 if chartjson: 590 if chartjson:
511 results_path = os.path.join(options.output_dir, 'results-chart.json') 591 results_path = os.path.join(options.output_dir, 'results-chart.json')
512 logging.critical('Dumping json to %s', results_path) 592 logging.critical('Dumping json to %s', results_path)
513 with open(results_path, 'w') as json_file: 593 with open(results_path, 'w') as json_file:
514 json.dump(chartjson, json_file) 594 json.dump(chartjson, json_file)
515 595
516 596
517 if __name__ == '__main__': 597 if __name__ == '__main__':
518 sys.exit(main(sys.argv)) 598 sys.exit(main(sys.argv))
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