Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: tools/bisect-builds.py

Issue 6788015: bisect-builds.py: Use the continuous archive, rather than the snapshots. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: '' Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 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 """Snapshot Build Bisect Tool 6 """Snapshot Build Bisect Tool
7 7
8 This script bisects a snapshot archive using binary search. It starts at 8 This script bisects a snapshot archive using binary search. It starts at
9 a bad revision (it will try to guess HEAD) and asks for a last known-good 9 a bad revision (it will try to guess HEAD) and asks for a last known-good
10 revision. It will then binary search across this revision range by downloading, 10 revision. It will then binary search across this revision range by downloading,
11 unzipping, and opening Chromium for you. After testing the specific revision, 11 unzipping, and opening Chromium for you. After testing the specific revision,
12 it will ask you whether it is good or bad before continuing the search. 12 it will ask you whether it is good or bad before continuing the search.
13 """ 13 """
14 14
15 # Base URL to download snapshots from. 15 # Base URL to download snapshots from.
16 BUILD_BASE_URL = 'http://build.chromium.org/f/chromium/snapshots/' 16 BUILD_BASE_URL = 'http://build.chromium.org/f/chromium/continuous/'
17
18 # The index file that lists all the builds. This lives in BUILD_BASE_URL.
19 BUILD_INDEX_FILE = 'all_builds.txt'
17 20
18 # The type (platform) of the build archive. This is what's passed in to the 21 # The type (platform) of the build archive. This is what's passed in to the
19 # '-a/--archive' option. 22 # '-a/--archive' option.
20 BUILD_ARCHIVE_TYPE = '' 23 BUILD_ARCHIVE_TYPE = ''
21 24
22 # The selected archive to bisect. 25 # The location of the builds. Format this with a (date, revision) tuple, which
23 BUILD_ARCHIVE_DIR = '' 26 # can be obtained through ParseIndexLine().
Evan Martin 2011/05/02 18:29:17 Might be more maintainable to use something like
24 27 BUILD_ARCHIVE_URL = '/%s/%d/'
25 # The location of the builds.
26 BUILD_ARCHIVE_URL = '/%d/'
27 28
28 # Name of the build archive. 29 # Name of the build archive.
29 BUILD_ZIP_NAME = '' 30 BUILD_ZIP_NAME = ''
30 31
31 # Directory name inside the archive. 32 # Directory name inside the archive.
32 BUILD_DIR_NAME = '' 33 BUILD_DIR_NAME = ''
33 34
34 # Name of the executable. 35 # Name of the executable.
35 BUILD_EXE_NAME = '' 36 BUILD_EXE_NAME = ''
36 37
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
81 os.chmod(name, info.external_attr >> 16L) 82 os.chmod(name, info.external_attr >> 16L)
82 os.chdir(pushd) 83 os.chdir(pushd)
83 except Exception, e: 84 except Exception, e:
84 print >>sys.stderr, e 85 print >>sys.stderr, e
85 sys.exit(1) 86 sys.exit(1)
86 87
87 88
88 def SetArchiveVars(archive): 89 def SetArchiveVars(archive):
89 """Set a bunch of global variables appropriate for the specified archive.""" 90 """Set a bunch of global variables appropriate for the specified archive."""
90 global BUILD_ARCHIVE_TYPE 91 global BUILD_ARCHIVE_TYPE
91 global BUILD_ARCHIVE_DIR
92 global BUILD_ZIP_NAME 92 global BUILD_ZIP_NAME
93 global BUILD_DIR_NAME 93 global BUILD_DIR_NAME
94 global BUILD_EXE_NAME 94 global BUILD_EXE_NAME
95 global BUILD_BASE_URL 95 global BUILD_BASE_URL
96 96
97 BUILD_ARCHIVE_TYPE = archive 97 BUILD_ARCHIVE_TYPE = archive
98 BUILD_ARCHIVE_DIR = 'chromium-rel-' + BUILD_ARCHIVE_TYPE
99 98
100 if BUILD_ARCHIVE_TYPE in ('linux', 'linux-64', 'linux-chromiumos'): 99 if BUILD_ARCHIVE_TYPE in ('linux', 'linux64', 'linux-chromiumos'):
101 BUILD_ZIP_NAME = 'chrome-linux.zip' 100 BUILD_ZIP_NAME = 'chrome-linux.zip'
102 BUILD_DIR_NAME = 'chrome-linux' 101 BUILD_DIR_NAME = 'chrome-linux'
103 BUILD_EXE_NAME = 'chrome' 102 BUILD_EXE_NAME = 'chrome'
104 elif BUILD_ARCHIVE_TYPE in ('mac'): 103 elif BUILD_ARCHIVE_TYPE in ('mac'):
105 BUILD_ZIP_NAME = 'chrome-mac.zip' 104 BUILD_ZIP_NAME = 'chrome-mac.zip'
106 BUILD_DIR_NAME = 'chrome-mac' 105 BUILD_DIR_NAME = 'chrome-mac'
107 BUILD_EXE_NAME = 'Chromium.app/Contents/MacOS/Chromium' 106 BUILD_EXE_NAME = 'Chromium.app/Contents/MacOS/Chromium'
108 elif BUILD_ARCHIVE_TYPE in ('xp'): 107 elif BUILD_ARCHIVE_TYPE in ('xp'):
109 BUILD_ZIP_NAME = 'chrome-win32.zip' 108 BUILD_ZIP_NAME = 'chrome-win32.zip'
110 BUILD_DIR_NAME = 'chrome-win32' 109 BUILD_DIR_NAME = 'chrome-win32'
111 BUILD_EXE_NAME = 'chrome.exe' 110 BUILD_EXE_NAME = 'chrome.exe'
112 111
113 BUILD_BASE_URL += BUILD_ARCHIVE_DIR
114 112
115 def ParseDirectoryIndex(url): 113 def ParseDirectoryIndex(url):
116 """Parses the HTML directory listing into a list of revision numbers.""" 114 """Parses the all_builds.txt index file. The format of this file is:
115 mac/2011-02-16/75130
116 mac/2011-02-16/75218
117 mac/2011-02-16/75226
118 mac/2011-02-16/75234
119 mac/2011-02-16/75184
120 This function will return a list of DATE/REVISION strings for the platform
121 specified by BUILD_ARCHIVE_TYPE.
122 """
117 handle = urllib.urlopen(url) 123 handle = urllib.urlopen(url)
118 dirindex = handle.read() 124 dirindex = handle.readlines()
119 handle.close() 125 handle.close()
120 return re.findall(r'<a href="([0-9]*)/">\1/</a>', dirindex) 126
127 # Only return values for the specified platform. Include the trailing slash to
128 # not confuse linux and linux64.
129 archtype = BUILD_ARCHIVE_TYPE + '/'
130 dirindex = filter(lambda l: l.startswith(archtype), dirindex)
131
132 # Remove the newline separator and the platform token.
133 dirindex = map(lambda l: l[len(archtype):].strip(), dirindex)
134 dirindex.sort()
135 return dirindex
136
137
138 def ParseIndexLine(iline):
139 """Takes an index line returned by ParseDirectoryIndex() and returns a
140 2-tuple of (date, revision). |date| is a string and |revision| is an int."""
141 split = iline.split('/')
142 assert(len(split) == 2)
143 return (split[0], int(split[1]))
144
145
146 def GetRevision(iline):
147 """Takes an index line, parses it, and returns the revision."""
148 return ParseIndexLine(iline)[1]
149
121 150
122 def GetRevList(good, bad): 151 def GetRevList(good, bad):
123 """Gets the list of revision numbers between |good| and |bad|.""" 152 """Gets the list of revision numbers between |good| and |bad|."""
124 # Download the main revlist. 153 # Download the main revlist.
125 revlist = ParseDirectoryIndex(BUILD_BASE_URL) 154 revlist = ParseDirectoryIndex(BUILD_BASE_URL + BUILD_INDEX_FILE)
126 revlist = map(int, revlist) 155
127 revlist = filter(lambda r: range(good, bad).__contains__(int(r)), revlist) 156 revrange = range(good, bad)
157 revlist = filter(lambda r: GetRevision(r) in revrange, revlist)
128 revlist.sort() 158 revlist.sort()
129 return revlist 159 return revlist
130 160
131 def TryRevision(rev, profile, args): 161
132 """Downloads revision |rev|, unzips it, and opens it for the user to test. 162 def TryRevision(iline, profile, args):
133 |profile| is the profile to use.""" 163 """Downloads revision from |iline|, unzips it, and opens it for the user to
164 test. |profile| is the profile to use."""
134 # Do this in a temp dir so we don't collide with user files. 165 # Do this in a temp dir so we don't collide with user files.
135 cwd = os.getcwd() 166 cwd = os.getcwd()
136 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 167 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
137 os.chdir(tempdir) 168 os.chdir(tempdir)
138 169
139 # Download the file. 170 # Download the file.
140 download_url = BUILD_BASE_URL + (BUILD_ARCHIVE_URL % rev) + BUILD_ZIP_NAME 171 download_url = BUILD_BASE_URL + BUILD_ARCHIVE_TYPE + \
141 def _Reporthook(blocknum, blocksize, totalsize): 172 (BUILD_ARCHIVE_URL % ParseIndexLine(iline)) + BUILD_ZIP_NAME
173 def _ReportHook(blocknum, blocksize, totalsize):
142 size = blocknum * blocksize 174 size = blocknum * blocksize
143 if totalsize == -1: # Total size not known. 175 if totalsize == -1: # Total size not known.
144 progress = "Received %d bytes" % size 176 progress = "Received %d bytes" % size
145 else: 177 else:
146 size = min(totalsize, size) 178 size = min(totalsize, size)
147 progress = "Received %d of %d bytes, %.2f%%" % ( 179 progress = "Received %d of %d bytes, %.2f%%" % (
148 size, totalsize, 100.0 * size / totalsize) 180 size, totalsize, 100.0 * size / totalsize)
149 # Send a \r to let all progress messages use just one line of output. 181 # Send a \r to let all progress messages use just one line of output.
150 sys.stdout.write("\r" + progress) 182 sys.stdout.write("\r" + progress)
151 sys.stdout.flush() 183 sys.stdout.flush()
152 try: 184 try:
153 print 'Fetching ' + download_url 185 print 'Fetching ' + download_url
154 urllib.urlretrieve(download_url, BUILD_ZIP_NAME, _Reporthook) 186 urllib.urlretrieve(download_url, BUILD_ZIP_NAME, _ReportHook)
155 print 187 print
156 except Exception, e: 188 except Exception, e:
157 print('Could not retrieve the download. Sorry.') 189 print('Could not retrieve the download. Sorry.')
158 sys.exit(-1) 190 sys.exit(-1)
159 191
160 # Unzip the file. 192 # Unzip the file.
161 print 'Unzipping ...' 193 print 'Unzipping ...'
162 UnzipFilenameToDir(BUILD_ZIP_NAME, os.curdir) 194 UnzipFilenameToDir(BUILD_ZIP_NAME, os.curdir)
163 195
164 # Tell the system to open the app. 196 # Tell the system to open the app.
165 args = ['--user-data-dir=%s' % profile] + args 197 args = ['--user-data-dir=%s' % profile] + args
166 flags = ' '.join(map(pipes.quote, args)) 198 flags = ' '.join(map(pipes.quote, args))
167 exe = os.path.join(os.getcwd(), BUILD_DIR_NAME, BUILD_EXE_NAME) 199 exe = os.path.join(os.getcwd(), BUILD_DIR_NAME, BUILD_EXE_NAME)
168 cmd = '%s %s' % (exe, flags) 200 cmd = '%s %s' % (exe, flags)
169 print 'Running %s' % cmd 201 print 'Running %s' % cmd
170 os.system(cmd) 202 os.system(cmd)
171 203
172 os.chdir(cwd) 204 os.chdir(cwd)
173 print 'Cleaning temp dir ...' 205 print 'Cleaning temp dir ...'
174 try: 206 try:
175 shutil.rmtree(tempdir, True) 207 shutil.rmtree(tempdir, True)
176 except Exception, e: 208 except Exception, e:
177 pass 209 pass
178 210
179 211
180 def AskIsGoodBuild(rev): 212 def AskIsGoodBuild(iline):
181 """Ask the user whether build |rev| is good or bad.""" 213 """Ask the user whether build from index line |iline| is good or bad."""
182 # Loop until we get a response that we can parse. 214 # Loop until we get a response that we can parse.
183 while True: 215 while True:
184 response = raw_input('\nBuild %d is [(g)ood/(b)ad]: ' % int(rev)) 216 response = raw_input('\nBuild %d is [(g)ood/(b)ad]: ' % GetRevision(iline))
185 if response and response in ('g', 'b'): 217 if response and response in ('g', 'b'):
186 return response == 'g' 218 return response == 'g'
187 219
188 def main(): 220 def main():
189 usage = ('%prog [options] [-- chromium-options]\n' 221 usage = ('%prog [options] [-- chromium-options]\n'
190 'Perform binary search on the snapshot builds.\n' 222 'Perform binary search on the snapshot builds.\n'
191 '\n' 223 '\n'
192 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 224 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
193 parser = optparse.OptionParser(usage=usage) 225 parser = optparse.OptionParser(usage=usage)
194 # Strangely, the default help output doesn't include the choice list. 226 # Strangely, the default help output doesn't include the choice list.
195 choices = ['mac', 'xp', 'linux', 'linux-64', 'linux-chromiumos'] 227 choices = ['mac', 'xp', 'linux', 'linux64']
228 # 'linux-chromiumos' lacks a continuous archive http://crbug.com/781 58
Evan Martin 2011/05/02 18:29:17 80 cols
196 parser.add_option('-a', '--archive', 229 parser.add_option('-a', '--archive',
197 choices = choices, 230 choices = choices,
198 help = 'The buildbot archive to bisect [%s].' % 231 help = 'The buildbot archive to bisect [%s].' %
199 '|'.join(choices)) 232 '|'.join(choices))
200 parser.add_option('-b', '--bad', type = 'int', 233 parser.add_option('-b', '--bad', type = 'int',
201 help = 'The bad revision to bisect to.') 234 help = 'The bad revision to bisect to.')
202 parser.add_option('-g', '--good', type = 'int', 235 parser.add_option('-g', '--good', type = 'int',
203 help = 'The last known good revision to bisect from.') 236 help = 'The last known good revision to bisect from.')
204 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 237 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
205 help = 'Profile to use; this will not reset every run. ' + 238 help = 'Profile to use; this will not reset every run. ' +
(...skipping 14 matching lines...) Expand all
220 253
221 SetArchiveVars(opts.archive) 254 SetArchiveVars(opts.archive)
222 255
223 # Pick a starting point, try to get HEAD for this. 256 # Pick a starting point, try to get HEAD for this.
224 if opts.bad: 257 if opts.bad:
225 bad_rev = opts.bad 258 bad_rev = opts.bad
226 else: 259 else:
227 bad_rev = 0 260 bad_rev = 0
228 try: 261 try:
229 # Location of the latest build revision number 262 # Location of the latest build revision number
230 BUILD_LATEST_URL = '%s/LATEST' % (BUILD_BASE_URL) 263 BUILD_LATEST_URL = '%s/LATEST/REVISION' % (BUILD_BASE_URL)
231 nh = urllib.urlopen(BUILD_LATEST_URL) 264 nh = urllib.urlopen(BUILD_LATEST_URL)
232 latest = int(nh.read()) 265 latest = int(nh.read())
233 nh.close() 266 nh.close()
234 bad_rev = raw_input('Bad revision [HEAD:%d]: ' % latest) 267 bad_rev = raw_input('Bad revision [HEAD:%d]: ' % latest)
235 if (bad_rev == ''): 268 if (bad_rev == ''):
236 bad_rev = latest 269 bad_rev = latest
237 bad_rev = int(bad_rev) 270 bad_rev = int(bad_rev)
238 except Exception, e: 271 except Exception, e:
239 print('Could not determine latest revision. This could be bad...') 272 print('Could not determine latest revision. This could be bad...')
240 bad_rev = int(raw_input('Bad revision: ')) 273 bad_rev = int(raw_input('Bad revision: '))
(...skipping 24 matching lines...) Expand all
265 last_known_good_rev = revlist[good] 298 last_known_good_rev = revlist[good]
266 299
267 # Binary search time! 300 # Binary search time!
268 while good < bad: 301 while good < bad:
269 candidates = revlist[good:bad] 302 candidates = revlist[good:bad]
270 num_poss = len(candidates) 303 num_poss = len(candidates)
271 if num_poss > 10: 304 if num_poss > 10:
272 print('%d candidates. %d tries left.' % 305 print('%d candidates. %d tries left.' %
273 (num_poss, round(math.log(num_poss, 2)))) 306 (num_poss, round(math.log(num_poss, 2))))
274 else: 307 else:
275 print('Candidates: %s' % revlist[good:bad]) 308 print('Candidates: %s' % map(GetRevision, revlist[good:bad]))
276 309
277 # Cut the problem in half... 310 # Cut the problem in half...
278 test = int((bad - good) / 2) + good 311 test = int((bad - good) / 2) + good
279 test_rev = revlist[test] 312 test_rev = revlist[test]
280 313
281 # Let the user give this rev a spin (in her own profile, if she wants). 314 # Let the user give this rev a spin (in her own profile, if she wants).
282 profile = opts.profile 315 profile = opts.profile
283 if not profile: 316 if not profile:
284 profile = 'profile' # In a temp dir. 317 profile = 'profile' # In a temp dir.
285 TryRevision(test_rev, profile, args) 318 TryRevision(test_rev, profile, args)
286 if AskIsGoodBuild(test_rev): 319 if AskIsGoodBuild(test_rev):
287 last_known_good_rev = revlist[good] 320 last_known_good_rev = revlist[good]
288 good = test + 1 321 good = test + 1
289 else: 322 else:
290 bad = test 323 bad = test
291 324
292 # We're done. Let the user know the results in an official manner. 325 # We're done. Let the user know the results in an official manner.
293 print('You are probably looking for build %d.' % revlist[bad]) 326 bad_revision = GetRevision(revlist[bad])
327 print('You are probably looking for build %d.' % bad_revision)
294 print('CHANGELOG URL:') 328 print('CHANGELOG URL:')
295 print(CHANGELOG_URL % (last_known_good_rev, revlist[bad])) 329 print(CHANGELOG_URL % (GetRevision(last_known_good_rev), bad_revision))
296 print('Built at revision:') 330 print('Built at revision:')
297 print(BUILD_VIEWVC_URL % revlist[bad]) 331 print(BUILD_VIEWVC_URL % bad_revision)
298 332
299 if __name__ == '__main__': 333 if __name__ == '__main__':
300 sys.exit(main()) 334 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698