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

Unified Diff: tools/binary_size/diagnose_apk_bloat.py

Issue 2847243005: diagnose_apk_bloat.py: fix error messages and simplify rev order. (Closed)
Patch Set: diagnose_apk_bloat.py: fix error messages and simplify rev order. 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/binary_size/README.md ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/binary_size/diagnose_apk_bloat.py
diff --git a/tools/binary_size/diagnose_apk_bloat.py b/tools/binary_size/diagnose_apk_bloat.py
index a49f5b7db643cd1ee3b3d4a309183dc0bf783a59..8a1d0ee9fc5216ee0ad70f3e522e8f2fb80e9fc4 100755
--- a/tools/binary_size/diagnose_apk_bloat.py
+++ b/tools/binary_size/diagnose_apk_bloat.py
@@ -27,13 +27,11 @@ _ALLOWED_CONSECUTIVE_FAILURES = 2
_DIFF_DETAILS_LINES_THRESHOLD = 100
_BUILDER_URL = \
'https://build.chromium.org/p/chromium.perf/builders/Android%20Builder'
-_CLOUD_OUT_DIR = os.path.join('out', 'Release')
_SRC_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
_DEFAULT_ARCHIVE_DIR = os.path.join(_SRC_ROOT, 'binary-size-bloat')
_DEFAULT_OUT_DIR = os.path.join(_SRC_ROOT, 'out', 'diagnose-apk-bloat')
-_DEFAULT_TARGET = 'monochrome_public_apk'
-
+_DEFAULT_ANDROID_TARGET = 'monochrome_public_apk'
_global_restore_checkout_func = None
@@ -44,8 +42,7 @@ def _SetRestoreFunc(subrepo):
_global_restore_checkout_func = lambda: _GitCmd(['checkout', branch], subrepo)
-_DiffResult = collections.namedtuple(
- 'DiffResult', ['name', 'value', 'units'])
+_DiffResult = collections.namedtuple('DiffResult', ['name', 'value', 'units'])
class BaseDiff(object):
@@ -74,12 +71,12 @@ class BaseDiff(object):
"""An iterable description of the cause of binary size bloat."""
raise NotImplementedError()
- def ProduceDiff(self, archive_dirs):
+ def ProduceDiff(self, before_dir, after_dir):
"""Prepare a binary size diff with ready to print results."""
raise NotImplementedError()
- def RunDiff(self, logfile, archive_dirs):
- self.ProduceDiff(archive_dirs)
+ def RunDiff(self, logfile, before_dir, after_dir):
+ self.ProduceDiff(before_dir, after_dir)
self.AppendResults(logfile)
@@ -111,10 +108,10 @@ class NativeDiff(BaseDiff):
def Summary(self):
return NativeDiff._RE_SUMMARY.match(self._diff).group(1)
- def ProduceDiff(self, archive_dirs):
- size_files = [os.path.join(a, self._size_name)
- for a in reversed(archive_dirs)]
- cmd = [self._supersize_path, 'diff'] + size_files
+ def ProduceDiff(self, before_dir, after_dir):
+ before_size = os.path.join(before_dir, self._size_name)
+ after_size = os.path.join(after_dir, self._size_name)
+ cmd = [self._supersize_path, 'diff', before_size, after_size]
self._diff = _RunCmd(cmd)[0].replace('{', '{{').replace('}', '}}')
@@ -143,42 +140,39 @@ class ResourceSizesDiff(BaseDiff):
return 'Normalized APK size: {:+,} {}'.format(
self.summary_stat.value, self.summary_stat.units)
- def ProduceDiff(self, archive_dirs):
- chartjsons = self._RunResourceSizes(archive_dirs)
+ def ProduceDiff(self, before_dir, after_dir):
+ before = self._RunResourceSizes(before_dir)
+ after = self._RunResourceSizes(after_dir)
diff = []
- with_patch = chartjsons[0]['charts']
- without_patch = chartjsons[1]['charts']
- for section, section_dict in with_patch.iteritems():
+ for section, section_dict in after.iteritems():
for subsection, v in section_dict.iteritems():
# Ignore entries when resource_sizes.py chartjson format has changed.
- if (section not in without_patch or
- subsection not in without_patch[section] or
- v['units'] != without_patch[section][subsection]['units']):
+ if (section not in before or
+ subsection not in before[section] or
+ v['units'] != before[section][subsection]['units']):
_Print('Found differing dict structures for resource_sizes.py, '
'skipping {} {}', section, subsection)
else:
diff.append(
_DiffResult(
'%s %s' % (section, subsection),
- v['value'] - without_patch[section][subsection]['value'],
+ v['value'] - before[section][subsection]['value'],
v['units']))
self._diff = sorted(diff, key=lambda x: abs(x.value), reverse=True)
- def _RunResourceSizes(self, archive_dirs):
- chartjsons = []
- for archive_dir in archive_dirs:
- apk_path = os.path.join(archive_dir, self._apk_name)
- chartjson_file = os.path.join(archive_dir, 'results-chart.json')
- cmd = [self._RESOURCE_SIZES_PATH, apk_path,'--output-dir', archive_dir,
- '--no-output-dir', '--chartjson']
- if self._slow_options:
- cmd += ['--estimate-patch-size']
- else:
- cmd += ['--no-static-initializer-check']
- _RunCmd(cmd)
- with open(chartjson_file) as f:
- chartjsons.append(json.load(f))
- return chartjsons
+ def _RunResourceSizes(self, archive_dir):
+ apk_path = os.path.join(archive_dir, self._apk_name)
+ chartjson_file = os.path.join(archive_dir, 'results-chart.json')
+ cmd = [self._RESOURCE_SIZES_PATH, apk_path,'--output-dir', archive_dir,
+ '--no-output-dir', '--chartjson']
+ if self._slow_options:
+ cmd += ['--estimate-patch-size']
+ else:
+ cmd += ['--no-static-initializer-check']
+ _RunCmd(cmd)
+ with open(chartjson_file) as f:
+ chartjson = json.load(f)
+ return chartjson['charts']
class _BuildHelper(object):
@@ -224,6 +218,14 @@ class _BuildHelper(object):
return os.path.join(self.output_directory, self.main_lib_path)
@property
+ def download_bucket(self):
+ return 'gs://chrome-perf/%s Builder/' % self.target_os.title()
+
+ @property
+ def download_output_dir(self):
+ return 'out/Release' if self.IsAndroid() else 'full-build-linux'
+
+ @property
def map_file_path(self):
return self.main_lib_path + '.map.gz'
@@ -244,6 +246,7 @@ class _BuildHelper(object):
else:
self.extra_gn_args_str = (' exclude_unwind_tables=true '
'ffmpeg_branding="Chrome" proprietary_codecs=true')
+ self.target = self.target if self.IsAndroid() else 'chrome'
def _GenGnCmd(self):
gn_args = 'is_official_build=true symbol_level=1'
@@ -271,6 +274,9 @@ class _BuildHelper(object):
return _RunCmd(
self._GenNinjaCmd(), print_stdout=True, exit_on_failure=False)[1]
+ def DownloadUrl(self, rev):
+ return self.download_bucket + 'full-build-linux_%s.zip' % rev
+
def IsAndroid(self):
return self.target_os == 'android'
@@ -332,31 +338,30 @@ class _DiffArchiveManager(object):
def IterArchives(self):
return iter(self.build_archives)
- def MaybeDiff(self, first_id, second_id):
+ def MaybeDiff(self, before_id, after_id):
"""Perform diffs given two build archives."""
- archives = [
- self.build_archives[first_id], self.build_archives[second_id]]
- diff_path = self._DiffFilePath(archives)
- if not self._CanDiff(archives):
+ before = self.build_archives[before_id]
+ after = self.build_archives[after_id]
+ diff_path = self._DiffFilePath(before, after)
+ if not self._CanDiff(before, after):
_Print('Skipping diff for {} due to missing build archives.', diff_path)
return
- metadata_path = self._DiffMetadataPath(archives)
+ metadata_path = self._DiffMetadataPath(before, after)
metadata = _GenerateMetadata(
- archives, self.build, metadata_path, self.subrepo)
+ [before, after], self.build, metadata_path, self.subrepo)
if _MetadataExists(metadata):
_Print('Skipping diff for {} and {}. Matching diff already exists: {}',
- archives[0].rev, archives[1].rev, diff_path)
+ before.rev, after.rev, diff_path)
else:
if os.path.exists(diff_path):
os.remove(diff_path)
- archive_dirs = [archives[0].dir, archives[1].dir]
with open(diff_path, 'a') as diff_file:
for d in self.diffs:
- d.RunDiff(diff_file, archive_dirs)
+ d.RunDiff(diff_file, before.dir, after.dir)
_Print('\nSee detailed diff results here: {}.', diff_path)
_WriteMetadata(metadata)
- self._AddDiffSummaryStat(archives)
+ self._AddDiffSummaryStat(before, after)
def Summarize(self):
if self._summary_stats:
@@ -369,7 +374,7 @@ class _DiffArchiveManager(object):
_PrintAndWriteToFile(f, '{:>+10} {} {} for range: {}..{}',
s.value, s.units, s.name, before, after)
- def _AddDiffSummaryStat(self, archives):
+ def _AddDiffSummaryStat(self, before, after):
stat = None
if self.build.IsAndroid():
summary_diff_type = ResourceSizesDiff
@@ -379,19 +384,19 @@ class _DiffArchiveManager(object):
if isinstance(d, summary_diff_type):
stat = d.summary_stat
if stat:
- self._summary_stats.append((stat, archives[1].rev, archives[0].rev))
+ self._summary_stats.append((stat, before.rev, after.rev))
- def _CanDiff(self, archives):
- return all(a.Exists() for a in archives)
+ def _CanDiff(self, before, after):
+ return before.Exists() and after.Exists()
- def _DiffFilePath(self, archives):
- return os.path.join(self._DiffDir(archives), 'diff_results.txt')
+ def _DiffFilePath(self, before, after):
+ return os.path.join(self._DiffDir(before, after), 'diff_results.txt')
- def _DiffMetadataPath(self, archives):
- return os.path.join(self._DiffDir(archives), 'metadata.txt')
+ def _DiffMetadataPath(self, before, after):
+ return os.path.join(self._DiffDir(before, after), 'metadata.txt')
- def _DiffDir(self, archives):
- archive_range = '%s..%s' % (archives[1].rev, archives[0].rev)
+ def _DiffDir(self, before, after):
+ archive_range = '%s..%s' % (before.rev, after.rev)
diff_path = os.path.join(self.archive_dir, 'diffs', archive_range)
_EnsureDirsExist(diff_path)
return diff_path
@@ -506,36 +511,49 @@ def _SyncAndBuild(archive, build, subrepo):
return retcode == 0
-def _GenerateRevList(with_patch, without_patch, all_in_range, subrepo):
+def _GenerateRevList(rev, reference_rev, all_in_range, subrepo):
"""Normalize and optionally generate a list of commits in the given range.
- Returns a list of revisions ordered from newest to oldest.
+ Returns:
+ A list of revisions ordered from oldest to newest.
"""
- cmd = ['git', '-C', subrepo, 'merge-base', '--is-ancestor', without_patch,
- with_patch]
- _, retcode = _RunCmd(cmd, exit_on_failure=False)
- assert not retcode and with_patch != without_patch, (
- 'Invalid revision arguments, rev_without_patch (%s) is newer than '
- 'rev_with_patch (%s)' % (without_patch, with_patch))
-
- rev_seq = '%s^..%s' % (without_patch, with_patch)
+ rev_seq = '%s^..%s' % (reference_rev, rev)
stdout = _GitCmd(['rev-list', rev_seq], subrepo)
- all_revs = stdout.splitlines()
+ all_revs = stdout.splitlines()[::-1]
if all_in_range:
revs = all_revs
else:
revs = [all_revs[0], all_revs[-1]]
- _VerifyUserAckCommitCount(len(revs))
+ if len(revs) >= _COMMIT_COUNT_WARN_THRESHOLD:
+ _VerifyUserAccepts(
+ 'You\'ve provided a commit range that contains %d commits' % len(revs))
return revs
-def _VerifyUserAckCommitCount(count):
- if count >= _COMMIT_COUNT_WARN_THRESHOLD:
- _Print('You\'ve provided a commit range that contains {} commits, do you '
- 'want to proceed? [y/n]', count)
- if raw_input('> ').lower() != 'y':
- _global_restore_checkout_func()
- sys.exit(1)
+def _ValidateRevs(rev, reference_rev, subrepo):
+ def git_fatal(args, message):
+ devnull = open(os.devnull, 'wb')
+ retcode = subprocess.call(
+ ['git', '-C', subrepo] + args, stdout=devnull, stderr=subprocess.STDOUT)
+ if retcode:
+ _Die(message)
+
+ if rev == reference_rev:
+ _Die('rev and reference-rev cannot be equal')
+ no_obj_message = ('%s either doesn\'t exist or your local repo is out of '
+ 'date, try "git fetch origin master"')
+ git_fatal(['cat-file', '-e', rev], no_obj_message % rev)
+ git_fatal(['cat-file', '-e', reference_rev], no_obj_message % reference_rev)
+ git_fatal(['merge-base', '--is-ancestor', reference_rev, rev],
+ 'reference-rev is newer than rev')
+ return rev, reference_rev
+
+
+def _VerifyUserAccepts(message):
+ _Print(message + 'Do you want to proceed? [y/n]')
+ if raw_input('> ').lower() != 'y':
+ _global_restore_checkout_func()
+ sys.exit()
def _EnsureDirectoryClean(subrepo):
@@ -573,29 +591,29 @@ def _DownloadBuildArtifacts(archive, build, supersize_path, depot_tools_path):
def _DownloadAndArchive(gsutil_path, archive, dl_dir, build, supersize_path):
- dl_file = 'full-build-linux_%s.zip' % archive.rev
- dl_url = 'gs://chrome-perf/Android Builder/%s' % dl_file
- dl_dst = os.path.join(dl_dir, dl_file)
+ dl_dst = os.path.join(dl_dir, archive.rev)
_Print('Downloading build artifacts for {}', archive.rev)
# gsutil writes stdout and stderr to stderr, so pipe stdout and stderr to
# sys.stdout.
- retcode = subprocess.call([gsutil_path, 'cp', dl_url, dl_dir],
- stdout=sys.stdout, stderr=subprocess.STDOUT)
+ retcode = subprocess.call(
+ [gsutil_path, 'cp', build.DownloadUrl(archive.rev), dl_dst],
+ stdout=sys.stdout, stderr=subprocess.STDOUT)
if retcode:
_Die('unexpected error while downloading {}. It may no longer exist on '
'the server or it may not have been uploaded yet (check {}). '
'Otherwise, you may not have the correct access permissions.',
- dl_url, _BUILDER_URL)
+ build.DownloadUrl(archive.rev), _BUILDER_URL)
# Files needed for supersize and resource_sizes. Paths relative to out dir.
- to_extract = [build.main_lib_path, build.map_file_path, 'args.gn',
- 'build_vars.txt', build.apk_path]
- extract_dir = os.path.join(os.path.splitext(dl_dst)[0], 'unzipped')
+ to_extract = [build.main_lib_path, build.map_file_path, 'args.gn']
+ if build.IsAndroid():
+ to_extract += ['build_vars.txt', build.apk_path]
+ extract_dir = dl_dst + '_' + 'unzipped'
# Storage bucket stores entire output directory including out/Release prefix.
_Print('Extracting build artifacts')
with zipfile.ZipFile(dl_dst, 'r') as z:
- _ExtractFiles(to_extract, _CLOUD_OUT_DIR, extract_dir, z)
- dl_out = os.path.join(extract_dir, _CLOUD_OUT_DIR)
+ _ExtractFiles(to_extract, build.download_output_dir, extract_dir, z)
+ dl_out = os.path.join(extract_dir, build.download_output_dir)
build.output_directory, output_directory = dl_out, build.output_directory
archive.ArchiveBuildResults(supersize_path)
build.output_directory = output_directory
@@ -700,27 +718,26 @@ def main():
action='store_true',
help='Allow downstream targets to be built.')
build_group.add_argument('--target',
- default=_DEFAULT_TARGET,
- help='GN APK target to build. '
- 'Default %s.' % _DEFAULT_TARGET)
+ default=_DEFAULT_ANDROID_TARGET,
+ help='GN APK target to build. Ignored for Linux. '
+ 'Default %s.' % _DEFAULT_ANDROID_TARGET)
if len(sys.argv) == 1:
parser.print_help()
sys.exit()
args = parser.parse_args()
build = _BuildHelper(args)
- if build.IsCloud():
- if build.IsLinux():
- parser.error('--cloud only works for android')
- if args.subrepo:
+ if build.IsCloud() and args.subrepo:
parser.error('--subrepo doesn\'t work with --cloud')
subrepo = args.subrepo or _SRC_ROOT
_EnsureDirectoryClean(subrepo)
_SetRestoreFunc(subrepo)
- revs = _GenerateRevList(args.rev,
- args.reference_rev or args.rev + '^',
- args.all,
- subrepo)
+ if build.IsLinux():
+ _VerifyUserAccepts('Linux diffs have known deficiencies (crbug/717550).')
+
+ rev, reference_rev = _ValidateRevs(
+ args.rev, args.reference_rev or args.rev + '^', subrepo)
+ revs = _GenerateRevList(rev, reference_rev, args.all, subrepo)
with _TmpCopyBinarySizeDir() as supersize_path:
diffs = [NativeDiff(build.size_name, supersize_path)]
if build.IsAndroid():
« no previous file with comments | « tools/binary_size/README.md ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698