[Checkins] SVN: zopetoolkit/doc/src/ztksphinx.py Query buildbots in parallel.

Marius Gedminas marius at pov.lt
Thu Oct 7 15:03:39 EDT 2010


Log message for revision 117372:
  Query buildbots in parallel.
  
  Helps somewhat (28s -> 17s), but not too much: winbot.zope.org takes 17s
  to process all XML-RPC requests.  It doesn't matter if they're sequential or
  concurrent; looks like Twisted serializes them all on the server side.
  
  http://winbot.zope.org/builders returns all build status in 0.6 seconds,
  so switching to screen-scraping HTML would be a win, latency-wise.
  
  

Changed:
  U   zopetoolkit/doc/src/ztksphinx.py

-=-
Modified: zopetoolkit/doc/src/ztksphinx.py
===================================================================
--- zopetoolkit/doc/src/ztksphinx.py	2010-10-07 18:51:40 UTC (rev 117371)
+++ zopetoolkit/doc/src/ztksphinx.py	2010-10-07 19:03:38 UTC (rev 117372)
@@ -1,11 +1,13 @@
-from random import random
 from docutils import nodes
-from sphinx.util.compat import Directive, make_admonition
+from sphinx.util.compat import Directive
 from xmlrpclib import ServerProxy
 
+import time
 import urllib
 import socket
+import threading
 
+
 def setup(app):
     app.add_config_value('buildbot_check', True, 'html')
     app.add_node(BuildbotColor,
@@ -13,6 +15,7 @@
     app.connect('doctree-resolved', process_buildbot_nodes)
     app.add_directive('buildbotresult', BuildbotDirective)
 
+
 class BuildbotDirective(Directive):
 
     has_content = True
@@ -29,37 +32,82 @@
 
         return [targetnode]
 
+
 def process_buildbot_nodes(app, doctree, fromdocname):
-    if app.config.buildbot_check:
-        socket_timeout = socket.getdefaulttimeout()
-        try:
-            socket.setdefaulttimeout(min(socket_timeout, 5))
-            for node in doctree.traverse(BuildbotColor):
-                buildbot_url = node.buildbot_url
-                try:
-                    buildbot_result = getBuildbotResult(buildbot_url)
-                except Exception, e:
-                    node.css_class = 'tests_could_not_determine'
-                    node.title = '%s: %s' % (e.__class__.__name__, e)
-                else:
-                    if buildbot_result:
-                        node.css_class = 'tests_passed'
-                    else:
-                        node.css_class = 'tests_not_passed'
-        finally:
-            socket.setdefaulttimeout(socket_timeout)
+    if not app.config.buildbot_check:
+        return
+    socket_timeout = socket.getdefaulttimeout()
+    try:
+        socket.setdefaulttimeout(min(socket_timeout, 5))
+        by_url = {}
+        for node in doctree.traverse(BuildbotColor):
+            url, builder = parse_builder_url(node.buildbot_url)
+            by_url.setdefault(url, []).append((node, builder))
+        jobs = []
+        for url, nodes in by_url.items():
+            thread = threading.Thread(target=update_buildbot_nodes,
+                                      args=(url, nodes),
+                                      name=url)
+            thread.start()
+            jobs.append(thread)
+        for thread in jobs:
+            thread.join()
+    finally:
+        socket.setdefaulttimeout(socket_timeout)
 
-def getBuildbotResult(url):
-    # url should end with '/buildbot/builders/$buildername'
+
+def parse_builder_url(url):
+    """Parse a builder URL into buildbot URL and builder name."""
     url = url.rstrip('/') # make sure trailing slashes don't cause failures
     xmlrpc_url = '/'.join(url.split('/')[:-2] + ['xmlrpc'])
     builder = urllib.unquote(url.split('/')[-1])
-    xmlrpc = ServerProxy(xmlrpc_url)
-    return xmlrpc.getLastBuildResults(builder) == 'success'
+    return xmlrpc_url, builder
 
+
+def update_buildbot_nodes(url, nodes_and_builders):
+    """Get build status of a number of builders and update document nodes.
+
+    ``nodes_and_builders`` is a list of tuples (node, builder_name).
+    """
+    results = get_buildbot_results(url, [b for n, b in nodes_and_builders])
+    for node, builder in nodes_and_builders:
+        result = results[builder]
+        if isinstance(result, Exception):
+            node.css_class = 'tests_could_not_determine'
+            node.title = '%s: %s' % (result.__class__.__name__, result)
+        elif result:
+            node.css_class = 'tests_passed'
+        else:
+            node.css_class = 'tests_not_passed'
+
+
+def get_buildbot_results(xmlrpc_url, builders):
+    """Return build status of a number of builders.
+
+    ``builders`` is a list of builder names.
+
+    Returns a dictionary mapping builder names to True/False or exception
+    objects, in case of errors.
+    """
+    start = time.time()
+    try:
+        xmlrpc = ServerProxy(xmlrpc_url)
+    except Exception, e:
+        return dict.fromkeys(builders, e)
+    results = {}
+    for builder in builders:
+        try:
+            results[builder] = (xmlrpc.getLastBuildResults(builder) == 'success')
+        except Exception, e:
+            results[builder] = e
+    print xmlrpc_url, '%.3fs' % (time.time() - start)
+    return results
+
+
 class BuildbotColor(nodes.Inline, nodes.TextElement):
     pass
 
+
 def visit_buildbot_node(self, node):
     kwargs = {'href' : node.buildbot_url}
     css_class = getattr(node, 'css_class', '')
@@ -71,6 +119,7 @@
     self.body.append(self.starttag(node, 'a', **kwargs))
     self.body.append(node.text)
 
+
 def depart_buildbot_node(self, node):
     self.body.append('</a>')
 



More information about the checkins mailing list