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

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 # Keep only one revision before and after if no exact match found.
Nico 2012/07/20 18:04:02 Now the comment is no longer true, right?
Vitaly Buka (NO REVIEWS) 2012/07/20 18:11:25 Done.
232 if str(build_number) == minrev: 229 if build_number > maxrev:
233 start_index = i 230 break
234 if str(build_number) == maxrev: 231 if build_number >= minrev:
235 end_index = i 232 final_list.append(str(build_number))
236 except urllib.HTTPError, e: 233 except urllib.HTTPError, e:
237 pass 234 pass
238 return final_list[start_index:end_index] 235 return final_list
239 236
240 def UnzipFilenameToDir(filename, dir): 237 def UnzipFilenameToDir(filename, dir):
241 """Unzip |filename| to directory |dir|.""" 238 """Unzip |filename| to directory |dir|."""
242 cwd = os.getcwd() 239 cwd = os.getcwd()
243 if not os.path.isabs(filename): 240 if not os.path.isabs(filename):
244 filename = os.path.join(cwd, filename) 241 filename = os.path.join(cwd, filename)
245 zf = zipfile.ZipFile(filename) 242 zf = zipfile.ZipFile(filename)
246 # Make base. 243 # Make base.
247 try: 244 try:
248 if not os.path.isdir(dir): 245 if not os.path.isdir(dir):
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
437 if official_builds: 434 if official_builds:
438 revlist = context.GetOfficialBuildsList() 435 revlist = context.GetOfficialBuildsList()
439 else: 436 else:
440 revlist = context.GetRevList() 437 revlist = context.GetRevList()
441 438
442 # Get a list of revisions to bisect across. 439 # Get a list of revisions to bisect across.
443 if len(revlist) < 2: # Don't have enough builds to bisect. 440 if len(revlist) < 2: # Don't have enough builds to bisect.
444 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 441 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
445 raise RuntimeError(msg) 442 raise RuntimeError(msg)
446 443
444 print 'Bisecting range [%s, %s].' % (revlist[0], revlist[-1])
445
447 # Figure out our bookends and first pivot point; fetch the pivot revision. 446 # Figure out our bookends and first pivot point; fetch the pivot revision.
448 good = 0 447 good = 0
449 bad = len(revlist) - 1 448 bad = len(revlist) - 1
450 pivot = bad / 2 449 pivot = bad / 2
451 rev = revlist[pivot] 450 rev = revlist[pivot]
452 zipfile = _GetDownloadPath(rev) 451 zipfile = _GetDownloadPath(rev)
453 initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile) 452 initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile)
454 initial_fetch.Start() 453 initial_fetch.Start()
455 initial_fetch.WaitFor() 454 initial_fetch.WaitFor()
456 455
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
561 webkit_re = re.compile(r'webkit_revision.:\D*(\d+)') 560 webkit_re = re.compile(r'webkit_revision.:\D*(\d+)')
562 url = urllib.urlopen(DEPS_FILE % rev) 561 url = urllib.urlopen(DEPS_FILE % rev)
563 m = webkit_re.search(url.read()) 562 m = webkit_re.search(url.read())
564 url.close() 563 url.close()
565 if m: 564 if m:
566 return int(m.group(1)) 565 return int(m.group(1))
567 else: 566 else:
568 raise Exception('Could not get webkit revision for cr rev %d' % rev) 567 raise Exception('Could not get webkit revision for cr rev %d' % rev)
569 568
570 569
570 def GetChromiumRevision(url):
571 """Returns the chromium revision read from given URL."""
572 try:
573 # Location of the latest build revision number
574 return int(urllib.urlopen(url).read())
575 except Exception, e:
576 print('Could not determine latest revision. This could be bad...')
577 return 999999999
578
579
571 def main(): 580 def main():
572 usage = ('%prog [options] [-- chromium-options]\n' 581 usage = ('%prog [options] [-- chromium-options]\n'
573 'Perform binary search on the snapshot builds.\n' 582 'Perform binary search on the snapshot builds.\n'
574 '\n' 583 '\n'
575 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 584 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
576 parser = optparse.OptionParser(usage=usage) 585 parser = optparse.OptionParser(usage=usage)
577 # Strangely, the default help output doesn't include the choice list. 586 # Strangely, the default help output doesn't include the choice list.
578 choices = ['mac', 'win', 'linux', 'linux64'] 587 choices = ['mac', 'win', 'linux', 'linux64']
579 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 588 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
580 parser.add_option('-a', '--archive', 589 parser.add_option('-a', '--archive',
581 choices = choices, 590 choices = choices,
582 help = 'The buildbot archive to bisect [%s].' % 591 help = 'The buildbot archive to bisect [%s].' %
583 '|'.join(choices)) 592 '|'.join(choices))
584 parser.add_option('-o', action="store_true", dest='official_builds', 593 parser.add_option('-o', action="store_true", dest='official_builds',
585 help = 'Bisect across official ' + 594 help = 'Bisect across official ' +
586 'Chrome builds (internal only) instead of ' + 595 'Chrome builds (internal only) instead of ' +
587 'Chromium archives.') 596 'Chromium archives.')
588 parser.add_option('-b', '--bad', type = 'str', 597 parser.add_option('-b', '--bad', type = 'str',
589 help = 'The bad revision to bisect to.') 598 help = 'The bad revision to bisect to.')
Robert Sesek 2012/07/20 18:12:27 Update the help to say it defaults to HEAD.
Vitaly Buka (NO REVIEWS) 2012/07/20 18:20:12 Done.
590 parser.add_option('-g', '--good', type = 'str', 599 parser.add_option('-g', '--good', type = 'str',
591 help = 'The last known good revision to bisect from.') 600 help = 'The last known good revision to bisect from.')
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