Changeset 56045 in webkit


Ignore:
Timestamp:
Mar 16, 2010 12:11:32 AM (14 years ago)
Author:
eric@webkit.org
Message:

2010-03-15 Eric Seidel <eric@webkit.org>

Reviewed by Adam Barth.

Add "what-broke" command for debugging when the tree broke
https://bugs.webkit.org/show_bug.cgi?id=36157

This is another step towards automated sheriffing of the webkit tree.
With this logic our scripts are able to determine what revision broke the
tree. Buildbot should do this for us, but unfortunately buildbot doesn't
expose this kind of aggregate information.

  • Scripts/webkitpy/buildbot.py:
    • Add new Builder and Build classes (which will eventually replace the custom dictionaries previously used).
    • Split out more network logic into _fetch methods which will eventually be their own class for mocking.
    • Use XMLRPC to communicate with the buildbot master instead of scraping build pages.
  • Scripts/webkitpy/buildbot_unittest.py:
    • Test the newly added buildbot classes.
  • Scripts/webkitpy/commands/queries.py:
    • Add an experimental what-broke command.
Location:
trunk/WebKitTools
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebKitTools/ChangeLog

    r56044 r56045  
     12010-03-15  Eric Seidel  <eric@webkit.org>
     2
     3        Reviewed by Adam Barth.
     4
     5        Add "what-broke" command for debugging when the tree broke
     6        https://bugs.webkit.org/show_bug.cgi?id=36157
     7
     8        This is another step towards automated sheriffing of the webkit tree.
     9        With this logic our scripts are able to determine what revision broke the
     10        tree.  Buildbot should do this for us, but unfortunately buildbot doesn't
     11        expose this kind of aggregate information.
     12
     13        * Scripts/webkitpy/buildbot.py:
     14         - Add new Builder and Build classes (which will eventually replace the custom dictionaries previously used).
     15         - Split out more network logic into _fetch methods which will eventually be their own class for mocking.
     16         - Use XMLRPC to communicate with the buildbot master instead of scraping build pages.
     17        * Scripts/webkitpy/buildbot_unittest.py:
     18         - Test the newly added buildbot classes.
     19        * Scripts/webkitpy/commands/queries.py:
     20         - Add an experimental what-broke command.
     21
    1222010-03-15  Daniel Bates  <dbates@rim.com>
    223
  • trunk/WebKitTools/Scripts/webkitpy/buildbot.py

    r55055 r56045  
    3030
    3131import re
     32import urllib
    3233import urllib2
     34import xmlrpclib
    3335
    3436# Import WebKit-specific modules.
     
    4042
    4143
    42 class BuildBot:
     44class Builder(object):
     45    def __init__(self, name, buildbot):
     46        self._name = unicode(name)
     47        self._buildbot = buildbot
     48        self._builds_cache = {}
     49
     50    def name(self):
     51        return self._name
     52
     53    def url(self):
     54        return "http://%s/builders/%s" % (self._buildbot.buildbot_host, urllib.quote(self._name))
     55
     56    def build(self, build_number):
     57        cached_build = self._builds_cache.get(build_number)
     58        if cached_build:
     59            return cached_build
     60
     61        build_dictionary = self._buildbot._fetch_xmlrpc_build_dictionary(self._name, build_number)
     62        if not build_dictionary:
     63            return None
     64        build = Build(build_dictionary, self)
     65        self._builds_cache[build_number] = build
     66        return build
     67
     68
     69class Build(object):
     70    def __init__(self, build_dictionary, builder):
     71        self._builder = builder
     72        # FIXME: Knowledge of the XMLRPC specifics should probably not go here.
     73        self._revision = int(build_dictionary['revision'])
     74        self._number = int(build_dictionary['number'])
     75        self._is_green = (build_dictionary['results'] == 0) # Undocumented, buildbot XMLRPC
     76
     77    def url(self):
     78        return "%s/builds/%s" % (self.builder().url(), self._number)
     79
     80    def builder(self):
     81        return self._builder
     82
     83    def revision(self):
     84        return self._revision
     85
     86    def is_green(self):
     87        return self._is_green
     88
     89    def previous_build(self):
     90        # previous_build() allows callers to avoid assuming build numbers are sequential.
     91        # They may not be sequential across all master changes, or when non-trunk builds are made.
     92        return self._builder.build(self._number - 1)
     93
     94
     95class BuildBot(object):
    4396
    4497    default_host = "build.webkit.org"
     
    4699    def __init__(self, host=default_host):
    47100        self.buildbot_host = host
    48         self.buildbot_server_url = "http://%s/" % self.buildbot_host
    49101
    50102        # If any Leopard builder/tester, Windows builder or Chromium builder is
     
    58110        ]
    59111
     112    # FIXME: This should create and return Buidler and Build objects instead
     113    # of a custom dictionary.
    60114    def _parse_builder_status_from_row(self, status_row):
    61115        # If WebKit's buildbot has an XMLRPC interface we could use, we could
     
    67121        name_link = status_cells[0].find('a')
    68122        builder['name'] = name_link.string
    69         # We could generate the builder_url from the name in a future version
    70         # of this code.
    71         builder['builder_url'] = self.buildbot_server_url + name_link['href']
    72123
    73124        status_link = status_cells[1].find('a')
     
    87138        builder['is_green'] = not re.search('fail',
    88139                                            status_cells[1].renderContents())
    89         # We could parse out the build number instead, but for now just store
    90         # the URL.
    91         builder['build_url'] = self.buildbot_server_url + status_link['href']
     140
     141        status_link_regexp = r"builders/(?P<builder_name>.*)/builds/(?P<build_number>\d+)"
     142        link_match = re.match(status_link_regexp, status_link['href'])
     143        builder['build_number'] = int(link_match.group("build_number"))
    92144
    93145        # We could parse out the current activity too.
    94 
    95146        return builder
    96147
     
    121172        return not self.red_core_builders()
    122173
     174    # FIXME: These _fetch methods should move to a networking class.
     175    def _fetch_xmlrpc_build_dictionary(self, builder_name, build_number):
     176        # The buildbot XMLRPC API is super-limited.
     177        # For one, you cannot fetch info on builds which are incomplete.
     178        proxy = xmlrpclib.ServerProxy("http://%s/xmlrpc" % self.buildbot_host)
     179        try:
     180            return proxy.getBuild(builder_name, int(build_number))
     181        except xmlrpclib.Fault, err:
     182            log("Error fetching data for build %s" % build_number)
     183            return None
     184
     185    def _fetch_one_box_per_builder(self):
     186        build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host
     187        return urllib2.urlopen(build_status_url)
     188
    123189    def builder_statuses(self):
    124         build_status_url = self.buildbot_server_url + 'one_box_per_builder'
    125         page = urllib2.urlopen(build_status_url)
    126         soup = BeautifulSoup(page)
    127 
    128190        builders = []
     191        soup = BeautifulSoup(self._fetch_one_box_per_builder())
    129192        status_table = soup.find('table')
    130193        for status_row in status_table.findAll('tr'):
     
    132195            builders.append(builder)
    133196        return builders
     197
     198    def builder_with_name(self, name):
     199        return Builder(name, self)
  • trunk/WebKitTools/Scripts/webkitpy/buildbot_unittest.py

    r55055 r56045  
    3333from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
    3434
     35
    3536class BuildBotTest(unittest.TestCase):
    3637
     
    5354    _expected_example_one_box_parsings = [
    5455        {
    55             'builder_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29',
    56             'build_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29/builds/3693',
    5756            'is_green': True,
     57            'build_number' : 3693,
    5858            'name': u'Windows Debug (Tests)',
    5959            'built_revision': 47380
    6060        },
    6161        {
    62             'builder_url': u'http://build.webkit.org/builders/SnowLeopard%20Intel%20Release',
    6362            'is_green': False,
    6463            'name': u'SnowLeopard Intel Release',
    6564        },
    6665        {
    67             'builder_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release',
    68             'build_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release/builds/654',
    6966            'is_green': False,
     67            'build_number' : 654,
    7068            'name': u'Qt Linux Release',
    7169            'built_revision': 47383
     
    152150        self.assertEquals(builders, expected_builders)
    153151
     152    def test_builder_with_name(self):
     153        buildbot = BuildBot()
     154
     155        builder = buildbot.builder_with_name("Test Builder")
     156        self.assertEqual(builder.name(), "Test Builder")
     157        self.assertEqual(builder.url(), "http://build.webkit.org/builders/Test%20Builder")
     158
     159        # Override _fetch_xmlrpc_build_dictionary function to not touch the network.
     160        def mock_fetch_xmlrpc_build_dictionary(self, build_number):
     161            build_dictionary = {
     162                "revision" : 2 * build_number,
     163                "number" : int(build_number),
     164                "results" : build_number % 2, # 0 means pass
     165            }
     166            return build_dictionary
     167        buildbot._fetch_xmlrpc_build_dictionary = mock_fetch_xmlrpc_build_dictionary
     168
     169        build = builder.build(10)
     170        self.assertEqual(build.builder(), builder)
     171        self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/10")
     172        self.assertEqual(build.revision(), 20)
     173        self.assertEqual(build.is_green(), True)
     174
     175        build = build.previous_build()
     176        self.assertEqual(build.builder(), builder)
     177        self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/9")
     178        self.assertEqual(build.revision(), 18)
     179        self.assertEqual(build.is_green(), False)
     180
     181
    154182if __name__ == '__main__':
    155183    unittest.main()
  • trunk/WebKitTools/Scripts/webkitpy/commands/queries.py

    r53414 r56045  
    104104            print patch_id
    105105
     106class WhatBroke(AbstractDeclarativeCommand):
     107    name = "what-broke"
     108    help_text = "Print the status of the %s buildbots" % BuildBot.default_host
     109    long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder
     110and displayes the status of each builder."""
     111
     112    def _longest_builder_name(self, builders):
     113        return max(map(lambda builder: len(builder["name"]), builders))
     114
     115    def _find_green_to_red_transition(self, builder_status, look_back_limit=30):
     116        # walk backwards until we find a green build
     117        builder = self.tool.buildbot.builder_with_name(builder_status["name"])
     118        red_build = builder.build(builder_status["build_number"])
     119        green_build = None
     120        look_back_count = 0
     121        while True:
     122            if look_back_count >= look_back_limit:
     123                break
     124            # Use a previous_build() method to avoid assuming build numbers are sequential.
     125            before_red_build = red_build.previous_build()
     126            if not before_red_build:
     127                break
     128            if before_red_build.is_green():
     129                green_build = before_red_build
     130                break
     131            red_build = before_red_build
     132            look_back_count += 1
     133        return (green_build, red_build)
     134
     135    def _print_builder_line(self, builder_name, max_name_width, status_message):
     136        print "%s : %s" % (builder_name.ljust(max_name_width), status_message)
     137
     138    def _print_status_for_builder(self, builder_status, name_width):
     139        # If the builder is green, print OK, exit.
     140        if builder_status["is_green"]:
     141            self._print_builder_line(builder_status["name"], name_width, "ok")
     142            return
     143
     144        (last_green_build, first_red_build) = self._find_green_to_red_transition(builder_status)
     145        if not last_green_build:
     146            self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: sometime before %s?)" % first_red_build.revision())
     147            return
     148
     149        suspect_revisions = range(first_red_build.revision(), last_green_build.revision(), -1)
     150        suspect_revisions.reverse()
     151        # FIXME: Parse reviewer and committer from red checkin
     152        self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: %s)" % suspect_revisions)
     153
     154    def execute(self, options, args, tool):
     155        builders = tool.buildbot.builder_statuses()
     156        longest_builder_name = self._longest_builder_name(builders)
     157        for builder in builders:
     158            self._print_status_for_builder(builder, name_width=longest_builder_name)
     159
    106160
    107161class TreeStatus(AbstractDeclarativeCommand):
Note: See TracChangeset for help on using the changeset viewer.