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

Side by Side Diff: tools/binary_size/diagnose_apk_bloat.py

Issue 2832223002: diagnose_apk_bloat.py: support pre supersize revs. (Closed)
Patch Set: Fix _global_restore_checkout_func Created 3 years, 8 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/env python 1 #!/usr/bin/env python
2 # Copyright 2017 The Chromium Authors. All rights reserved. 2 # Copyright 2017 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 """Tool for finding the cause of APK bloat. 6 """Tool for finding the cause of APK bloat.
7 7
8 Run diagnose_apk_bloat.py -h for detailed usage help. 8 Run diagnose_apk_bloat.py -h for detailed usage help.
9 """ 9 """
10 10
11 import argparse 11 import argparse
12 import collections 12 import collections
13 from contextlib import contextmanager
13 import distutils.spawn 14 import distutils.spawn
14 import itertools
15 import json 15 import json
16 import multiprocessing 16 import multiprocessing
17 import os 17 import os
18 import shutil 18 import shutil
19 import subprocess 19 import subprocess
20 import sys 20 import sys
21 import tempfile 21 import tempfile
22 import zipfile 22 import zipfile
23 23
24 _COMMIT_COUNT_WARN_THRESHOLD = 15 24 _COMMIT_COUNT_WARN_THRESHOLD = 15
25 _ALLOWED_CONSECUTIVE_FAILURES = 2 25 _ALLOWED_CONSECUTIVE_FAILURES = 2
26 _BUILDER_URL = \ 26 _BUILDER_URL = \
27 'https://build.chromium.org/p/chromium.perf/builders/Android%20Builder' 27 'https://build.chromium.org/p/chromium.perf/builders/Android%20Builder'
28 _CLOUD_OUT_DIR = os.path.join('out', 'Release') 28 _CLOUD_OUT_DIR = os.path.join('out', 'Release')
29 _SRC_ROOT = os.path.abspath( 29 _SRC_ROOT = os.path.abspath(
30 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) 30 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
31 _DEFAULT_ARCHIVE_DIR = os.path.join(_SRC_ROOT, 'binary-size-bloat') 31 _DEFAULT_ARCHIVE_DIR = os.path.join(_SRC_ROOT, 'binary-size-bloat')
32 _DEFAULT_OUT_DIR = os.path.join(_SRC_ROOT, 'out', 'diagnose-apk-bloat') 32 _DEFAULT_OUT_DIR = os.path.join(_SRC_ROOT, 'out', 'diagnose-apk-bloat')
33 _DEFAULT_TARGET = 'monochrome_public_apk' 33 _DEFAULT_TARGET = 'monochrome_public_apk'
34 34
35 35
36 _global_restore_checkout_func = None 36 _global_restore_checkout_func = None
37 37
38 38
39 def _RestoreFunc(subrepo): 39 def _SetRestoreFunc(subrepo):
40 branch = _GitCmd(['rev-parse', '--abbrev-ref', 'HEAD'], subrepo) 40 branch = _GitCmd(['rev-parse', '--abbrev-ref', 'HEAD'], subrepo)
41 return lambda: _GitCmd(['checkout', branch], subrepo) 41 global _global_restore_checkout_func
42 _global_restore_checkout_func = lambda: _GitCmd(['checkout', branch], subrepo)
42 43
43 44
44 class BaseDiff(object): 45 class BaseDiff(object):
45 """Base class capturing binary size diffs.""" 46 """Base class capturing binary size diffs."""
46 def __init__(self, name): 47 def __init__(self, name):
47 self.name = name 48 self.name = name
48 self.banner = '\n' + '*' * 30 + name + '*' * 30 49 self.banner = '\n' + '*' * 30 + name + '*' * 30
49 50
50 def AppendResults(self, logfile): 51 def AppendResults(self, logfile):
51 """Print and write diff results to an open |logfile|.""" 52 """Print and write diff results to an open |logfile|."""
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 235
235 class _BuildArchive(object): 236 class _BuildArchive(object):
236 """Class for managing a directory with build results and build metadata.""" 237 """Class for managing a directory with build results and build metadata."""
237 def __init__(self, rev, base_archive_dir, build, subrepo): 238 def __init__(self, rev, base_archive_dir, build, subrepo):
238 self.build = build 239 self.build = build
239 self.dir = os.path.join(base_archive_dir, rev) 240 self.dir = os.path.join(base_archive_dir, rev)
240 metadata_path = os.path.join(self.dir, 'metadata.txt') 241 metadata_path = os.path.join(self.dir, 'metadata.txt')
241 self.rev = rev 242 self.rev = rev
242 self.metadata = _GenerateMetadata([self], build, metadata_path, subrepo) 243 self.metadata = _GenerateMetadata([self], build, metadata_path, subrepo)
243 244
244 def ArchiveBuildResults(self): 245 def ArchiveBuildResults(self, bs_dir):
245 """Save build artifacts necessary for diffing.""" 246 """Save build artifacts necessary for diffing."""
246 _Print('Saving build results to: {}', self.dir) 247 _Print('Saving build results to: {}', self.dir)
247 _EnsureDirsExist(self.dir) 248 _EnsureDirsExist(self.dir)
248 build = self.build 249 build = self.build
249 self._ArchiveFile(build.main_lib_path) 250 self._ArchiveFile(build.main_lib_path)
250 lib_name_noext = os.path.splitext(os.path.basename(build.main_lib_path))[0] 251 lib_name_noext = os.path.splitext(os.path.basename(build.main_lib_path))[0]
251 size_path = os.path.join(self.dir, lib_name_noext + '.size') 252 size_path = os.path.join(self.dir, lib_name_noext + '.size')
252 supersize_path = os.path.join(_SRC_ROOT, 'tools/binary_size/supersize') 253 supersize_path = os.path.join(bs_dir, 'supersize')
253 tool_prefix = _FindToolPrefix(build.output_directory) 254 tool_prefix = _FindToolPrefix(build.output_directory)
254 supersize_cmd = [supersize_path, 'archive', size_path, '--elf-file', 255 supersize_cmd = [supersize_path, 'archive', size_path, '--elf-file',
255 build.main_lib_path, '--tool-prefix', tool_prefix, 256 build.main_lib_path, '--tool-prefix', tool_prefix,
256 '--output-directory', build.output_directory, 257 '--output-directory', build.output_directory,
257 '--no-source-paths'] 258 '--no-source-paths']
258 if build.IsAndroid(): 259 if build.IsAndroid():
259 supersize_cmd += ['--apk-file', build.abs_apk_path] 260 supersize_cmd += ['--apk-file', build.abs_apk_path]
260 self._ArchiveFile(build.abs_apk_path) 261 self._ArchiveFile(build.abs_apk_path)
261 262
262 _RunCmd(supersize_cmd) 263 _RunCmd(supersize_cmd)
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
471 _Print('Failure: please ensure working directory is clean.') 472 _Print('Failure: please ensure working directory is clean.')
472 sys.exit() 473 sys.exit()
473 474
474 475
475 def _Die(s, *args, **kwargs): 476 def _Die(s, *args, **kwargs):
476 _Print('Failure: ' + s, *args, **kwargs) 477 _Print('Failure: ' + s, *args, **kwargs)
477 _global_restore_checkout_func() 478 _global_restore_checkout_func()
478 sys.exit(1) 479 sys.exit(1)
479 480
480 481
481 def _DownloadBuildArtifacts(archive, build, depot_tools_path=None): 482 def _DownloadBuildArtifacts(archive, build, bs_dir, depot_tools_path):
482 """Download artifacts from arm32 chromium perf builder.""" 483 """Download artifacts from arm32 chromium perf builder."""
483 if depot_tools_path: 484 if depot_tools_path:
484 gsutil_path = os.path.join(depot_tools_path, 'gsutil.py') 485 gsutil_path = os.path.join(depot_tools_path, 'gsutil.py')
485 else: 486 else:
486 gsutil_path = distutils.spawn.find_executable('gsutil.py') 487 gsutil_path = distutils.spawn.find_executable('gsutil.py')
487 488
488 if not gsutil_path: 489 if not gsutil_path:
489 _Die('gsutil.py not found, please provide path to depot_tools via ' 490 _Die('gsutil.py not found, please provide path to depot_tools via '
490 '--depot-tools-path or add it to your PATH') 491 '--depot-tools-path or add it to your PATH')
491 492
492 download_dir = tempfile.mkdtemp(dir=_SRC_ROOT) 493 download_dir = tempfile.mkdtemp(dir=_SRC_ROOT)
493 try: 494 try:
494 _DownloadAndArchive(gsutil_path, archive, download_dir, build) 495 _DownloadAndArchive(gsutil_path, archive, download_dir, build, bs_dir)
495 finally: 496 finally:
496 shutil.rmtree(download_dir) 497 shutil.rmtree(download_dir)
497 498
498 499
499 def _DownloadAndArchive(gsutil_path, archive, dl_dir, build): 500 def _DownloadAndArchive(gsutil_path, archive, dl_dir, build, bs_dir):
500 dl_file = 'full-build-linux_%s.zip' % archive.rev 501 dl_file = 'full-build-linux_%s.zip' % archive.rev
501 dl_url = 'gs://chrome-perf/Android Builder/%s' % dl_file 502 dl_url = 'gs://chrome-perf/Android Builder/%s' % dl_file
502 dl_dst = os.path.join(dl_dir, dl_file) 503 dl_dst = os.path.join(dl_dir, dl_file)
503 _Print('Downloading build artifacts for {}', archive.rev) 504 _Print('Downloading build artifacts for {}', archive.rev)
504 # gsutil writes stdout and stderr to stderr, so pipe stdout and stderr to 505 # gsutil writes stdout and stderr to stderr, so pipe stdout and stderr to
505 # sys.stdout. 506 # sys.stdout.
506 retcode = subprocess.call([gsutil_path, 'cp', dl_url, dl_dir], 507 retcode = subprocess.call([gsutil_path, 'cp', dl_url, dl_dir],
507 stdout=sys.stdout, stderr=subprocess.STDOUT) 508 stdout=sys.stdout, stderr=subprocess.STDOUT)
508 if retcode: 509 if retcode:
509 _Die('unexpected error while downloading {}. It may no longer exist on ' 510 _Die('unexpected error while downloading {}. It may no longer exist on '
510 'the server or it may not have been uploaded yet (check {}). ' 511 'the server or it may not have been uploaded yet (check {}). '
511 'Otherwise, you may not have the correct access permissions.', 512 'Otherwise, you may not have the correct access permissions.',
512 dl_url, _BUILDER_URL) 513 dl_url, _BUILDER_URL)
513 514
514 # Files needed for supersize and resource_sizes. Paths relative to out dir. 515 # Files needed for supersize and resource_sizes. Paths relative to out dir.
515 to_extract = [build.main_lib_name, build.map_file_name, 'args.gn', 516 to_extract = [build.main_lib_name, build.map_file_name, 'args.gn',
516 'build_vars.txt', build.apk_path] 517 'build_vars.txt', build.apk_path]
517 extract_dir = os.path.join(os.path.splitext(dl_dst)[0], 'unzipped') 518 extract_dir = os.path.join(os.path.splitext(dl_dst)[0], 'unzipped')
518 # Storage bucket stores entire output directory including out/Release prefix. 519 # Storage bucket stores entire output directory including out/Release prefix.
519 _Print('Extracting build artifacts') 520 _Print('Extracting build artifacts')
520 with zipfile.ZipFile(dl_dst, 'r') as z: 521 with zipfile.ZipFile(dl_dst, 'r') as z:
521 _ExtractFiles(to_extract, _CLOUD_OUT_DIR, extract_dir, z) 522 _ExtractFiles(to_extract, _CLOUD_OUT_DIR, extract_dir, z)
522 dl_out = os.path.join(extract_dir, _CLOUD_OUT_DIR) 523 dl_out = os.path.join(extract_dir, _CLOUD_OUT_DIR)
523 build.output_directory, output_directory = dl_out, build.output_directory 524 build.output_directory, output_directory = dl_out, build.output_directory
524 archive.ArchiveBuildResults() 525 archive.ArchiveBuildResults(bs_dir)
525 build.output_directory = output_directory 526 build.output_directory = output_directory
526 527
527 528
528 def _ExtractFiles(to_extract, prefix, dst, z): 529 def _ExtractFiles(to_extract, prefix, dst, z):
529 zip_infos = z.infolist() 530 zip_infos = z.infolist()
530 assert all(info.filename.startswith(prefix) for info in zip_infos), ( 531 assert all(info.filename.startswith(prefix) for info in zip_infos), (
531 'Storage bucket folder structure doesn\'t start with %s' % prefix) 532 'Storage bucket folder structure doesn\'t start with %s' % prefix)
532 to_extract = [os.path.join(prefix, f) for f in to_extract] 533 to_extract = [os.path.join(prefix, f) for f in to_extract]
533 for f in to_extract: 534 for f in to_extract:
534 z.extract(f, path=dst) 535 z.extract(f, path=dst)
535 536
536 537
537 def _Print(s, *args, **kwargs): 538 def _Print(s, *args, **kwargs):
538 print s.format(*args, **kwargs) 539 print s.format(*args, **kwargs)
539 540
540 541
541 def _PrintAndWriteToFile(logfile, s): 542 def _PrintAndWriteToFile(logfile, s):
542 """Print |s| to |logfile| and stdout.""" 543 """Print |s| to |logfile| and stdout."""
543 _Print(s) 544 _Print(s)
544 logfile.write('%s\n' % s) 545 logfile.write('%s\n' % s)
545 546
546 547
548 @contextmanager
549 def _TmpBinarySizeDir():
550 """Recursively copy files to a temp dir and yield the tmp binary_size dir."""
551 # Needs to be at same level of nesting as the real //tools/binary_size
552 # since supersize uses this to find d3 in //third_party.
553 tmp_dir = tempfile.mkdtemp(dir=_SRC_ROOT)
554 try:
555 bs_dir = os.path.join(tmp_dir, 'binary_size')
556 shutil.copytree(os.path.join(_SRC_ROOT, 'tools', 'binary_size'), bs_dir)
557 yield bs_dir
558 finally:
559 shutil.rmtree(tmp_dir)
560
561
547 def main(): 562 def main():
548 parser = argparse.ArgumentParser( 563 parser = argparse.ArgumentParser(
549 description='Find the cause of APK size bloat.') 564 description='Find the cause of APK size bloat.')
550 parser.add_argument('--archive-dir', 565 parser.add_argument('--archive-directory',
551 default=_DEFAULT_ARCHIVE_DIR, 566 default=_DEFAULT_ARCHIVE_DIR,
552 help='Where results are stored.') 567 help='Where results are stored.')
553 parser.add_argument('rev', 568 parser.add_argument('rev',
554 help='Find binary size bloat for this commit.') 569 help='Find binary size bloat for this commit.')
555 parser.add_argument('--reference-rev', 570 parser.add_argument('--reference-rev',
556 help='Older rev to diff against. If not supplied, ' 571 help='Older rev to diff against. If not supplied, '
557 'the previous commit to rev will be used.') 572 'the previous commit to rev will be used.')
558 parser.add_argument('--all', 573 parser.add_argument('--all',
559 action='store_true', 574 action='store_true',
560 help='Build/download all revs from --reference-rev to ' 575 help='Build/download all revs from --reference-rev to '
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
611 args = parser.parse_args() 626 args = parser.parse_args()
612 build = _BuildHelper(args) 627 build = _BuildHelper(args)
613 if build.IsCloud(): 628 if build.IsCloud():
614 if build.IsLinux(): 629 if build.IsLinux():
615 parser.error('--cloud only works for android') 630 parser.error('--cloud only works for android')
616 if args.subrepo: 631 if args.subrepo:
617 parser.error('--subrepo doesn\'t work with --cloud') 632 parser.error('--subrepo doesn\'t work with --cloud')
618 633
619 subrepo = args.subrepo or _SRC_ROOT 634 subrepo = args.subrepo or _SRC_ROOT
620 _EnsureDirectoryClean(subrepo) 635 _EnsureDirectoryClean(subrepo)
621 _global_restore_checkout_func = _RestoreFunc(subrepo) 636 _SetRestoreFunc(subrepo)
622 revs = _GenerateRevList(args.rev, 637 revs = _GenerateRevList(args.rev,
623 args.reference_rev or args.rev + '^', 638 args.reference_rev or args.rev + '^',
624 args.all, 639 args.all,
625 subrepo) 640 subrepo)
626 diffs = [] 641 diffs = []
627 if build.IsAndroid(): 642 if build.IsAndroid():
628 diffs += [ 643 diffs += [
629 ResourceSizesDiff( 644 ResourceSizesDiff(
630 build.apk_name, slow_options=args.include_slow_options) 645 build.apk_name, slow_options=args.include_slow_options)
631 ] 646 ]
632 diff_mngr = _DiffArchiveManager(revs, args.archive_dir, diffs, build, subrepo) 647 diff_mngr = _DiffArchiveManager(
648 revs, args.archive_directory, diffs, build, subrepo)
633 consecutive_failures = 0 649 consecutive_failures = 0
634 for i, archive in enumerate(diff_mngr.IterArchives()): 650 with _TmpBinarySizeDir() as bs_dir:
635 if archive.Exists(): 651 for i, archive in enumerate(diff_mngr.IterArchives()):
636 _Print('Found matching metadata for {}, skipping build step.', 652 if archive.Exists():
637 archive.rev) 653 _Print('Found matching metadata for {}, skipping build step.',
638 else: 654 archive.rev)
639 if build.IsCloud():
640 _DownloadBuildArtifacts(archive, build,
641 depot_tools_path=args.depot_tools_path)
642 else: 655 else:
643 build_success = _SyncAndBuild(archive, build, subrepo) 656 if build.IsCloud():
644 if not build_success: 657 _DownloadBuildArtifacts(archive, build, bs_dir, args.depot_tools_path)
645 consecutive_failures += 1
646 if consecutive_failures > _ALLOWED_CONSECUTIVE_FAILURES:
647 _Die('{} builds failed in a row, last failure was {}.',
648 consecutive_failures, archive.rev)
649 else: 658 else:
650 archive.ArchiveBuildResults() 659 build_success = _SyncAndBuild(archive, build, subrepo)
651 consecutive_failures = 0 660 if not build_success:
661 consecutive_failures += 1
662 if consecutive_failures > _ALLOWED_CONSECUTIVE_FAILURES:
663 _Die('{} builds failed in a row, last failure was {}.',
664 consecutive_failures, archive.rev)
665 else:
666 archive.ArchiveBuildResults(bs_dir)
667 consecutive_failures = 0
652 668
653 if i != 0: 669 if i != 0:
654 diff_mngr.MaybeDiff(i - 1, i) 670 diff_mngr.MaybeDiff(i - 1, i)
655 671
656 _global_restore_checkout_func() 672 _global_restore_checkout_func()
657 673
658 if __name__ == '__main__': 674 if __name__ == '__main__':
659 sys.exit(main()) 675 sys.exit(main())
660 676
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