Index: tools/bisect-perf-regression.py
|
diff --git a/tools/bisect-perf-regression.py b/tools/bisect-perf-regression.py
|
index 6b98b4b40dcb34b7b2a7ce3f2b3f10b58fad4743..d87d86ac2bdce5b90eb7afafee038d94fa79cec5 100755
|
--- a/tools/bisect-perf-regression.py
|
+++ b/tools/bisect-perf-regression.py
|
@@ -177,6 +177,13 @@ 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'
|
+
|
+
|
def _AddAdditionalDepotInfo(depot_info):
|
"""Adds additional depot info to the global depot variables."""
|
global DEPOT_DEPS_NAME
|
@@ -1968,6 +1975,15 @@ class BisectPerformanceMetrics(object):
|
return False
|
return True
|
|
+ def _IsBisectModeUsingMetric(self):
|
+ return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV]
|
+
|
+ def _IsBisectModeReturnCode(self):
|
+ return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE]
|
+
|
+ def _IsBisectModeStandardDeviation(self):
|
+ return self.opts.bisect_mode in [BISECT_MODE_STD_DEV]
|
+
|
def RunPerformanceTestAndParseResults(
|
self, command_to_run, metric, reset_on_first_run=False,
|
upload_on_last_run=False, results_label=None):
|
@@ -2022,15 +2038,15 @@ class BisectPerformanceMetrics(object):
|
output_of_all_runs = ''
|
for i in xrange(self.opts.repeat_test_count):
|
# Can ignore the return code since if the tests fail, it won't return 0.
|
+ current_args = copy.copy(args)
|
+ if is_telemetry:
|
+ if i == 0 and reset_on_first_run:
|
+ current_args.append('--reset-results')
|
+ elif i == self.opts.repeat_test_count - 1 and upload_on_last_run:
|
+ current_args.append('--upload-results')
|
+ if results_label:
|
+ current_args.append('--results-label=%s' % results_label)
|
try:
|
- current_args = copy.copy(args)
|
- if is_telemetry:
|
- if i == 0 and reset_on_first_run:
|
- current_args.append('--reset-results')
|
- elif i == self.opts.repeat_test_count - 1 and upload_on_last_run:
|
- current_args.append('--upload-results')
|
- if results_label:
|
- current_args.append('--results-label=%s' % results_label)
|
(output, return_code) = RunProcessAndRetrieveOutput(current_args,
|
cwd=self.src_cwd)
|
except OSError, e:
|
@@ -2050,11 +2066,17 @@ class BisectPerformanceMetrics(object):
|
if self.opts.output_buildbot_annotations:
|
print output
|
|
- metric_values += self.ParseMetricValuesFromOutput(metric, output)
|
+ if self._IsBisectModeUsingMetric():
|
+ metric_values += self.ParseMetricValuesFromOutput(metric, output)
|
+ # If we're bisecting on a metric (ie, changes in the mean or
|
+ # standard deviation) and no metric values are produced, bail out.
|
+ if not metric_values:
|
+ break
|
+ elif self._IsBisectModeReturnCode():
|
+ metric_values.append(return_code)
|
|
elapsed_minutes = (time.time() - start_time) / 60.0
|
-
|
- if elapsed_minutes >= self.opts.max_time_minutes or not metric_values:
|
+ if elapsed_minutes >= self.opts.max_time_minutes:
|
break
|
|
if len(metric_values) == 0:
|
@@ -2063,22 +2085,43 @@ class BisectPerformanceMetrics(object):
|
# that were found in the output here.
|
return (err_text, failure_code, output_of_all_runs)
|
|
- # Need to get the average value if there were multiple values.
|
- truncated_mean = CalculateTruncatedMean(metric_values,
|
- self.opts.truncate_percent)
|
- standard_err = CalculateStandardError(metric_values)
|
- standard_dev = CalculateStandardDeviation(metric_values)
|
-
|
- values = {
|
- 'mean': truncated_mean,
|
- 'std_err': standard_err,
|
- 'std_dev': standard_dev,
|
- 'values': metric_values,
|
- }
|
-
|
- print 'Results of performance test: %12f %12f' % (
|
- truncated_mean, standard_err)
|
- print
|
+ # If we're bisecting on return codes, we're really just looking for zero vs
|
+ # non-zero.
|
+ if self._IsBisectModeReturnCode():
|
+ # If any of the return codes is non-zero, output 1.
|
+ overall_return_code = 0 if (
|
+ all(current_value == 0 for current_value in metric_values)) else 1
|
+
|
+ values = {
|
+ 'mean': overall_return_code,
|
+ 'std_err': 0.0,
|
+ 'std_dev': 0.0,
|
+ 'values': metric_values,
|
+ }
|
+
|
+ print 'Results of performance test: Command returned with %d' % (
|
+ overall_return_code)
|
+ print
|
+ else:
|
+ # Need to get the average value if there were multiple values.
|
+ truncated_mean = CalculateTruncatedMean(metric_values,
|
+ self.opts.truncate_percent)
|
+ standard_err = CalculateStandardError(metric_values)
|
+ standard_dev = CalculateStandardDeviation(metric_values)
|
+
|
+ if self._IsBisectModeStandardDeviation():
|
+ metric_values = [standard_dev]
|
+
|
+ values = {
|
+ 'mean': truncated_mean,
|
+ 'std_err': standard_err,
|
+ 'std_dev': standard_dev,
|
+ 'values': metric_values,
|
+ }
|
+
|
+ print 'Results of performance test: %12f %12f' % (
|
+ truncated_mean, standard_err)
|
+ print
|
return (values, success_code, output_of_all_runs)
|
|
def FindAllRevisionsToSync(self, revision, depot):
|
@@ -2339,7 +2382,7 @@ class BisectPerformanceMetrics(object):
|
return ('Failed to sync revision: [%s]' % (str(revision, )),
|
BUILD_RESULT_FAIL)
|
|
- def CheckIfRunPassed(self, current_value, known_good_value, known_bad_value):
|
+ def _CheckIfRunPassed(self, current_value, known_good_value, known_bad_value):
|
"""Given known good and bad values, decide if the current_value passed
|
or failed.
|
|
@@ -2352,8 +2395,14 @@ class BisectPerformanceMetrics(object):
|
True if the current_value is closer to the known_good_value than the
|
known_bad_value.
|
"""
|
- dist_to_good_value = abs(current_value['mean'] - known_good_value['mean'])
|
- dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean'])
|
+ if self.opts.bisect_mode == 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'] -
|
+ known_bad_value['std_dev'])
|
+ else:
|
+ dist_to_good_value = abs(current_value['mean'] - known_good_value['mean'])
|
+ dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean'])
|
|
return dist_to_good_value < dist_to_bad_value
|
|
@@ -2909,9 +2958,9 @@ class BisectPerformanceMetrics(object):
|
next_revision_data['perf_time'] = run_results[3]
|
next_revision_data['build_time'] = run_results[4]
|
|
- passed_regression = self.CheckIfRunPassed(run_results[0],
|
- known_good_value,
|
- known_bad_value)
|
+ 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]
|
@@ -2966,17 +3015,23 @@ class BisectPerformanceMetrics(object):
|
print " __o_\___ Aw Snap! We hit a speed bump!"
|
print "=-O----O-'__.~.___________________________________"
|
print
|
- print 'Bisect reproduced a %.02f%% (+-%.02f%%) change in the %s metric.' % (
|
- results_dict['regression_size'], results_dict['regression_std_err'],
|
- '/'.join(self.opts.metric))
|
+ if self._IsBisectModeReturnCode():
|
+ print ('Bisect reproduced a change in return codes while running the '
|
+ 'performance test.')
|
+ else:
|
+ print ('Bisect reproduced a %.02f%% (+-%.02f%%) change in the '
|
+ '%s metric.' % (results_dict['regression_size'],
|
+ results_dict['regression_std_err'], '/'.join(self.opts.metric)))
|
self._PrintConfidence(results_dict)
|
|
def _PrintFailedBanner(self, results_dict):
|
print
|
- print ('Bisect could not reproduce a change in the '
|
- '%s/%s metric.' % (self.opts.metric[0], self.opts.metric[1]))
|
+ if self._IsBisectModeReturnCode():
|
+ print 'Bisect could not reproduce a change in the return code.'
|
+ else:
|
+ print ('Bisect could not reproduce a change in the '
|
+ '%s metric.' % '/'.join(self.opts.metric))
|
print
|
- self._PrintConfidence(results_dict)
|
|
def _GetViewVCLinkFromDepotAndHash(self, cl, depot):
|
info = self.source_control.QueryRevisionInfo(cl,
|
@@ -3013,6 +3068,53 @@ class BisectPerformanceMetrics(object):
|
print 'Commit : %s' % cl
|
print 'Date : %s' % info['date']
|
|
+ def _PrintTableRow(self, column_widths, row_data):
|
+ 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 _PrintTestedCommitsHeader(self):
|
+ if self.opts.bisect_mode == BISECT_MODE_MEAN:
|
+ self._PrintTableRow(
|
+ [20, 70, 14, 12, 13],
|
+ ['Depot', 'Commit SHA', 'Mean', 'Std. Error', 'State'])
|
+ elif self.opts.bisect_mode == BISECT_MODE_STD_DEV:
|
+ self._PrintTableRow(
|
+ [20, 70, 14, 12, 13],
|
+ ['Depot', 'Commit SHA', 'Std. Error', 'Mean', 'State'])
|
+ elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
|
+ self._PrintTableRow(
|
+ [20, 70, 14, 13],
|
+ ['Depot', 'Commit SHA', 'Return Code', 'State'])
|
+ else:
|
+ assert False, "Invalid bisect_mode specified."
|
+ print ' %20s %70s %14s %13s' % ('Depot'.center(20, ' '),
|
+ 'Commit SHA'.center(70, ' '), 'Return Code'.center(14, ' '),
|
+ 'State'.center(13, ' '))
|
+
|
+ def _PrintTestedCommitsEntry(self, current_data, 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']
|
+ self._PrintTableRow(
|
+ [20, 70, 12, 14, 13],
|
+ [current_data['depot'], 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']
|
+ self._PrintTableRow(
|
+ [20, 70, 12, 14, 13],
|
+ [current_data['depot'], cl_link, std_error, mean, state_str])
|
+ elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
|
+ mean = '%d' % current_data['value']['mean']
|
+ self._PrintTableRow(
|
+ [20, 70, 14, 13],
|
+ [current_data['depot'], cl_link, mean, state_str])
|
+
|
def _PrintTestedCommitsTable(self, revision_data_sorted,
|
first_working_revision, last_broken_revision, confidence,
|
final_step=True):
|
@@ -3021,9 +3123,7 @@ class BisectPerformanceMetrics(object):
|
print 'Tested commits:'
|
else:
|
print 'Partial results:'
|
- print ' %20s %70s %12s %14s %13s' % ('Depot'.center(20, ' '),
|
- 'Commit SHA'.center(70, ' '), 'Mean'.center(12, ' '),
|
- 'Std. Error'.center(14, ' '), 'State'.center(13, ' '))
|
+ self._PrintTestedCommitsHeader()
|
state = 0
|
for current_id, current_data in revision_data_sorted:
|
if current_data['value']:
|
@@ -3049,16 +3149,11 @@ class BisectPerformanceMetrics(object):
|
state_str = ''
|
state_str = state_str.center(13, ' ')
|
|
- std_error = ('+-%.02f' %
|
- current_data['value']['std_err']).center(14, ' ')
|
- mean = ('%.02f' % current_data['value']['mean']).center(12, ' ')
|
cl_link = self._GetViewVCLinkFromDepotAndHash(current_id,
|
current_data['depot'])
|
if not cl_link:
|
cl_link = current_id
|
- print ' %20s %70s %12s %14s %13s' % (
|
- current_data['depot'].center(20, ' '), cl_link.center(70, ' '),
|
- mean, std_error, state_str)
|
+ self._PrintTestedCommitsEntry(current_data, cl_link, state_str)
|
|
def _PrintReproSteps(self):
|
print
|
@@ -3433,6 +3528,7 @@ class BisectOptions(object):
|
self.target_arch = 'ia32'
|
self.builder_host = None
|
self.builder_port = None
|
+ self.bisect_mode = BISECT_MODE_MEAN
|
|
def _CreateCommandLineParser(self):
|
"""Creates a parser with bisect options.
|
@@ -3487,6 +3583,13 @@ class BisectOptions(object):
|
'truncated mean. Values will be clamped to range [0, '
|
'25]. Default value is 25 (highest/lowest 25% will be '
|
'discarded).')
|
+ group.add_option('--bisect_mode',
|
+ type='choice',
|
+ choices=[BISECT_MODE_MEAN, BISECT_MODE_STD_DEV,
|
+ BISECT_MODE_RETURN_CODE],
|
+ default=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)
|
|
group = optparse.OptionGroup(parser, 'Build options')
|
@@ -3586,7 +3689,7 @@ class BisectOptions(object):
|
if not opts.bad_revision:
|
raise RuntimeError('missing required parameter: --bad_revision')
|
|
- if not opts.metric:
|
+ if not opts.metric and opts.bisect_mode != BISECT_MODE_RETURN_CODE:
|
raise RuntimeError('missing required parameter: --metric')
|
|
if opts.gs_bucket:
|
@@ -3614,7 +3717,8 @@ class BisectOptions(object):
|
raise RuntimeError('missing required parameter: --working_directory')
|
|
metric_values = opts.metric.split('/')
|
- if len(metric_values) != 2:
|
+ if (len(metric_values) != 2 and
|
+ opts.bisect_mode != BISECT_MODE_RETURN_CODE):
|
raise RuntimeError("Invalid metric specified: [%s]" % opts.metric)
|
|
opts.metric = metric_values
|
|