Chromium Code Reviews| 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 |