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 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 119 True if the string can be converted to an int. | 119 True if the string can be converted to an int. |
| 120 """ | 120 """ |
| 121 try: | 121 try: |
| 122 int(string_to_check) | 122 int(string_to_check) |
| 123 | 123 |
| 124 return True | 124 return True |
| 125 except ValueError: | 125 except ValueError: |
| 126 return False | 126 return False |
| 127 | 127 |
| 128 | 128 |
| 129 def OutputAnnotationStepStart(name): | |
| 130 """Outputs appropriate annotation to signal the start of a step to | |
| 131 a trybot. | |
| 132 | |
| 133 Args: | |
| 134 name: The name of the step. | |
| 135 """ | |
| 136 print '@@@SEED_STEP %s@@@' % name | |
| 137 print '@@@STEP_CURSOR %s@@@' % name | |
| 138 print '@@@STEP_STARTED@@@' | |
| 139 | |
| 140 | |
| 141 def OutputAnnotationStepClosed(): | |
| 142 """Outputs appropriate annotation to signal the closing of a step to | |
| 143 a trybot.""" | |
| 144 print '@@@STEP_CLOSED@@@' | |
| 145 | |
| 146 | |
| 129 def RunProcess(command): | 147 def RunProcess(command): |
| 130 """Run an arbitrary command, returning its output and return code. | 148 """Run an arbitrary command, returning its output and return code. |
| 131 | 149 |
| 132 Args: | 150 Args: |
| 133 command: A list containing the command and args to execute. | 151 command: A list containing the command and args to execute. |
| 134 | 152 |
| 135 Returns: | 153 Returns: |
| 136 A tuple of the output and return code. | 154 A tuple of the output and return code. |
| 137 """ | 155 """ |
| 138 # On Windows, use shell=True to get PATH interpretation. | 156 # On Windows, use shell=True to get PATH interpretation. |
| (...skipping 633 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 772 for i in xrange(num_depot_revisions): | 790 for i in xrange(num_depot_revisions): |
| 773 r = revisions[i] | 791 r = revisions[i] |
| 774 | 792 |
| 775 revision_data[r] = {'revision' : r, | 793 revision_data[r] = {'revision' : r, |
| 776 'depot' : depot, | 794 'depot' : depot, |
| 777 'value' : None, | 795 'value' : None, |
| 778 'passed' : '?', | 796 'passed' : '?', |
| 779 'sort' : i + sort + 1} | 797 'sort' : i + sort + 1} |
| 780 | 798 |
| 781 def PrintRevisionsToBisectMessage(self, revision_list, depot): | 799 def PrintRevisionsToBisectMessage(self, revision_list, depot): |
| 800 if self.opts.output_buildbot_annotations: | |
| 801 list_length = len(revision_list) | |
|
tonyg
2013/02/14 01:21:01
inline?
shatch
2013/02/14 01:37:47
Done.
| |
| 802 step_name = 'Bisection Range: [%s - %s]' %\ | |
| 803 (revision_list[list_length-1], revision_list[0]) | |
|
tonyg
2013/02/14 01:21:01
indent continued line by 4 spaces
shatch
2013/02/14 01:37:47
Done.
| |
| 804 OutputAnnotationStepStart(step_name) | |
| 805 | |
| 782 print | 806 print |
| 783 print 'Revisions to bisect on [%s]:' % depot | 807 print 'Revisions to bisect on [%s]:' % depot |
| 784 for revision_id in revision_list: | 808 for revision_id in revision_list: |
| 785 print ' -> %s' % (revision_id, ) | 809 print ' -> %s' % (revision_id, ) |
| 786 print | 810 print |
| 787 | 811 |
| 812 if self.opts.output_buildbot_annotations: | |
| 813 OutputAnnotationStepClosed() | |
| 814 | |
| 788 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): | 815 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): |
| 789 """Given known good and bad revisions, run a binary search on all | 816 """Given known good and bad revisions, run a binary search on all |
| 790 intermediate revisions to determine the CL where the performance regression | 817 intermediate revisions to determine the CL where the performance regression |
| 791 occurred. | 818 occurred. |
| 792 | 819 |
| 793 Args: | 820 Args: |
| 794 command_to_run: Specify the command to execute the performance test. | 821 command_to_run: Specify the command to execute the performance test. |
| 795 good_revision: Number/tag of the known good revision. | 822 good_revision: Number/tag of the known good revision. |
| 796 bad_revision: Number/tag of the known bad revision. | 823 bad_revision: Number/tag of the known bad revision. |
| 797 metric: The performance metric to monitor. | 824 metric: The performance metric to monitor. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 839 'src') | 866 'src') |
| 840 | 867 |
| 841 if bad_revision is None: | 868 if bad_revision is None: |
| 842 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) | 869 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) |
| 843 return results | 870 return results |
| 844 | 871 |
| 845 if good_revision is None: | 872 if good_revision is None: |
| 846 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) | 873 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) |
| 847 return results | 874 return results |
| 848 | 875 |
| 876 if self.opts.output_buildbot_annotations: | |
| 877 OutputAnnotationStepStart('Gathering Revisions') | |
| 878 | |
| 849 print 'Gathering revision range for bisection.' | 879 print 'Gathering revision range for bisection.' |
| 850 | 880 |
| 851 # Retrieve a list of revisions to do bisection on. | 881 # Retrieve a list of revisions to do bisection on. |
| 852 src_revision_list = self.GetRevisionList(bad_revision, good_revision) | 882 src_revision_list = self.GetRevisionList(bad_revision, good_revision) |
| 853 | 883 |
| 884 if self.opts.output_buildbot_annotations: | |
| 885 OutputAnnotationStepClosed() | |
| 886 | |
| 854 if src_revision_list: | 887 if src_revision_list: |
| 855 # revision_data will store information about a revision such as the | 888 # revision_data will store information about a revision such as the |
| 856 # depot it came from, the webkit/V8 revision at that time, | 889 # depot it came from, the webkit/V8 revision at that time, |
| 857 # performance timing, build state, etc... | 890 # performance timing, build state, etc... |
| 858 revision_data = results['revision_data'] | 891 revision_data = results['revision_data'] |
| 859 | 892 |
| 860 # revision_list is the list we're binary searching through at the moment. | 893 # revision_list is the list we're binary searching through at the moment. |
| 861 revision_list = [] | 894 revision_list = [] |
| 862 | 895 |
| 863 sort_key_ids = 0 | 896 sort_key_ids = 0 |
| 864 | 897 |
| 865 for current_revision_id in src_revision_list: | 898 for current_revision_id in src_revision_list: |
| 866 sort_key_ids += 1 | 899 sort_key_ids += 1 |
| 867 | 900 |
| 868 revision_data[current_revision_id] = {'value' : None, | 901 revision_data[current_revision_id] = {'value' : None, |
| 869 'passed' : '?', | 902 'passed' : '?', |
| 870 'depot' : 'chromium', | 903 'depot' : 'chromium', |
| 871 'external' : None, | 904 'external' : None, |
| 872 'sort' : sort_key_ids} | 905 'sort' : sort_key_ids} |
| 873 revision_list.append(current_revision_id) | 906 revision_list.append(current_revision_id) |
| 874 | 907 |
| 875 min_revision = 0 | 908 min_revision = 0 |
| 876 max_revision = len(revision_list) - 1 | 909 max_revision = len(revision_list) - 1 |
| 877 | 910 |
| 878 self.PrintRevisionsToBisectMessage(revision_list, 'src') | 911 self.PrintRevisionsToBisectMessage(revision_list, 'src') |
| 879 | 912 |
| 913 if self.opts.output_buildbot_annotations: | |
| 914 OutputAnnotationStepStart('Gathering Reference Values') | |
| 915 | |
| 880 print 'Gathering reference values for bisection.' | 916 print 'Gathering reference values for bisection.' |
| 881 | 917 |
| 882 # Perform the performance tests on the good and bad revisions, to get | 918 # Perform the performance tests on the good and bad revisions, to get |
| 883 # reference values. | 919 # reference values. |
| 884 (bad_results, good_results) = self.GatherReferenceValues(good_revision, | 920 (bad_results, good_results) = self.GatherReferenceValues(good_revision, |
| 885 bad_revision, | 921 bad_revision, |
| 886 command_to_run, | 922 command_to_run, |
| 887 metric) | 923 metric) |
| 888 | 924 |
| 925 if self.opts.output_buildbot_annotations: | |
| 926 OutputAnnotationStepClosed() | |
| 927 | |
| 889 if bad_results[1]: | 928 if bad_results[1]: |
| 890 results['error'] = bad_results[0] | 929 results['error'] = bad_results[0] |
| 891 return results | 930 return results |
| 892 | 931 |
| 893 if good_results[1]: | 932 if good_results[1]: |
| 894 results['error'] = good_results[0] | 933 results['error'] = good_results[0] |
| 895 return results | 934 return results |
| 896 | 935 |
| 897 | 936 |
| 898 # We need these reference values to determine if later runs should be | 937 # We need these reference values to determine if later runs should be |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 978 else: | 1017 else: |
| 979 next_revision_index = int((max_revision - min_revision) / 2) +\ | 1018 next_revision_index = int((max_revision - min_revision) / 2) +\ |
| 980 min_revision | 1019 min_revision |
| 981 | 1020 |
| 982 next_revision_id = revision_list[next_revision_index] | 1021 next_revision_id = revision_list[next_revision_index] |
| 983 next_revision_data = revision_data[next_revision_id] | 1022 next_revision_data = revision_data[next_revision_id] |
| 984 next_revision_depot = next_revision_data['depot'] | 1023 next_revision_depot = next_revision_data['depot'] |
| 985 | 1024 |
| 986 self.ChangeToDepotWorkingDirectory(next_revision_depot) | 1025 self.ChangeToDepotWorkingDirectory(next_revision_depot) |
| 987 | 1026 |
| 1027 if self.opts.output_buildbot_annotations: | |
| 1028 step_name = 'Working on [%s]' % next_revision_id | |
| 1029 OutputAnnotationStepStart(step_name) | |
| 1030 | |
| 988 print 'Working on revision: [%s]' % next_revision_id | 1031 print 'Working on revision: [%s]' % next_revision_id |
| 989 | 1032 |
| 990 run_results = self.SyncBuildAndRunRevision(next_revision_id, | 1033 run_results = self.SyncBuildAndRunRevision(next_revision_id, |
| 991 next_revision_depot, | 1034 next_revision_depot, |
| 992 command_to_run, | 1035 command_to_run, |
| 993 metric) | 1036 metric) |
| 994 | 1037 |
| 1038 if self.opts.output_buildbot_annotations: | |
| 1039 OutputAnnotationStepClosed() | |
| 1040 | |
| 995 # If the build is successful, check whether or not the metric | 1041 # If the build is successful, check whether or not the metric |
| 996 # had regressed. | 1042 # had regressed. |
| 997 if not run_results[1]: | 1043 if not run_results[1]: |
| 998 if next_revision_depot == 'chromium': | 1044 if next_revision_depot == 'chromium': |
| 999 next_revision_data['external'] = run_results[2] | 1045 next_revision_data['external'] = run_results[2] |
| 1000 | 1046 |
| 1001 passed_regression = self.CheckIfRunPassed(run_results[0], | 1047 passed_regression = self.CheckIfRunPassed(run_results[0], |
| 1002 known_good_value, | 1048 known_good_value, |
| 1003 known_bad_value) | 1049 known_bad_value) |
| 1004 | 1050 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 1026 def FormatAndPrintResults(self, bisect_results): | 1072 def FormatAndPrintResults(self, bisect_results): |
| 1027 """Prints the results from a bisection run in a readable format. | 1073 """Prints the results from a bisection run in a readable format. |
| 1028 | 1074 |
| 1029 Args | 1075 Args |
| 1030 bisect_results: The results from a bisection test run. | 1076 bisect_results: The results from a bisection test run. |
| 1031 """ | 1077 """ |
| 1032 revision_data = bisect_results['revision_data'] | 1078 revision_data = bisect_results['revision_data'] |
| 1033 revision_data_sorted = sorted(revision_data.iteritems(), | 1079 revision_data_sorted = sorted(revision_data.iteritems(), |
| 1034 key = lambda x: x[1]['sort']) | 1080 key = lambda x: x[1]['sort']) |
| 1035 | 1081 |
| 1082 if self.opts.output_buildbot_annotations: | |
| 1083 OutputAnnotationStepStart('Results') | |
| 1084 | |
| 1036 print | 1085 print |
| 1037 print 'Full results of bisection:' | 1086 print 'Full results of bisection:' |
| 1038 for current_id, current_data in revision_data_sorted: | 1087 for current_id, current_data in revision_data_sorted: |
| 1039 build_status = current_data['passed'] | 1088 build_status = current_data['passed'] |
| 1040 | 1089 |
| 1041 if type(build_status) is bool: | 1090 if type(build_status) is bool: |
| 1042 build_status = int(build_status) | 1091 build_status = int(build_status) |
| 1043 | 1092 |
| 1044 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) | 1093 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) |
| 1045 print | 1094 print |
| 1046 | 1095 |
| 1047 # Find range where it possibly broke. | 1096 # Find range where it possibly broke. |
| 1048 first_working_revision = None | 1097 first_working_revision = None |
| 1049 last_broken_revision = None | 1098 last_broken_revision = None |
| 1050 | 1099 |
| 1051 for k, v in revision_data_sorted: | 1100 for k, v in revision_data_sorted: |
| 1052 if v['passed'] == 1: | 1101 if v['passed'] == 1: |
| 1053 if not first_working_revision: | 1102 if not first_working_revision: |
| 1054 first_working_revision = k | 1103 first_working_revision = k |
| 1055 | 1104 |
| 1056 if not v['passed']: | 1105 if not v['passed']: |
| 1057 last_broken_revision = k | 1106 last_broken_revision = k |
| 1058 | 1107 |
| 1059 if last_broken_revision != None and first_working_revision != None: | 1108 if last_broken_revision != None and first_working_revision != None: |
| 1060 print 'Results: Regression was detected as a result of changes on:' | 1109 print 'Results: Regression may have occurred in range:' |
| 1061 print ' -> First Bad Revision: [%s] [%s]' %\ | 1110 print ' -> First Bad Revision: [%s] [%s]' %\ |
| 1062 (last_broken_revision, | 1111 (last_broken_revision, |
| 1063 revision_data[last_broken_revision]['depot']) | 1112 revision_data[last_broken_revision]['depot']) |
| 1064 print ' -> Last Good Revision: [%s] [%s]' %\ | 1113 print ' -> Last Good Revision: [%s] [%s]' %\ |
| 1065 (first_working_revision, | 1114 (first_working_revision, |
| 1066 revision_data[first_working_revision]['depot']) | 1115 revision_data[first_working_revision]['depot']) |
| 1067 | 1116 |
| 1117 if self.opts.output_buildbot_annotations: | |
| 1118 OutputAnnotationStepClosed() | |
| 1119 | |
| 1068 | 1120 |
| 1069 def DetermineAndCreateSourceControl(): | 1121 def DetermineAndCreateSourceControl(): |
| 1070 """Attempts to determine the underlying source control workflow and returns | 1122 """Attempts to determine the underlying source control workflow and returns |
| 1071 a SourceControl object. | 1123 a SourceControl object. |
| 1072 | 1124 |
| 1073 Returns: | 1125 Returns: |
| 1074 An instance of a SourceControl object, or None if the current workflow | 1126 An instance of a SourceControl object, or None if the current workflow |
| 1075 is unsupported. | 1127 is unsupported. |
| 1076 """ | 1128 """ |
| 1077 | 1129 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 1105 help='A revision to start bisection where performance' + | 1157 help='A revision to start bisection where performance' + |
| 1106 ' test is known to pass. Must be earlier than the ' + | 1158 ' test is known to pass. Must be earlier than the ' + |
| 1107 'bad revision. May be either a git or svn revision.') | 1159 'bad revision. May be either a git or svn revision.') |
| 1108 parser.add_option('-m', '--metric', | 1160 parser.add_option('-m', '--metric', |
| 1109 type='str', | 1161 type='str', |
| 1110 help='The desired metric to bisect on. For example ' + | 1162 help='The desired metric to bisect on. For example ' + |
| 1111 '"vm_rss_final_b/vm_rss_f_b"') | 1163 '"vm_rss_final_b/vm_rss_f_b"') |
| 1112 parser.add_option('--use_goma', | 1164 parser.add_option('--use_goma', |
| 1113 action="store_true", | 1165 action="store_true", |
| 1114 help='Add a bunch of extra threads for goma.') | 1166 help='Add a bunch of extra threads for goma.') |
| 1167 parser.add_option('--output_buildbot_annotations', | |
| 1168 action="store_true", | |
| 1169 help='Add extra annotation output for buildbot.') | |
| 1115 parser.add_option('--debug_ignore_build', | 1170 parser.add_option('--debug_ignore_build', |
| 1116 action="store_true", | 1171 action="store_true", |
| 1117 help='DEBUG: Don\'t perform builds.') | 1172 help='DEBUG: Don\'t perform builds.') |
| 1118 parser.add_option('--debug_ignore_sync', | 1173 parser.add_option('--debug_ignore_sync', |
| 1119 action="store_true", | 1174 action="store_true", |
| 1120 help='DEBUG: Don\'t perform syncs.') | 1175 help='DEBUG: Don\'t perform syncs.') |
| 1121 parser.add_option('--debug_ignore_perf_test', | 1176 parser.add_option('--debug_ignore_perf_test', |
| 1122 action="store_true", | 1177 action="store_true", |
| 1123 help='DEBUG: Don\'t perform performance tests.') | 1178 help='DEBUG: Don\'t perform performance tests.') |
| 1124 (opts, args) = parser.parse_args() | 1179 (opts, args) = parser.parse_args() |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1185 if not(bisect_results['error']): | 1240 if not(bisect_results['error']): |
| 1186 bisect_test.FormatAndPrintResults(bisect_results) | 1241 bisect_test.FormatAndPrintResults(bisect_results) |
| 1187 return 0 | 1242 return 0 |
| 1188 else: | 1243 else: |
| 1189 print 'Error: ' + bisect_results['error'] | 1244 print 'Error: ' + bisect_results['error'] |
| 1190 print | 1245 print |
| 1191 return 1 | 1246 return 1 |
| 1192 | 1247 |
| 1193 if __name__ == '__main__': | 1248 if __name__ == '__main__': |
| 1194 sys.exit(main()) | 1249 sys.exit(main()) |
| OLD | NEW |