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