| #!/usr/bin/env python |
| # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| |
| # Convenience script for running run_on_app.py finding minimum memory need for |
| # compiling a given app back in time. This utilizes the prebuilt r8 jars on |
| # cloud storage. |
| # The script find all commits that exists on cloud storage in the given range. |
| # It will then run the oldest and newest such commit, and gradually fill in |
| # the commits in between. |
| |
| import optparse |
| import os |
| import subprocess |
| import sys |
| import utils |
| |
| MASTER_COMMITS = 'gs://r8-releases/raw/master' |
| APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome'] |
| COMPILERS = ['d8', 'r8'] |
| |
| def ParseOptions(argv): |
| result = optparse.OptionParser() |
| result.add_option('--compiler', |
| help='The compiler to use', |
| default='d8', |
| choices=COMPILERS) |
| result.add_option('--app', |
| help='What app to run on', |
| default='gmail', |
| choices=APPS) |
| result.add_option('--top', |
| default=utils.get_HEAD_sha1(), |
| help='The most recent commit to test') |
| result.add_option('--bottom', |
| help='The oldest commit to test') |
| result.add_option('--output', |
| default='build', |
| help='Directory where to output results') |
| result.add_option('--timeout', |
| type=int, |
| default=0, |
| help='Set timeout instead of waiting for OOM.') |
| return result.parse_args(argv) |
| |
| |
| class GitCommit(object): |
| def __init__(self, git_hash, destination_dir, destination, timestamp): |
| self.git_hash = git_hash |
| self.destination_dir = destination_dir |
| self.destination = destination |
| self.timestamp = timestamp |
| |
| def __str__(self): |
| return '%s : %s (%s)' % (self.git_hash, self.destination, self.timestamp) |
| |
| def __repr__(self): |
| return self.__str__() |
| |
| def git_commit_from_hash(hash): |
| commit_timestamp = subprocess.check_output(['git', 'show', '--no-patch', |
| '--no-notes', '--pretty=\'%ct\'', |
| hash]).strip().strip('\'') |
| destination_dir = '%s/%s/' % (MASTER_COMMITS, hash) |
| destination = '%s%s' % (destination_dir, 'r8.jar') |
| commit = GitCommit(hash, destination_dir, destination, commit_timestamp) |
| return commit |
| |
| def enumerate_git_commits(options): |
| top = options.top if options.top else utils.get_HEAD_sha1() |
| # TODO(ricow): if not set, search back 1000 |
| if not options.bottom: |
| raise Exception('No bottom specified') |
| bottom = options.bottom |
| output = subprocess.check_output(['git', 'rev-list', '--first-parent', top]) |
| found_bottom = False |
| commits = [] |
| for c in output.splitlines(): |
| commits.append(git_commit_from_hash(c.strip())) |
| if c.strip() == bottom: |
| found_bottom = True |
| break |
| if not found_bottom: |
| raise Exception('Bottom not found, did you not use a merge commit') |
| return commits |
| |
| def get_available_commits(commits): |
| cloud_commits = subprocess.check_output(['gsutil.py', 'ls', MASTER_COMMITS]).splitlines() |
| available_commits = [] |
| for commit in commits: |
| if commit.destination_dir in cloud_commits: |
| available_commits.append(commit) |
| return available_commits |
| |
| def print_commits(commits): |
| for commit in commits: |
| print(commit) |
| |
| def permutate_range(start, end): |
| diff = end - start |
| assert diff >= 0 |
| if diff == 1: |
| return [start, end] |
| if diff == 0: |
| return [start] |
| half = end - (diff / 2) |
| numbers = [half] |
| first_half = permutate_range(start, half - 1) |
| second_half = permutate_range(half + 1, end) |
| for index in range(len(first_half)): |
| numbers.append(first_half[index]) |
| if index < len(second_half): |
| numbers.append(second_half[index]) |
| return numbers |
| |
| def permutate(number_of_commits): |
| assert number_of_commits > 0 |
| numbers = permutate_range(0, number_of_commits - 1) |
| assert all(n in numbers for n in range(number_of_commits)) |
| return numbers |
| |
| def pull_r8_from_cloud(commit): |
| utils.download_file_from_cloud_storage(commit.destination, utils.R8_JAR) |
| |
| def run_on_app(options, commit): |
| app = options.app |
| compiler = options.compiler |
| cmd = ['tools/run_on_app.py', |
| '--app', app, |
| '--compiler', compiler, |
| '--timeout', str(options.timeout), |
| '--no-build', '--find-min-xmx'] |
| stdout = subprocess.check_output(cmd) |
| output_path = options.output or 'build' |
| time_commit = '%s_%s' % (commit.timestamp, commit.git_hash) |
| time_commit_path = os.path.join(output_path, time_commit) |
| if not os.path.exists(time_commit_path): |
| os.makedirs(time_commit_path) |
| stdout_path = os.path.join(time_commit_path, 'stdout') |
| with open(stdout_path, 'w') as f: |
| f.write(stdout) |
| print('Wrote stdout to: %s' % stdout_path) |
| |
| |
| def benchmark(commits, options): |
| commit_permutations = permutate(len(commits)) |
| count = 0 |
| for index in commit_permutations: |
| count += 1 |
| print('Running commit %s out of %s' % (count, len(commits))) |
| commit = commits[index] |
| if not utils.cloud_storage_exists(commit.destination): |
| # We may have a directory, but no r8.jar |
| continue |
| pull_r8_from_cloud(commit) |
| print('Running for commit: %s' % commit.git_hash) |
| run_on_app(options, commit) |
| |
| def main(argv): |
| (options, args) = ParseOptions(argv) |
| if not options.app: |
| raise Exception('Please specify an app') |
| commits = enumerate_git_commits(options) |
| available_commits = get_available_commits(commits) |
| print('Running for:') |
| print_commits(available_commits) |
| benchmark(available_commits, options) |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |