Chromium Code Reviews| 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..e375354a3bafe9bfc78fc82b5775a42ee7f9d1f8 100644 | 
| --- a/tools/perf/core/trybot_command.py | 
| +++ b/tools/perf/core/trybot_command.py | 
| @@ -3,13 +3,14 @@ | 
| # found in the LICENSE file. | 
| import argparse | 
| -import os | 
| +import json | 
| import logging | 
| +import os | 
| import platform | 
| import re | 
| import subprocess | 
| import urllib2 | 
| -import json | 
| + | 
| from core import path_util | 
| @@ -20,9 +21,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 +55,23 @@ 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() | 
| + | 
| +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 +90,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,22 +126,42 @@ def _GetBuilderNames(trybot_name, builders): | 
| return platform_and_bots | 
| -def _RunProcess(cmd): | 
| - logging.debug('Running process: "%s"', ' '.join(cmd)) | 
| - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
| - out, err = proc.communicate() | 
| - returncode = proc.poll() | 
| - return (returncode, out, err) | 
| +_GIT_CMD = 'git' | 
| -_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): | 
| + """Runs the GIT command with the given arguments. | 
| 
 
eakuefner
2016/08/30 17:33:49
nit: s/GIT/git/
 
prasadv
2016/08/30 18:49:40
Done.
 
 | 
| + | 
| + Args: | 
| + cmd: GIT command arguments. | 
| + msg_on_error: Message to be displayed on GIT command error. | 
| + ignore_return_code: Ignores the return code for GIT command | 
| 
 
eakuefner
2016/08/30 17:33:49
nit: end with period?
 
prasadv
2016/08/30 18:49:40
Done.
 
 | 
| + | 
| + Returns: | 
| + The output of the GIT command as string. | 
| + | 
| + Raises: | 
| + TrybotError: This exception is raised when GIT command fails. | 
| + """ | 
| + proc = subprocess.Popen( | 
| + [_GIT_CMD] + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
| 
 
eakuefner
2016/08/30 17:33:50
Can you just write [_GIT_CMD, cmd]?
 
prasadv
2016/08/30 18:49:40
cmd is a list of arguments for git command and I t
 
 | 
| + output, err = proc.communicate() | 
| + returncode = proc.poll() | 
| + if returncode: | 
| + if ignore_return_code: | 
| + return None | 
| + raise TrybotError('%s. \n%s' % (msg_on_error, err)) | 
| + | 
| + return output | 
| + | 
| + | 
| 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 +180,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 +210,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 +220,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 +232,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() | 
| @@ -282,89 +303,19 @@ class Trybot(command_line.ArgParseCommand): | 
| if extra_args is None: | 
| 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=(',', ': ')) | 
| - | 
| 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) | 
| + self._AttemptTryjob(CHROMIUM_SRC_PATH, options, extra_args) | 
| + except TrybotError, error: | 
| + print error | 
| 
 
nednguyen
2016/08/29 23:15:22
Why print error then return 1? Wouldn't not catchi
 
prasadv
2016/08/30 18:49:40
Most of the TryjobError exceptions are raised when
 
 | 
| + return 1 | 
| + 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 +325,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,17 +335,15 @@ 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') | 
| elif any('x64' in bot for bot in self._builder_names[bot_platform]): | 
| arguments.insert(1, '--browser=release_x64') | 
| target_arch = 'x64' | 
| else: | 
| + | 
| arguments.insert(1, '--browser=release') | 
| command = ' '.join(arguments) | 
| @@ -407,100 +356,111 @@ 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, | 
| 
 
eakuefner
2016/08/30 17:33:49
nit: cases:
 
prasadv
2016/08/30 18:49:40
Done.
 
 | 
| + 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 _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) | 
| + | 
| + arguments = [options.benchmark_name] + extra_args | 
| + | 
| + rietveld_url = self._UploadPatchToRietveld(repo_name) | 
| + print ('\nUploaded try job to rietveld.\nview 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) | 
| + except TrybotError, err: | 
| + print err | 
| + | 
| + def _UploadPatchToRietveld(self, repo_name): | 
| + """Uploads the patch to rietveld and returns rietveld URL.""" | 
| + output = RunGit(['cl', 'upload', '-f', '--bypass-hooks', '-m', | 
| + 'CL for %s perf tryjob' % repo_name], | 
| 
 
nednguyen
2016/08/29 23:15:22
nits: would be nice to include benchmark name & bo
 
eakuefner
2016/08/30 17:33:50
The title should be the same as the first/only lin
 
prasadv
2016/08/30 18:49:40
Yes, CL title will be the first line of the messag
 
nednguyen
2016/08/30 18:55:23
It seems not very ideal that this overwrites exist
 
 | 
| + '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): | 
| + """Executes perf try job with benchmark test properties. | 
| + | 
| + Args: | 
| + bot_platform: Name of the platform to be generated. | 
| + arguments: Command line arguments. | 
| + | 
| + 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)]) | 
| + | 
| + 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) | 
| + print 'Perf Try job sent to rietveld for %s platform.' % bot_platform |