OLD | NEW |
---|---|
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 Loading... | |
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 BISECT_MODE_MEAN = 'mean' | |
181 BISECT_MODE_STD_DEV = 'std_dev' | |
182 BISECT_MODE_RETURN_CODE = 'return_code' | |
183 | |
184 | |
180 def _AddAdditionalDepotInfo(depot_info): | 185 def _AddAdditionalDepotInfo(depot_info): |
181 """Adds additional depot info to the global depot variables.""" | 186 """Adds additional depot info to the global depot variables.""" |
182 global DEPOT_DEPS_NAME | 187 global DEPOT_DEPS_NAME |
183 global DEPOT_NAMES | 188 global DEPOT_NAMES |
184 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + | 189 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + |
185 depot_info.items()) | 190 depot_info.items()) |
186 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 191 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
187 | 192 |
188 | 193 |
189 def CalculateTruncatedMean(data_set, truncate_percent): | 194 def CalculateTruncatedMean(data_set, truncate_percent): |
(...skipping 1771 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1961 path_to_generate = os.path.join('tools', 'perf', 'generate_profile') | 1966 path_to_generate = os.path.join('tools', 'perf', 'generate_profile') |
1962 | 1967 |
1963 if arg_dict.has_key('--profile-dir') and arg_dict.has_key('--browser'): | 1968 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']) | 1969 profile_path, profile_type = os.path.split(arg_dict['--profile-dir']) |
1965 return not RunProcess(['python', path_to_generate, | 1970 return not RunProcess(['python', path_to_generate, |
1966 '--profile-type-to-generate', profile_type, | 1971 '--profile-type-to-generate', profile_type, |
1967 '--browser', arg_dict['--browser'], '--output-dir', profile_path]) | 1972 '--browser', arg_dict['--browser'], '--output-dir', profile_path]) |
1968 return False | 1973 return False |
1969 return True | 1974 return True |
1970 | 1975 |
1976 def _IsBisectModeUsingMetric(self): | |
1977 return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV] | |
1978 | |
1979 def _IsBisectModeReturnCode(self): | |
1980 return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE] | |
1981 | |
1982 def _IsBisectModeStandardDeviation(self): | |
1983 return self.opts.bisect_mode in [BISECT_MODE_STD_DEV] | |
1984 | |
1971 def RunPerformanceTestAndParseResults( | 1985 def RunPerformanceTestAndParseResults( |
1972 self, command_to_run, metric, reset_on_first_run=False, | 1986 self, command_to_run, metric, reset_on_first_run=False, |
1973 upload_on_last_run=False, results_label=None): | 1987 upload_on_last_run=False, results_label=None): |
1974 """Runs a performance test on the current revision and parses the results. | 1988 """Runs a performance test on the current revision and parses the results. |
1975 | 1989 |
1976 Args: | 1990 Args: |
1977 command_to_run: The command to be run to execute the performance test. | 1991 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. | 1992 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. | 1993 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. | 1994 reset_on_first_run: If True, pass the flag --reset-results on first run. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2026 current_args = copy.copy(args) | 2040 current_args = copy.copy(args) |
2027 if is_telemetry: | 2041 if is_telemetry: |
2028 if i == 0 and reset_on_first_run: | 2042 if i == 0 and reset_on_first_run: |
2029 current_args.append('--reset-results') | 2043 current_args.append('--reset-results') |
2030 elif i == self.opts.repeat_test_count - 1 and upload_on_last_run: | 2044 elif i == self.opts.repeat_test_count - 1 and upload_on_last_run: |
2031 current_args.append('--upload-results') | 2045 current_args.append('--upload-results') |
2032 if results_label: | 2046 if results_label: |
2033 current_args.append('--results-label=%s' % results_label) | 2047 current_args.append('--results-label=%s' % results_label) |
2034 (output, return_code) = RunProcessAndRetrieveOutput(current_args, | 2048 (output, return_code) = RunProcessAndRetrieveOutput(current_args, |
2035 cwd=self.src_cwd) | 2049 cwd=self.src_cwd) |
2050 output_of_all_runs += output | |
tonyg
2014/04/25 20:38:42
I'm not following why this block moved into the tr
shatch
2014/04/25 22:46:03
Oops, leftover from earlier version. Also moved th
| |
2051 if self.opts.output_buildbot_annotations: | |
2052 print output | |
2053 | |
2054 if self._IsBisectModeUsingMetric(): | |
2055 metric_values += self.ParseMetricValuesFromOutput(metric, output) | |
2056 # If we're bisecting on a metric (ie, changes in the mean or | |
2057 # standard deviation) and no metric values are produced, bail out. | |
2058 if not metric_values: | |
2059 break | |
2060 elif self._IsBisectModeReturnCode(): | |
2061 metric_values.append(return_code) | |
2062 | |
2063 elapsed_minutes = (time.time() - start_time) / 60.0 | |
2064 if elapsed_minutes >= self.opts.max_time_minutes: | |
2065 break | |
2036 except OSError, e: | 2066 except OSError, e: |
2037 if e.errno == errno.ENOENT: | 2067 if e.errno == errno.ENOENT: |
2038 err_text = ('Something went wrong running the performance test. ' | 2068 err_text = ('Something went wrong running the performance test. ' |
2039 'Please review the command line:\n\n') | 2069 'Please review the command line:\n\n') |
2040 if 'src/' in ' '.join(args): | 2070 if 'src/' in ' '.join(args): |
2041 err_text += ('Check that you haven\'t accidentally specified a ' | 2071 err_text += ('Check that you haven\'t accidentally specified a ' |
2042 'path with src/ in the command.\n\n') | 2072 'path with src/ in the command.\n\n') |
2043 err_text += ' '.join(args) | 2073 err_text += ' '.join(args) |
2044 err_text += '\n' | 2074 err_text += '\n' |
2045 | 2075 |
2046 return (err_text, failure_code) | 2076 return (err_text, failure_code) |
2047 raise | 2077 raise |
2048 | 2078 |
2049 output_of_all_runs += output | |
2050 if self.opts.output_buildbot_annotations: | |
2051 print output | |
2052 | |
2053 metric_values += self.ParseMetricValuesFromOutput(metric, output) | |
2054 | |
2055 elapsed_minutes = (time.time() - start_time) / 60.0 | |
2056 | |
2057 if elapsed_minutes >= self.opts.max_time_minutes or not metric_values: | |
2058 break | |
2059 | |
2060 if len(metric_values) == 0: | 2079 if len(metric_values) == 0: |
2061 err_text = 'Metric %s was not found in the test output.' % metric | 2080 err_text = 'Metric %s was not found in the test output.' % metric |
2062 # TODO(qyearsley): Consider also getting and displaying a list of metrics | 2081 # TODO(qyearsley): Consider also getting and displaying a list of metrics |
2063 # that were found in the output here. | 2082 # that were found in the output here. |
2064 return (err_text, failure_code, output_of_all_runs) | 2083 return (err_text, failure_code, output_of_all_runs) |
2065 | 2084 |
2066 # Need to get the average value if there were multiple values. | 2085 # If we're bisecting on return codes, we're really just looking for zero vs |
2067 truncated_mean = CalculateTruncatedMean(metric_values, | 2086 # non-zero. |
2068 self.opts.truncate_percent) | 2087 if self._IsBisectModeReturnCode(): |
2069 standard_err = CalculateStandardError(metric_values) | 2088 # If any of the return codes is non-zero, output 1. |
2070 standard_dev = CalculateStandardDeviation(metric_values) | 2089 overall_return_code = 0 if ( |
2090 all(current_value == 0 for current_value in metric_values)) else 1 | |
2071 | 2091 |
2072 values = { | 2092 values = { |
2073 'mean': truncated_mean, | 2093 'mean': overall_return_code, |
2074 'std_err': standard_err, | 2094 'std_err': 0.0, |
2075 'std_dev': standard_dev, | 2095 'std_dev': 0.0, |
2076 'values': metric_values, | 2096 'values': metric_values, |
2077 } | 2097 } |
2078 | 2098 |
2079 print 'Results of performance test: %12f %12f' % ( | 2099 print 'Results of performance test: Command returned with %d' % ( |
2080 truncated_mean, standard_err) | 2100 overall_return_code) |
2081 print | 2101 print |
2102 else: | |
2103 # Need to get the average value if there were multiple values. | |
2104 truncated_mean = CalculateTruncatedMean(metric_values, | |
2105 self.opts.truncate_percent) | |
2106 standard_err = CalculateStandardError(metric_values) | |
2107 standard_dev = CalculateStandardDeviation(metric_values) | |
2108 | |
2109 if self._IsBisectModeStandardDeviation(): | |
2110 metric_values = [standard_dev] | |
2111 | |
2112 values = { | |
2113 'mean': truncated_mean, | |
2114 'std_err': standard_err, | |
2115 'std_dev': standard_dev, | |
2116 'values': metric_values, | |
2117 } | |
2118 | |
2119 print 'Results of performance test: %12f %12f' % ( | |
2120 truncated_mean, standard_err) | |
2121 print | |
2082 return (values, success_code, output_of_all_runs) | 2122 return (values, success_code, output_of_all_runs) |
2083 | 2123 |
2084 def FindAllRevisionsToSync(self, revision, depot): | 2124 def FindAllRevisionsToSync(self, revision, depot): |
2085 """Finds all dependant revisions and depots that need to be synced for a | 2125 """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 | 2126 given revision. This is only useful in the git workflow, as an svn depot |
2087 may be split into multiple mirrors. | 2127 may be split into multiple mirrors. |
2088 | 2128 |
2089 ie. skia is broken up into 3 git mirrors over skia/src, skia/gyp, and | 2129 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 | 2130 skia/include. To sync skia/src properly, one has to find the proper |
2091 revisions in skia/gyp and skia/include. | 2131 revisions in skia/gyp and skia/include. |
(...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2332 return results | 2372 return results |
2333 else: | 2373 else: |
2334 return ('Failed to build revision: [%s]' % (str(revision, )), | 2374 return ('Failed to build revision: [%s]' % (str(revision, )), |
2335 BUILD_RESULT_FAIL) | 2375 BUILD_RESULT_FAIL) |
2336 else: | 2376 else: |
2337 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) | 2377 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) |
2338 else: | 2378 else: |
2339 return ('Failed to sync revision: [%s]' % (str(revision, )), | 2379 return ('Failed to sync revision: [%s]' % (str(revision, )), |
2340 BUILD_RESULT_FAIL) | 2380 BUILD_RESULT_FAIL) |
2341 | 2381 |
2342 def CheckIfRunPassed(self, current_value, known_good_value, known_bad_value): | 2382 def _CheckIfRunPassed(self, current_value, known_good_value, known_bad_value): |
2343 """Given known good and bad values, decide if the current_value passed | 2383 """Given known good and bad values, decide if the current_value passed |
2344 or failed. | 2384 or failed. |
2345 | 2385 |
2346 Args: | 2386 Args: |
2347 current_value: The value of the metric being checked. | 2387 current_value: The value of the metric being checked. |
2348 known_bad_value: The reference value for a "failed" run. | 2388 known_bad_value: The reference value for a "failed" run. |
2349 known_good_value: The reference value for a "passed" run. | 2389 known_good_value: The reference value for a "passed" run. |
2350 | 2390 |
2351 Returns: | 2391 Returns: |
2352 True if the current_value is closer to the known_good_value than the | 2392 True if the current_value is closer to the known_good_value than the |
2353 known_bad_value. | 2393 known_bad_value. |
2354 """ | 2394 """ |
2355 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) | 2395 if self.opts.bisect_mode == BISECT_MODE_STD_DEV: |
2356 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) | 2396 dist_to_good_value = abs(current_value['std_dev'] - |
2397 known_good_value['std_dev']) | |
2398 dist_to_bad_value = abs(current_value['std_dev'] - | |
2399 known_bad_value['std_dev']) | |
2400 else: | |
2401 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) | |
2402 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) | |
2357 | 2403 |
2358 return dist_to_good_value < dist_to_bad_value | 2404 return dist_to_good_value < dist_to_bad_value |
2359 | 2405 |
2360 def _GetDepotDirectory(self, depot_name): | 2406 def _GetDepotDirectory(self, depot_name): |
2361 if depot_name == 'chromium': | 2407 if depot_name == 'chromium': |
2362 return self.src_cwd | 2408 return self.src_cwd |
2363 elif depot_name == 'cros': | 2409 elif depot_name == 'cros': |
2364 return self.cros_cwd | 2410 return self.cros_cwd |
2365 elif depot_name in DEPOT_NAMES: | 2411 elif depot_name in DEPOT_NAMES: |
2366 return self.depot_cwd[depot_name] | 2412 return self.depot_cwd[depot_name] |
(...skipping 535 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2902 metric, skippable=True) | 2948 metric, skippable=True) |
2903 | 2949 |
2904 # If the build is successful, check whether or not the metric | 2950 # If the build is successful, check whether or not the metric |
2905 # had regressed. | 2951 # had regressed. |
2906 if not run_results[1]: | 2952 if not run_results[1]: |
2907 if len(run_results) > 2: | 2953 if len(run_results) > 2: |
2908 next_revision_data['external'] = run_results[2] | 2954 next_revision_data['external'] = run_results[2] |
2909 next_revision_data['perf_time'] = run_results[3] | 2955 next_revision_data['perf_time'] = run_results[3] |
2910 next_revision_data['build_time'] = run_results[4] | 2956 next_revision_data['build_time'] = run_results[4] |
2911 | 2957 |
2912 passed_regression = self.CheckIfRunPassed(run_results[0], | 2958 passed_regression = self._CheckIfRunPassed(run_results[0], |
2913 known_good_value, | 2959 known_good_value, |
2914 known_bad_value) | 2960 known_bad_value) |
2915 | 2961 |
2916 next_revision_data['passed'] = passed_regression | 2962 next_revision_data['passed'] = passed_regression |
2917 next_revision_data['value'] = run_results[0] | 2963 next_revision_data['value'] = run_results[0] |
2918 | 2964 |
2919 if passed_regression: | 2965 if passed_regression: |
2920 max_revision = next_revision_index | 2966 max_revision = next_revision_index |
2921 else: | 2967 else: |
2922 min_revision = next_revision_index | 2968 min_revision = next_revision_index |
2923 else: | 2969 else: |
2924 if run_results[1] == BUILD_RESULT_SKIPPED: | 2970 if run_results[1] == BUILD_RESULT_SKIPPED: |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2959 # "Confidence in Bisection Results: 100%" to decide whether or not | 3005 # "Confidence in Bisection Results: 100%" to decide whether or not |
2960 # to cc the author(s). If you change this, please update the perf | 3006 # to cc the author(s). If you change this, please update the perf |
2961 # dashboard as well. | 3007 # dashboard as well. |
2962 print 'Confidence in Bisection Results: %d%%' % results_dict['confidence'] | 3008 print 'Confidence in Bisection Results: %d%%' % results_dict['confidence'] |
2963 | 3009 |
2964 def _PrintBanner(self, results_dict): | 3010 def _PrintBanner(self, results_dict): |
2965 print | 3011 print |
2966 print " __o_\___ Aw Snap! We hit a speed bump!" | 3012 print " __o_\___ Aw Snap! We hit a speed bump!" |
2967 print "=-O----O-'__.~.___________________________________" | 3013 print "=-O----O-'__.~.___________________________________" |
2968 print | 3014 print |
2969 print 'Bisect reproduced a %.02f%% (+-%.02f%%) change in the %s metric.' % ( | 3015 if self._IsBisectModeReturnCode(): |
2970 results_dict['regression_size'], results_dict['regression_std_err'], | 3016 print ('Bisect reproduced a change in return codes while running the ' |
2971 '/'.join(self.opts.metric)) | 3017 'performance test.') |
3018 else: | |
3019 print ('Bisect reproduced a %.02f%% (+-%.02f%%) change in the ' | |
3020 '%s metric.' % (results_dict['regression_size'], | |
3021 results_dict['regression_std_err'], '/'.join(self.opts.metric))) | |
2972 self._PrintConfidence(results_dict) | 3022 self._PrintConfidence(results_dict) |
2973 | 3023 |
2974 def _PrintFailedBanner(self, results_dict): | 3024 def _PrintFailedBanner(self, results_dict): |
2975 print | 3025 print |
2976 print ('Bisect could not reproduce a change in the ' | 3026 if self._IsBisectModeReturnCode(): |
2977 '%s/%s metric.' % (self.opts.metric[0], self.opts.metric[1])) | 3027 print 'Bisect could not reproduce a change in the return code.' |
3028 else: | |
3029 print ('Bisect could not reproduce a change in the ' | |
3030 '%s metric.' % '/'.join(self.opts.metric)) | |
2978 print | 3031 print |
2979 self._PrintConfidence(results_dict) | 3032 self._PrintConfidence(results_dict) |
tonyg
2014/04/25 20:38:42
Looking at the output in the CL description, it se
shatch
2014/04/25 22:46:03
Done.
| |
2980 | 3033 |
2981 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): | 3034 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): |
2982 info = self.source_control.QueryRevisionInfo(cl, | 3035 info = self.source_control.QueryRevisionInfo(cl, |
2983 self._GetDepotDirectory(depot)) | 3036 self._GetDepotDirectory(depot)) |
2984 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): | 3037 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): |
2985 try: | 3038 try: |
2986 # Format is "git-svn-id: svn://....@123456 <other data>" | 3039 # 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] | 3040 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] |
2988 svn_revision = svn_line[0].split('@') | 3041 svn_revision = svn_line[0].split('@') |
2989 svn_revision = svn_revision[1].split(' ')[0] | 3042 svn_revision = svn_revision[1].split(' ')[0] |
(...skipping 16 matching lines...) Expand all Loading... | |
3006 print 'Link : %s' % commit_link | 3059 print 'Link : %s' % commit_link |
3007 else: | 3060 else: |
3008 print | 3061 print |
3009 print 'Failed to parse svn revision from body:' | 3062 print 'Failed to parse svn revision from body:' |
3010 print | 3063 print |
3011 print info['body'] | 3064 print info['body'] |
3012 print | 3065 print |
3013 print 'Commit : %s' % cl | 3066 print 'Commit : %s' % cl |
3014 print 'Date : %s' % info['date'] | 3067 print 'Date : %s' % info['date'] |
3015 | 3068 |
3069 def _PrintTestedCommitsHeader(self): | |
tonyg
2014/04/25 20:38:42
These are now fairly hard to read and a little red
shatch
2014/04/25 22:46:03
Yeah, looks a lot more readable this way.
| |
3070 if self.opts.bisect_mode == BISECT_MODE_MEAN: | |
3071 print ' %20s %70s %12s %14s %13s' % ('Depot'.center(20, ' '), | |
3072 'Commit SHA'.center(70, ' '), 'Mean'.center(12, ' '), | |
3073 'Std. Error'.center(14, ' '), 'State'.center(13, ' ')) | |
3074 elif self.opts.bisect_mode == BISECT_MODE_STD_DEV: | |
3075 print ' %20s %70s %14s %12s %13s' % ('Depot'.center(20, ' '), | |
3076 'Commit SHA'.center(70, ' '), 'Std. Dev'.center(14, ' '), | |
3077 'Mean'.center(12, ' '), 'State'.center(13, ' ')) | |
3078 elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE: | |
3079 print ' %20s %70s %14s %13s' % ('Depot'.center(20, ' '), | |
3080 'Commit SHA'.center(70, ' '), 'Return Code'.center(14, ' '), | |
3081 'State'.center(13, ' ')) | |
3082 | |
3083 def _PrintTestedCommitsEntry(self, current_data, cl_link, state_str): | |
3084 if self.opts.bisect_mode == BISECT_MODE_MEAN: | |
3085 std_error = ('+-%.02f' % | |
3086 current_data['value']['std_err']).center(14, ' ') | |
3087 mean = ('%.02f' % current_data['value']['mean']).center(12, ' ') | |
3088 print ' %20s %70s %12s %14s %13s' % ( | |
3089 current_data['depot'].center(20, ' '), cl_link.center(70, ' '), | |
3090 mean, std_error, state_str) | |
3091 elif self.opts.bisect_mode == BISECT_MODE_STD_DEV: | |
3092 std_dev = ('+-%.02f' % | |
3093 current_data['value']['std_dev']).center(14, ' ') | |
3094 mean = ('%.02f' % current_data['value']['mean']).center(12, ' ') | |
3095 print ' %20s %70s %14s %12s %13s' % ( | |
3096 current_data['depot'].center(20, ' '), cl_link.center(70, ' '), | |
3097 std_dev, mean, state_str) | |
3098 elif self.opts.bisect_mode == BISECT_MODE_RETURN_CODE: | |
3099 mean = ('%d' % current_data['value']['mean']).center(12, ' ') | |
3100 print ' %20s %70s %14s %13s' % ( | |
3101 current_data['depot'].center(20, ' '), cl_link.center(70, ' '), | |
3102 mean, state_str) | |
3103 | |
3016 def _PrintTestedCommitsTable(self, revision_data_sorted, | 3104 def _PrintTestedCommitsTable(self, revision_data_sorted, |
3017 first_working_revision, last_broken_revision, confidence, | 3105 first_working_revision, last_broken_revision, confidence, |
3018 final_step=True): | 3106 final_step=True): |
3019 print | 3107 print |
3020 if final_step: | 3108 if final_step: |
3021 print 'Tested commits:' | 3109 print 'Tested commits:' |
3022 else: | 3110 else: |
3023 print 'Partial results:' | 3111 print 'Partial results:' |
3024 print ' %20s %70s %12s %14s %13s' % ('Depot'.center(20, ' '), | 3112 self._PrintTestedCommitsHeader() |
3025 'Commit SHA'.center(70, ' '), 'Mean'.center(12, ' '), | |
3026 'Std. Error'.center(14, ' '), 'State'.center(13, ' ')) | |
3027 state = 0 | 3113 state = 0 |
3028 for current_id, current_data in revision_data_sorted: | 3114 for current_id, current_data in revision_data_sorted: |
3029 if current_data['value']: | 3115 if current_data['value']: |
3030 if (current_id == last_broken_revision or | 3116 if (current_id == last_broken_revision or |
3031 current_id == first_working_revision): | 3117 current_id == first_working_revision): |
3032 # If confidence is too low, don't add this empty line since it's | 3118 # If confidence is too low, don't add this empty line since it's |
3033 # used to put focus on a suspected CL. | 3119 # used to put focus on a suspected CL. |
3034 if confidence and final_step: | 3120 if confidence and final_step: |
3035 print | 3121 print |
3036 state += 1 | 3122 state += 1 |
3037 if state == 2 and not final_step: | 3123 if state == 2 and not final_step: |
3038 # Just want a separation between "bad" and "good" cl's. | 3124 # Just want a separation between "bad" and "good" cl's. |
3039 print | 3125 print |
3040 | 3126 |
3041 state_str = 'Bad' | 3127 state_str = 'Bad' |
3042 if state == 1 and final_step: | 3128 if state == 1 and final_step: |
3043 state_str = 'Suspected CL' | 3129 state_str = 'Suspected CL' |
3044 elif state == 2: | 3130 elif state == 2: |
3045 state_str = 'Good' | 3131 state_str = 'Good' |
3046 | 3132 |
3047 # If confidence is too low, don't bother outputting good/bad. | 3133 # If confidence is too low, don't bother outputting good/bad. |
3048 if not confidence: | 3134 if not confidence: |
3049 state_str = '' | 3135 state_str = '' |
3050 state_str = state_str.center(13, ' ') | 3136 state_str = state_str.center(13, ' ') |
3051 | 3137 |
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, | 3138 cl_link = self._GetViewVCLinkFromDepotAndHash(current_id, |
3056 current_data['depot']) | 3139 current_data['depot']) |
3057 if not cl_link: | 3140 if not cl_link: |
3058 cl_link = current_id | 3141 cl_link = current_id |
3059 print ' %20s %70s %12s %14s %13s' % ( | 3142 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 | 3143 |
3063 def _PrintReproSteps(self): | 3144 def _PrintReproSteps(self): |
3064 print | 3145 print |
3065 print 'To reproduce locally:' | 3146 print 'To reproduce locally:' |
3066 print '$ ' + self.opts.command | 3147 print '$ ' + self.opts.command |
3067 if bisect_utils.IsTelemetryCommand(self.opts.command): | 3148 if bisect_utils.IsTelemetryCommand(self.opts.command): |
3068 print | 3149 print |
3069 print 'Also consider passing --profiler=list to see available profilers.' | 3150 print 'Also consider passing --profiler=list to see available profilers.' |
3070 | 3151 |
3071 def _PrintOtherRegressions(self, other_regressions, revision_data): | 3152 def _PrintOtherRegressions(self, other_regressions, revision_data): |
(...skipping 354 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3426 self.no_custom_deps = False | 3507 self.no_custom_deps = False |
3427 self.working_directory = None | 3508 self.working_directory = None |
3428 self.extra_src = None | 3509 self.extra_src = None |
3429 self.debug_ignore_build = None | 3510 self.debug_ignore_build = None |
3430 self.debug_ignore_sync = None | 3511 self.debug_ignore_sync = None |
3431 self.debug_ignore_perf_test = None | 3512 self.debug_ignore_perf_test = None |
3432 self.gs_bucket = None | 3513 self.gs_bucket = None |
3433 self.target_arch = 'ia32' | 3514 self.target_arch = 'ia32' |
3434 self.builder_host = None | 3515 self.builder_host = None |
3435 self.builder_port = None | 3516 self.builder_port = None |
3517 self.bisect_mode = BISECT_MODE_MEAN | |
3436 | 3518 |
3437 def _CreateCommandLineParser(self): | 3519 def _CreateCommandLineParser(self): |
3438 """Creates a parser with bisect options. | 3520 """Creates a parser with bisect options. |
3439 | 3521 |
3440 Returns: | 3522 Returns: |
3441 An instance of optparse.OptionParser. | 3523 An instance of optparse.OptionParser. |
3442 """ | 3524 """ |
3443 usage = ('%prog [options] [-- chromium-options]\n' | 3525 usage = ('%prog [options] [-- chromium-options]\n' |
3444 'Perform binary search on revision history to find a minimal ' | 3526 'Perform binary search on revision history to find a minimal ' |
3445 'range of revisions where a peformance metric regressed.\n') | 3527 'range of revisions where a peformance metric regressed.\n') |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3480 'doesn\'t exceed --max_time_minutes. Values will be ' | 3562 'doesn\'t exceed --max_time_minutes. Values will be ' |
3481 'clamped to range [1, 60].' | 3563 'clamped to range [1, 60].' |
3482 'Default value is 20.') | 3564 'Default value is 20.') |
3483 group.add_option('-t', '--truncate_percent', | 3565 group.add_option('-t', '--truncate_percent', |
3484 type='int', | 3566 type='int', |
3485 default=25, | 3567 default=25, |
3486 help='The highest/lowest % are discarded to form a ' | 3568 help='The highest/lowest % are discarded to form a ' |
3487 'truncated mean. Values will be clamped to range [0, ' | 3569 'truncated mean. Values will be clamped to range [0, ' |
3488 '25]. Default value is 25 (highest/lowest 25% will be ' | 3570 '25]. Default value is 25 (highest/lowest 25% will be ' |
3489 'discarded).') | 3571 'discarded).') |
3572 group.add_option('--bisect_mode', | |
3573 type='choice', | |
3574 choices=[BISECT_MODE_MEAN, BISECT_MODE_STD_DEV, | |
3575 BISECT_MODE_RETURN_CODE], | |
3576 default=BISECT_MODE_MEAN, | |
3577 help='The bisect mode. Choices are to bisect on the ' | |
3578 'difference in mean, std_dev, or return_code.') | |
3490 parser.add_option_group(group) | 3579 parser.add_option_group(group) |
3491 | 3580 |
3492 group = optparse.OptionGroup(parser, 'Build options') | 3581 group = optparse.OptionGroup(parser, 'Build options') |
3493 group.add_option('-w', '--working_directory', | 3582 group.add_option('-w', '--working_directory', |
3494 type='str', | 3583 type='str', |
3495 help='Path to the working directory where the script ' | 3584 help='Path to the working directory where the script ' |
3496 'will do an initial checkout of the chromium depot. The ' | 3585 'will do an initial checkout of the chromium depot. The ' |
3497 'files will be placed in a subdirectory "bisect" under ' | 3586 'files will be placed in a subdirectory "bisect" under ' |
3498 'working_directory and that will be used to perform the ' | 3587 'working_directory and that will be used to perform the ' |
3499 'bisection. This parameter is optional, if it is not ' | 3588 'bisection. This parameter is optional, if it is not ' |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3579 try: | 3668 try: |
3580 if not opts.command: | 3669 if not opts.command: |
3581 raise RuntimeError('missing required parameter: --command') | 3670 raise RuntimeError('missing required parameter: --command') |
3582 | 3671 |
3583 if not opts.good_revision: | 3672 if not opts.good_revision: |
3584 raise RuntimeError('missing required parameter: --good_revision') | 3673 raise RuntimeError('missing required parameter: --good_revision') |
3585 | 3674 |
3586 if not opts.bad_revision: | 3675 if not opts.bad_revision: |
3587 raise RuntimeError('missing required parameter: --bad_revision') | 3676 raise RuntimeError('missing required parameter: --bad_revision') |
3588 | 3677 |
3589 if not opts.metric: | 3678 if not opts.metric and opts.bisect_mode != BISECT_MODE_RETURN_CODE: |
3590 raise RuntimeError('missing required parameter: --metric') | 3679 raise RuntimeError('missing required parameter: --metric') |
3591 | 3680 |
3592 if opts.gs_bucket: | 3681 if opts.gs_bucket: |
3593 if not cloud_storage.List(opts.gs_bucket): | 3682 if not cloud_storage.List(opts.gs_bucket): |
3594 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) | 3683 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) |
3595 if not opts.builder_host: | 3684 if not opts.builder_host: |
3596 raise RuntimeError('Must specify try server hostname, when ' | 3685 raise RuntimeError('Must specify try server hostname, when ' |
3597 'gs_bucket is used: --builder_host') | 3686 'gs_bucket is used: --builder_host') |
3598 if not opts.builder_port: | 3687 if not opts.builder_port: |
3599 raise RuntimeError('Must specify try server port number, when ' | 3688 raise RuntimeError('Must specify try server port number, when ' |
3600 'gs_bucket is used: --builder_port') | 3689 'gs_bucket is used: --builder_port') |
3601 if opts.target_platform == 'cros': | 3690 if opts.target_platform == 'cros': |
3602 # Run sudo up front to make sure credentials are cached for later. | 3691 # Run sudo up front to make sure credentials are cached for later. |
3603 print 'Sudo is required to build cros:' | 3692 print 'Sudo is required to build cros:' |
3604 print | 3693 print |
3605 RunProcess(['sudo', 'true']) | 3694 RunProcess(['sudo', 'true']) |
3606 | 3695 |
3607 if not opts.cros_board: | 3696 if not opts.cros_board: |
3608 raise RuntimeError('missing required parameter: --cros_board') | 3697 raise RuntimeError('missing required parameter: --cros_board') |
3609 | 3698 |
3610 if not opts.cros_remote_ip: | 3699 if not opts.cros_remote_ip: |
3611 raise RuntimeError('missing required parameter: --cros_remote_ip') | 3700 raise RuntimeError('missing required parameter: --cros_remote_ip') |
3612 | 3701 |
3613 if not opts.working_directory: | 3702 if not opts.working_directory: |
3614 raise RuntimeError('missing required parameter: --working_directory') | 3703 raise RuntimeError('missing required parameter: --working_directory') |
3615 | 3704 |
3616 metric_values = opts.metric.split('/') | 3705 metric_values = opts.metric.split('/') |
3617 if len(metric_values) != 2: | 3706 if (len(metric_values) != 2 and |
3707 opts.bisect_mode != BISECT_MODE_RETURN_CODE): | |
3618 raise RuntimeError("Invalid metric specified: [%s]" % opts.metric) | 3708 raise RuntimeError("Invalid metric specified: [%s]" % opts.metric) |
3619 | 3709 |
3620 opts.metric = metric_values | 3710 opts.metric = metric_values |
3621 opts.repeat_test_count = min(max(opts.repeat_test_count, 1), 100) | 3711 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) | 3712 opts.max_time_minutes = min(max(opts.max_time_minutes, 1), 60) |
3623 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) | 3713 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) |
3624 opts.truncate_percent = opts.truncate_percent / 100.0 | 3714 opts.truncate_percent = opts.truncate_percent / 100.0 |
3625 | 3715 |
3626 for k, v in opts.__dict__.iteritems(): | 3716 for k, v in opts.__dict__.iteritems(): |
3627 assert hasattr(self, k), "Invalid %s attribute in BisectOptions." % k | 3717 assert hasattr(self, k), "Invalid %s attribute in BisectOptions." % k |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3719 # The perf dashboard scrapes the "results" step in order to comment on | 3809 # 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. | 3810 # bugs. If you change this, please update the perf dashboard as well. |
3721 bisect_utils.OutputAnnotationStepStart('Results') | 3811 bisect_utils.OutputAnnotationStepStart('Results') |
3722 print 'Error: %s' % e.message | 3812 print 'Error: %s' % e.message |
3723 if opts.output_buildbot_annotations: | 3813 if opts.output_buildbot_annotations: |
3724 bisect_utils.OutputAnnotationStepClosed() | 3814 bisect_utils.OutputAnnotationStepClosed() |
3725 return 1 | 3815 return 1 |
3726 | 3816 |
3727 if __name__ == '__main__': | 3817 if __name__ == '__main__': |
3728 sys.exit(main()) | 3818 sys.exit(main()) |
OLD | NEW |