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 17 matching lines...) Expand all Loading... |
28 An example usage (using git hashes): | 28 An example usage (using git hashes): |
29 | 29 |
30 ./tools/bisect-perf-regression.py -c\ | 30 ./tools/bisect-perf-regression.py -c\ |
31 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ | 31 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ |
32 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ | 32 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ |
33 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ | 33 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ |
34 -m shutdown/simple-user-quit | 34 -m shutdown/simple-user-quit |
35 | 35 |
36 """ | 36 """ |
37 | 37 |
38 | 38 import errno |
| 39 import imp |
| 40 import optparse |
| 41 import os |
39 import re | 42 import re |
40 import os | 43 import shlex |
41 import imp | 44 import subprocess |
42 import sys | 45 import sys |
43 import shlex | |
44 import optparse | |
45 import subprocess | |
46 | 46 |
47 | 47 |
48 # The additional repositories that might need to be bisected. | 48 # The additional repositories that might need to be bisected. |
49 # If the repository has any dependant repositories (such as skia/src needs | 49 # If the repository has any dependant repositories (such as skia/src needs |
50 # skia/include and skia/gyp to be updated), specify them in the 'depends' | 50 # skia/include and skia/gyp to be updated), specify them in the 'depends' |
51 # so that they're synced appropriately. | 51 # so that they're synced appropriately. |
52 # Format is: | 52 # Format is: |
53 # src: path to the working directory. | 53 # src: path to the working directory. |
54 # recurse: True if this repositry will get bisected. | 54 # recurse: True if this repositry will get bisected. |
55 # depends: A list of other repositories that are actually part of the same | 55 # depends: A list of other repositories that are actually part of the same |
(...skipping 26 matching lines...) Expand all Loading... |
82 "recurse" : False, | 82 "recurse" : False, |
83 "svn" : "http://skia.googlecode.com/svn/trunk/gyp", | 83 "svn" : "http://skia.googlecode.com/svn/trunk/gyp", |
84 "depends" : None | 84 "depends" : None |
85 } | 85 } |
86 } | 86 } |
87 | 87 |
88 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 88 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
89 | 89 |
90 FILE_DEPS_GIT = '.DEPS.git' | 90 FILE_DEPS_GIT = '.DEPS.git' |
91 | 91 |
| 92 GCLIENT_SPEC = """ |
| 93 solutions = [ |
| 94 { "name" : "src", |
| 95 "url" : "https://chromium.googlesource.com/chromium/src.git", |
| 96 "deps_file" : ".DEPS.git", |
| 97 "managed" : True, |
| 98 "custom_deps" : { |
| 99 }, |
| 100 "safesync_url": "", |
| 101 }, |
| 102 { "name" : "src-internal", |
| 103 "url" : "ssh://gerrit-int.chromium.org:29419/" + |
| 104 "chrome/src-internal.git", |
| 105 "deps_file" : ".DEPS.git", |
| 106 }, |
| 107 ] |
| 108 """ |
| 109 GCLIENT_SPEC = ''.join([l for l in GCLIENT_SPEC.splitlines()]) |
| 110 |
92 | 111 |
93 | 112 |
94 def IsStringFloat(string_to_check): | 113 def IsStringFloat(string_to_check): |
95 """Checks whether or not the given string can be converted to a floating | 114 """Checks whether or not the given string can be converted to a floating |
96 point number. | 115 point number. |
97 | 116 |
98 Args: | 117 Args: |
99 string_to_check: Input string to check if it can be converted to a float. | 118 string_to_check: Input string to check if it can be converted to a float. |
100 | 119 |
101 Returns: | 120 Returns: |
(...skipping 1025 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1127 """ | 1146 """ |
1128 | 1147 |
1129 (output, return_code) = RunGit(['rev-parse', '--is-inside-work-tree']) | 1148 (output, return_code) = RunGit(['rev-parse', '--is-inside-work-tree']) |
1130 | 1149 |
1131 if output.strip() == 'true': | 1150 if output.strip() == 'true': |
1132 return GitSourceControl() | 1151 return GitSourceControl() |
1133 | 1152 |
1134 return None | 1153 return None |
1135 | 1154 |
1136 | 1155 |
| 1156 def CreateAndChangeToSourceDirectory(working_directory): |
| 1157 """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If |
| 1158 the function is successful, the current working directory will change to that |
| 1159 of the new 'bisect' directory. |
| 1160 |
| 1161 Returns: |
| 1162 True if the directory was successfully created (or already existed). |
| 1163 """ |
| 1164 cwd = os.getcwd() |
| 1165 os.chdir(working_directory) |
| 1166 try: |
| 1167 os.mkdir('bisect') |
| 1168 except OSError, e: |
| 1169 if e.errno != errno.EEXIST: |
| 1170 return False |
| 1171 os.chdir('bisect') |
| 1172 return True |
| 1173 |
| 1174 |
| 1175 def RunGClient(params): |
| 1176 """Runs gclient with the specified parameters. |
| 1177 |
| 1178 Args: |
| 1179 params: A list of parameters to pass to gclient. |
| 1180 |
| 1181 Returns: |
| 1182 The return code of the call. |
| 1183 """ |
| 1184 cmd = ['gclient'] + params |
| 1185 return subprocess.call(cmd) |
| 1186 |
| 1187 |
| 1188 def RunGClientAndCreateConfig(): |
| 1189 """Runs gclient and creates a config containing both src and src-internal. |
| 1190 |
| 1191 Returns: |
| 1192 The return code of the call. |
| 1193 """ |
| 1194 return_code = RunGClient( |
| 1195 ['config', '--spec=%s' % GCLIENT_SPEC, '--git-deps']) |
| 1196 return return_code |
| 1197 |
| 1198 |
| 1199 def RunGClientAndSync(): |
| 1200 """Runs gclient and does a normal sync. |
| 1201 |
| 1202 Returns: |
| 1203 The return code of the call. |
| 1204 """ |
| 1205 return RunGClient(['sync']) |
| 1206 |
| 1207 |
| 1208 def SetupGitDepot(output_buildbot_annotations): |
| 1209 """Sets up the depot for the bisection. The depot will be located in a |
| 1210 subdirectory called 'bisect'. |
| 1211 |
| 1212 Returns: |
| 1213 True if gclient successfully created the config file and did a sync, False |
| 1214 otherwise. |
| 1215 """ |
| 1216 name = 'Setting up Bisection Depot' |
| 1217 |
| 1218 if output_buildbot_annotations: |
| 1219 OutputAnnotationStepStart(name) |
| 1220 |
| 1221 passed = False |
| 1222 |
| 1223 if not RunGClientAndCreateConfig(): |
| 1224 if not RunGClientAndSync(): |
| 1225 passed = True |
| 1226 |
| 1227 if output_buildbot_annotations: |
| 1228 print |
| 1229 OutputAnnotationStepClosed() |
| 1230 |
| 1231 return passed |
| 1232 |
| 1233 |
1137 def main(): | 1234 def main(): |
1138 | 1235 |
1139 usage = ('%prog [options] [-- chromium-options]\n' | 1236 usage = ('%prog [options] [-- chromium-options]\n' |
1140 'Perform binary search on revision history to find a minimal ' | 1237 'Perform binary search on revision history to find a minimal ' |
1141 'range of revisions where a peformance metric regressed.\n') | 1238 'range of revisions where a peformance metric regressed.\n') |
1142 | 1239 |
1143 parser = optparse.OptionParser(usage=usage) | 1240 parser = optparse.OptionParser(usage=usage) |
1144 | 1241 |
1145 parser.add_option('-c', '--command', | 1242 parser.add_option('-c', '--command', |
1146 type='str', | 1243 type='str', |
1147 help='A command to execute your performance test at' + | 1244 help='A command to execute your performance test at' + |
1148 ' each point in the bisection.') | 1245 ' each point in the bisection.') |
1149 parser.add_option('-b', '--bad_revision', | 1246 parser.add_option('-b', '--bad_revision', |
1150 type='str', | 1247 type='str', |
1151 help='A bad revision to start bisection. ' + | 1248 help='A bad revision to start bisection. ' + |
1152 'Must be later than good revision. May be either a git' + | 1249 'Must be later than good revision. May be either a git' + |
1153 ' or svn revision.') | 1250 ' or svn revision.') |
1154 parser.add_option('-g', '--good_revision', | 1251 parser.add_option('-g', '--good_revision', |
1155 type='str', | 1252 type='str', |
1156 help='A revision to start bisection where performance' + | 1253 help='A revision to start bisection where performance' + |
1157 ' test is known to pass. Must be earlier than the ' + | 1254 ' test is known to pass. Must be earlier than the ' + |
1158 'bad revision. May be either a git or svn revision.') | 1255 'bad revision. May be either a git or svn revision.') |
1159 parser.add_option('-m', '--metric', | 1256 parser.add_option('-m', '--metric', |
1160 type='str', | 1257 type='str', |
1161 help='The desired metric to bisect on. For example ' + | 1258 help='The desired metric to bisect on. For example ' + |
1162 '"vm_rss_final_b/vm_rss_f_b"') | 1259 '"vm_rss_final_b/vm_rss_f_b"') |
| 1260 parser.add_option('-w', '--working_directory', |
| 1261 type='str', |
| 1262 help='Path to the working directory where the script will ' |
| 1263 'do an initial checkout of the chromium depot. The ' |
| 1264 'files will be placed in a subdirectory "bisect" under ' |
| 1265 'working_directory and that will be used to perform the ' |
| 1266 'bisection. This parameter is optional, if it is not ' |
| 1267 'supplied, the script will work from the current depot.') |
1163 parser.add_option('--use_goma', | 1268 parser.add_option('--use_goma', |
1164 action="store_true", | 1269 action="store_true", |
1165 help='Add a bunch of extra threads for goma.') | 1270 help='Add a bunch of extra threads for goma.') |
1166 parser.add_option('--output_buildbot_annotations', | 1271 parser.add_option('--output_buildbot_annotations', |
1167 action="store_true", | 1272 action="store_true", |
1168 help='Add extra annotation output for buildbot.') | 1273 help='Add extra annotation output for buildbot.') |
1169 parser.add_option('--debug_ignore_build', | 1274 parser.add_option('--debug_ignore_build', |
1170 action="store_true", | 1275 action="store_true", |
1171 help='DEBUG: Don\'t perform builds.') | 1276 help='DEBUG: Don\'t perform builds.') |
1172 parser.add_option('--debug_ignore_sync', | 1277 parser.add_option('--debug_ignore_sync', |
(...skipping 27 matching lines...) Expand all Loading... |
1200 print | 1305 print |
1201 parser.print_help() | 1306 parser.print_help() |
1202 return 1 | 1307 return 1 |
1203 | 1308 |
1204 # Haven't tested the script out on any other platforms yet. | 1309 # Haven't tested the script out on any other platforms yet. |
1205 if not os.name in ['posix']: | 1310 if not os.name in ['posix']: |
1206 print "Sorry, this platform isn't supported yet." | 1311 print "Sorry, this platform isn't supported yet." |
1207 print | 1312 print |
1208 return 1 | 1313 return 1 |
1209 | 1314 |
| 1315 metric_values = opts.metric.split('/') |
| 1316 if len(metric_values) != 2: |
| 1317 print "Invalid metric specified: [%s]" % (opts.metric,) |
| 1318 print |
| 1319 return 1 |
| 1320 |
| 1321 if opts.working_directory: |
| 1322 if not CreateAndChangeToSourceDirectory(opts.working_directory): |
| 1323 print 'Error: Could not create bisect directory.' |
| 1324 print |
| 1325 return 1 |
| 1326 |
| 1327 if not SetupGitDepot(opts.output_buildbot_annotations): |
| 1328 print 'Error: Failed to grab source.' |
| 1329 print |
| 1330 return 1 |
| 1331 |
| 1332 os.chdir(os.path.join(os.getcwd(), 'src')) |
1210 | 1333 |
1211 # Check what source control method they're using. Only support git workflow | 1334 # Check what source control method they're using. Only support git workflow |
1212 # at the moment. | 1335 # at the moment. |
1213 source_control = DetermineAndCreateSourceControl() | 1336 source_control = DetermineAndCreateSourceControl() |
1214 | 1337 |
1215 if not source_control: | 1338 if not source_control: |
1216 print "Sorry, only the git workflow is supported at the moment." | 1339 print "Sorry, only the git workflow is supported at the moment." |
1217 print | 1340 print |
1218 return 1 | 1341 return 1 |
1219 | 1342 |
1220 # gClient sync seems to fail if you're not in master branch. | 1343 # gClient sync seems to fail if you're not in master branch. |
1221 if not source_control.IsInProperBranch() and not opts.debug_ignore_sync: | 1344 if not source_control.IsInProperBranch() and not opts.debug_ignore_sync: |
1222 print "You must switch to master branch to run bisection." | 1345 print "You must switch to master branch to run bisection." |
1223 print | 1346 print |
1224 return 1 | 1347 return 1 |
1225 | 1348 |
1226 metric_values = opts.metric.split('/') | |
1227 if len(metric_values) < 2: | |
1228 print "Invalid metric specified: [%s]" % (opts.metric,) | |
1229 print | |
1230 return 1 | |
1231 | |
1232 | |
1233 bisect_test = BisectPerformanceMetrics(source_control, opts) | 1349 bisect_test = BisectPerformanceMetrics(source_control, opts) |
1234 bisect_results = bisect_test.Run(opts.command, | 1350 bisect_results = bisect_test.Run(opts.command, |
1235 opts.bad_revision, | 1351 opts.bad_revision, |
1236 opts.good_revision, | 1352 opts.good_revision, |
1237 metric_values) | 1353 metric_values) |
1238 | 1354 |
1239 if not(bisect_results['error']): | 1355 if not(bisect_results['error']): |
1240 bisect_test.FormatAndPrintResults(bisect_results) | 1356 bisect_test.FormatAndPrintResults(bisect_results) |
1241 return 0 | 1357 return 0 |
1242 else: | 1358 else: |
1243 print 'Error: ' + bisect_results['error'] | 1359 print 'Error: ' + bisect_results['error'] |
1244 print | 1360 print |
1245 return 1 | 1361 return 1 |
1246 | 1362 |
1247 if __name__ == '__main__': | 1363 if __name__ == '__main__': |
1248 sys.exit(main()) | 1364 sys.exit(main()) |
OLD | NEW |