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 1500 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1511 updated_deps_content = self.UpdateDepsContents( | 1511 updated_deps_content = self.UpdateDepsContents( |
1512 deps_contents, depot, revision, deps_var) | 1512 deps_contents, depot, revision, deps_var) |
1513 # Write changes to DEPS file | 1513 # Write changes to DEPS file |
1514 if updated_deps_content: | 1514 if updated_deps_content: |
1515 WriteStringToFile(updated_deps_content, deps_file) | 1515 WriteStringToFile(updated_deps_content, deps_file) |
1516 return True | 1516 return True |
1517 except IOError, e: | 1517 except IOError, e: |
1518 print 'Something went wrong while updating DEPS file. [%s]' % e | 1518 print 'Something went wrong while updating DEPS file. [%s]' % e |
1519 return False | 1519 return False |
1520 | 1520 |
1521 | |
1522 def CreateDEPSPatch(self, depot, revision): | 1521 def CreateDEPSPatch(self, depot, revision): |
1523 """Modifies DEPS and returns diff as text. | 1522 """Modifies DEPS and returns diff as text. |
1524 | 1523 |
1525 Args: | 1524 Args: |
1526 depot: Current depot being bisected. | 1525 depot: Current depot being bisected. |
1527 revision: A git hash revision of the dependency repository. | 1526 revision: A git hash revision of the dependency repository. |
1528 | 1527 |
1529 Returns: | 1528 Returns: |
1530 A tuple with git hash of chromium revision and DEPS patch text. | 1529 A tuple with git hash of chromium revision and DEPS patch text. |
1531 """ | 1530 """ |
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1657 """ | 1656 """ |
1658 success_code, failure_code = 0, -1 | 1657 success_code, failure_code = 0, -1 |
1659 | 1658 |
1660 if self.opts.debug_ignore_perf_test: | 1659 if self.opts.debug_ignore_perf_test: |
1661 fake_results = { | 1660 fake_results = { |
1662 'mean': 0.0, | 1661 'mean': 0.0, |
1663 'std_err': 0.0, | 1662 'std_err': 0.0, |
1664 'std_dev': 0.0, | 1663 'std_dev': 0.0, |
1665 'values': [0.0] | 1664 'values': [0.0] |
1666 } | 1665 } |
| 1666 |
| 1667 # When debug_fake_test_mean is set, its value is returned as the mean |
| 1668 # and the flag is cleared so that further calls behave as if it wasn't |
| 1669 # set (returning the fake_results dict as defined above). |
| 1670 if self.opts.debug_fake_first_test_mean: |
| 1671 fake_results['mean'] = float(self.opts.debug_fake_first_test_mean) |
| 1672 self.opts.debug_fake_first_test_mean = 0 |
| 1673 |
1667 return (fake_results, success_code) | 1674 return (fake_results, success_code) |
1668 | 1675 |
1669 # For Windows platform set posix=False, to parse windows paths correctly. | 1676 # For Windows platform set posix=False, to parse windows paths correctly. |
1670 # On Windows, path separators '\' or '\\' are replace by '' when posix=True, | 1677 # On Windows, path separators '\' or '\\' are replace by '' when posix=True, |
1671 # refer to http://bugs.python.org/issue1724822. By default posix=True. | 1678 # refer to http://bugs.python.org/issue1724822. By default posix=True. |
1672 args = shlex.split(command_to_run, posix=not bisect_utils.IsWindowsHost()) | 1679 args = shlex.split(command_to_run, posix=not bisect_utils.IsWindowsHost()) |
1673 | 1680 |
1674 if not _GenerateProfileIfNecessary(args): | 1681 if not _GenerateProfileIfNecessary(args): |
1675 err_text = 'Failed to generate profile for performance test.' | 1682 err_text = 'Failed to generate profile for performance test.' |
1676 return (err_text, failure_code) | 1683 return (err_text, failure_code) |
(...skipping 730 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2407 self.PrintRevisionsToBisectMessage(revision_list, target_depot) | 2414 self.PrintRevisionsToBisectMessage(revision_list, target_depot) |
2408 | 2415 |
2409 if self.opts.output_buildbot_annotations: | 2416 if self.opts.output_buildbot_annotations: |
2410 bisect_utils.OutputAnnotationStepStart('Gathering Reference Values') | 2417 bisect_utils.OutputAnnotationStepStart('Gathering Reference Values') |
2411 | 2418 |
2412 print 'Gathering reference values for bisection.' | 2419 print 'Gathering reference values for bisection.' |
2413 | 2420 |
2414 # Perform the performance tests on the good and bad revisions, to get | 2421 # Perform the performance tests on the good and bad revisions, to get |
2415 # reference values. | 2422 # reference values. |
2416 bad_results, good_results = self.GatherReferenceValues(good_revision, | 2423 bad_results, good_results = self.GatherReferenceValues(good_revision, |
2417 bad_revision, | 2424 bad_revision, |
2418 command_to_run, | 2425 command_to_run, |
2419 metric, | 2426 metric, |
2420 target_depot) | 2427 target_depot) |
2421 | 2428 |
2422 if self.opts.output_buildbot_annotations: | 2429 if self.opts.output_buildbot_annotations: |
2423 bisect_utils.OutputAnnotationStepClosed() | 2430 bisect_utils.OutputAnnotationStepClosed() |
2424 | 2431 |
2425 if bad_results[1]: | 2432 if bad_results[1]: |
2426 results.error = ('An error occurred while building and running ' | 2433 results.error = ('An error occurred while building and running ' |
2427 'the \'bad\' reference value. The bisect cannot continue without ' | 2434 'the \'bad\' reference value. The bisect cannot continue without ' |
2428 'a working \'bad\' revision to start from.\n\nError: %s' % | 2435 'a working \'bad\' revision to start from.\n\nError: %s' % |
2429 bad_results[0]) | 2436 bad_results[0]) |
2430 return results | 2437 return results |
2431 | 2438 |
2432 if good_results[1]: | 2439 if good_results[1]: |
2433 results.error = ('An error occurred while building and running ' | 2440 results.error = ('An error occurred while building and running ' |
2434 'the \'good\' reference value. The bisect cannot continue without ' | 2441 'the \'good\' reference value. The bisect cannot continue without ' |
2435 'a working \'good\' revision to start from.\n\nError: %s' % | 2442 'a working \'good\' revision to start from.\n\nError: %s' % |
2436 good_results[0]) | 2443 good_results[0]) |
2437 return results | 2444 return results |
2438 | 2445 |
2439 | |
2440 # We need these reference values to determine if later runs should be | 2446 # We need these reference values to determine if later runs should be |
2441 # classified as pass or fail. | 2447 # classified as pass or fail. |
2442 known_bad_value = bad_results[0] | 2448 known_bad_value = bad_results[0] |
2443 known_good_value = good_results[0] | 2449 known_good_value = good_results[0] |
2444 | 2450 |
| 2451 # Check the direction of improvement only if the improvement_direction |
| 2452 # option is set to a specific direction (1 for higher is better or -1 for |
| 2453 # lower is better). |
| 2454 improvement_dir = self.opts.improvement_direction |
| 2455 if improvement_dir: |
| 2456 higher_is_better = improvement_dir > 0 |
| 2457 if higher_is_better: |
| 2458 message = "Expecting higher values to be better for this metric, " |
| 2459 else: |
| 2460 message = "Expecting lower values to be better for this metric, " |
| 2461 metric_increased = known_bad_value['mean'] > known_good_value['mean'] |
| 2462 if metric_increased: |
| 2463 message += "and the metric appears to have increased. " |
| 2464 else: |
| 2465 message += "and the metric appears to have decreased. " |
| 2466 if ((higher_is_better and metric_increased) or |
| 2467 (not higher_is_better and not metric_increased)): |
| 2468 results.error = (message + 'Then, the test results for the ends of ' |
| 2469 'the given \'good\' - \'bad\' range of revisions ' |
| 2470 'represent an improvement (and not a regression).') |
| 2471 return results |
| 2472 print message, "Therefore we continue to bisect." |
| 2473 |
2445 # Can just mark the good and bad revisions explicitly here since we | 2474 # Can just mark the good and bad revisions explicitly here since we |
2446 # already know the results. | 2475 # already know the results. |
2447 bad_revision_data = revision_data[revision_list[0]] | 2476 bad_revision_data = revision_data[revision_list[0]] |
2448 bad_revision_data['external'] = bad_results[2] | 2477 bad_revision_data['external'] = bad_results[2] |
2449 bad_revision_data['perf_time'] = bad_results[3] | 2478 bad_revision_data['perf_time'] = bad_results[3] |
2450 bad_revision_data['build_time'] = bad_results[4] | 2479 bad_revision_data['build_time'] = bad_results[4] |
2451 bad_revision_data['passed'] = False | 2480 bad_revision_data['passed'] = False |
2452 bad_revision_data['value'] = known_bad_value | 2481 bad_revision_data['value'] = known_bad_value |
2453 | 2482 |
2454 good_revision_data = revision_data[revision_list[max_revision]] | 2483 good_revision_data = revision_data[revision_list[max_revision]] |
(...skipping 477 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2932 self.max_time_minutes = 20 | 2961 self.max_time_minutes = 20 |
2933 self.metric = None | 2962 self.metric = None |
2934 self.command = None | 2963 self.command = None |
2935 self.output_buildbot_annotations = None | 2964 self.output_buildbot_annotations = None |
2936 self.no_custom_deps = False | 2965 self.no_custom_deps = False |
2937 self.working_directory = None | 2966 self.working_directory = None |
2938 self.extra_src = None | 2967 self.extra_src = None |
2939 self.debug_ignore_build = None | 2968 self.debug_ignore_build = None |
2940 self.debug_ignore_sync = None | 2969 self.debug_ignore_sync = None |
2941 self.debug_ignore_perf_test = None | 2970 self.debug_ignore_perf_test = None |
| 2971 self.debug_fake_first_test_mean = 0 |
2942 self.gs_bucket = None | 2972 self.gs_bucket = None |
2943 self.target_arch = 'ia32' | 2973 self.target_arch = 'ia32' |
2944 self.target_build_type = 'Release' | 2974 self.target_build_type = 'Release' |
2945 self.builder_host = None | 2975 self.builder_host = None |
2946 self.builder_port = None | 2976 self.builder_port = None |
2947 self.bisect_mode = BISECT_MODE_MEAN | 2977 self.bisect_mode = BISECT_MODE_MEAN |
| 2978 self.improvement_direction = 0 |
2948 | 2979 |
2949 @staticmethod | 2980 @staticmethod |
2950 def _CreateCommandLineParser(): | 2981 def _CreateCommandLineParser(): |
2951 """Creates a parser with bisect options. | 2982 """Creates a parser with bisect options. |
2952 | 2983 |
2953 Returns: | 2984 Returns: |
2954 An instance of optparse.OptionParser. | 2985 An instance of optparse.OptionParser. |
2955 """ | 2986 """ |
2956 usage = ('%prog [options] [-- chromium-options]\n' | 2987 usage = ('%prog [options] [-- chromium-options]\n' |
2957 'Perform binary search on revision history to find a minimal ' | 2988 'Perform binary search on revision history to find a minimal ' |
(...skipping 13 matching lines...) Expand all Loading... |
2971 ' or svn revision.') | 3002 ' or svn revision.') |
2972 group.add_option('-g', '--good_revision', | 3003 group.add_option('-g', '--good_revision', |
2973 type='str', | 3004 type='str', |
2974 help='A revision to start bisection where performance' + | 3005 help='A revision to start bisection where performance' + |
2975 ' test is known to pass. Must be earlier than the ' + | 3006 ' test is known to pass. Must be earlier than the ' + |
2976 'bad revision. May be either a git or svn revision.') | 3007 'bad revision. May be either a git or svn revision.') |
2977 group.add_option('-m', '--metric', | 3008 group.add_option('-m', '--metric', |
2978 type='str', | 3009 type='str', |
2979 help='The desired metric to bisect on. For example ' + | 3010 help='The desired metric to bisect on. For example ' + |
2980 '"vm_rss_final_b/vm_rss_f_b"') | 3011 '"vm_rss_final_b/vm_rss_f_b"') |
| 3012 group.add_option('-d', '--improvement_direction', |
| 3013 type='int', |
| 3014 default=0, |
| 3015 help='An integer number representing the direction of ' + |
| 3016 'improvement. 1 for higher is better, -1 for lower is ' + |
| 3017 'better, 0 for ignore (default).') |
2981 group.add_option('-r', '--repeat_test_count', | 3018 group.add_option('-r', '--repeat_test_count', |
2982 type='int', | 3019 type='int', |
2983 default=20, | 3020 default=20, |
2984 help='The number of times to repeat the performance ' | 3021 help='The number of times to repeat the performance ' |
2985 'test. Values will be clamped to range [1, 100]. ' | 3022 'test. Values will be clamped to range [1, 100]. ' |
2986 'Default value is 20.') | 3023 'Default value is 20.') |
2987 group.add_option('--max_time_minutes', | 3024 group.add_option('--max_time_minutes', |
2988 type='int', | 3025 type='int', |
2989 default=20, | 3026 default=20, |
2990 help='The maximum time (in minutes) to take running the ' | 3027 help='The maximum time (in minutes) to take running the ' |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3091 group = optparse.OptionGroup(parser, 'Debug options') | 3128 group = optparse.OptionGroup(parser, 'Debug options') |
3092 group.add_option('--debug_ignore_build', | 3129 group.add_option('--debug_ignore_build', |
3093 action='store_true', | 3130 action='store_true', |
3094 help='DEBUG: Don\'t perform builds.') | 3131 help='DEBUG: Don\'t perform builds.') |
3095 group.add_option('--debug_ignore_sync', | 3132 group.add_option('--debug_ignore_sync', |
3096 action='store_true', | 3133 action='store_true', |
3097 help='DEBUG: Don\'t perform syncs.') | 3134 help='DEBUG: Don\'t perform syncs.') |
3098 group.add_option('--debug_ignore_perf_test', | 3135 group.add_option('--debug_ignore_perf_test', |
3099 action='store_true', | 3136 action='store_true', |
3100 help='DEBUG: Don\'t perform performance tests.') | 3137 help='DEBUG: Don\'t perform performance tests.') |
| 3138 group.add_option('--debug_fake_first_test_mean', |
| 3139 type='int', |
| 3140 default='0', |
| 3141 help=('DEBUG: When faking performance tests, return this ' |
| 3142 'value as the mean of the first performance test, ' |
| 3143 'and return a mean of 0.0 for further tests.')) |
3101 parser.add_option_group(group) | 3144 parser.add_option_group(group) |
3102 return parser | 3145 return parser |
3103 | 3146 |
3104 def ParseCommandLine(self): | 3147 def ParseCommandLine(self): |
3105 """Parses the command line for bisect options.""" | 3148 """Parses the command line for bisect options.""" |
3106 parser = self._CreateCommandLineParser() | 3149 parser = self._CreateCommandLineParser() |
3107 opts, _ = parser.parse_args() | 3150 opts, _ = parser.parse_args() |
3108 | 3151 |
3109 try: | 3152 try: |
3110 if not opts.command: | 3153 if not opts.command: |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3246 # bugs. If you change this, please update the perf dashboard as well. | 3289 # bugs. If you change this, please update the perf dashboard as well. |
3247 bisect_utils.OutputAnnotationStepStart('Results') | 3290 bisect_utils.OutputAnnotationStepStart('Results') |
3248 print 'Error: %s' % e.message | 3291 print 'Error: %s' % e.message |
3249 if opts.output_buildbot_annotations: | 3292 if opts.output_buildbot_annotations: |
3250 bisect_utils.OutputAnnotationStepClosed() | 3293 bisect_utils.OutputAnnotationStepClosed() |
3251 return 1 | 3294 return 1 |
3252 | 3295 |
3253 | 3296 |
3254 if __name__ == '__main__': | 3297 if __name__ == '__main__': |
3255 sys.exit(main()) | 3298 sys.exit(main()) |
OLD | NEW |