Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(281)

Side by Side Diff: tools/bisect-perf-regression.py

Issue 255943002: [bisect] - First pass bisect functional breakages. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Changes from review. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/run-bisect-perf-regression.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Performance Test Bisect Tool 6 """Performance Test Bisect Tool
7 7
8 This script bisects a series of changelists using binary search. It starts at 8 This script bisects a series of changelists using binary search. It starts at
9 a bad revision where a performance metric has regressed, and asks for a last 9 a bad revision where a performance metric has regressed, and asks for a last
10 known-good revision. It will then binary search across this revision range by 10 known-good revision. It will then binary search across this revision range by
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after
170 # once build is produced, it reads SHA value from this file and appends it 170 # once build is produced, it reads SHA value from this file and appends it
171 # to build archive filename. 171 # to build archive filename.
172 DEPS_SHA_PATCH = """diff --git src/DEPS.sha src/DEPS.sha 172 DEPS_SHA_PATCH = """diff --git src/DEPS.sha src/DEPS.sha
173 new file mode 100644 173 new file mode 100644
174 --- /dev/null 174 --- /dev/null
175 +++ src/DEPS.sha 175 +++ src/DEPS.sha
176 @@ -0,0 +1 @@ 176 @@ -0,0 +1 @@
177 +%(deps_sha)s 177 +%(deps_sha)s
178 """ 178 """
179 179
180 # The possible values of the --bisect_mode flag, which determines what to
181 # use when classifying a revision as "good" or "bad".
182 BISECT_MODE_MEAN = 'mean'
183 BISECT_MODE_STD_DEV = 'std_dev'
184 BISECT_MODE_RETURN_CODE = 'return_code'
185
186
180 def _AddAdditionalDepotInfo(depot_info): 187 def _AddAdditionalDepotInfo(depot_info):
181 """Adds additional depot info to the global depot variables.""" 188 """Adds additional depot info to the global depot variables."""
182 global DEPOT_DEPS_NAME 189 global DEPOT_DEPS_NAME
183 global DEPOT_NAMES 190 global DEPOT_NAMES
184 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + 191 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() +
185 depot_info.items()) 192 depot_info.items())
186 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() 193 DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
187 194
188 195
189 def CalculateTruncatedMean(data_set, truncate_percent): 196 def CalculateTruncatedMean(data_set, truncate_percent):
(...skipping 1771 matching lines...) Expand 10 before | Expand all | Expand 10 after
1961 path_to_generate = os.path.join('tools', 'perf', 'generate_profile') 1968 path_to_generate = os.path.join('tools', 'perf', 'generate_profile')
1962 1969
1963 if arg_dict.has_key('--profile-dir') and arg_dict.has_key('--browser'): 1970 if arg_dict.has_key('--profile-dir') and arg_dict.has_key('--browser'):
1964 profile_path, profile_type = os.path.split(arg_dict['--profile-dir']) 1971 profile_path, profile_type = os.path.split(arg_dict['--profile-dir'])
1965 return not RunProcess(['python', path_to_generate, 1972 return not RunProcess(['python', path_to_generate,
1966 '--profile-type-to-generate', profile_type, 1973 '--profile-type-to-generate', profile_type,
1967 '--browser', arg_dict['--browser'], '--output-dir', profile_path]) 1974 '--browser', arg_dict['--browser'], '--output-dir', profile_path])
1968 return False 1975 return False
1969 return True 1976 return True
1970 1977
1978 def _IsBisectModeUsingMetric(self):
1979 return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV]
1980
1981 def _IsBisectModeReturnCode(self):
1982 return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE]
1983
1984 def _IsBisectModeStandardDeviation(self):
1985 return self.opts.bisect_mode in [BISECT_MODE_STD_DEV]
1986
1971 def RunPerformanceTestAndParseResults( 1987 def RunPerformanceTestAndParseResults(
1972 self, command_to_run, metric, reset_on_first_run=False, 1988 self, command_to_run, metric, reset_on_first_run=False,
1973 upload_on_last_run=False, results_label=None): 1989 upload_on_last_run=False, results_label=None):
1974 """Runs a performance test on the current revision and parses the results. 1990 """Runs a performance test on the current revision and parses the results.
1975 1991
1976 Args: 1992 Args:
1977 command_to_run: The command to be run to execute the performance test. 1993 command_to_run: The command to be run to execute the performance test.
1978 metric: The metric to parse out from the results of the performance test. 1994 metric: The metric to parse out from the results of the performance test.
1979 This is the result chart name and trace name, separated by slash. 1995 This is the result chart name and trace name, separated by slash.
1980 reset_on_first_run: If True, pass the flag --reset-results on first run. 1996 reset_on_first_run: If True, pass the flag --reset-results on first run.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2015 if self.opts.target_platform == 'cros' and is_telemetry: 2031 if self.opts.target_platform == 'cros' and is_telemetry:
2016 args.append('--remote=%s' % self.opts.cros_remote_ip) 2032 args.append('--remote=%s' % self.opts.cros_remote_ip)
2017 args.append('--identity=%s' % CROS_TEST_KEY_PATH) 2033 args.append('--identity=%s' % CROS_TEST_KEY_PATH)
2018 2034
2019 start_time = time.time() 2035 start_time = time.time()
2020 2036
2021 metric_values = [] 2037 metric_values = []
2022 output_of_all_runs = '' 2038 output_of_all_runs = ''
2023 for i in xrange(self.opts.repeat_test_count): 2039 for i in xrange(self.opts.repeat_test_count):
2024 # Can ignore the return code since if the tests fail, it won't return 0. 2040 # Can ignore the return code since if the tests fail, it won't return 0.
2041 current_args = copy.copy(args)
2042 if is_telemetry:
2043 if i == 0 and reset_on_first_run:
2044 current_args.append('--reset-results')
2045 elif i == self.opts.repeat_test_count - 1 and upload_on_last_run:
2046 current_args.append('--upload-results')
2047 if results_label:
2048 current_args.append('--results-label=%s' % results_label)
2025 try: 2049 try:
2026 current_args = copy.copy(args)
2027 if is_telemetry:
2028 if i == 0 and reset_on_first_run:
2029 current_args.append('--reset-results')
2030 elif i == self.opts.repeat_test_count - 1 and upload_on_last_run:
2031 current_args.append('--upload-results')
2032 if results_label:
2033 current_args.append('--results-label=%s' % results_label)
2034 (output, return_code) = RunProcessAndRetrieveOutput(current_args, 2050 (output, return_code) = RunProcessAndRetrieveOutput(current_args,
2035 cwd=self.src_cwd) 2051 cwd=self.src_cwd)
2036 except OSError, e: 2052 except OSError, e:
2037 if e.errno == errno.ENOENT: 2053 if e.errno == errno.ENOENT:
2038 err_text = ('Something went wrong running the performance test. ' 2054 err_text = ('Something went wrong running the performance test. '
2039 'Please review the command line:\n\n') 2055 'Please review the command line:\n\n')
2040 if 'src/' in ' '.join(args): 2056 if 'src/' in ' '.join(args):
2041 err_text += ('Check that you haven\'t accidentally specified a ' 2057 err_text += ('Check that you haven\'t accidentally specified a '
2042 'path with src/ in the command.\n\n') 2058 'path with src/ in the command.\n\n')
2043 err_text += ' '.join(args) 2059 err_text += ' '.join(args)
2044 err_text += '\n' 2060 err_text += '\n'
2045 2061
2046 return (err_text, failure_code) 2062 return (err_text, failure_code)
2047 raise 2063 raise
2048 2064
2049 output_of_all_runs += output 2065 output_of_all_runs += output
2050 if self.opts.output_buildbot_annotations: 2066 if self.opts.output_buildbot_annotations:
2051 print output 2067 print output
2052 2068
2053 metric_values += self.ParseMetricValuesFromOutput(metric, output) 2069 if self._IsBisectModeUsingMetric():
2070 metric_values += self.ParseMetricValuesFromOutput(metric, output)
2071 # If we're bisecting on a metric (ie, changes in the mean or
2072 # standard deviation) and no metric values are produced, bail out.
2073 if not metric_values:
2074 break
2075 elif self._IsBisectModeReturnCode():
2076 metric_values.append(return_code)
2054 2077
2055 elapsed_minutes = (time.time() - start_time) / 60.0 2078 elapsed_minutes = (time.time() - start_time) / 60.0
2056 2079 if elapsed_minutes >= self.opts.max_time_minutes:
2057 if elapsed_minutes >= self.opts.max_time_minutes or not metric_values:
2058 break 2080 break
2059 2081
2060 if len(metric_values) == 0: 2082 if len(metric_values) == 0:
2061 err_text = 'Metric %s was not found in the test output.' % metric 2083 err_text = 'Metric %s was not found in the test output.' % metric
2062 # TODO(qyearsley): Consider also getting and displaying a list of metrics 2084 # TODO(qyearsley): Consider also getting and displaying a list of metrics
2063 # that were found in the output here. 2085 # that were found in the output here.
2064 return (err_text, failure_code, output_of_all_runs) 2086 return (err_text, failure_code, output_of_all_runs)
2065 2087
2066 # Need to get the average value if there were multiple values. 2088 # If we're bisecting on return codes, we're really just looking for zero vs
2067 truncated_mean = CalculateTruncatedMean(metric_values, 2089 # non-zero.
2068 self.opts.truncate_percent) 2090 if self._IsBisectModeReturnCode():
2069 standard_err = CalculateStandardError(metric_values) 2091 # If any of the return codes is non-zero, output 1.
2070 standard_dev = CalculateStandardDeviation(metric_values) 2092 overall_return_code = 0 if (
2093 all(current_value == 0 for current_value in metric_values)) else 1
2071 2094
2072 values = { 2095 values = {
2073 'mean': truncated_mean, 2096 'mean': overall_return_code,
2074 'std_err': standard_err, 2097 'std_err': 0.0,
2075 'std_dev': standard_dev, 2098 'std_dev': 0.0,
2076 'values': metric_values, 2099 'values': metric_values,
2077 } 2100 }
2078 2101
2079 print 'Results of performance test: %12f %12f' % ( 2102 print 'Results of performance test: Command returned with %d' % (
2080 truncated_mean, standard_err) 2103 overall_return_code)
2081 print 2104 print
2105 else:
2106 # Need to get the average value if there were multiple values.
2107 truncated_mean = CalculateTruncatedMean(metric_values,
2108 self.opts.truncate_percent)
2109 standard_err = CalculateStandardError(metric_values)
2110 standard_dev = CalculateStandardDeviation(metric_values)
2111
2112 if self._IsBisectModeStandardDeviation():
2113 metric_values = [standard_dev]
2114
2115 values = {
2116 'mean': truncated_mean,
2117 'std_err': standard_err,
2118 'std_dev': standard_dev,
2119 'values': metric_values,
2120 }
2121
2122 print 'Results of performance test: %12f %12f' % (
2123 truncated_mean, standard_err)
2124 print
2082 return (values, success_code, output_of_all_runs) 2125 return (values, success_code, output_of_all_runs)
2083 2126
2084 def FindAllRevisionsToSync(self, revision, depot): 2127 def FindAllRevisionsToSync(self, revision, depot):
2085 """Finds all dependant revisions and depots that need to be synced for a 2128 """Finds all dependant revisions and depots that need to be synced for a
2086 given revision. This is only useful in the git workflow, as an svn depot 2129 given revision. This is only useful in the git workflow, as an svn depot
2087 may be split into multiple mirrors. 2130 may be split into multiple mirrors.
2088 2131
2089 ie. skia is broken up into 3 git mirrors over skia/src, skia/gyp, and 2132 ie. skia is broken up into 3 git mirrors over skia/src, skia/gyp, and
2090 skia/include. To sync skia/src properly, one has to find the proper 2133 skia/include. To sync skia/src properly, one has to find the proper
2091 revisions in skia/gyp and skia/include. 2134 revisions in skia/gyp and skia/include.
(...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after
2332 return results 2375 return results
2333 else: 2376 else:
2334 return ('Failed to build revision: [%s]' % (str(revision, )), 2377 return ('Failed to build revision: [%s]' % (str(revision, )),
2335 BUILD_RESULT_FAIL) 2378 BUILD_RESULT_FAIL)
2336 else: 2379 else:
2337 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) 2380 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL)
2338 else: 2381 else:
2339 return ('Failed to sync revision: [%s]' % (str(revision, )), 2382 return ('Failed to sync revision: [%s]' % (str(revision, )),
2340 BUILD_RESULT_FAIL) 2383 BUILD_RESULT_FAIL)
2341 2384
2342 def CheckIfRunPassed(self, current_value, known_good_value, known_bad_value): 2385 def _CheckIfRunPassed(self, current_value, known_good_value, known_bad_value):
2343 """Given known good and bad values, decide if the current_value passed 2386 """Given known good and bad values, decide if the current_value passed
2344 or failed. 2387 or failed.
2345 2388
2346 Args: 2389 Args:
2347 current_value: The value of the metric being checked. 2390 current_value: The value of the metric being checked.
2348 known_bad_value: The reference value for a "failed" run. 2391 known_bad_value: The reference value for a "failed" run.
2349 known_good_value: The reference value for a "passed" run. 2392 known_good_value: The reference value for a "passed" run.
2350 2393
2351 Returns: 2394 Returns:
2352 True if the current_value is closer to the known_good_value than the 2395 True if the current_value is closer to the known_good_value than the
2353 known_bad_value. 2396 known_bad_value.
2354 """ 2397 """
2355 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) 2398 if self.opts.bisect_mode == BISECT_MODE_STD_DEV:
2356 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) 2399 dist_to_good_value = abs(current_value['std_dev'] -
2400 known_good_value['std_dev'])
2401 dist_to_bad_value = abs(current_value['std_dev'] -
2402 known_bad_value['std_dev'])
2403 else:
2404 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean'])
2405 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean'])
2357 2406
2358 return dist_to_good_value < dist_to_bad_value 2407 return dist_to_good_value < dist_to_bad_value
2359 2408
2360 def _GetDepotDirectory(self, depot_name): 2409 def _GetDepotDirectory(self, depot_name):
2361 if depot_name == 'chromium': 2410 if depot_name == 'chromium':
2362 return self.src_cwd 2411 return self.src_cwd
2363 elif depot_name == 'cros': 2412 elif depot_name == 'cros':
2364 return self.cros_cwd 2413 return self.cros_cwd
2365 elif depot_name in DEPOT_NAMES: 2414 elif depot_name in DEPOT_NAMES:
2366 return self.depot_cwd[depot_name] 2415 return self.depot_cwd[depot_name]
(...skipping 535 matching lines...) Expand 10 before | Expand all | Expand 10 after
2902 metric, skippable=True) 2951 metric, skippable=True)
2903 2952
2904 # If the build is successful, check whether or not the metric 2953 # If the build is successful, check whether or not the metric
2905 # had regressed. 2954 # had regressed.
2906 if not run_results[1]: 2955 if not run_results[1]:
2907 if len(run_results) > 2: 2956 if len(run_results) > 2:
2908 next_revision_data['external'] = run_results[2] 2957 next_revision_data['external'] = run_results[2]
2909 next_revision_data['perf_time'] = run_results[3] 2958 next_revision_data['perf_time'] = run_results[3]
2910 next_revision_data['build_time'] = run_results[4] 2959 next_revision_data['build_time'] = run_results[4]
2911 2960
2912 passed_regression = self.CheckIfRunPassed(run_results[0], 2961 passed_regression = self._CheckIfRunPassed(run_results[0],
2913 known_good_value, 2962 known_good_value,
2914 known_bad_value) 2963 known_bad_value)
2915 2964
2916 next_revision_data['passed'] = passed_regression 2965 next_revision_data['passed'] = passed_regression
2917 next_revision_data['value'] = run_results[0] 2966 next_revision_data['value'] = run_results[0]
2918 2967
2919 if passed_regression: 2968 if passed_regression:
2920 max_revision = next_revision_index 2969 max_revision = next_revision_index
2921 else: 2970 else:
2922 min_revision = next_revision_index 2971 min_revision = next_revision_index
2923 else: 2972 else:
2924 if run_results[1] == BUILD_RESULT_SKIPPED: 2973 if run_results[1] == BUILD_RESULT_SKIPPED:
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2959 # "Confidence in Bisection Results: 100%" to decide whether or not 3008 # "Confidence in Bisection Results: 100%" to decide whether or not
2960 # to cc the author(s). If you change this, please update the perf 3009 # to cc the author(s). If you change this, please update the perf
2961 # dashboard as well. 3010 # dashboard as well.
2962 print 'Confidence in Bisection Results: %d%%' % results_dict['confidence'] 3011 print 'Confidence in Bisection Results: %d%%' % results_dict['confidence']
2963 3012
2964 def _PrintBanner(self, results_dict): 3013 def _PrintBanner(self, results_dict):
2965 print 3014 print
2966 print " __o_\___ Aw Snap! We hit a speed bump!" 3015 print " __o_\___ Aw Snap! We hit a speed bump!"
2967 print "=-O----O-'__.~.___________________________________" 3016 print "=-O----O-'__.~.___________________________________"
2968 print 3017 print
2969 print 'Bisect reproduced a %.02f%% (+-%.02f%%) change in the %s metric.' % ( 3018 if self._IsBisectModeReturnCode():
2970 results_dict['regression_size'], results_dict['regression_std_err'], 3019 print ('Bisect reproduced a change in return codes while running the '
2971 '/'.join(self.opts.metric)) 3020 'performance test.')
3021 else:
3022 print ('Bisect reproduced a %.02f%% (+-%.02f%%) change in the '
3023 '%s metric.' % (results_dict['regression_size'],
3024 results_dict['regression_std_err'], '/'.join(self.opts.metric)))
2972 self._PrintConfidence(results_dict) 3025 self._PrintConfidence(results_dict)
2973 3026
2974 def _PrintFailedBanner(self, results_dict): 3027 def _PrintFailedBanner(self, results_dict):
2975 print 3028 print
2976 print ('Bisect could not reproduce a change in the ' 3029 if self._IsBisectModeReturnCode():
2977 '%s/%s metric.' % (self.opts.metric[0], self.opts.metric[1])) 3030 print 'Bisect could not reproduce a change in the return code.'
3031 else:
3032 print ('Bisect could not reproduce a change in the '
3033 '%s metric.' % '/'.join(self.opts.metric))
2978 print 3034 print
2979 self._PrintConfidence(results_dict)
2980 3035
2981 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): 3036 def _GetViewVCLinkFromDepotAndHash(self, cl, depot):
2982 info = self.source_control.QueryRevisionInfo(cl, 3037 info = self.source_control.QueryRevisionInfo(cl,
2983 self._GetDepotDirectory(depot)) 3038 self._GetDepotDirectory(depot))
2984 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): 3039 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'):
2985 try: 3040 try:
2986 # Format is "git-svn-id: svn://....@123456 <other data>" 3041 # Format is "git-svn-id: svn://....@123456 <other data>"
2987 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] 3042 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i]
2988 svn_revision = svn_line[0].split('@') 3043 svn_revision = svn_line[0].split('@')
2989 svn_revision = svn_revision[1].split(' ')[0] 3044 svn_revision = svn_revision[1].split(' ')[0]
(...skipping 16 matching lines...) Expand all
3006 print 'Link : %s' % commit_link 3061 print 'Link : %s' % commit_link
3007 else: 3062 else:
3008 print 3063 print
3009 print 'Failed to parse svn revision from body:' 3064 print 'Failed to parse svn revision from body:'
3010 print 3065 print
3011 print info['body'] 3066 print info['body']
3012 print 3067 print
3013 print 'Commit : %s' % cl 3068 print 'Commit : %s' % cl
3014 print 'Date : %s' % info['date'] 3069 print 'Date : %s' % info['date']
3015 3070
3071 def _PrintTableRow(self, column_widths, row_data):
3072 assert len(column_widths) == len(row_data)
3073
3074 text = ''
3075 for i in xrange(len(column_widths)):
3076 current_row_data = row_data[i].center(column_widths[i], ' ')
3077 text += ('%%%ds' % column_widths[i]) % current_row_data
3078 print text
3079
3080 def _PrintTestedCommitsHeader(self):
3081 if self.opts.bisect_mode == BISECT_MODE_MEAN:
3082 self._PrintTableRow(
3083 [20, 70, 14, 12, 13],
3084 ['Depot', 'Commit SHA', 'Mean', 'Std. Error', 'State'])
3085 elif self.opts.bisect_mode == BISECT_MODE_STD_DEV:
3086 self._PrintTableRow(
3087 [20, 70, 14, 12, 13],
3088 ['Depot', 'Commit SHA', 'Std. Error', 'Mean', 'State'])
3089 elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
3090 self._PrintTableRow(
3091 [20, 70, 14, 13],
3092 ['Depot', 'Commit SHA', 'Return Code', 'State'])
3093 else:
3094 assert False, "Invalid bisect_mode specified."
3095 print ' %20s %70s %14s %13s' % ('Depot'.center(20, ' '),
3096 'Commit SHA'.center(70, ' '), 'Return Code'.center(14, ' '),
3097 'State'.center(13, ' '))
3098
3099 def _PrintTestedCommitsEntry(self, current_data, cl_link, state_str):
3100 if self.opts.bisect_mode == BISECT_MODE_MEAN:
3101 std_error = '+-%.02f' % current_data['value']['std_err']
3102 mean = '%.02f' % current_data['value']['mean']
3103 self._PrintTableRow(
3104 [20, 70, 12, 14, 13],
3105 [current_data['depot'], cl_link, mean, std_error, state_str])
3106 elif self.opts.bisect_mode == BISECT_MODE_STD_DEV:
3107 std_error = '+-%.02f' % current_data['value']['std_err']
3108 mean = '%.02f' % current_data['value']['mean']
3109 self._PrintTableRow(
3110 [20, 70, 12, 14, 13],
3111 [current_data['depot'], cl_link, std_error, mean, state_str])
3112 elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE:
3113 mean = '%d' % current_data['value']['mean']
3114 self._PrintTableRow(
3115 [20, 70, 14, 13],
3116 [current_data['depot'], cl_link, mean, state_str])
3117
3016 def _PrintTestedCommitsTable(self, revision_data_sorted, 3118 def _PrintTestedCommitsTable(self, revision_data_sorted,
3017 first_working_revision, last_broken_revision, confidence, 3119 first_working_revision, last_broken_revision, confidence,
3018 final_step=True): 3120 final_step=True):
3019 print 3121 print
3020 if final_step: 3122 if final_step:
3021 print 'Tested commits:' 3123 print 'Tested commits:'
3022 else: 3124 else:
3023 print 'Partial results:' 3125 print 'Partial results:'
3024 print ' %20s %70s %12s %14s %13s' % ('Depot'.center(20, ' '), 3126 self._PrintTestedCommitsHeader()
3025 'Commit SHA'.center(70, ' '), 'Mean'.center(12, ' '),
3026 'Std. Error'.center(14, ' '), 'State'.center(13, ' '))
3027 state = 0 3127 state = 0
3028 for current_id, current_data in revision_data_sorted: 3128 for current_id, current_data in revision_data_sorted:
3029 if current_data['value']: 3129 if current_data['value']:
3030 if (current_id == last_broken_revision or 3130 if (current_id == last_broken_revision or
3031 current_id == first_working_revision): 3131 current_id == first_working_revision):
3032 # If confidence is too low, don't add this empty line since it's 3132 # If confidence is too low, don't add this empty line since it's
3033 # used to put focus on a suspected CL. 3133 # used to put focus on a suspected CL.
3034 if confidence and final_step: 3134 if confidence and final_step:
3035 print 3135 print
3036 state += 1 3136 state += 1
3037 if state == 2 and not final_step: 3137 if state == 2 and not final_step:
3038 # Just want a separation between "bad" and "good" cl's. 3138 # Just want a separation between "bad" and "good" cl's.
3039 print 3139 print
3040 3140
3041 state_str = 'Bad' 3141 state_str = 'Bad'
3042 if state == 1 and final_step: 3142 if state == 1 and final_step:
3043 state_str = 'Suspected CL' 3143 state_str = 'Suspected CL'
3044 elif state == 2: 3144 elif state == 2:
3045 state_str = 'Good' 3145 state_str = 'Good'
3046 3146
3047 # If confidence is too low, don't bother outputting good/bad. 3147 # If confidence is too low, don't bother outputting good/bad.
3048 if not confidence: 3148 if not confidence:
3049 state_str = '' 3149 state_str = ''
3050 state_str = state_str.center(13, ' ') 3150 state_str = state_str.center(13, ' ')
3051 3151
3052 std_error = ('+-%.02f' %
3053 current_data['value']['std_err']).center(14, ' ')
3054 mean = ('%.02f' % current_data['value']['mean']).center(12, ' ')
3055 cl_link = self._GetViewVCLinkFromDepotAndHash(current_id, 3152 cl_link = self._GetViewVCLinkFromDepotAndHash(current_id,
3056 current_data['depot']) 3153 current_data['depot'])
3057 if not cl_link: 3154 if not cl_link:
3058 cl_link = current_id 3155 cl_link = current_id
3059 print ' %20s %70s %12s %14s %13s' % ( 3156 self._PrintTestedCommitsEntry(current_data, cl_link, state_str)
3060 current_data['depot'].center(20, ' '), cl_link.center(70, ' '),
3061 mean, std_error, state_str)
3062 3157
3063 def _PrintReproSteps(self): 3158 def _PrintReproSteps(self):
3064 print 3159 print
3065 print 'To reproduce locally:' 3160 print 'To reproduce locally:'
3066 print '$ ' + self.opts.command 3161 print '$ ' + self.opts.command
3067 if bisect_utils.IsTelemetryCommand(self.opts.command): 3162 if bisect_utils.IsTelemetryCommand(self.opts.command):
3068 print 3163 print
3069 print 'Also consider passing --profiler=list to see available profilers.' 3164 print 'Also consider passing --profiler=list to see available profilers.'
3070 3165
3071 def _PrintOtherRegressions(self, other_regressions, revision_data): 3166 def _PrintOtherRegressions(self, other_regressions, revision_data):
(...skipping 354 matching lines...) Expand 10 before | Expand all | Expand 10 after
3426 self.no_custom_deps = False 3521 self.no_custom_deps = False
3427 self.working_directory = None 3522 self.working_directory = None
3428 self.extra_src = None 3523 self.extra_src = None
3429 self.debug_ignore_build = None 3524 self.debug_ignore_build = None
3430 self.debug_ignore_sync = None 3525 self.debug_ignore_sync = None
3431 self.debug_ignore_perf_test = None 3526 self.debug_ignore_perf_test = None
3432 self.gs_bucket = None 3527 self.gs_bucket = None
3433 self.target_arch = 'ia32' 3528 self.target_arch = 'ia32'
3434 self.builder_host = None 3529 self.builder_host = None
3435 self.builder_port = None 3530 self.builder_port = None
3531 self.bisect_mode = BISECT_MODE_MEAN
3436 3532
3437 def _CreateCommandLineParser(self): 3533 def _CreateCommandLineParser(self):
3438 """Creates a parser with bisect options. 3534 """Creates a parser with bisect options.
3439 3535
3440 Returns: 3536 Returns:
3441 An instance of optparse.OptionParser. 3537 An instance of optparse.OptionParser.
3442 """ 3538 """
3443 usage = ('%prog [options] [-- chromium-options]\n' 3539 usage = ('%prog [options] [-- chromium-options]\n'
3444 'Perform binary search on revision history to find a minimal ' 3540 'Perform binary search on revision history to find a minimal '
3445 'range of revisions where a peformance metric regressed.\n') 3541 'range of revisions where a peformance metric regressed.\n')
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
3480 'doesn\'t exceed --max_time_minutes. Values will be ' 3576 'doesn\'t exceed --max_time_minutes. Values will be '
3481 'clamped to range [1, 60].' 3577 'clamped to range [1, 60].'
3482 'Default value is 20.') 3578 'Default value is 20.')
3483 group.add_option('-t', '--truncate_percent', 3579 group.add_option('-t', '--truncate_percent',
3484 type='int', 3580 type='int',
3485 default=25, 3581 default=25,
3486 help='The highest/lowest % are discarded to form a ' 3582 help='The highest/lowest % are discarded to form a '
3487 'truncated mean. Values will be clamped to range [0, ' 3583 'truncated mean. Values will be clamped to range [0, '
3488 '25]. Default value is 25 (highest/lowest 25% will be ' 3584 '25]. Default value is 25 (highest/lowest 25% will be '
3489 'discarded).') 3585 'discarded).')
3586 group.add_option('--bisect_mode',
3587 type='choice',
3588 choices=[BISECT_MODE_MEAN, BISECT_MODE_STD_DEV,
3589 BISECT_MODE_RETURN_CODE],
3590 default=BISECT_MODE_MEAN,
3591 help='The bisect mode. Choices are to bisect on the '
3592 'difference in mean, std_dev, or return_code.')
3490 parser.add_option_group(group) 3593 parser.add_option_group(group)
3491 3594
3492 group = optparse.OptionGroup(parser, 'Build options') 3595 group = optparse.OptionGroup(parser, 'Build options')
3493 group.add_option('-w', '--working_directory', 3596 group.add_option('-w', '--working_directory',
3494 type='str', 3597 type='str',
3495 help='Path to the working directory where the script ' 3598 help='Path to the working directory where the script '
3496 'will do an initial checkout of the chromium depot. The ' 3599 'will do an initial checkout of the chromium depot. The '
3497 'files will be placed in a subdirectory "bisect" under ' 3600 'files will be placed in a subdirectory "bisect" under '
3498 'working_directory and that will be used to perform the ' 3601 'working_directory and that will be used to perform the '
3499 'bisection. This parameter is optional, if it is not ' 3602 'bisection. This parameter is optional, if it is not '
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
3579 try: 3682 try:
3580 if not opts.command: 3683 if not opts.command:
3581 raise RuntimeError('missing required parameter: --command') 3684 raise RuntimeError('missing required parameter: --command')
3582 3685
3583 if not opts.good_revision: 3686 if not opts.good_revision:
3584 raise RuntimeError('missing required parameter: --good_revision') 3687 raise RuntimeError('missing required parameter: --good_revision')
3585 3688
3586 if not opts.bad_revision: 3689 if not opts.bad_revision:
3587 raise RuntimeError('missing required parameter: --bad_revision') 3690 raise RuntimeError('missing required parameter: --bad_revision')
3588 3691
3589 if not opts.metric: 3692 if not opts.metric and opts.bisect_mode != BISECT_MODE_RETURN_CODE:
3590 raise RuntimeError('missing required parameter: --metric') 3693 raise RuntimeError('missing required parameter: --metric')
3591 3694
3592 if opts.gs_bucket: 3695 if opts.gs_bucket:
3593 if not cloud_storage.List(opts.gs_bucket): 3696 if not cloud_storage.List(opts.gs_bucket):
3594 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) 3697 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket)
3595 if not opts.builder_host: 3698 if not opts.builder_host:
3596 raise RuntimeError('Must specify try server hostname, when ' 3699 raise RuntimeError('Must specify try server hostname, when '
3597 'gs_bucket is used: --builder_host') 3700 'gs_bucket is used: --builder_host')
3598 if not opts.builder_port: 3701 if not opts.builder_port:
3599 raise RuntimeError('Must specify try server port number, when ' 3702 raise RuntimeError('Must specify try server port number, when '
3600 'gs_bucket is used: --builder_port') 3703 'gs_bucket is used: --builder_port')
3601 if opts.target_platform == 'cros': 3704 if opts.target_platform == 'cros':
3602 # Run sudo up front to make sure credentials are cached for later. 3705 # Run sudo up front to make sure credentials are cached for later.
3603 print 'Sudo is required to build cros:' 3706 print 'Sudo is required to build cros:'
3604 print 3707 print
3605 RunProcess(['sudo', 'true']) 3708 RunProcess(['sudo', 'true'])
3606 3709
3607 if not opts.cros_board: 3710 if not opts.cros_board:
3608 raise RuntimeError('missing required parameter: --cros_board') 3711 raise RuntimeError('missing required parameter: --cros_board')
3609 3712
3610 if not opts.cros_remote_ip: 3713 if not opts.cros_remote_ip:
3611 raise RuntimeError('missing required parameter: --cros_remote_ip') 3714 raise RuntimeError('missing required parameter: --cros_remote_ip')
3612 3715
3613 if not opts.working_directory: 3716 if not opts.working_directory:
3614 raise RuntimeError('missing required parameter: --working_directory') 3717 raise RuntimeError('missing required parameter: --working_directory')
3615 3718
3616 metric_values = opts.metric.split('/') 3719 metric_values = opts.metric.split('/')
3617 if len(metric_values) != 2: 3720 if (len(metric_values) != 2 and
3721 opts.bisect_mode != BISECT_MODE_RETURN_CODE):
3618 raise RuntimeError("Invalid metric specified: [%s]" % opts.metric) 3722 raise RuntimeError("Invalid metric specified: [%s]" % opts.metric)
3619 3723
3620 opts.metric = metric_values 3724 opts.metric = metric_values
3621 opts.repeat_test_count = min(max(opts.repeat_test_count, 1), 100) 3725 opts.repeat_test_count = min(max(opts.repeat_test_count, 1), 100)
3622 opts.max_time_minutes = min(max(opts.max_time_minutes, 1), 60) 3726 opts.max_time_minutes = min(max(opts.max_time_minutes, 1), 60)
3623 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) 3727 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25)
3624 opts.truncate_percent = opts.truncate_percent / 100.0 3728 opts.truncate_percent = opts.truncate_percent / 100.0
3625 3729
3626 for k, v in opts.__dict__.iteritems(): 3730 for k, v in opts.__dict__.iteritems():
3627 assert hasattr(self, k), "Invalid %s attribute in BisectOptions." % k 3731 assert hasattr(self, k), "Invalid %s attribute in BisectOptions." % k
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
3719 # The perf dashboard scrapes the "results" step in order to comment on 3823 # The perf dashboard scrapes the "results" step in order to comment on
3720 # bugs. If you change this, please update the perf dashboard as well. 3824 # bugs. If you change this, please update the perf dashboard as well.
3721 bisect_utils.OutputAnnotationStepStart('Results') 3825 bisect_utils.OutputAnnotationStepStart('Results')
3722 print 'Error: %s' % e.message 3826 print 'Error: %s' % e.message
3723 if opts.output_buildbot_annotations: 3827 if opts.output_buildbot_annotations:
3724 bisect_utils.OutputAnnotationStepClosed() 3828 bisect_utils.OutputAnnotationStepClosed()
3725 return 1 3829 return 1
3726 3830
3727 if __name__ == '__main__': 3831 if __name__ == '__main__':
3728 sys.exit(main()) 3832 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | tools/run-bisect-perf-regression.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698