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 23431026: add the option to bisect blink builds as well and have blink revisions look in the REVISIONS file a… (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 3 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,
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 # The root URL for storage. 15 # The root URL for storage.
16 BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots' 16 CHROMIUM_BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-sn apshots'
17 WEBKIT_BASE_URL = 'http://commondatastorage.googleapis.com/chromium-webkit-snaps hots'
17 18
18 # The root URL for official builds. 19 # The root URL for official builds.
19 OFFICIAL_BASE_URL = 'http://master.chrome.corp.google.com/official_builds' 20 OFFICIAL_BASE_URL = 'http://master.chrome.corp.google.com/official_builds'
20 21
21 # Changelogs URL. 22 # Changelogs URL.
22 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ 23 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \
23 'perf/dashboard/ui/changelog.html?' \ 24 'perf/dashboard/ui/changelog.html?' \
24 'url=/trunk/src&range=%d%%3A%d' 25 'url=/trunk/src&range=%d%%3A%d'
25 26
26 # Official Changelogs URL. 27 # Official Changelogs URL.
27 OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\ 28 OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\
28 'changelog?old_version=%s&new_version=%s' 29 'changelog?old_version=%s&new_version=%s'
29 30
30 # DEPS file URL. 31 # DEPS file URL.
31 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' 32 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d'
32 # Blink Changelogs URL. 33 # Blink Changelogs URL.
33 BLINK_CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ 34 BLINK_CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \
34 'perf/dashboard/ui/changelog_blink.html?' \ 35 'perf/dashboard/ui/changelog_blink.html?' \
35 'url=/trunk&range=%d%%3A%d' 36 'url=/trunk&range=%d%%3A%d'
36 37
37 DONE_MESSAGE_GOOD_MIN = 'You are probably looking for a change made after %s ' \ 38 DONE_MESSAGE_GOOD_MIN = 'You are probably looking for a change made after %s ' \
38 '(known good), but no later than %s (first known bad).' 39 '(known good), but no later than %s (first known bad).'
39 DONE_MESSAGE_GOOD_MAX = 'You are probably looking for a change made after %s ' \ 40 DONE_MESSAGE_GOOD_MAX = 'You are probably looking for a change made after %s ' \
40 '(known bad), but no later than %s (first known good).' 41 '(known bad), but no later than %s (first known good).'
41 42
42 ############################################################################### 43 ###############################################################################
43 44
45 import json
44 import math 46 import math
45 import optparse 47 import optparse
46 import os 48 import os
47 import pipes 49 import pipes
48 import re 50 import re
49 import shutil 51 import shutil
50 import subprocess 52 import subprocess
51 import sys 53 import sys
52 import tempfile 54 import tempfile
53 import threading 55 import threading
54 import urllib 56 import urllib
55 from distutils.version import LooseVersion 57 from distutils.version import LooseVersion
56 from xml.etree import ElementTree 58 from xml.etree import ElementTree
57 import zipfile 59 import zipfile
58 60
59 61
60 class PathContext(object): 62 class PathContext(object):
61 """A PathContext is used to carry the information used to construct URLs and 63 """A PathContext is used to carry the information used to construct URLs and
62 paths when dealing with the storage server and archives.""" 64 paths when dealing with the storage server and archives."""
63 def __init__(self, platform, good_revision, bad_revision, is_official, 65 def __init__(self, base_url, platform, good_revision, bad_revision,
64 is_aura): 66 is_official, is_aura):
65 super(PathContext, self).__init__() 67 super(PathContext, self).__init__()
66 # Store off the input parameters. 68 # Store off the input parameters.
69 self.base_url = base_url
67 self.platform = platform # What's passed in to the '-a/--archive' option. 70 self.platform = platform # What's passed in to the '-a/--archive' option.
68 self.good_revision = good_revision 71 self.good_revision = good_revision
69 self.bad_revision = bad_revision 72 self.bad_revision = bad_revision
70 self.is_official = is_official 73 self.is_official = is_official
71 self.is_aura = is_aura 74 self.is_aura = is_aura
72 75
73 # The name of the ZIP file in a revision directory on the server. 76 # The name of the ZIP file in a revision directory on the server.
74 self.archive_name = None 77 self.archive_name = None
75 78
76 # Set some internal members: 79 # Set some internal members:
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 self._listing_platform_dir = 'Mac/' 123 self._listing_platform_dir = 'Mac/'
121 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' 124 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
122 elif self.platform == 'win': 125 elif self.platform == 'win':
123 self._listing_platform_dir = 'Win/' 126 self._listing_platform_dir = 'Win/'
124 127
125 def GetListingURL(self, marker=None): 128 def GetListingURL(self, marker=None):
126 """Returns the URL for a directory listing, with an optional marker.""" 129 """Returns the URL for a directory listing, with an optional marker."""
127 marker_param = '' 130 marker_param = ''
128 if marker: 131 if marker:
129 marker_param = '&marker=' + str(marker) 132 marker_param = '&marker=' + str(marker)
130 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \ 133 return self.base_url + '/?delimiter=/&prefix=' + \
131 marker_param 134 self._listing_platform_dir + marker_param
132 135
133 def GetDownloadURL(self, revision): 136 def GetDownloadURL(self, revision):
134 """Gets the download URL for a build archive of a specific revision.""" 137 """Gets the download URL for a build archive of a specific revision."""
135 if self.is_official: 138 if self.is_official:
136 return "%s/%s/%s%s" % ( 139 return "%s/%s/%s%s" % (
137 OFFICIAL_BASE_URL, revision, self._listing_platform_dir, 140 OFFICIAL_BASE_URL, revision, self._listing_platform_dir,
138 self.archive_name) 141 self.archive_name)
139 else: 142 else:
140 return "%s/%s%s/%s" % ( 143 return "%s/%s%s/%s" % (self.base_url, self._listing_platform_dir,
141 BASE_URL, self._listing_platform_dir, revision, self.archive_name) 144 revision, self.archive_name)
142 145
143 def GetLastChangeURL(self): 146 def GetLastChangeURL(self):
144 """Returns a URL to the LAST_CHANGE file.""" 147 """Returns a URL to the LAST_CHANGE file."""
145 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE' 148 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE'
146 149
147 def GetLaunchPath(self): 150 def GetLaunchPath(self):
148 """Returns a relative path (presumably from the archive extraction location) 151 """Returns a relative path (presumably from the archive extraction location)
149 that is used to run the executable.""" 152 that is used to run the executable."""
150 return os.path.join(self._archive_extract_dir, self._binary_name) 153 return os.path.join(self._archive_extract_dir, self._binary_name)
151 154
152 def IsAuraBuild(self, build): 155 def IsAuraBuild(self, build):
153 """Check the given build is Aura.""" 156 """Check the given build is Aura."""
154 return build.split('.')[3] == '1' 157 return build.split('.')[3] == '1'
155 158
(...skipping 253 matching lines...) Expand 10 before | Expand all | Expand 10 after
409 os.unlink(self.zipfile) 412 os.unlink(self.zipfile)
410 413
411 def WaitFor(self): 414 def WaitFor(self):
412 """Prints a message and waits for the download to complete. The download 415 """Prints a message and waits for the download to complete. The download
413 must have been started previously.""" 416 must have been started previously."""
414 print "Downloading revision %s..." % str(self.rev) 417 print "Downloading revision %s..." % str(self.rev)
415 self.progress_event.set() # Display progress of download. 418 self.progress_event.set() # Display progress of download.
416 self.thread.join() 419 self.thread.join()
417 420
418 421
419 def Bisect(platform, 422 def Bisect(base_url,
423 platform,
420 official_builds, 424 official_builds,
421 is_aura, 425 is_aura,
422 good_rev=0, 426 good_rev=0,
423 bad_rev=0, 427 bad_rev=0,
424 num_runs=1, 428 num_runs=1,
425 command="%p %a", 429 command="%p %a",
426 try_args=(), 430 try_args=(),
427 profile=None, 431 profile=None,
428 evaluate=AskIsGoodBuild): 432 evaluate=AskIsGoodBuild):
429 """Given known good and known bad revisions, run a binary search on all 433 """Given known good and known bad revisions, run a binary search on all
(...skipping 20 matching lines...) Expand all
450 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 454 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
451 is run on rev 75. 455 is run on rev 75.
452 456
453 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 457 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
454 is run on rev 25. 458 is run on rev 25.
455 """ 459 """
456 460
457 if not profile: 461 if not profile:
458 profile = 'profile' 462 profile = 'profile'
459 463
460 context = PathContext(platform, good_rev, bad_rev, official_builds, is_aura) 464 context = PathContext(base_url, platform, good_rev, bad_rev,
465 official_builds, is_aura)
461 cwd = os.getcwd() 466 cwd = os.getcwd()
462 467
463
464
465 print "Downloading list of known revisions..." 468 print "Downloading list of known revisions..."
466 _GetDownloadPath = lambda rev: os.path.join(cwd, 469 _GetDownloadPath = lambda rev: os.path.join(cwd,
467 '%s-%s' % (str(rev), context.archive_name)) 470 '%s-%s' % (str(rev), context.archive_name))
468 if official_builds: 471 if official_builds:
469 revlist = context.GetOfficialBuildsList() 472 revlist = context.GetOfficialBuildsList()
470 else: 473 else:
471 revlist = context.GetRevList() 474 revlist = context.GetRevList()
472 475
473 # Get a list of revisions to bisect across. 476 # Get a list of revisions to bisect across.
474 if len(revlist) < 2: # Don't have enough builds to bisect. 477 if len(revlist) < 2: # Don't have enough builds to bisect.
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after
599 except OSError: 602 except OSError:
600 pass 603 pass
601 sys.exit(0) 604 sys.exit(0)
602 605
603 rev = revlist[pivot] 606 rev = revlist[pivot]
604 607
605 return (revlist[minrev], revlist[maxrev]) 608 return (revlist[minrev], revlist[maxrev])
606 609
607 610
608 def GetBlinkRevisionForChromiumRevision(rev): 611 def GetBlinkRevisionForChromiumRevision(rev):
609 """Returns the blink revision that was in chromium's DEPS file at 612 """Returns the blink revision that was in REVISIONS file at
610 chromium revision |rev|.""" 613 chromium revision |rev|."""
611 # . doesn't match newlines without re.DOTALL, so this is safe. 614 # . doesn't match newlines without re.DOTALL, so this is safe.
612 blink_re = re.compile(r'webkit_revision.:\D*(\d+)') 615 file_url = "%s/%s%d/REVISIONS" % (self.base_url,
613 url = urllib.urlopen(DEPS_FILE % rev) 616 self._listing_platform_dir, rev)
614 m = blink_re.search(url.read()) 617 url = urllib.urlopen(file_url)
618 data = json.loads(url.read())
615 url.close() 619 url.close()
616 if m: 620 if data['webkit_revision']:
617 return int(m.group(1)) 621 return data['webkit_revision']
618 else: 622 else:
619 raise Exception('Could not get blink revision for cr rev %d' % rev) 623 raise Exception('Could not get webkit revision for cr rev %d' % rev)
620 624
621 625
622 def GetChromiumRevision(url): 626 def GetChromiumRevision(url):
623 """Returns the chromium revision read from given URL.""" 627 """Returns the chromium revision read from given URL."""
624 try: 628 try:
625 # Location of the latest build revision number 629 # Location of the latest build revision number
626 return int(urllib.urlopen(url).read()) 630 return int(urllib.urlopen(url).read())
627 except Exception, e: 631 except Exception, e:
628 print('Could not determine latest revision. This could be bad...') 632 print('Could not determine latest revision. This could be bad...')
629 return 999999999 633 return 999999999
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
673 help = 'Number of times to run each build before asking ' + 677 help = 'Number of times to run each build before asking ' +
674 'if it\'s good or bad. Temporary profiles are reused.', 678 'if it\'s good or bad. Temporary profiles are reused.',
675 default = 1) 679 default = 1)
676 parser.add_option('-c', '--command', type = 'str', 680 parser.add_option('-c', '--command', type = 'str',
677 help = 'Command to execute. %p and %a refer to Chrome ' + 681 help = 'Command to execute. %p and %a refer to Chrome ' +
678 'executable and specified extra arguments respectively. ' + 682 'executable and specified extra arguments respectively. ' +
679 'Use %s to specify all extra arguments as one string. ' + 683 'Use %s to specify all extra arguments as one string. ' +
680 'Defaults to "%p %a". Note that any extra paths ' + 684 'Defaults to "%p %a". Note that any extra paths ' +
681 'specified should be absolute.', 685 'specified should be absolute.',
682 default = '%p %a'); 686 default = '%p %a');
687 parser.add_option('-l', '--blink', action='store_true',
688 help = 'Use Blink bisect instead of Chromium. ')
683 parser.add_option('--aura', 689 parser.add_option('--aura',
684 dest='aura', 690 dest='aura',
685 action='store_true', 691 action='store_true',
686 default=False, 692 default=False,
687 help='Allow the script to bisect aura builds') 693 help='Allow the script to bisect aura builds')
688 694
689 (opts, args) = parser.parse_args() 695 (opts, args) = parser.parse_args()
690 696
691 if opts.archive is None: 697 if opts.archive is None:
692 print 'Error: missing required parameter: --archive' 698 print 'Error: missing required parameter: --archive'
693 print 699 print
694 parser.print_help() 700 parser.print_help()
695 return 1 701 return 1
696 702
697 if opts.aura: 703 if opts.aura:
698 if opts.archive != 'win' or not opts.official_builds: 704 if opts.archive != 'win' or not opts.official_builds:
699 print 'Error: Aura is supported only on Windows platform '\ 705 print 'Error: Aura is supported only on Windows platform '\
700 'and official builds.' 706 'and official builds.'
701 return 1 707 return 1
702 708
709 if opts.blink:
710 base_url = WEBKIT_BASE_URL
711 else:
712 base_url = CHROMIUM_BASE_URL
713
714
703 # Create the context. Initialize 0 for the revisions as they are set below. 715 # Create the context. Initialize 0 for the revisions as they are set below.
704 context = PathContext(opts.archive, 0, 0, opts.official_builds, opts.aura) 716 context = PathContext(base_url, opts.archive, 0, 0,
717 opts.official_builds, opts.aura)
705 # Pick a starting point, try to get HEAD for this. 718 # Pick a starting point, try to get HEAD for this.
706 if opts.bad: 719 if opts.bad:
707 bad_rev = opts.bad 720 bad_rev = opts.bad
708 else: 721 else:
709 bad_rev = '999.0.0.0' 722 bad_rev = '999.0.0.0'
710 if not opts.official_builds: 723 if not opts.official_builds:
711 bad_rev = GetChromiumRevision(context.GetLastChangeURL()) 724 bad_rev = GetChromiumRevision(context.GetLastChangeURL())
712 725
713 # Find out when we were good. 726 # Find out when we were good.
714 if opts.good: 727 if opts.good:
715 good_rev = opts.good 728 good_rev = opts.good
716 else: 729 else:
717 good_rev = '0.0.0.0' if opts.official_builds else 0 730 good_rev = '0.0.0.0' if opts.official_builds else 0
718 731
719 if opts.official_builds: 732 if opts.official_builds:
720 good_rev = LooseVersion(good_rev) 733 good_rev = LooseVersion(good_rev)
721 bad_rev = LooseVersion(bad_rev) 734 bad_rev = LooseVersion(bad_rev)
722 else: 735 else:
723 good_rev = int(good_rev) 736 good_rev = int(good_rev)
724 bad_rev = int(bad_rev) 737 bad_rev = int(bad_rev)
725 738
726 if opts.times < 1: 739 if opts.times < 1:
727 print('Number of times to run (%d) must be greater than or equal to 1.' % 740 print('Number of times to run (%d) must be greater than or equal to 1.' %
728 opts.times) 741 opts.times)
729 parser.print_help() 742 parser.print_help()
730 return 1 743 return 1
731 744
732 (min_chromium_rev, max_chromium_rev) = Bisect( 745 (min_chromium_rev, max_chromium_rev) = Bisect(
733 opts.archive, opts.official_builds, opts.aura, good_rev, bad_rev, 746 base_url, opts.archive, opts.official_builds, opts.aura, good_rev,
734 opts.times, opts.command, args, opts.profile) 747 bad_rev, opts.times, opts.command, args, opts.profile)
735 748
736 # Get corresponding blink revisions. 749 # Get corresponding blink revisions.
737 try: 750 try:
738 min_blink_rev = GetBlinkRevisionForChromiumRevision(min_chromium_rev) 751 min_blink_rev = GetBlinkRevisionForChromiumRevision(context,
Dirk Pranke 2013/09/17 00:42:18 I don't think you need (or want) the 'context' par
739 max_blink_rev = GetBlinkRevisionForChromiumRevision(max_chromium_rev) 752 min_chromium_rev)
753 max_blink_rev = GetBlinkRevisionForChromiumRevision(context,
754 max_chromium_rev)
740 except Exception, e: 755 except Exception, e:
741 # Silently ignore the failure. 756 # Silently ignore the failure.
742 min_blink_rev, max_blink_rev = 0, 0 757 min_blink_rev, max_blink_rev = 0, 0
743 758
744 # We're done. Let the user know the results in an official manner. 759 # We're done. Let the user know the results in an official manner.
745 if good_rev > bad_rev: 760 if good_rev > bad_rev:
746 print DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev), str(max_chromium_rev)) 761 print DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev), str(max_chromium_rev))
747 else: 762 else:
748 print DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev), str(max_chromium_rev)) 763 print DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev), str(max_chromium_rev))
749 764
750 if min_blink_rev != max_blink_rev: 765 if min_blink_rev != max_blink_rev:
751 print 'BLINK CHANGELOG URL:' 766 print 'BLINK CHANGELOG URL:'
752 print ' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev) 767 print ' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev)
753 print 'CHANGELOG URL:' 768 print 'CHANGELOG URL:'
754 if opts.official_builds: 769 if opts.official_builds:
755 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) 770 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
756 else: 771 else:
757 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) 772 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
758 773
759 if __name__ == '__main__': 774 if __name__ == '__main__':
760 sys.exit(main()) 775 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