OLD | NEW |
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 """ |
11 | 11 |
12 import collections | 12 import collections |
13 import json | 13 import json |
14 import logging | 14 import logging |
15 import operator | 15 import operator |
16 import optparse | 16 import optparse |
17 import os | 17 import os |
18 import re | 18 import re |
19 import struct | 19 import struct |
20 import sys | 20 import sys |
21 import tempfile | 21 import tempfile |
22 import zipfile | 22 import zipfile |
23 import zlib | 23 import zlib |
24 | 24 |
25 import devil_chromium | 25 import devil_chromium |
26 from devil.utils import cmd_helper | 26 from devil.utils import cmd_helper |
| 27 import method_count |
27 from pylib import constants | 28 from pylib import constants |
28 from pylib.constants import host_paths | 29 from pylib.constants import host_paths |
29 | 30 |
30 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') | 31 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') |
31 | 32 |
32 # Prepend the grit module from the source tree so it takes precedence over other | 33 # Prepend the grit module from the source tree so it takes precedence over other |
33 # grit versions that might present in the search path. | 34 # grit versions that might present in the search path. |
34 with host_paths.SysPath(_GRIT_PATH, 1): | 35 with host_paths.SysPath(_GRIT_PATH, 1): |
35 from grit.format import data_pack # pylint: disable=import-error | 36 from grit.format import data_pack # pylint: disable=import-error |
36 | 37 |
37 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): | 38 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): |
38 import perf_tests_results_helper # pylint: disable=import-error | 39 import perf_tests_results_helper # pylint: disable=import-error |
39 | 40 |
| 41 |
40 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk | 42 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk |
41 # https://bugs.python.org/issue14315 | 43 # https://bugs.python.org/issue14315 |
42 def _PatchedDecodeExtra(self): | 44 def _PatchedDecodeExtra(self): |
43 # Try to decode the extra field. | 45 # Try to decode the extra field. |
44 extra = self.extra | 46 extra = self.extra |
45 unpack = struct.unpack | 47 unpack = struct.unpack |
46 while len(extra) >= 4: | 48 while len(extra) >= 4: |
47 tp, ln = unpack('<HH', extra[:4]) | 49 tp, ln = unpack('<HH', extra[:4]) |
48 if tp == 1: | 50 if tp == 1: |
49 if ln >= 24: | 51 if ln >= 24: |
(...skipping 297 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
347 if m: | 349 if m: |
348 i = int(m.group('id')) | 350 i = int(m.group('id')) |
349 name = m.group('name') | 351 name = m.group('name') |
350 if i in id_name_map and name != id_name_map[i]: | 352 if i in id_name_map and name != id_name_map[i]: |
351 print 'WARNING: Resource ID conflict %s (%s vs %s)' % ( | 353 print 'WARNING: Resource ID conflict %s (%s vs %s)' % ( |
352 i, id_name_map[i], name) | 354 i, id_name_map[i], name) |
353 id_name_map[i] = name | 355 id_name_map[i] = name |
354 return id_name_map | 356 return id_name_map |
355 | 357 |
356 | 358 |
357 def PrintStaticInitializersCount(so_with_symbols_path, chartjson=None): | 359 def _PrintStaticInitializersCountFromApk(apk_filename, chartjson=None): |
358 """Emits the performance result for static initializers found in the provided | 360 print 'Finding static initializers (can take a minute)' |
359 shared library. Additionally, files for which static initializers were | 361 with zipfile.ZipFile(apk_filename) as z: |
360 found are printed on the standard output. | 362 infolist = z.infolist() |
| 363 out_dir = constants.GetOutDirectory() |
| 364 si_count = 0 |
| 365 for zip_info in infolist: |
| 366 # Check file size to account for placeholder libraries. |
| 367 if zip_info.filename.endswith('.so') and zip_info.file_size > 0: |
| 368 unstripped_path = os.path.join(out_dir, 'lib.unstripped', |
| 369 os.path.basename(zip_info.filename)) |
| 370 if os.path.exists(unstripped_path): |
| 371 si_count += _PrintStaticInitializersCount(unstripped_path) |
| 372 else: |
| 373 raise Exception('Unstripped .so not found. Looked here: %s', |
| 374 unstripped_path) |
| 375 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, |
| 376 'count') |
| 377 |
| 378 |
| 379 def _PrintStaticInitializersCount(so_with_symbols_path): |
| 380 """Counts the number of static initializers in the given shared library. |
| 381 Additionally, files for which static initializers were found are printed |
| 382 on the standard output. |
361 | 383 |
362 Args: | 384 Args: |
363 so_with_symbols_path: Path to the unstripped libchrome.so file. | 385 so_with_symbols_path: Path to the unstripped libchrome.so file. |
| 386 |
| 387 Returns: |
| 388 The number of static initializers found. |
364 """ | 389 """ |
365 # GetStaticInitializers uses get-static-initializers.py to get a list of all | 390 # GetStaticInitializers uses get-static-initializers.py to get a list of all |
366 # static initializers. This does not work on all archs (particularly arm). | 391 # static initializers. This does not work on all archs (particularly arm). |
367 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed. | 392 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed. |
368 si_count = CountStaticInitializers(so_with_symbols_path) | 393 si_count = CountStaticInitializers(so_with_symbols_path) |
369 static_initializers = GetStaticInitializers(so_with_symbols_path) | 394 static_initializers = GetStaticInitializers(so_with_symbols_path) |
370 if si_count != len(static_initializers): | 395 static_initializers_count = len(static_initializers) - 1 # Minus summary. |
| 396 if si_count != static_initializers_count: |
371 print ('There are %d files with static initializers, but ' | 397 print ('There are %d files with static initializers, but ' |
372 'dump-static-initializers found %d:' % | 398 'dump-static-initializers found %d:' % |
373 (si_count, len(static_initializers))) | 399 (si_count, static_initializers_count)) |
374 else: | 400 else: |
375 print 'Found %d files with static initializers:' % si_count | 401 print '%s - Found %d files with static initializers:' % ( |
| 402 os.path.basename(so_with_symbols_path), si_count) |
376 print '\n'.join(static_initializers) | 403 print '\n'.join(static_initializers) |
377 | 404 |
378 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', | 405 return si_count |
379 si_count, 'count') | |
380 | 406 |
381 def _FormatBytes(byts): | 407 def _FormatBytes(byts): |
382 """Pretty-print a number of bytes.""" | 408 """Pretty-print a number of bytes.""" |
383 if byts > 2**20.0: | 409 if byts > 2**20.0: |
384 byts /= 2**20.0 | 410 byts /= 2**20.0 |
385 return '%.2fm' % byts | 411 return '%.2fm' % byts |
386 if byts > 2**10.0: | 412 if byts > 2**10.0: |
387 byts /= 2**10.0 | 413 byts /= 2**10.0 |
388 return '%.2fk' % byts | 414 return '%.2fk' % byts |
389 return str(byts) | 415 return str(byts) |
390 | 416 |
391 | 417 |
392 def _CalculateCompressedSize(file_path): | 418 def _CalculateCompressedSize(file_path): |
393 CHUNK_SIZE = 256 * 1024 | 419 CHUNK_SIZE = 256 * 1024 |
394 compressor = zlib.compressobj() | 420 compressor = zlib.compressobj() |
395 total_size = 0 | 421 total_size = 0 |
396 with open(file_path, 'rb') as f: | 422 with open(file_path, 'rb') as f: |
397 for chunk in iter(lambda: f.read(CHUNK_SIZE), ''): | 423 for chunk in iter(lambda: f.read(CHUNK_SIZE), ''): |
398 total_size += len(compressor.compress(chunk)) | 424 total_size += len(compressor.compress(chunk)) |
399 total_size += len(compressor.flush()) | 425 total_size += len(compressor.flush()) |
400 return total_size | 426 return total_size |
401 | 427 |
402 | 428 |
| 429 def _PrintDexAnalysis(apk_filename, chartjson=None): |
| 430 sizes = method_count.ExtractSizesFromZip(apk_filename) |
| 431 |
| 432 graph_title = os.path.basename(apk_filename) + '_Dex' |
| 433 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE |
| 434 for key, label in dex_metrics.iteritems(): |
| 435 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') |
| 436 |
| 437 graph_title = '%sCache' % graph_title |
| 438 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], |
| 439 'bytes') |
| 440 |
| 441 |
403 def main(argv): | 442 def main(argv): |
404 usage = """Usage: %prog [options] file1 file2 ... | 443 usage = """Usage: %prog [options] file1 file2 ... |
405 | 444 |
406 Pass any number of files to graph their sizes. Any files with the extension | 445 Pass any number of files to graph their sizes. Any files with the extension |
407 '.apk' will be broken down into their components on a separate graph.""" | 446 '.apk' will be broken down into their components on a separate graph.""" |
408 option_parser = optparse.OptionParser(usage=usage) | 447 option_parser = optparse.OptionParser(usage=usage) |
409 option_parser.add_option('--so-path', help='Path to libchrome.so.') | 448 option_parser.add_option('--so-path', |
| 449 help='Obsolete. Pass .so as positional arg instead.') |
410 option_parser.add_option('--so-with-symbols-path', | 450 option_parser.add_option('--so-with-symbols-path', |
411 help='Path to libchrome.so with symbols.') | 451 help='Mostly obsolete. Use .so within .apk instead.') |
412 option_parser.add_option('--min-pak-resource-size', type='int', | 452 option_parser.add_option('--min-pak-resource-size', type='int', |
413 default=20*1024, | 453 default=20*1024, |
414 help='Minimum byte size of displayed pak resources.') | 454 help='Minimum byte size of displayed pak resources.') |
415 option_parser.add_option('--build_type', dest='build_type', default='Debug', | 455 option_parser.add_option('--build_type', dest='build_type', default='Debug', |
416 help='Sets the build type, default is Debug.') | 456 help='Obsoleted by --chromium-output-directory.') |
417 option_parser.add_option('--chromium-output-directory', | 457 option_parser.add_option('--chromium-output-directory', |
418 help='Location of the build artifacts. ' | 458 help='Location of the build artifacts. ' |
419 'Takes precidence over --build_type.') | 459 'Takes precidence over --build_type.') |
420 option_parser.add_option('--chartjson', action="store_true", | 460 option_parser.add_option('--chartjson', action="store_true", |
421 help='Sets output mode to chartjson.') | 461 help='Sets output mode to chartjson.') |
422 option_parser.add_option('--output-dir', default='.', | 462 option_parser.add_option('--output-dir', default='.', |
423 help='Directory to save chartjson to.') | 463 help='Directory to save chartjson to.') |
424 option_parser.add_option('-d', '--device', | 464 option_parser.add_option('-d', '--device', |
425 help='Dummy option for perf runner.') | 465 help='Dummy option for perf runner.') |
426 options, args = option_parser.parse_args(argv) | 466 options, args = option_parser.parse_args(argv) |
(...skipping 10 matching lines...) Expand all Loading... |
437 # more. | 477 # more. |
438 if options.so_path: | 478 if options.so_path: |
439 files.append(options.so_path) | 479 files.append(options.so_path) |
440 | 480 |
441 if not files: | 481 if not files: |
442 option_parser.error('Must specify a file') | 482 option_parser.error('Must specify a file') |
443 | 483 |
444 devil_chromium.Initialize() | 484 devil_chromium.Initialize() |
445 | 485 |
446 if options.so_with_symbols_path: | 486 if options.so_with_symbols_path: |
447 PrintStaticInitializersCount( | 487 si_count = _PrintStaticInitializersCount(options.so_with_symbols_path) |
448 options.so_with_symbols_path, chartjson=chartjson) | 488 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, |
| 489 'count') |
449 | 490 |
450 PrintResourceSizes(files, chartjson=chartjson) | 491 PrintResourceSizes(files, chartjson=chartjson) |
451 | 492 |
452 for f in files: | 493 for f in files: |
453 if f.endswith('.apk'): | 494 if f.endswith('.apk'): |
454 PrintApkAnalysis(f, chartjson=chartjson) | 495 PrintApkAnalysis(f, chartjson=chartjson) |
455 PrintPakAnalysis(f, options.min_pak_resource_size) | 496 PrintPakAnalysis(f, options.min_pak_resource_size) |
| 497 _PrintDexAnalysis(f, chartjson=chartjson) |
| 498 if not options.so_with_symbols_path: |
| 499 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson) |
456 | 500 |
457 if chartjson: | 501 if chartjson: |
458 results_path = os.path.join(options.output_dir, 'results-chart.json') | 502 results_path = os.path.join(options.output_dir, 'results-chart.json') |
459 logging.critical('Dumping json to %s', results_path) | 503 logging.critical('Dumping json to %s', results_path) |
460 with open(results_path, 'w') as json_file: | 504 with open(results_path, 'w') as json_file: |
461 json.dump(chartjson, json_file) | 505 json.dump(chartjson, json_file) |
462 | 506 |
463 | 507 |
464 if __name__ == '__main__': | 508 if __name__ == '__main__': |
465 sys.exit(main(sys.argv)) | 509 sys.exit(main(sys.argv)) |
OLD | NEW |