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(): |