Index: tools/perf/core/trybot_command.py |
diff --git a/tools/perf/core/trybot_command.py b/tools/perf/core/trybot_command.py |
index 7d97fb52bb5b21ea088649783e34716cd7766bd4..af44a808c529a321eae753836904f56e596d6cad 100644 |
--- a/tools/perf/core/trybot_command.py |
+++ b/tools/perf/core/trybot_command.py |
@@ -3,13 +3,15 @@ |
# found in the LICENSE file. |
import argparse |
-import os |
+import json |
import logging |
+import os |
import platform |
import re |
import subprocess |
+import sys |
import urllib2 |
-import json |
+ |
from core import path_util |
@@ -20,9 +22,6 @@ from telemetry.util import command_line |
from telemetry.util import matching |
-CHROMIUM_CONFIG_FILENAME = 'tools/run-perf-test.cfg' |
-BLINK_CONFIG_FILENAME = 'Tools/run-perf-test.cfg' |
-SUCCESS, NO_CHANGES, ERROR = range(3) |
# Unsupported Perf bisect bots. |
EXCLUDED_BOTS = { |
'win_xp_perf_bisect', # Goma issues: crbug.com/330900 |
@@ -57,20 +56,41 @@ INCLUDE_BOTS = [ |
] |
# Default try bot to use incase builbot is unreachable. |
-DEFAULT_TRYBOTS = [ |
+DEFAULT_TRYBOTS = [ |
'linux_perf_bisect', |
'mac_10_11_perf_bisect', |
'winx64_10_perf_bisect', |
'android_s5_perf_bisect', |
] |
-assert not set(DEFAULT_TRYBOTS) & set(EXCLUDED_BOTS), ( 'A trybot cannot ' |
- 'present in both Default as well as Excluded bots lists.') |
+CHROMIUM_SRC_PATH = path_util.GetChromiumSrcDir() |
+REPO_INFO_MAP = { |
+ 'src': { |
+ 'src': 'src', |
+ 'url': 'https://chromium.googlesource.com/chromium/src.git', |
+ }, |
+ 'v8': { |
+ 'src': 'src/v8', |
+ 'url': 'https://chromium.googlesource.com/v8/v8.git', |
+ }, |
+ 'skia': { |
+ 'src': 'src/third_party/skia', |
+ 'url': 'https://chromium.googlesource.com/skia.git', |
+ }, |
+ 'angle': { |
+ 'src': 'src/third_party/angle', |
+ 'url': 'https://chromium.googlesource.com/angle/angle.git', |
+ } |
+} |
+ |
+assert not set(DEFAULT_TRYBOTS) & set(EXCLUDED_BOTS), ( |
+ 'A trybot cannot present in both Default as well as Excluded bots lists.') |
+ |
class TrybotError(Exception): |
def __str__(self): |
- return '%s\nError running tryjob.' % self.args[0] |
+ return '(ERROR) Perf Try Job: %s' % self.args[0] |
def _GetTrybotList(builders): |
@@ -89,7 +109,7 @@ def _GetBotPlatformFromTrybotName(trybot_name): |
def _GetBuilderNames(trybot_name, builders): |
- """ Return platform and its available bot name as dictionary.""" |
+ """Return platform and its available bot name as dictionary.""" |
os_names = ['linux', 'android', 'mac', 'win'] |
if 'all' not in trybot_name: |
bot = ['%s_perf_bisect' % trybot_name.replace('-', '_')] |
@@ -125,6 +145,14 @@ def _GetBuilderNames(trybot_name, builders): |
return platform_and_bots |
+_GIT_CMD = 'git' |
+ |
+ |
+if platform.system() == 'Windows': |
+ # On windows, the git command is installed as 'git.bat' |
+ _GIT_CMD = 'git.bat' |
+ |
+ |
def _RunProcess(cmd): |
logging.debug('Running process: "%s"', ' '.join(cmd)) |
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
@@ -133,14 +161,19 @@ def _RunProcess(cmd): |
return (returncode, out, err) |
-_GIT_CMD = 'git' |
-if platform.system() == 'Windows': |
- # On windows, the git command is installed as 'git.bat' |
- _GIT_CMD = 'git.bat' |
+def RunGit(cmd, msg_on_error='', ignore_return_code=False): |
+ try: |
+ output = subprocess.check_output([_GIT_CMD] + cmd, stderr=subprocess.STDOUT) |
+ return output.decode(sys.stdout.encoding).strip() |
+ except subprocess.CalledProcessError as e: |
+ if ignore_return_code: |
+ return None |
+ raise TrybotError('%s. \n%s' % ( |
+ msg_on_error, e.output.decode(sys.stdout.encoding))) |
class Trybot(command_line.ArgParseCommand): |
- """ Run telemetry perf benchmark on trybot """ |
+ """Run telemetry perf benchmark on trybot.""" |
usage = 'botname benchmark_name [<benchmark run options>]' |
_builders = None |
@@ -159,10 +192,10 @@ class Trybot(command_line.ArgParseCommand): |
# In case of any kind of exception, allow tryjobs to use default trybots. |
# Possible exception are ssl.SSLError, urllib2.URLError, |
# socket.timeout, socket.error. |
- except Exception: |
+ except Exception: # pylint: disable=broad-except |
# Incase of any exception return default trybots. |
print ('WARNING: Unable to reach builbot to retrieve trybot ' |
- 'information, tryjob will use default trybots.') |
+ 'information, tryjob will use default trybots.') |
cls._builders = DEFAULT_TRYBOTS |
else: |
builders = json.loads(f.read()).keys() |
@@ -189,9 +222,9 @@ class Trybot(command_line.ArgParseCommand): |
if arg == '--browser' or arg.startswith('--browser='): |
parser.error('--browser=... is not allowed when running trybot.') |
all_benchmarks = discover.DiscoverClasses( |
- start_dir=path_util.GetPerfBenchmarksDir(), |
- top_level_dir=path_util.GetPerfDir(), |
- base_class=benchmark.Benchmark).values() |
+ start_dir=path_util.GetPerfBenchmarksDir(), |
+ top_level_dir=path_util.GetPerfDir(), |
+ base_class=benchmark.Benchmark).values() |
all_benchmark_names = [b.Name() for b in all_benchmarks] |
all_benchmarks_by_names = {b.Name(): b for b in all_benchmarks} |
benchmark_class = all_benchmarks_by_names.get(options.benchmark_name, None) |
@@ -199,9 +232,9 @@ class Trybot(command_line.ArgParseCommand): |
possible_benchmark_names = matching.GetMostLikelyMatchedObject( |
all_benchmark_names, options.benchmark_name) |
parser.error( |
- 'No benchmark named "%s". Do you mean any of those benchmarks ' |
- 'below?\n%s' % |
- (options.benchmark_name, '\n'.join(possible_benchmark_names))) |
+ 'No benchmark named "%s". Do you mean any of those benchmarks ' |
+ 'below?\n%s' % ( |
+ options.benchmark_name, '\n'.join(possible_benchmark_names))) |
is_benchmark_disabled, reason = cls.IsBenchmarkDisabledOnTrybotPlatform( |
benchmark_class, options.trybot) |
also_run_disabled_option = '--also-run-disabled-tests' |
@@ -211,7 +244,7 @@ class Trybot(command_line.ArgParseCommand): |
@classmethod |
def IsBenchmarkDisabledOnTrybotPlatform(cls, benchmark_class, trybot_name): |
- """ Return whether benchmark will be disabled on trybot platform. |
+ """Return whether benchmark will be disabled on trybot platform. |
Note that we cannot tell with certainty whether the benchmark will be |
disabled on the trybot platform since the disable logic in ShouldDisable() |
@@ -271,6 +304,15 @@ class Trybot(command_line.ArgParseCommand): |
help=('specify which benchmark to run. To see all available benchmarks,' |
' run `run_benchmark list`'), |
metavar='<benchmark name>') |
+ parser.add_argument( |
+ '--repo_path', type=str, default=CHROMIUM_SRC_PATH, |
+ help='specify which repo path to use for perf try job\n', |
+ metavar='<repo name>') |
+ parser.add_argument( |
+ '--deps_revision', type=str, default=None, |
+ help='specify DEPS revision to be used by Chromium.\n', |
+ metavar='<repo name>') |
+ |
def Run(self, options, extra_args=None): |
"""Sends a tryjob to a perf trybot. |
@@ -283,88 +325,30 @@ class Trybot(command_line.ArgParseCommand): |
extra_args = [] |
self._InitializeBuilderNames(options.trybot) |
- arguments = [options.benchmark_name] + extra_args |
- |
- # First check if there are chromium changes to upload. |
- status = self._AttemptTryjob(CHROMIUM_CONFIG_FILENAME, arguments) |
- if status not in [SUCCESS, ERROR]: |
- # If we got here, there are no chromium changes to upload. Try blink. |
- os.chdir('third_party/WebKit/') |
- status = self._AttemptTryjob(BLINK_CONFIG_FILENAME, arguments) |
- os.chdir('../..') |
- if status not in [SUCCESS, ERROR]: |
- logging.error('No local changes found in chromium or blink trees. ' |
- 'browser=%s argument sends local changes to the ' |
- 'perf trybot(s): %s.', options.trybot, |
- self._builder_names.values()) |
- return 1 |
- return 0 |
- |
- def _UpdateConfigAndRunTryjob(self, bot_platform, cfg_file_path, arguments): |
- """Updates perf config file, uploads changes and excutes perf try job. |
- |
- Args: |
- bot_platform: Name of the platform to be generated. |
- cfg_file_path: Perf config file path. |
- |
- Returns: |
- (result, msg) where result is one of: |
- SUCCESS if a tryjob was sent |
- NO_CHANGES if there was nothing to try, |
- ERROR if a tryjob was attempted but an error encountered |
- and msg is an error message if an error was encountered, or rietveld |
- url if success, otherwise throws TrybotError exception. |
- """ |
- config = self._GetPerfConfig(bot_platform, arguments) |
- config_to_write = 'config = %s' % json.dumps( |
- config, sort_keys=True, indent=2, separators=(',', ': ')) |
- |
+ original_workdir = os.getcwd() |
+ repo_path = os.path.abspath(options.repo_path) |
try: |
- with open(cfg_file_path, 'r') as config_file: |
- if config_to_write == config_file.read(): |
- return NO_CHANGES, '' |
- except IOError: |
- msg = 'Cannot find %s. Please run from src dir.' % cfg_file_path |
- return (ERROR, msg) |
- |
- with open(cfg_file_path, 'w') as config_file: |
- config_file.write(config_to_write) |
- # Commit the config changes locally. |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'commit', '-a', '-m', 'bisect config: %s' % bot_platform]) |
- if returncode: |
- raise TrybotError('Could not commit bisect config change for %s,' |
- ' error %s' % (bot_platform, err)) |
- # Upload the CL to rietveld and run a try job. |
- returncode, out, err = _RunProcess([ |
- _GIT_CMD, 'cl', 'upload', '-f', '--bypass-hooks', '-m', |
- 'CL for perf tryjob on %s' % bot_platform |
- ]) |
- if returncode: |
- raise TrybotError('Could not upload to rietveld for %s, error %s' % |
- (bot_platform, err)) |
- |
- match = re.search(r'https://codereview.chromium.org/[\d]+', out) |
- if not match: |
- raise TrybotError('Could not upload CL to rietveld for %s! Output %s' % |
- (bot_platform, out)) |
- rietveld_url = match.group(0) |
- # Generate git try command for available bots. |
- git_try_command = [_GIT_CMD, 'cl', 'try', '-m', 'tryserver.chromium.perf'] |
- for bot in self._builder_names[bot_platform]: |
- git_try_command.extend(['-b', bot]) |
- returncode, out, err = _RunProcess(git_try_command) |
- if returncode: |
- raise TrybotError('Could not try CL for %s, error %s' % |
- (bot_platform, err)) |
- |
- return (SUCCESS, rietveld_url) |
+ # Check the existence of repo path. |
+ if not os.path.exists(repo_path): |
+ raise TrybotError('Repository path "%s" does not exists, please check ' |
+ 'the value of <repo_path> argument.' % repo_path) |
+ # Change to the repo directory. |
+ os.chdir(repo_path) |
+ self._AttemptTryjob(repo_path, options, extra_args) |
+ except TrybotError, error: |
+ print error |
+ return 1 |
+ finally: |
+ # Restore to original working directory. |
+ os.chdir(original_workdir) |
+ return 0 |
def _GetPerfConfig(self, bot_platform, arguments): |
"""Generates the perf config for try job. |
Args: |
bot_platform: Name of the platform to be generated. |
+ arguments: Command line arguments. |
Returns: |
A dictionary with perf config parameters. |
@@ -374,7 +358,7 @@ class Trybot(command_line.ArgParseCommand): |
# Always set verbose logging for later debugging |
if '-v' not in arguments and '--verbose' not in arguments: |
- arguments.append('--verbose') |
+ arguments.append('--verbose') |
# Generate the command line for the perf trybots |
target_arch = 'ia32' |
@@ -384,10 +368,8 @@ class Trybot(command_line.ArgParseCommand): |
'Trybot does not suport --chrome-root option set directly ' |
'through command line since it may contain references to your local ' |
'directory') |
- if bot_platform in ['win', 'win-x64']: |
- arguments.insert(0, 'python tools\\perf\\run_benchmark') |
- else: |
- arguments.insert(0, './tools/perf/run_benchmark') |
+ |
+ arguments.insert(0, 'src/tools/perf/run_benchmark') |
if bot_platform == 'android': |
arguments.insert(1, '--browser=android-chromium') |
@@ -407,100 +389,152 @@ class Trybot(command_line.ArgParseCommand): |
'target_arch': target_arch, |
} |
- def _AttemptTryjob(self, cfg_file_path, arguments): |
- """Attempts to run a tryjob from the current directory. |
- |
- This is run once for chromium, and if it returns NO_CHANGES, once for blink. |
+ def _GetRepoAndBranchName(self, repo_path): |
+ """Gets the repository name and working branch name. |
Args: |
- cfg_file_path: Path to the config file for the try job. |
+ repo_path: Path to the repository. |
Returns: |
- Returns SUCCESS if a tryjob was sent, NO_CHANGES if there was nothing to |
- try, ERROR if a tryjob was attempted but an error encountered. |
+ Repository name and branch name as tuple. |
+ |
+ Raises: |
+ TrybotError: This exception is raised for the following cases, |
+ 1. Try job is for non-git repository or in invalid branch. |
+ 2. Un-committed changes in the current branch. |
+ 3. No local commits in the current branch. |
""" |
- source_repo = 'chromium' |
- if cfg_file_path == BLINK_CONFIG_FILENAME: |
- source_repo = 'blink' |
- |
- # TODO(prasadv): This method is quite long, we should consider refactor |
- # this by extracting to helper methods. |
- returncode, original_branchname, err = _RunProcess( |
- [_GIT_CMD, 'rev-parse', '--abbrev-ref', 'HEAD']) |
- if returncode: |
- msg = 'Must be in a git repository to send changes to trybots.' |
- if err: |
- msg += '\nGit error: %s' % err |
- logging.error(msg) |
- return ERROR |
- |
- original_branchname = original_branchname.strip() |
+ # If command runs successfully, then the output will be repo root path. |
+ # and current branch name. |
+ output = RunGit(['rev-parse', '--abbrev-ref', '--show-toplevel', 'HEAD'], |
+ ('%s is not a git repository, must be in a git repository ' |
+ 'to send changes to trybots' % os.getcwd())) |
+ |
+ repo_info = output.split() |
+ # Assuming the base directory name is same as repo project name set in |
+ # codereviews.settings file. |
+ repo_name = os.path.basename(repo_info[0]).strip() |
+ branch_name = repo_info[1].strip() |
+ |
+ if branch_name == 'HEAD': |
+ raise TrybotError('Not on a valid branch, looks like branch ' |
+ 'is dettached. [branch:%s]' % branch_name) |
# Check if the tree is dirty: make sure the index is up to date and then |
# run diff-index |
- _RunProcess([_GIT_CMD, 'update-index', '--refresh', '-q']) |
- returncode, out, err = _RunProcess([_GIT_CMD, 'diff-index', 'HEAD']) |
- if out: |
- logging.error( |
- 'Cannot send a try job with a dirty tree. Commit locally first.') |
- return ERROR |
+ RunGit(['update-index', '--refresh', '-q'], ignore_return_code=True) |
+ output = RunGit(['diff-index', 'HEAD']) |
+ if output: |
+ raise TrybotError( |
+ 'Cannot send a try job with a dirty tree. Please commit ' |
+ 'your changes locally first in %s repository.' % repo_path) |
# Make sure the tree does have local commits. |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'log', 'origin/master..HEAD']) |
- if not out: |
- return NO_CHANGES |
- |
- # Create/check out the telemetry-tryjob branch, and edit the configs |
- # for the tryjob there. |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'checkout', '-b', 'telemetry-tryjob']) |
- if returncode: |
- logging.error('Error creating branch telemetry-tryjob. ' |
- 'Please delete it if it exists.\n%s', err) |
- return ERROR |
- try: |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'branch', '--set-upstream-to', 'origin/master']) |
- if returncode: |
- logging.error('Error in git branch --set-upstream-to: %s', err) |
- return ERROR |
- for bot_platform in self._builder_names: |
- if not self._builder_names[bot_platform]: |
- logging.warning('No builder is found for %s', bot_platform) |
- continue |
- try: |
- results, output = self._UpdateConfigAndRunTryjob( |
- bot_platform, cfg_file_path, arguments) |
- if results == ERROR: |
- logging.error(output) |
- return ERROR |
- elif results == NO_CHANGES: |
- print ('Skip the try job run on %s because it has been tried in ' |
- 'previous try job run. ' % bot_platform) |
- else: |
- print ('Uploaded %s try job to rietveld for %s platform. ' |
- 'View progress at %s' % (source_repo, bot_platform, output)) |
- except TrybotError, err: |
- print err |
- logging.error(err) |
- finally: |
- # Checkout original branch and delete telemetry-tryjob branch. |
- # TODO(prasadv): This finally block could be extracted out to be a |
- # separate function called _CleanupBranch. |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'checkout', original_branchname]) |
- if returncode: |
- logging.error('Could not check out %s. Please check it out and ' |
- 'manually delete the telemetry-tryjob branch. ' |
- ': %s', original_branchname, err) |
- return ERROR # pylint: disable=lost-exception |
- logging.info('Checked out original branch: %s', original_branchname) |
- returncode, out, err = _RunProcess( |
- [_GIT_CMD, 'branch', '-D', 'telemetry-tryjob']) |
- if returncode: |
- logging.error('Could not delete telemetry-tryjob branch. ' |
- 'Please delete it manually: %s', err) |
- return ERROR # pylint: disable=lost-exception |
- logging.info('Deleted temp branch: telemetry-tryjob') |
- return SUCCESS |
+ output = RunGit(['footers', 'HEAD']) |
+ if output: |
+ raise TrybotError('No local changes found in %s repository.' % repo_path) |
+ |
+ return (repo_name, branch_name) |
+ |
+ def _GetBaseGitHashForRepo(self, branch_name, git_url): |
+ """Gets the base revision for the repo on which local changes are made.""" |
+ while not self._IsRepoSupported(branch_name, git_url): |
+ branch_name = RunGit([ |
+ 'rev-parse', '--abbrev-ref', '%s@{upstream}' % branch_name]) |
+ return RunGit(['rev-parse', '%s@{upstream}' % branch_name]) |
+ |
+ def _IsRepoSupported(self, current_branch, repo_git_url): |
+ cur_remote = RunGit(['config', 'branch.%s.remote'% current_branch]) |
+ if cur_remote == '.': |
+ return False |
+ cur_remote_url= RunGit(['config', 'remote.%s.url' % cur_remote]) |
+ if cur_remote_url.lower() == repo_git_url: |
+ return True |
+ raise TrybotError('Remote url on remote %s is not recognized ' |
+ 'on branch: %s' % (cur_remote, cur_remote_url)) |
+ |
+ def _AttemptTryjob(self, repo_path, options, extra_args): |
+ """Attempts to run a tryjob from a repo directory. |
+ |
+ Args: |
+ repo_path: Path to the repository. |
+ options: Command line arguments to run benchmark. |
+ extra_args: Extra arugments to run benchmark. |
+ """ |
+ repo_name, branch_name = self._GetRepoAndBranchName(repo_path) |
+ |
+ repo_info = REPO_INFO_MAP.get(repo_name, None) |
+ if not repo_info: |
+ raise TrybotError('Unsupported repository %s' % repo_name) |
+ |
+ rietveld_url = self._UploadPatchToRietveld(repo_name) |
+ print ('\nUploaded patch to rietveld.' |
+ '\n\tRepo Name: %s\n\tPath: %s\n\tBranch: %s' % ( |
+ repo_name, repo_path, branch_name)) |
+ |
+ deps_override = None |
+ if repo_name != 'src': |
+ if not options.deps_revision: |
+ options.deps_revision = self._GetBaseGitHashForRepo( |
+ branch_name, repo_info.get('url')) |
+ deps_override = {repo_info.get('src'): options.deps_revision} |
+ |
+ arguments = [options.benchmark_name] + extra_args |
+ |
+ rietveld_url = self._UploadPatchToRietveld(repo_name) |
+ print ('\nUploaded try job to rietveld.\n' |
+ 'view progress here %s.\n\tRepo Name: %s\n\tPath: %s\n\tBranch: %s' % ( |
+ rietveld_url, repo_name, repo_path, branch_name)) |
+ |
+ for bot_platform in self._builder_names: |
+ if not self._builder_names[bot_platform]: |
+ logging.warning('No builder is found for %s', bot_platform) |
+ continue |
+ try: |
+ self._RunTryJob(bot_platform, arguments, deps_override) |
+ print ('Perf Try job sent to rietveld for %s platform.\n' % |
+ bot_platform) |
+ except TrybotError, err: |
+ print err |
+ |
+ def _UploadPatchToRietveld(self, repo_name): |
+ # Upload the CL to rietveld and run a try job. |
+ output = RunGit(['cl', 'upload', '-f', '--bypass-hooks', '-m', |
+ 'CL for %s perf tryjob' % repo_name], |
+ 'Could not upload to rietveld for %s' % repo_name) |
+ |
+ match = re.search(r'https://codereview.chromium.org/[\d]+', output) |
+ if not match: |
+ raise TrybotError('Could not upload CL to rietveld for %s! Output %s' % |
+ (repo_name, output)) |
+ return match.group(0) |
+ |
+ |
+ def _RunTryJob(self, bot_platform, arguments, deps_override): |
+ """Generates perf try config, uploads changes and excutes perf try job. |
+ |
+ Args: |
+ bot_platform: Name of the platform to be generated. |
+ arguments: Command line arguments. |
+ deps_override: DEPS revision if needs to be overridden. |
+ |
+ Raises: |
+ TrybotError: When trybot fails to upload CL or run git try. |
+ """ |
+ config = self._GetPerfConfig(bot_platform, arguments) |
+ |
+ # Generate git try command for available bots. |
+ git_try_command = ['cl', 'try', '-m', 'tryserver.chromium.perf'] |
+ |
+ # Add Perf Test config to git try --properties arg. |
+ git_try_command.extend(['-p', 'perf_try_config=%s' % json.dumps(config)]) |
+ |
+ # Add deps overrides to git try --properties arg. |
+ if deps_override: |
+ git_try_command.extend([ |
+ '-p', 'deps_revision_overrides=%s' % json.dumps(deps_override)]) |
+ |
+ for bot in self._builder_names[bot_platform]: |
+ git_try_command.extend(['-b', bot]) |
+ |
+ RunGit(git_try_command, 'Could not try CL for %s' % bot_platform) |