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

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

Issue 10704213: Bisect official build with providing bad/good revisions. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 5 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/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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,
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after
209 """Gets the list of official build numbers between self.good_revision and 209 """Gets the list of official build numbers between self.good_revision and
210 self.bad_revision.""" 210 self.bad_revision."""
211 # Download the revlist and filter for just the range between good and bad. 211 # Download the revlist and filter for just the range between good and bad.
212 minrev = self.good_revision 212 minrev = self.good_revision
213 maxrev = self.bad_revision 213 maxrev = self.bad_revision
214 handle = urllib.urlopen(OFFICIAL_BASE_URL) 214 handle = urllib.urlopen(OFFICIAL_BASE_URL)
215 dirindex = handle.read() 215 dirindex = handle.read()
216 handle.close() 216 handle.close()
217 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex) 217 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
218 final_list = [] 218 final_list = []
219 start_index = 0
220 end_index = 0
221 i = 0 219 i = 0
222
223 parsed_build_numbers = [LooseVersion(x) for x in build_numbers] 220 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
224 for build_number in sorted(parsed_build_numbers): 221 for build_number in sorted(parsed_build_numbers):
225 path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \ 222 path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \
226 self._listing_platform_dir + self.archive_name 223 self._listing_platform_dir + self.archive_name
227 i = i + 1 224 i = i + 1
228 try: 225 try:
229 connection = urllib.urlopen(path) 226 connection = urllib.urlopen(path)
230 connection.close() 227 connection.close()
231 final_list.append(str(build_number)) 228 if build_number > maxrev:
232 if str(build_number) == minrev: 229 break
233 start_index = i 230 if build_number >= minrev:
234 if str(build_number) == maxrev: 231 final_list.append(str(build_number))
235 end_index = i
236 except urllib.HTTPError, e: 232 except urllib.HTTPError, e:
237 pass 233 pass
238 return final_list[start_index:end_index] 234 return final_list
239 235
240 def UnzipFilenameToDir(filename, dir): 236 def UnzipFilenameToDir(filename, dir):
241 """Unzip |filename| to directory |dir|.""" 237 """Unzip |filename| to directory |dir|."""
242 cwd = os.getcwd() 238 cwd = os.getcwd()
243 if not os.path.isabs(filename): 239 if not os.path.isabs(filename):
244 filename = os.path.join(cwd, filename) 240 filename = os.path.join(cwd, filename)
245 zf = zipfile.ZipFile(filename) 241 zf = zipfile.ZipFile(filename)
246 # Make base. 242 # Make base.
247 try: 243 try:
248 if not os.path.isdir(dir): 244 if not os.path.isdir(dir):
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
437 if official_builds: 433 if official_builds:
438 revlist = context.GetOfficialBuildsList() 434 revlist = context.GetOfficialBuildsList()
439 else: 435 else:
440 revlist = context.GetRevList() 436 revlist = context.GetRevList()
441 437
442 # Get a list of revisions to bisect across. 438 # Get a list of revisions to bisect across.
443 if len(revlist) < 2: # Don't have enough builds to bisect. 439 if len(revlist) < 2: # Don't have enough builds to bisect.
444 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 440 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
445 raise RuntimeError(msg) 441 raise RuntimeError(msg)
446 442
443 print 'Bisecting range [%s, %s].' % (revlist[0], revlist[-1])
444
447 # Figure out our bookends and first pivot point; fetch the pivot revision. 445 # Figure out our bookends and first pivot point; fetch the pivot revision.
448 good = 0 446 good = 0
449 bad = len(revlist) - 1 447 bad = len(revlist) - 1
450 pivot = bad / 2 448 pivot = bad / 2
451 rev = revlist[pivot] 449 rev = revlist[pivot]
452 zipfile = _GetDownloadPath(rev) 450 zipfile = _GetDownloadPath(rev)
453 initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile) 451 initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile)
454 initial_fetch.Start() 452 initial_fetch.Start()
455 initial_fetch.WaitFor() 453 initial_fetch.WaitFor()
456 454
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
561 webkit_re = re.compile(r'webkit_revision.:\D*(\d+)') 559 webkit_re = re.compile(r'webkit_revision.:\D*(\d+)')
562 url = urllib.urlopen(DEPS_FILE % rev) 560 url = urllib.urlopen(DEPS_FILE % rev)
563 m = webkit_re.search(url.read()) 561 m = webkit_re.search(url.read())
564 url.close() 562 url.close()
565 if m: 563 if m:
566 return int(m.group(1)) 564 return int(m.group(1))
567 else: 565 else:
568 raise Exception('Could not get webkit revision for cr rev %d' % rev) 566 raise Exception('Could not get webkit revision for cr rev %d' % rev)
569 567
570 568
569 def GetChromiumRevision(url):
570 """Returns the chromium revision read from given URL."""
571 try:
572 # Location of the latest build revision number
573 return int(urllib.urlopen(url).read())
574 except Exception, e:
575 print('Could not determine latest revision. This could be bad...')
576 return 999999999
577
578
571 def main(): 579 def main():
572 usage = ('%prog [options] [-- chromium-options]\n' 580 usage = ('%prog [options] [-- chromium-options]\n'
573 'Perform binary search on the snapshot builds.\n' 581 'Perform binary search on the snapshot builds.\n'
574 '\n' 582 '\n'
575 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 583 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
576 parser = optparse.OptionParser(usage=usage) 584 parser = optparse.OptionParser(usage=usage)
577 # Strangely, the default help output doesn't include the choice list. 585 # Strangely, the default help output doesn't include the choice list.
578 choices = ['mac', 'win', 'linux', 'linux64'] 586 choices = ['mac', 'win', 'linux', 'linux64']
579 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 587 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
580 parser.add_option('-a', '--archive', 588 parser.add_option('-a', '--archive',
581 choices = choices, 589 choices = choices,
582 help = 'The buildbot archive to bisect [%s].' % 590 help = 'The buildbot archive to bisect [%s].' %
583 '|'.join(choices)) 591 '|'.join(choices))
584 parser.add_option('-o', action="store_true", dest='official_builds', 592 parser.add_option('-o', action="store_true", dest='official_builds',
585 help = 'Bisect across official ' + 593 help = 'Bisect across official ' +
586 'Chrome builds (internal only) instead of ' + 594 'Chrome builds (internal only) instead of ' +
587 'Chromium archives.') 595 'Chromium archives.')
588 parser.add_option('-b', '--bad', type = 'str', 596 parser.add_option('-b', '--bad', type = 'str',
589 help = 'The bad revision to bisect to.') 597 help = 'The bad revision to bisect to. Default is HEAD.')
590 parser.add_option('-g', '--good', type = 'str', 598 parser.add_option('-g', '--good', type = 'str',
591 help = 'The last known good revision to bisect from.') 599 help = 'The last known good revision to bisect from. ' +
600 'Default is 0.')
592 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 601 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
593 help = 'Profile to use; this will not reset every run. ' + 602 help = 'Profile to use; this will not reset every run. ' +
594 'Defaults to a clean profile.', default = 'profile') 603 'Defaults to a clean profile.', default = 'profile')
595 parser.add_option('-t', '--times', type = 'int', 604 parser.add_option('-t', '--times', type = 'int',
596 help = 'Number of times to run each build before asking ' + 605 help = 'Number of times to run each build before asking ' +
597 'if it\'s good or bad. Temporary profiles are reused.', 606 'if it\'s good or bad. Temporary profiles are reused.',
598 default = 1) 607 default = 1)
599 (opts, args) = parser.parse_args() 608 (opts, args) = parser.parse_args()
600 609
601 if opts.archive is None: 610 if opts.archive is None:
602 print 'Error: missing required parameter: --archive' 611 print 'Error: missing required parameter: --archive'
603 print 612 print
604 parser.print_help() 613 parser.print_help()
605 return 1 614 return 1
606 615
607 if opts.bad and opts.good and (opts.good > opts.bad):
608 print ('The good revision (%d) must precede the bad revision (%d).\n' %
609 (opts.good, opts.bad))
610 parser.print_help()
611 return 1
612
613 # Create the context. Initialize 0 for the revisions as they are set below. 616 # Create the context. Initialize 0 for the revisions as they are set below.
614 context = PathContext(opts.archive, 0, 0, opts.official_builds) 617 context = PathContext(opts.archive, 0, 0, opts.official_builds)
615
616 if opts.official_builds and opts.bad is None:
617 print >>sys.stderr, 'Bisecting official builds requires a bad build number.'
618 parser.print_help()
619 return 1
620
621 # Pick a starting point, try to get HEAD for this. 618 # Pick a starting point, try to get HEAD for this.
622 if opts.bad: 619 if opts.bad:
623 bad_rev = opts.bad 620 bad_rev = opts.bad
624 else: 621 else:
625 bad_rev = 0 622 bad_rev = '999.0.0.0'
626 try: 623 if not opts.official_builds:
627 # Location of the latest build revision number 624 bad_rev = GetChromiumRevision(context.GetLastChangeURL())
628 nh = urllib.urlopen(context.GetLastChangeURL())
629 latest = int(nh.read())
630 nh.close()
631 bad_rev = raw_input('Bad revision [HEAD:%d]: ' % latest)
632 if (bad_rev == ''):
633 bad_rev = latest
634 bad_rev = int(bad_rev)
635 except Exception, e:
636 print('Could not determine latest revision. This could be bad...')
637 bad_rev = int(raw_input('Bad revision: '))
638 625
639 # Find out when we were good. 626 # Find out when we were good.
640 if opts.good: 627 if opts.good:
641 good_rev = opts.good 628 good_rev = opts.good
642 else: 629 else:
643 good_rev = 0 630 good_rev = '0.0.0.0' if opts.official_builds else 0
644 try: 631
645 good_rev = int(raw_input('Last known good [0]: ')) 632 if opts.official_builds:
646 except Exception, e: 633 good_rev = LooseVersion(good_rev)
647 pass 634 bad_rev = LooseVersion(bad_rev)
635 else:
636 good_rev = int(good_rev)
637 bad_rev = int(bad_rev)
638
639 if good_rev > bad_rev:
640 print ('The good revision (%s) must precede the bad revision (%s).\n' %
641 (good_rev, bad_rev))
642 parser.print_help()
643 return 1
648 644
649 if opts.times < 1: 645 if opts.times < 1:
650 print('Number of times to run (%d) must be greater than or equal to 1.' % 646 print('Number of times to run (%d) must be greater than or equal to 1.' %
651 opts.times) 647 opts.times)
652 parser.print_help() 648 parser.print_help()
653 return 1 649 return 1
654 650
655 (last_known_good_rev, first_known_bad_rev) = Bisect( 651 (last_known_good_rev, first_known_bad_rev) = Bisect(
656 opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, args, 652 opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, args,
657 opts.profile) 653 opts.profile)
(...skipping 15 matching lines...) Expand all
673 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 669 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
674 last_known_good_webkit_rev) 670 last_known_good_webkit_rev)
675 print 'CHANGELOG URL:' 671 print 'CHANGELOG URL:'
676 if opts.official_builds: 672 if opts.official_builds:
677 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 673 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
678 else: 674 else:
679 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 675 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
680 676
681 if __name__ == '__main__': 677 if __name__ == '__main__':
682 sys.exit(main()) 678 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