| Index: tools/auto_bisect/bisect_perf_regression.py
|
| diff --git a/tools/auto_bisect/bisect_perf_regression.py b/tools/auto_bisect/bisect_perf_regression.py
|
| index db63bba122194624b1536c4ba8d1bfef619fb539..f90074f4d7533adda3937ef5e8740afa4898efef 100755
|
| --- a/tools/auto_bisect/bisect_perf_regression.py
|
| +++ b/tools/auto_bisect/bisect_perf_regression.py
|
| @@ -33,7 +33,6 @@ Example usage using git hashes:
|
| """
|
|
|
| import copy
|
| -import datetime
|
| import errno
|
| import hashlib
|
| import optparse
|
| @@ -49,7 +48,9 @@ import zipfile
|
| sys.path.append(os.path.join(
|
| os.path.dirname(__file__), os.path.pardir, 'telemetry'))
|
|
|
| +from bisect_printer import BisectPrinter
|
| from bisect_results import BisectResults
|
| +from bisect_state import BisectState
|
| import bisect_utils
|
| import builder
|
| import math_utils
|
| @@ -57,99 +58,6 @@ import request_build
|
| import source_control
|
| from telemetry.util import cloud_storage
|
|
|
| -# Below is the map of "depot" names to information about each depot. Each depot
|
| -# is a repository, and in the process of bisecting, revision ranges in these
|
| -# repositories may also be bisected.
|
| -#
|
| -# Each depot information dictionary may contain:
|
| -# src: Path to the working directory.
|
| -# recurse: True if this repository will get bisected.
|
| -# depends: A list of other repositories that are actually part of the same
|
| -# repository in svn. If the repository has any dependent repositories
|
| -# (e.g. skia/src needs skia/include and skia/gyp to be updated), then
|
| -# they are specified here.
|
| -# svn: URL of SVN repository. Needed for git workflow to resolve hashes to
|
| -# SVN revisions.
|
| -# from: Parent depot that must be bisected before this is bisected.
|
| -# deps_var: Key name in vars variable in DEPS file that has revision
|
| -# information.
|
| -DEPOT_DEPS_NAME = {
|
| - 'chromium': {
|
| - 'src': 'src',
|
| - 'recurse': True,
|
| - 'depends': None,
|
| - 'from': ['cros', 'android-chrome'],
|
| - 'viewvc':
|
| - 'http://src.chromium.org/viewvc/chrome?view=revision&revision=',
|
| - 'deps_var': 'chromium_rev'
|
| - },
|
| - 'webkit': {
|
| - 'src': 'src/third_party/WebKit',
|
| - 'recurse': True,
|
| - 'depends': None,
|
| - 'from': ['chromium'],
|
| - 'viewvc':
|
| - 'http://src.chromium.org/viewvc/blink?view=revision&revision=',
|
| - 'deps_var': 'webkit_revision'
|
| - },
|
| - 'angle': {
|
| - 'src': 'src/third_party/angle',
|
| - 'src_old': 'src/third_party/angle_dx11',
|
| - 'recurse': True,
|
| - 'depends': None,
|
| - 'from': ['chromium'],
|
| - 'platform': 'nt',
|
| - 'deps_var': 'angle_revision'
|
| - },
|
| - 'v8': {
|
| - 'src': 'src/v8',
|
| - 'recurse': True,
|
| - 'depends': None,
|
| - 'from': ['chromium'],
|
| - 'custom_deps': bisect_utils.GCLIENT_CUSTOM_DEPS_V8,
|
| - 'viewvc': 'https://code.google.com/p/v8/source/detail?r=',
|
| - 'deps_var': 'v8_revision'
|
| - },
|
| - 'v8_bleeding_edge': {
|
| - 'src': 'src/v8_bleeding_edge',
|
| - 'recurse': True,
|
| - 'depends': None,
|
| - 'svn': 'https://v8.googlecode.com/svn/branches/bleeding_edge',
|
| - 'from': ['v8'],
|
| - 'viewvc': 'https://code.google.com/p/v8/source/detail?r=',
|
| - 'deps_var': 'v8_revision'
|
| - },
|
| - 'skia/src': {
|
| - 'src': 'src/third_party/skia/src',
|
| - 'recurse': True,
|
| - 'svn': 'http://skia.googlecode.com/svn/trunk/src',
|
| - 'depends': ['skia/include', 'skia/gyp'],
|
| - 'from': ['chromium'],
|
| - 'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
|
| - 'deps_var': 'skia_revision'
|
| - },
|
| - 'skia/include': {
|
| - 'src': 'src/third_party/skia/include',
|
| - 'recurse': False,
|
| - 'svn': 'http://skia.googlecode.com/svn/trunk/include',
|
| - 'depends': None,
|
| - 'from': ['chromium'],
|
| - 'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
|
| - 'deps_var': 'None'
|
| - },
|
| - 'skia/gyp': {
|
| - 'src': 'src/third_party/skia/gyp',
|
| - 'recurse': False,
|
| - 'svn': 'http://skia.googlecode.com/svn/trunk/gyp',
|
| - 'depends': None,
|
| - 'from': ['chromium'],
|
| - 'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
|
| - 'deps_var': 'None'
|
| - }
|
| -}
|
| -
|
| -DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
|
| -
|
| # The script is in chromium/src/tools/auto_bisect. Throughout this script,
|
| # we use paths to other things in the chromium/src repository.
|
|
|
| @@ -167,9 +75,6 @@ MAX_MAC_BUILD_TIME = 14400
|
| MAX_WIN_BUILD_TIME = 14400
|
| MAX_LINUX_BUILD_TIME = 14400
|
|
|
| -# The percentage at which confidence is considered high.
|
| -HIGH_CONFIDENCE = 95
|
| -
|
| # Patch template to add a new file, DEPS.sha under src folder.
|
| # This file contains SHA1 value of the DEPS changes made while bisecting
|
| # dependency repositories. This patch send along with DEPS patch to try server.
|
| @@ -184,72 +89,6 @@ new file mode 100644
|
| +%(deps_sha)s
|
| """
|
|
|
| -# The possible values of the --bisect_mode flag, which determines what to
|
| -# use when classifying a revision as "good" or "bad".
|
| -BISECT_MODE_MEAN = 'mean'
|
| -BISECT_MODE_STD_DEV = 'std_dev'
|
| -BISECT_MODE_RETURN_CODE = 'return_code'
|
| -
|
| -# The perf dashboard looks for a string like "Estimated Confidence: 95%"
|
| -# to decide whether or not to cc the author(s). If you change this, please
|
| -# update the perf dashboard as well.
|
| -RESULTS_BANNER = """
|
| -===== BISECT JOB RESULTS =====
|
| -Status: %(status)s
|
| -
|
| -Test Command: %(command)s
|
| -Test Metric: %(metrics)s
|
| -Relative Change: %(change)s
|
| -Estimated Confidence: %(confidence).02f%%"""
|
| -
|
| -# The perf dashboard specifically looks for the string
|
| -# "Author : " to parse out who to cc on a bug. If you change the
|
| -# formatting here, please update the perf dashboard as well.
|
| -RESULTS_REVISION_INFO = """
|
| -===== SUSPECTED CL(s) =====
|
| -Subject : %(subject)s
|
| -Author : %(author)s%(email_info)s%(commit_info)s
|
| -Commit : %(cl)s
|
| -Date : %(cl_date)s"""
|
| -
|
| -REPRO_STEPS_LOCAL = """
|
| -==== INSTRUCTIONS TO REPRODUCE ====
|
| -To run locally:
|
| - - Use the test command given under 'BISECT JOB RESULTS' above.
|
| - - Consider using a profiler. Pass --profiler=list to list available profilers.
|
| -"""
|
| -
|
| -REPRO_STEPS_TRYJOB = """
|
| -To reproduce on a performance try bot:
|
| - 1. Edit run-perf-test.cfg
|
| - 2. $ git try -b <bot> --svn_repo='svn://svn.chromium.org/chrome-try/try-perf'
|
| -
|
| -Notes:
|
| - a) Follow the in-file instructions in run-perf-test.cfg.
|
| - b) run-perf-test.cfg is under tools/ or under third_party/WebKit/Tools.
|
| - c) Do your edits preferably under a new git branch.
|
| - d) --browser=release and --browser=android-chromium-testshell are supported
|
| - depending on the platform (desktop|android).
|
| - e) Strip any src/ directories from the head of relative path names.
|
| - f) Make sure to use the appropriate bot on step 3.
|
| -
|
| -For more details please visit
|
| -https://sites.google.com/a/chromium.org/dev/developers/performance-try-bots"""
|
| -
|
| -REPRO_STEPS_TRYJOB_TELEMETRY = """
|
| -To reproduce on a performance try bot:
|
| -%(command)s
|
| -(Where <bot-name> comes from tools/perf/run_benchmark --browser=list)
|
| -
|
| -For more details please visit
|
| -https://sites.google.com/a/chromium.org/dev/developers/performance-try-bots
|
| -"""
|
| -
|
| -RESULTS_THANKYOU = """
|
| -| O O | Visit http://www.chromium.org/developers/core-principles for Chrome's
|
| -| X | policy on perf regressions. Contact chrome-perf-dashboard-team with any
|
| -| / \ | questions or suggestions about bisecting. THANK YOU."""
|
| -
|
| # Git branch name used to run bisect try jobs.
|
| BISECT_TRYJOB_BRANCH = 'bisect-tryjob'
|
| # Git master branch name.
|
| @@ -265,14 +104,6 @@ class RunGitError(Exception):
|
| return '%s\nError executing git command.' % self.args[0]
|
|
|
|
|
| -def _AddAdditionalDepotInfo(depot_info):
|
| - """Adds additional depot info to the global depot variables."""
|
| - global DEPOT_DEPS_NAME
|
| - global DEPOT_NAMES
|
| - DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + depot_info.items())
|
| - DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
|
| -
|
| -
|
| def GetSHA1HexDigest(contents):
|
| """Returns SHA1 hex digest of the given string."""
|
| return hashlib.sha1(contents).hexdigest()
|
| @@ -570,7 +401,7 @@ def _UpdateDEPSForAngle(revision, depot, deps_file):
|
| in such cases check "deps" dictionary variable that matches
|
| angle.git@[a-fA-F0-9]{40}$ and replace git hash.
|
| """
|
| - deps_var = DEPOT_DEPS_NAME[depot]['deps_var']
|
| + deps_var = bisect_utils.DEPOT_DEPS_NAME[depot]['deps_var']
|
| try:
|
| deps_contents = ReadStringFromFile(deps_file)
|
| # Check whether the depot and revision pattern in DEPS file vars variable
|
| @@ -758,85 +589,14 @@ def _GenerateProfileIfNecessary(command_args):
|
| return True
|
|
|
|
|
| -def _AddRevisionsIntoRevisionData(revisions, depot, sort, revision_data):
|
| - """Adds new revisions to the revision_data dictionary and initializes them.
|
| -
|
| - Args:
|
| - revisions: List of revisions to add.
|
| - depot: Depot that's currently in use (src, webkit, etc...)
|
| - sort: Sorting key for displaying revisions.
|
| - revision_data: A dictionary to add the new revisions into.
|
| - Existing revisions will have their sort keys adjusted.
|
| - """
|
| - num_depot_revisions = len(revisions)
|
| -
|
| - for _, v in revision_data.iteritems():
|
| - if v['sort'] > sort:
|
| - v['sort'] += num_depot_revisions
|
| -
|
| - for i in xrange(num_depot_revisions):
|
| - r = revisions[i]
|
| - revision_data[r] = {
|
| - 'revision' : r,
|
| - 'depot' : depot,
|
| - 'value' : None,
|
| - 'perf_time' : 0,
|
| - 'build_time' : 0,
|
| - 'passed' : '?',
|
| - 'sort' : i + sort + 1,
|
| - }
|
| -
|
| -
|
| -def _PrintThankYou():
|
| - print RESULTS_THANKYOU
|
| -
|
| -
|
| -def _PrintTableRow(column_widths, row_data):
|
| - """Prints out a row in a formatted table that has columns aligned.
|
| -
|
| - Args:
|
| - column_widths: A list of column width numbers.
|
| - row_data: A list of items for each column in this row.
|
| - """
|
| - assert len(column_widths) == len(row_data)
|
| - text = ''
|
| - for i in xrange(len(column_widths)):
|
| - current_row_data = row_data[i].center(column_widths[i], ' ')
|
| - text += ('%%%ds' % column_widths[i]) % current_row_data
|
| - print text
|
| -
|
| -
|
| -def _PrintStepTime(revision_data_sorted):
|
| - """Prints information about how long various steps took.
|
| -
|
| - Args:
|
| - revision_data_sorted: The sorted list of revision data dictionaries."""
|
| - step_perf_time_avg = 0.0
|
| - step_build_time_avg = 0.0
|
| - step_count = 0.0
|
| - for _, current_data in revision_data_sorted:
|
| - if current_data['value']:
|
| - step_perf_time_avg += current_data['perf_time']
|
| - step_build_time_avg += current_data['build_time']
|
| - step_count += 1
|
| - if step_count:
|
| - step_perf_time_avg = step_perf_time_avg / step_count
|
| - step_build_time_avg = step_build_time_avg / step_count
|
| - print
|
| - print 'Average build time : %s' % datetime.timedelta(
|
| - seconds=int(step_build_time_avg))
|
| - print 'Average test time : %s' % datetime.timedelta(
|
| - seconds=int(step_perf_time_avg))
|
| -
|
| -
|
| class DepotDirectoryRegistry(object):
|
|
|
| def __init__(self, src_cwd):
|
| self.depot_cwd = {}
|
| - for depot in DEPOT_NAMES:
|
| + for depot in bisect_utils.DEPOT_NAMES:
|
| # The working directory of each depot is just the path to the depot, but
|
| # since we're already in 'src', we can skip that part.
|
| - path_in_src = DEPOT_DEPS_NAME[depot]['src'][4:]
|
| + path_in_src = bisect_utils.DEPOT_DEPS_NAME[depot]['src'][4:]
|
| self.AddDepot(depot, os.path.join(src_cwd, path_in_src))
|
|
|
| self.AddDepot('chromium', src_cwd)
|
| @@ -1051,8 +811,8 @@ class BisectPerformanceMetrics(object):
|
| 'bleeding_edge revision r')[1]
|
| bleeding_edge_revision = int(bleeding_edge_revision.split(')')[0])
|
| git_revision = source_control.ResolveToRevision(
|
| - bleeding_edge_revision, 'v8_bleeding_edge', DEPOT_DEPS_NAME, 1,
|
| - cwd=v8_bleeding_edge_dir)
|
| + bleeding_edge_revision, 'v8_bleeding_edge',
|
| + bisect_utils.DEPOT_DEPS_NAME, 1, cwd=v8_bleeding_edge_dir)
|
| return git_revision
|
| except (IndexError, ValueError):
|
| pass
|
| @@ -1060,8 +820,8 @@ class BisectPerformanceMetrics(object):
|
| if not git_revision:
|
| # Wasn't successful, try the old way of looking for "Prepare push to"
|
| git_revision = source_control.ResolveToRevision(
|
| - int(commit_position) - 1, 'v8_bleeding_edge', DEPOT_DEPS_NAME, -1,
|
| - cwd=v8_bleeding_edge_dir)
|
| + int(commit_position) - 1, 'v8_bleeding_edge',
|
| + bisect_utils.DEPOT_DEPS_NAME, -1, cwd=v8_bleeding_edge_dir)
|
|
|
| if git_revision:
|
| revision_info = source_control.QueryRevisionInfo(git_revision,
|
| @@ -1125,7 +885,7 @@ class BisectPerformanceMetrics(object):
|
|
|
| rxp = re.compile(".git@(?P<revision>[a-fA-F0-9]+)")
|
| results = {}
|
| - for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems():
|
| + for depot_name, depot_data in bisect_utils.DEPOT_DEPS_NAME.iteritems():
|
| if (depot_data.get('platform') and
|
| depot_data.get('platform') != os.name):
|
| continue
|
| @@ -1154,10 +914,10 @@ class BisectPerformanceMetrics(object):
|
| for depot_name, depot_revision in parse_results.iteritems():
|
| depot_revision = depot_revision.strip('@')
|
| print depot_name, depot_revision
|
| - for current_name, current_data in DEPOT_DEPS_NAME.iteritems():
|
| - if (current_data.has_key('deps_var') and
|
| - current_data['deps_var'] == depot_name):
|
| - src_name = current_name
|
| + for cur_name, cur_data in bisect_utils.DEPOT_DEPS_NAME.iteritems():
|
| + if (cur_data.has_key('deps_var') and
|
| + cur_data['deps_var'] == depot_name):
|
| + src_name = cur_name
|
| results[src_name] = depot_revision
|
| break
|
| return results
|
| @@ -1436,8 +1196,8 @@ class BisectPerformanceMetrics(object):
|
| if (self.opts.target_platform in ['chromium', 'android'] and
|
| self.opts.gs_bucket):
|
| return (depot == 'chromium' or
|
| - 'chromium' in DEPOT_DEPS_NAME[depot]['from'] or
|
| - 'v8' in DEPOT_DEPS_NAME[depot]['from'])
|
| + 'chromium' in bisect_utils.DEPOT_DEPS_NAME[depot]['from'] or
|
| + 'v8' in bisect_utils.DEPOT_DEPS_NAME[depot]['from'])
|
| return False
|
|
|
| def UpdateDepsContents(self, deps_contents, depot, git_revision, deps_key):
|
| @@ -1500,7 +1260,7 @@ class BisectPerformanceMetrics(object):
|
| if not os.path.exists(deps_file):
|
| return False
|
|
|
| - deps_var = DEPOT_DEPS_NAME[depot]['deps_var']
|
| + deps_var = bisect_utils.DEPOT_DEPS_NAME[depot]['deps_var']
|
| # Don't update DEPS file if deps_var is not set in DEPOT_DEPS_NAME.
|
| if not deps_var:
|
| print 'DEPS update not supported for Depot: %s', depot
|
| @@ -1545,8 +1305,8 @@ class BisectPerformanceMetrics(object):
|
| if not chromium_sha:
|
| raise RuntimeError('Failed to determine Chromium revision for %s' %
|
| revision)
|
| - if ('chromium' in DEPOT_DEPS_NAME[depot]['from'] or
|
| - 'v8' in DEPOT_DEPS_NAME[depot]['from']):
|
| + if ('chromium' in bisect_utils.DEPOT_DEPS_NAME[depot]['from'] or
|
| + 'v8' in bisect_utils.DEPOT_DEPS_NAME[depot]['from']):
|
| # Checkout DEPS file for the current chromium revision.
|
| if source_control.CheckoutFileAtRevision(
|
| bisect_utils.FILE_DEPS, chromium_sha, cwd=self.src_cwd):
|
| @@ -1601,13 +1361,14 @@ class BisectPerformanceMetrics(object):
|
| return not bisect_utils.RunGClient(['runhooks'], cwd=self.src_cwd)
|
|
|
| def _IsBisectModeUsingMetric(self):
|
| - return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV]
|
| + return self.opts.bisect_mode in [bisect_utils.BISECT_MODE_MEAN,
|
| + bisect_utils.BISECT_MODE_STD_DEV]
|
|
|
| def _IsBisectModeReturnCode(self):
|
| - return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE]
|
| + return self.opts.bisect_mode in [bisect_utils.BISECT_MODE_RETURN_CODE]
|
|
|
| def _IsBisectModeStandardDeviation(self):
|
| - return self.opts.bisect_mode in [BISECT_MODE_STD_DEV]
|
| + return self.opts.bisect_mode in [bisect_utils.BISECT_MODE_STD_DEV]
|
|
|
| def GetCompatibleCommand(self, command_to_run, revision, depot):
|
| """Return a possibly modified test command depending on the revision.
|
| @@ -1816,20 +1577,20 @@ class BisectPerformanceMetrics(object):
|
| # figure out for each mirror which git revision to grab. There's no
|
| # guarantee that the SVN revision will exist for each of the dependent
|
| # depots, so we have to grep the git logs and grab the next earlier one.
|
| - if not is_base and DEPOT_DEPS_NAME[depot]['depends']:
|
| + if not is_base and bisect_utils.DEPOT_DEPS_NAME[depot]['depends']:
|
| commit_position = source_control.GetCommitPosition(revision)
|
|
|
| - for d in DEPOT_DEPS_NAME[depot]['depends']:
|
| + for d in bisect_utils.DEPOT_DEPS_NAME[depot]['depends']:
|
| self.depot_registry.ChangeToDepotDir(d)
|
|
|
| dependant_rev = source_control.ResolveToRevision(
|
| - commit_position, d, DEPOT_DEPS_NAME, -1000)
|
| + commit_position, d, bisect_utils.DEPOT_DEPS_NAME, -1000)
|
|
|
| if dependant_rev:
|
| revisions_to_sync.append([d, dependant_rev])
|
|
|
| num_resolved = len(revisions_to_sync)
|
| - num_needed = len(DEPOT_DEPS_NAME[depot]['depends'])
|
| + num_needed = len(bisect_utils.DEPOT_DEPS_NAME[depot]['depends'])
|
|
|
| self.depot_registry.ChangeToDepotDir(depot)
|
|
|
| @@ -1967,7 +1728,7 @@ class BisectPerformanceMetrics(object):
|
| if not self._SyncAllRevisions(revisions_to_sync, sync_client):
|
| return ('Failed to sync: [%s]' % str(revision), BUILD_RESULT_FAIL)
|
|
|
| - # Try to do any post-sync steps. This may include "gclient runhooks".
|
| + # Try to do any post-sync steps. This may include "gclient runhooks".
|
| if not self._RunPostSync(depot):
|
| return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL)
|
|
|
| @@ -2031,7 +1792,8 @@ class BisectPerformanceMetrics(object):
|
| # want so that all the dependencies sync properly as well.
|
| # i.e. gclient sync src@<SHA1>
|
| if sync_client == 'gclient':
|
| - revision = '%s@%s' % (DEPOT_DEPS_NAME[depot]['src'], revision)
|
| + revision = '%s@%s' % (bisect_utils.DEPOT_DEPS_NAME[depot]['src'],
|
| + revision)
|
|
|
| sync_success = source_control.SyncToRevision(revision, sync_client)
|
| if not sync_success:
|
| @@ -2052,7 +1814,7 @@ class BisectPerformanceMetrics(object):
|
| True if the current_value is closer to the known_good_value than the
|
| known_bad_value.
|
| """
|
| - if self.opts.bisect_mode == BISECT_MODE_STD_DEV:
|
| + if self.opts.bisect_mode == bisect_utils.BISECT_MODE_STD_DEV:
|
| dist_to_good_value = abs(current_value['std_dev'] -
|
| known_good_value['std_dev'])
|
| dist_to_bad_value = abs(current_value['std_dev'] -
|
| @@ -2063,18 +1825,18 @@ class BisectPerformanceMetrics(object):
|
|
|
| return dist_to_good_value < dist_to_bad_value
|
|
|
| - def _FillInV8BleedingEdgeInfo(self, min_revision_data, max_revision_data):
|
| - r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_data['revision'],
|
| + def _FillInV8BleedingEdgeInfo(self, min_revision_state, max_revision_state):
|
| + r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_state.revision,
|
| search_forward=True)
|
| - r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_data['revision'],
|
| + r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_state.revision,
|
| search_forward=False)
|
| - min_revision_data['external']['v8_bleeding_edge'] = r1
|
| - max_revision_data['external']['v8_bleeding_edge'] = r2
|
| + min_revision_state.external['v8_bleeding_edge'] = r1
|
| + max_revision_state.external['v8_bleeding_edge'] = r2
|
|
|
| if (not self._GetV8BleedingEdgeFromV8TrunkIfMappable(
|
| - min_revision_data['revision'])
|
| + min_revision_state.revision)
|
| or not self._GetV8BleedingEdgeFromV8TrunkIfMappable(
|
| - max_revision_data['revision'])):
|
| + max_revision_state.revision)):
|
| self.warnings.append(
|
| 'Trunk revisions in V8 did not map directly to bleeding_edge. '
|
| 'Attempted to expand the range to find V8 rolls which did map '
|
| @@ -2082,54 +1844,54 @@ class BisectPerformanceMetrics(object):
|
| 'valid.')
|
|
|
| def _FindNextDepotToBisect(
|
| - self, current_depot, min_revision_data, max_revision_data):
|
| + self, current_depot, min_revision_state, max_revision_state):
|
| """Decides which depot the script should dive into next (if any).
|
|
|
| Args:
|
| current_depot: Current depot being bisected.
|
| - min_revision_data: Data about the earliest revision in the bisect range.
|
| - max_revision_data: Data about the latest revision in the bisect range.
|
| + min_revision_state: State of the earliest revision in the bisect range.
|
| + max_revision_state: State of the latest revision in the bisect range.
|
|
|
| Returns:
|
| Name of the depot to bisect next, or None.
|
| """
|
| external_depot = None
|
| - for next_depot in DEPOT_NAMES:
|
| - if DEPOT_DEPS_NAME[next_depot].has_key('platform'):
|
| - if DEPOT_DEPS_NAME[next_depot]['platform'] != os.name:
|
| + for next_depot in bisect_utils.DEPOT_NAMES:
|
| + if bisect_utils.DEPOT_DEPS_NAME[next_depot].has_key('platform'):
|
| + if bisect_utils.DEPOT_DEPS_NAME[next_depot]['platform'] != os.name:
|
| continue
|
|
|
| - if not (DEPOT_DEPS_NAME[next_depot]['recurse']
|
| - and min_revision_data['depot']
|
| - in DEPOT_DEPS_NAME[next_depot]['from']):
|
| + if not (bisect_utils.DEPOT_DEPS_NAME[next_depot]['recurse']
|
| + and min_revision_state.depot
|
| + in bisect_utils.DEPOT_DEPS_NAME[next_depot]['from']):
|
| continue
|
|
|
| if current_depot == 'v8':
|
| # We grab the bleeding_edge info here rather than earlier because we
|
| # finally have the revision range. From that we can search forwards and
|
| # backwards to try to match trunk revisions to bleeding_edge.
|
| - self._FillInV8BleedingEdgeInfo(min_revision_data, max_revision_data)
|
| + self._FillInV8BleedingEdgeInfo(min_revision_state, max_revision_state)
|
|
|
| - if (min_revision_data['external'].get(next_depot) ==
|
| - max_revision_data['external'].get(next_depot)):
|
| + if (min_revision_state.external.get(next_depot) ==
|
| + max_revision_state.external.get(next_depot)):
|
| continue
|
|
|
| - if (min_revision_data['external'].get(next_depot) and
|
| - max_revision_data['external'].get(next_depot)):
|
| + if (min_revision_state.external.get(next_depot) and
|
| + max_revision_state.external.get(next_depot)):
|
| external_depot = next_depot
|
| break
|
|
|
| return external_depot
|
|
|
| def PrepareToBisectOnDepot(
|
| - self, current_depot, end_revision, start_revision, previous_revision):
|
| + self, current_depot, start_revision, end_revision, previous_revision):
|
| """Changes to the appropriate directory and gathers a list of revisions
|
| to bisect between |start_revision| and |end_revision|.
|
|
|
| Args:
|
| current_depot: The depot we want to bisect.
|
| - end_revision: End of the revision range.
|
| start_revision: Start of the revision range.
|
| + end_revision: End of the revision range.
|
| previous_revision: The last revision we synced to on |previous_depot|.
|
|
|
| Returns:
|
| @@ -2142,10 +1904,11 @@ class BisectPerformanceMetrics(object):
|
|
|
| # V8 (and possibly others) is merged in periodically. Bisecting
|
| # this directory directly won't give much good info.
|
| - if DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'):
|
| + if bisect_utils.DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'):
|
| config_path = os.path.join(self.src_cwd, '..')
|
| - if bisect_utils.RunGClientAndCreateConfig(self.opts,
|
| - DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path):
|
| + if bisect_utils.RunGClientAndCreateConfig(
|
| + self.opts, bisect_utils.DEPOT_DEPS_NAME[current_depot]['custom_deps'],
|
| + cwd=config_path):
|
| return []
|
| if bisect_utils.RunGClient(
|
| ['sync', '--revision', previous_revision], cwd=self.src_cwd):
|
| @@ -2199,8 +1962,8 @@ class BisectPerformanceMetrics(object):
|
|
|
| def PrintRevisionsToBisectMessage(self, revision_list, depot):
|
| if self.opts.output_buildbot_annotations:
|
| - step_name = 'Bisection Range: [%s - %s]' % (
|
| - revision_list[len(revision_list)-1], revision_list[0])
|
| + step_name = 'Bisection Range: [%s:%s - %s]' % (depot, revision_list[-1],
|
| + revision_list[0])
|
| bisect_utils.OutputAnnotationStepStart(step_name)
|
|
|
| print
|
| @@ -2340,8 +2103,6 @@ class BisectPerformanceMetrics(object):
|
| Returns:
|
| A BisectResults object.
|
| """
|
| - results = BisectResults(self.depot_registry)
|
| -
|
| # Choose depot to bisect first
|
| target_depot = 'chromium'
|
| if self.opts.target_platform == 'cros':
|
| @@ -2354,25 +2115,25 @@ class BisectPerformanceMetrics(object):
|
|
|
| # If they passed SVN revisions, we can try match them to git SHA1 hashes.
|
| bad_revision = source_control.ResolveToRevision(
|
| - bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100)
|
| + bad_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, 100)
|
| good_revision = source_control.ResolveToRevision(
|
| - good_revision_in, target_depot, DEPOT_DEPS_NAME, -100)
|
| + good_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, -100)
|
|
|
| os.chdir(cwd)
|
| if bad_revision is None:
|
| - results.error = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in
|
| - return results
|
| + return BisectResults(
|
| + error='Couldn\'t resolve [%s] to SHA1.' % bad_revision_in)
|
|
|
| if good_revision is None:
|
| - results.error = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in
|
| - return results
|
| + return BisectResults(
|
| + error='Couldn\'t resolve [%s] to SHA1.' % good_revision_in)
|
|
|
| # Check that they didn't accidentally swap good and bad revisions.
|
| if not self.CheckIfRevisionsInProperOrder(
|
| target_depot, good_revision, bad_revision):
|
| - results.error = ('bad_revision < good_revision, did you swap these '
|
| - 'by mistake?')
|
| - return results
|
| + return BisectResults(error='bad_revision < good_revision, did you swap '
|
| + 'these by mistake?')
|
| +
|
| bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange(
|
| bad_revision, good_revision, good_revision_in)
|
| if self.opts.output_buildbot_annotations:
|
| @@ -2380,45 +2141,17 @@ class BisectPerformanceMetrics(object):
|
|
|
| cannot_bisect = self.CanPerformBisect(good_revision, bad_revision)
|
| if cannot_bisect:
|
| - results.error = cannot_bisect.get('error')
|
| - return results
|
| + return BisectResults(error=cannot_bisect.get('error'))
|
|
|
| print 'Gathering revision range for bisection.'
|
| # Retrieve a list of revisions to do bisection on.
|
| - src_revision_list = self.GetRevisionList(
|
| - target_depot, bad_revision, good_revision)
|
| + revision_list = self.GetRevisionList(target_depot, bad_revision,
|
| + good_revision)
|
|
|
| if self.opts.output_buildbot_annotations:
|
| bisect_utils.OutputAnnotationStepClosed()
|
|
|
| - if src_revision_list:
|
| - # revision_data will store information about a revision such as the
|
| - # depot it came from, the webkit/V8 revision at that time,
|
| - # performance timing, build state, etc...
|
| - revision_data = results.revision_data
|
| -
|
| - # revision_list is the list we're binary searching through at the moment.
|
| - revision_list = []
|
| -
|
| - sort_key_ids = 0
|
| -
|
| - for current_revision_id in src_revision_list:
|
| - sort_key_ids += 1
|
| -
|
| - revision_data[current_revision_id] = {
|
| - 'value' : None,
|
| - 'passed' : '?',
|
| - 'depot' : target_depot,
|
| - 'external' : None,
|
| - 'perf_time' : 0,
|
| - 'build_time' : 0,
|
| - 'sort' : sort_key_ids,
|
| - }
|
| - revision_list.append(current_revision_id)
|
| -
|
| - min_revision = 0
|
| - max_revision = len(revision_list) - 1
|
| -
|
| + if revision_list:
|
| self.PrintRevisionsToBisectMessage(revision_list, target_depot)
|
|
|
| if self.opts.output_buildbot_annotations:
|
| @@ -2438,18 +2171,18 @@ class BisectPerformanceMetrics(object):
|
| bisect_utils.OutputAnnotationStepClosed()
|
|
|
| if bad_results[1]:
|
| - results.error = ('An error occurred while building and running '
|
| - 'the \'bad\' reference value. The bisect cannot continue without '
|
| - 'a working \'bad\' revision to start from.\n\nError: %s' %
|
| - bad_results[0])
|
| - return results
|
| + error = ('An error occurred while building and running the \'bad\' '
|
| + 'reference value. The bisect cannot continue without '
|
| + 'a working \'bad\' revision to start from.\n\nError: %s' %
|
| + bad_results[0])
|
| + return BisectResults(error=error)
|
|
|
| if good_results[1]:
|
| - results.error = ('An error occurred while building and running '
|
| - 'the \'good\' reference value. The bisect cannot continue without '
|
| - 'a working \'good\' revision to start from.\n\nError: %s' %
|
| - good_results[0])
|
| - return results
|
| + error = ('An error occurred while building and running the \'good\' '
|
| + 'reference value. The bisect cannot continue without '
|
| + 'a working \'good\' revision to start from.\n\nError: %s' %
|
| + good_results[0])
|
| + return BisectResults(error=error)
|
|
|
| # We need these reference values to determine if later runs should be
|
| # classified as pass or fail.
|
| @@ -2461,7 +2194,7 @@ class BisectPerformanceMetrics(object):
|
| # lower is better).
|
| improvement_dir = self.opts.improvement_direction
|
| if improvement_dir:
|
| - higher_is_better = improvement_dir > 0
|
| + higher_is_better = improvement_dir > 0
|
| if higher_is_better:
|
| message = "Expecting higher values to be better for this metric, "
|
| else:
|
| @@ -2473,49 +2206,55 @@ class BisectPerformanceMetrics(object):
|
| message += "and the metric appears to have decreased. "
|
| if ((higher_is_better and metric_increased) or
|
| (not higher_is_better and not metric_increased)):
|
| - results.error = (message + 'Then, the test results for the ends of '
|
| - 'the given \'good\' - \'bad\' range of revisions '
|
| - 'represent an improvement (and not a regression).')
|
| - return results
|
| + error = (message + 'Then, the test results for the ends of the given '
|
| + '\'good\' - \'bad\' range of revisions represent an '
|
| + 'improvement (and not a regression).')
|
| + return BisectResults(error=error)
|
| print message, "Therefore we continue to bisect."
|
|
|
| + bisect_state = BisectState(target_depot, revision_list)
|
| + revision_states = bisect_state.GetRevisionStates()
|
| +
|
| + min_revision = 0
|
| + max_revision = len(revision_states) - 1
|
| +
|
| # Can just mark the good and bad revisions explicitly here since we
|
| # already know the results.
|
| - bad_revision_data = revision_data[revision_list[0]]
|
| - bad_revision_data['external'] = bad_results[2]
|
| - bad_revision_data['perf_time'] = bad_results[3]
|
| - bad_revision_data['build_time'] = bad_results[4]
|
| - bad_revision_data['passed'] = False
|
| - bad_revision_data['value'] = known_bad_value
|
| -
|
| - good_revision_data = revision_data[revision_list[max_revision]]
|
| - good_revision_data['external'] = good_results[2]
|
| - good_revision_data['perf_time'] = good_results[3]
|
| - good_revision_data['build_time'] = good_results[4]
|
| - good_revision_data['passed'] = True
|
| - good_revision_data['value'] = known_good_value
|
| -
|
| - next_revision_depot = target_depot
|
| + bad_revision_state = revision_states[min_revision]
|
| + bad_revision_state.external = bad_results[2]
|
| + bad_revision_state.perf_time = bad_results[3]
|
| + bad_revision_state.build_time = bad_results[4]
|
| + bad_revision_state.passed = False
|
| + bad_revision_state.value = known_bad_value
|
| +
|
| + good_revision_state = revision_states[max_revision]
|
| + good_revision_state.external = good_results[2]
|
| + good_revision_state.perf_time = good_results[3]
|
| + good_revision_state.build_time = good_results[4]
|
| + good_revision_state.passed = True
|
| + good_revision_state.value = known_good_value
|
| +
|
| + bisect_printer = BisectPrinter(self.opts, self.depot_registry)
|
|
|
| while True:
|
| - if not revision_list:
|
| + if not revision_states:
|
| break
|
|
|
| - min_revision_data = revision_data[revision_list[min_revision]]
|
| - max_revision_data = revision_data[revision_list[max_revision]]
|
| -
|
| if max_revision - min_revision <= 1:
|
| - current_depot = min_revision_data['depot']
|
| - if min_revision_data['passed'] == '?':
|
| + min_revision_state = revision_states[min_revision]
|
| + max_revision_state = revision_states[max_revision]
|
| + current_depot = min_revision_state.depot
|
| + # TODO(sergiyb): Under which conditions can first two branches be hit?
|
| + if min_revision_state.passed == '?':
|
| next_revision_index = min_revision
|
| - elif max_revision_data['passed'] == '?':
|
| + elif max_revision_state.passed == '?':
|
| next_revision_index = max_revision
|
| elif current_depot in ['android-chrome', 'cros', 'chromium', 'v8']:
|
| - previous_revision = revision_list[min_revision]
|
| + previous_revision = revision_states[min_revision].revision
|
| # If there were changes to any of the external libraries we track,
|
| # should bisect the changes there as well.
|
| external_depot = self._FindNextDepotToBisect(
|
| - current_depot, min_revision_data, max_revision_data)
|
| + current_depot, min_revision_state, max_revision_state)
|
| # If there was no change in any of the external depots, the search
|
| # is over.
|
| if not external_depot:
|
| @@ -2527,33 +2266,30 @@ class BisectPerformanceMetrics(object):
|
| 'bleeding_edge.')
|
| break
|
|
|
| - earliest_revision = max_revision_data['external'][external_depot]
|
| - latest_revision = min_revision_data['external'][external_depot]
|
| + earliest_revision = max_revision_state.external[external_depot]
|
| + latest_revision = min_revision_state.external[external_depot]
|
|
|
| new_revision_list = self.PrepareToBisectOnDepot(
|
| - external_depot, latest_revision, earliest_revision,
|
| + external_depot, earliest_revision, latest_revision,
|
| previous_revision)
|
|
|
| if not new_revision_list:
|
| - results.error = ('An error occurred attempting to retrieve '
|
| - 'revision range: [%s..%s]' %
|
| - (earliest_revision, latest_revision))
|
| - return results
|
| -
|
| - _AddRevisionsIntoRevisionData(
|
| - new_revision_list, external_depot, min_revision_data['sort'],
|
| - revision_data)
|
| -
|
| - # Reset the bisection and perform it on the newly inserted
|
| - # changelists.
|
| - revision_list = new_revision_list
|
| + error = ('An error occurred attempting to retrieve revision '
|
| + 'range: [%s..%s]' % (earliest_revision, latest_revision))
|
| + return BisectResults(error=error)
|
| +
|
| + revision_states = bisect_state.CreateRevisionStatesAfter(
|
| + external_depot, new_revision_list, current_depot,
|
| + previous_revision)
|
| +
|
| + # Reset the bisection and perform it on the newly inserted states.
|
| min_revision = 0
|
| - max_revision = len(revision_list) - 1
|
| - sort_key_ids += len(revision_list)
|
| + max_revision = len(revision_states) - 1
|
|
|
| print ('Regression in metric %s appears to be the result of '
|
| 'changes in [%s].' % (metric, external_depot))
|
|
|
| + revision_list = [state.revision for state in revision_states]
|
| self.PrintRevisionsToBisectMessage(revision_list, external_depot)
|
|
|
| continue
|
| @@ -2563,36 +2299,34 @@ class BisectPerformanceMetrics(object):
|
| next_revision_index = (int((max_revision - min_revision) / 2) +
|
| min_revision)
|
|
|
| - next_revision_id = revision_list[next_revision_index]
|
| - next_revision_data = revision_data[next_revision_id]
|
| - next_revision_depot = next_revision_data['depot']
|
| + next_revision_state = revision_states[next_revision_index]
|
| + next_revision = next_revision_state.revision
|
| + next_depot = next_revision_state.depot
|
|
|
| - self.depot_registry.ChangeToDepotDir(next_revision_depot)
|
| + self.depot_registry.ChangeToDepotDir(next_depot)
|
|
|
| + message = 'Working on [%s:%s]' % (next_depot, next_revision)
|
| + print message
|
| if self.opts.output_buildbot_annotations:
|
| - step_name = 'Working on [%s]' % next_revision_id
|
| - bisect_utils.OutputAnnotationStepStart(step_name)
|
| + bisect_utils.OutputAnnotationStepStart(message)
|
|
|
| - print 'Working on revision: [%s]' % next_revision_id
|
| -
|
| - run_results = self.RunTest(
|
| - next_revision_id, next_revision_depot, command_to_run, metric,
|
| - skippable=True)
|
| + run_results = self.RunTest(next_revision, next_depot, command_to_run,
|
| + metric, skippable=True)
|
|
|
| # If the build is successful, check whether or not the metric
|
| # had regressed.
|
| if not run_results[1]:
|
| if len(run_results) > 2:
|
| - next_revision_data['external'] = run_results[2]
|
| - next_revision_data['perf_time'] = run_results[3]
|
| - next_revision_data['build_time'] = run_results[4]
|
| + next_revision_state.external = run_results[2]
|
| + next_revision_state.perf_time = run_results[3]
|
| + next_revision_state.build_time = run_results[4]
|
|
|
| passed_regression = self._CheckIfRunPassed(run_results[0],
|
| known_good_value,
|
| known_bad_value)
|
|
|
| - next_revision_data['passed'] = passed_regression
|
| - next_revision_data['value'] = run_results[0]
|
| + next_revision_state.passed = passed_regression
|
| + next_revision_state.value = run_results[0]
|
|
|
| if passed_regression:
|
| max_revision = next_revision_index
|
| @@ -2600,313 +2334,28 @@ class BisectPerformanceMetrics(object):
|
| min_revision = next_revision_index
|
| else:
|
| if run_results[1] == BUILD_RESULT_SKIPPED:
|
| - next_revision_data['passed'] = 'Skipped'
|
| + next_revision_state.passed = 'Skipped'
|
| elif run_results[1] == BUILD_RESULT_FAIL:
|
| - next_revision_data['passed'] = 'Build Failed'
|
| + next_revision_state.passed = 'Build Failed'
|
|
|
| print run_results[0]
|
|
|
| # If the build is broken, remove it and redo search.
|
| - revision_list.pop(next_revision_index)
|
| + revision_states.pop(next_revision_index)
|
|
|
| max_revision -= 1
|
|
|
| if self.opts.output_buildbot_annotations:
|
| - self._PrintPartialResults(results)
|
| + bisect_printer.PrintPartialResults(bisect_state)
|
| bisect_utils.OutputAnnotationStepClosed()
|
| - else:
|
| - # Weren't able to sync and retrieve the revision range.
|
| - results.error = ('An error occurred attempting to retrieve revision '
|
| - 'range: [%s..%s]' % (good_revision, bad_revision))
|
|
|
| - return results
|
| -
|
| - def _PrintPartialResults(self, results):
|
| - results_dict = results.GetResultsDict()
|
| - self._PrintTestedCommitsTable(results_dict['revision_data_sorted'],
|
| - results_dict['first_working_revision'],
|
| - results_dict['last_broken_revision'],
|
| - 100, final_step=False)
|
| -
|
| - def _ConfidenceLevelStatus(self, results_dict):
|
| - if not results_dict['confidence']:
|
| - return None
|
| - confidence_status = 'Successful with %(level)s confidence%(warning)s.'
|
| - if results_dict['confidence'] >= HIGH_CONFIDENCE:
|
| - level = 'high'
|
| - else:
|
| - level = 'low'
|
| - warning = ' and warnings'
|
| - if not self.warnings:
|
| - warning = ''
|
| - return confidence_status % {'level': level, 'warning': warning}
|
| -
|
| - def _GetViewVCLinkFromDepotAndHash(self, revision_id, depot):
|
| - """Gets link to the repository browser."""
|
| - info = source_control.QueryRevisionInfo(revision_id,
|
| - self.depot_registry.GetDepotDir(depot))
|
| - if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'):
|
| - try:
|
| - # Format is "git-svn-id: svn://....@123456 <other data>"
|
| - svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i]
|
| - svn_revision = svn_line[0].split('@')
|
| - svn_revision = svn_revision[1].split(' ')[0]
|
| - return DEPOT_DEPS_NAME[depot]['viewvc'] + svn_revision
|
| - except IndexError:
|
| - return ''
|
| - return ''
|
| -
|
| - def _PrintRevisionInfo(self, cl, info, depot=None):
|
| - email_info = ''
|
| - if not info['email'].startswith(info['author']):
|
| - email_info = '\nEmail : %s' % info['email']
|
| - commit_link = self._GetViewVCLinkFromDepotAndHash(cl, depot)
|
| - if commit_link:
|
| - commit_info = '\nLink : %s' % commit_link
|
| - else:
|
| - commit_info = ('\nFailed to parse SVN revision from body:\n%s' %
|
| - info['body'])
|
| - print RESULTS_REVISION_INFO % {
|
| - 'subject': info['subject'],
|
| - 'author': info['author'],
|
| - 'email_info': email_info,
|
| - 'commit_info': commit_info,
|
| - 'cl': cl,
|
| - 'cl_date': info['date']
|
| - }
|
| -
|
| - def _PrintTestedCommitsHeader(self):
|
| - if self.opts.bisect_mode == BISECT_MODE_MEAN:
|
| - _PrintTableRow(
|
| - [20, 12, 70, 14, 12, 13],
|
| - ['Depot', 'Position', 'SHA', 'Mean', 'Std. Error', 'State'])
|
| - elif self.opts.bisect_mode == BISECT_MODE_STD_DEV:
|
| - _PrintTableRow(
|
| - [20, 12, 70, 14, 12, 13],
|
| - ['Depot', 'Position', 'SHA', 'Std. Error', 'Mean', 'State'])
|
| - elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
|
| - _PrintTableRow(
|
| - [20, 12, 70, 14, 13],
|
| - ['Depot', 'Position', 'SHA', 'Return Code', 'State'])
|
| - else:
|
| - assert False, 'Invalid bisect_mode specified.'
|
| -
|
| - def _PrintTestedCommitsEntry(self, current_data, commit_position, cl_link,
|
| - state_str):
|
| - if self.opts.bisect_mode == BISECT_MODE_MEAN:
|
| - std_error = '+-%.02f' % current_data['value']['std_err']
|
| - mean = '%.02f' % current_data['value']['mean']
|
| - _PrintTableRow(
|
| - [20, 12, 70, 12, 14, 13],
|
| - [current_data['depot'], commit_position, cl_link, mean, std_error,
|
| - state_str])
|
| - elif self.opts.bisect_mode == BISECT_MODE_STD_DEV:
|
| - std_error = '+-%.02f' % current_data['value']['std_err']
|
| - mean = '%.02f' % current_data['value']['mean']
|
| - _PrintTableRow(
|
| - [20, 12, 70, 12, 14, 13],
|
| - [current_data['depot'], commit_position, cl_link, std_error, mean,
|
| - state_str])
|
| - elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
|
| - mean = '%d' % current_data['value']['mean']
|
| - _PrintTableRow(
|
| - [20, 12, 70, 14, 13],
|
| - [current_data['depot'], commit_position, cl_link, mean,
|
| - state_str])
|
| -
|
| - def _PrintTestedCommitsTable(
|
| - self, revision_data_sorted, first_working_revision, last_broken_revision,
|
| - confidence, final_step=True):
|
| - print
|
| - if final_step:
|
| - print '===== TESTED COMMITS ====='
|
| - else:
|
| - print '===== PARTIAL RESULTS ====='
|
| - self._PrintTestedCommitsHeader()
|
| - state = 0
|
| - for current_id, current_data in revision_data_sorted:
|
| - if current_data['value']:
|
| - if (current_id == last_broken_revision or
|
| - current_id == first_working_revision):
|
| - # If confidence is too low, don't add this empty line since it's
|
| - # used to put focus on a suspected CL.
|
| - if confidence and final_step:
|
| - print
|
| - state += 1
|
| - if state == 2 and not final_step:
|
| - # Just want a separation between "bad" and "good" cl's.
|
| - print
|
| -
|
| - state_str = 'Bad'
|
| - if state == 1 and final_step:
|
| - state_str = 'Suspected CL'
|
| - elif state == 2:
|
| - state_str = 'Good'
|
| -
|
| - # If confidence is too low, don't bother outputting good/bad.
|
| - if not confidence:
|
| - state_str = ''
|
| - state_str = state_str.center(13, ' ')
|
| -
|
| - cl_link = self._GetViewVCLinkFromDepotAndHash(current_id,
|
| - current_data['depot'])
|
| - if not cl_link:
|
| - cl_link = current_id
|
| - commit_position = source_control.GetCommitPosition(
|
| - current_id, self.depot_registry.GetDepotDir(current_data['depot']))
|
| - commit_position = str(commit_position)
|
| - if not commit_position:
|
| - commit_position = ''
|
| - self._PrintTestedCommitsEntry(current_data, commit_position, cl_link,
|
| - state_str)
|
| -
|
| - def _PrintReproSteps(self):
|
| - """Prints out a section of the results explaining how to run the test.
|
| -
|
| - This message includes the command used to run the test.
|
| - """
|
| - command = '$ ' + self.opts.command
|
| - if bisect_utils.IsTelemetryCommand(self.opts.command):
|
| - command += ('\nAlso consider passing --profiler=list to see available '
|
| - 'profilers.')
|
| - print REPRO_STEPS_LOCAL
|
| - if bisect_utils.IsTelemetryCommand(self.opts.command):
|
| - telemetry_command = re.sub(r'--browser=[^\s]+',
|
| - '--browser=<bot-name>',
|
| - command)
|
| - print REPRO_STEPS_TRYJOB_TELEMETRY % {'command': telemetry_command}
|
| + return BisectResults(bisect_state, self.depot_registry, self.opts,
|
| + self.warnings)
|
| else:
|
| - print REPRO_STEPS_TRYJOB
|
| -
|
| - def _PrintOtherRegressions(self, other_regressions, revision_data):
|
| - """Prints a section of the results about other potential regressions."""
|
| - print
|
| - print 'Other regressions may have occurred:'
|
| - print ' %8s %70s %10s' % ('Depot'.center(8, ' '),
|
| - 'Range'.center(70, ' '), 'Confidence'.center(10, ' '))
|
| - for regression in other_regressions:
|
| - current_id, previous_id, confidence = regression
|
| - current_data = revision_data[current_id]
|
| - previous_data = revision_data[previous_id]
|
| -
|
| - current_link = self._GetViewVCLinkFromDepotAndHash(current_id,
|
| - current_data['depot'])
|
| - previous_link = self._GetViewVCLinkFromDepotAndHash(previous_id,
|
| - previous_data['depot'])
|
| -
|
| - # If we can't map it to a viewable URL, at least show the original hash.
|
| - if not current_link:
|
| - current_link = current_id
|
| - if not previous_link:
|
| - previous_link = previous_id
|
| -
|
| - print ' %8s %70s %s' % (
|
| - current_data['depot'], current_link,
|
| - ('%d%%' % confidence).center(10, ' '))
|
| - print ' %8s %70s' % (
|
| - previous_data['depot'], previous_link)
|
| - print
|
| -
|
| - def _CheckForWarnings(self, results_dict):
|
| - if len(results_dict['culprit_revisions']) > 1:
|
| - self.warnings.append('Due to build errors, regression range could '
|
| - 'not be narrowed down to a single commit.')
|
| - if self.opts.repeat_test_count == 1:
|
| - self.warnings.append('Tests were only set to run once. This may '
|
| - 'be insufficient to get meaningful results.')
|
| - if 0 < results_dict['confidence'] < HIGH_CONFIDENCE:
|
| - self.warnings.append('Confidence is not high. Try bisecting again '
|
| - 'with increased repeat_count, larger range, or '
|
| - 'on another metric.')
|
| - if not results_dict['confidence']:
|
| - self.warnings.append('Confidence score is 0%. Try bisecting again on '
|
| - 'another platform or another metric.')
|
| -
|
| - def FormatAndPrintResults(self, bisect_results):
|
| - """Prints the results from a bisection run in a readable format.
|
| -
|
| - Args:
|
| - bisect_results: The results from a bisection test run.
|
| - """
|
| - results_dict = bisect_results.GetResultsDict()
|
| -
|
| - self._CheckForWarnings(results_dict)
|
| -
|
| - if self.opts.output_buildbot_annotations:
|
| - bisect_utils.OutputAnnotationStepStart('Build Status Per Revision')
|
| -
|
| - print
|
| - print 'Full results of bisection:'
|
| - for current_id, current_data in results_dict['revision_data_sorted']:
|
| - build_status = current_data['passed']
|
| -
|
| - if type(build_status) is bool:
|
| - if build_status:
|
| - build_status = 'Good'
|
| - else:
|
| - build_status = 'Bad'
|
| -
|
| - print ' %20s %40s %s' % (current_data['depot'],
|
| - current_id, build_status)
|
| - print
|
| -
|
| - if self.opts.output_buildbot_annotations:
|
| - bisect_utils.OutputAnnotationStepClosed()
|
| - # The perf dashboard scrapes the "results" step in order to comment on
|
| - # bugs. If you change this, please update the perf dashboard as well.
|
| - bisect_utils.OutputAnnotationStepStart('Results')
|
| -
|
| - self._PrintBanner(results_dict)
|
| - self._PrintWarnings()
|
| -
|
| - if results_dict['culprit_revisions'] and results_dict['confidence']:
|
| - for culprit in results_dict['culprit_revisions']:
|
| - cl, info, depot = culprit
|
| - self._PrintRevisionInfo(cl, info, depot)
|
| - if results_dict['other_regressions']:
|
| - self._PrintOtherRegressions(results_dict['other_regressions'],
|
| - results_dict['revision_data'])
|
| - self._PrintTestedCommitsTable(results_dict['revision_data_sorted'],
|
| - results_dict['first_working_revision'],
|
| - results_dict['last_broken_revision'],
|
| - results_dict['confidence'])
|
| - _PrintStepTime(results_dict['revision_data_sorted'])
|
| - self._PrintReproSteps()
|
| - _PrintThankYou()
|
| - if self.opts.output_buildbot_annotations:
|
| - bisect_utils.OutputAnnotationStepClosed()
|
| -
|
| - def _PrintBanner(self, results_dict):
|
| - if self._IsBisectModeReturnCode():
|
| - metrics = 'N/A'
|
| - change = 'Yes'
|
| - else:
|
| - metrics = '/'.join(self.opts.metric)
|
| - change = '%.02f%% (+/-%.02f%%)' % (
|
| - results_dict['regression_size'], results_dict['regression_std_err'])
|
| -
|
| - if results_dict['culprit_revisions'] and results_dict['confidence']:
|
| - status = self._ConfidenceLevelStatus(results_dict)
|
| - else:
|
| - status = 'Failure, could not reproduce.'
|
| - change = 'Bisect could not reproduce a change.'
|
| -
|
| - print RESULTS_BANNER % {
|
| - 'status': status,
|
| - 'command': self.opts.command,
|
| - 'metrics': metrics,
|
| - 'change': change,
|
| - 'confidence': results_dict['confidence'],
|
| - }
|
| -
|
| - def _PrintWarnings(self):
|
| - """Prints a list of warning strings if there are any."""
|
| - if not self.warnings:
|
| - return
|
| - print
|
| - print 'WARNINGS:'
|
| - for w in set(self.warnings):
|
| - print ' ! %s' % w
|
| + # Weren't able to sync and retrieve the revision range.
|
| + error = ('An error occurred attempting to retrieve revision range: '
|
| + '[%s..%s]' % (good_revision, bad_revision))
|
| + return BisectResults(error=error)
|
|
|
|
|
| def _IsPlatformSupported():
|
| @@ -2982,7 +2431,7 @@ class BisectOptions(object):
|
| self.target_build_type = 'Release'
|
| self.builder_host = None
|
| self.builder_port = None
|
| - self.bisect_mode = BISECT_MODE_MEAN
|
| + self.bisect_mode = bisect_utils.BISECT_MODE_MEAN
|
| self.improvement_direction = 0
|
|
|
| @staticmethod
|
| @@ -3047,9 +2496,10 @@ class BisectOptions(object):
|
| 'discarded).')
|
| group.add_option('--bisect_mode',
|
| type='choice',
|
| - choices=[BISECT_MODE_MEAN, BISECT_MODE_STD_DEV,
|
| - BISECT_MODE_RETURN_CODE],
|
| - default=BISECT_MODE_MEAN,
|
| + choices=[bisect_utils.BISECT_MODE_MEAN,
|
| + bisect_utils.BISECT_MODE_STD_DEV,
|
| + bisect_utils.BISECT_MODE_RETURN_CODE],
|
| + default=bisect_utils.BISECT_MODE_MEAN,
|
| help='The bisect mode. Choices are to bisect on the '
|
| 'difference in mean, std_dev, or return_code.')
|
| parser.add_option_group(group)
|
| @@ -3167,7 +2617,8 @@ class BisectOptions(object):
|
| if not opts.bad_revision:
|
| raise RuntimeError('missing required parameter: --bad_revision')
|
|
|
| - if not opts.metric and opts.bisect_mode != BISECT_MODE_RETURN_CODE:
|
| + if (not opts.metric and
|
| + opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE):
|
| raise RuntimeError('missing required parameter: --metric')
|
|
|
| if opts.gs_bucket:
|
| @@ -3194,7 +2645,7 @@ class BisectOptions(object):
|
| if not opts.working_directory:
|
| raise RuntimeError('missing required parameter: --working_directory')
|
|
|
| - if opts.bisect_mode != BISECT_MODE_RETURN_CODE:
|
| + if opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE:
|
| metric_values = opts.metric.split('/')
|
| if len(metric_values) != 2:
|
| raise RuntimeError('Invalid metric specified: [%s]' % opts.metric)
|
| @@ -3230,7 +2681,7 @@ class BisectOptions(object):
|
| assert hasattr(opts, k), 'Invalid %s attribute in BisectOptions.' % k
|
| setattr(opts, k, v)
|
|
|
| - if opts.metric and opts.bisect_mode != BISECT_MODE_RETURN_CODE:
|
| + if opts.metric and opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE:
|
| metric_values = opts.metric.split('/')
|
| if len(metric_values) != 2:
|
| raise RuntimeError('Invalid metric specified: [%s]' % opts.metric)
|
| @@ -3254,7 +2705,7 @@ def main():
|
| extra_src = bisect_utils.LoadExtraSrc(opts.extra_src)
|
| if not extra_src:
|
| raise RuntimeError('Invalid or missing --extra_src.')
|
| - _AddAdditionalDepotInfo(extra_src.GetAdditionalDepotInfo())
|
| + bisect_utils.AddAdditionalDepotInfo(extra_src.GetAdditionalDepotInfo())
|
|
|
| if opts.working_directory:
|
| custom_deps = bisect_utils.DEFAULT_GCLIENT_CUSTOM_DEPS
|
| @@ -3280,14 +2731,13 @@ def main():
|
| not opts.working_directory):
|
| raise RuntimeError('You must switch to master branch to run bisection.')
|
| bisect_test = BisectPerformanceMetrics(opts)
|
| + bisect_printer = BisectPrinter(opts, bisect_test.depot_registry)
|
| try:
|
| - bisect_results = bisect_test.Run(opts.command,
|
| - opts.bad_revision,
|
| - opts.good_revision,
|
| - opts.metric)
|
| - if bisect_results.error:
|
| - raise RuntimeError(bisect_results.error)
|
| - bisect_test.FormatAndPrintResults(bisect_results)
|
| + results = bisect_test.Run(opts.command, opts.bad_revision,
|
| + opts.good_revision, opts.metric)
|
| + if results.error:
|
| + raise RuntimeError(results.error)
|
| + bisect_printer.FormatAndPrintResults(results)
|
| return 0
|
| finally:
|
| bisect_test.PerformCleanup()
|
|
|