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 # 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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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()) |
OLD | NEW |