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

Unified Diff: commit-queue/buildbot_json.py

Issue 135363007: Delete public commit queue to avoid confusion after move to internal repo (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 6 years, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « commit-queue/async_push.py ('k') | commit-queue/codereview.settings » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: commit-queue/buildbot_json.py
===================================================================
--- commit-queue/buildbot_json.py (revision 249146)
+++ commit-queue/buildbot_json.py (working copy)
@@ -1,1455 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file at
-# http://src.chromium.org/viewvc/chrome/trunk/src/LICENSE
-# This file is NOT under GPL.
-
-"""Queries buildbot through the json interface.
-"""
-
-__author__ = 'maruel@chromium.org'
-__version__ = '1.2'
-
-import code
-import datetime
-import functools
-import json
-import logging
-import optparse
-import time
-import urllib
-import urllib2
-import sys
-
-try:
- from natsort import natsorted
-except ImportError:
- # natsorted is a simple helper to sort "naturally", e.g. "vm40" is sorted
- # after "vm7". Defaults to normal sorting.
- natsorted = sorted
-
-
-# These values are buildbot constants used for Build and BuildStep.
-# This line was copied from master/buildbot/status/builder.py.
-SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6)
-
-
-## Generic node caching code.
-
-
-class Node(object):
- """Root class for all nodes in the graph.
-
- Provides base functionality for any node in the graph, independent if it has
- children or not or if its content can be addressed through an url or needs to
- be fetched as part of another node.
-
- self.printable_attributes is only used for self documentation and for str()
- implementation.
- """
- printable_attributes = []
-
- def __init__(self, parent, url):
- self.printable_attributes = self.printable_attributes[:]
- if url:
- self.printable_attributes.append('url')
- url = url.rstrip('/')
- if parent is not None:
- self.printable_attributes.append('parent')
- self.url = url
- self.parent = parent
-
- def __str__(self):
- return self.to_string()
-
- def __repr__(self):
- """Embeds key if present."""
- key = getattr(self, 'key', None)
- if key is not None:
- return '<%s key=%s>' % (self.__class__.__name__, key)
- cached_keys = getattr(self, 'cached_keys', None)
- if cached_keys is not None:
- return '<%s keys=%s>' % (self.__class__.__name__, cached_keys)
- return super(Node, self).__repr__()
-
- def to_string(self, maximum=100):
- out = ['%s:' % self.__class__.__name__]
- assert not 'printable_attributes' in self.printable_attributes
-
- def limit(txt):
- txt = str(txt)
- if maximum > 0:
- if len(txt) > maximum + 2:
- txt = txt[:maximum] + '...'
- return txt
-
- for k in sorted(self.printable_attributes):
- if k == 'parent':
- # Avoid infinite recursion.
- continue
- out.append(limit(' %s: %r' % (k, getattr(self, k))))
- return '\n'.join(out)
-
- def refresh(self):
- """Refreshes the data."""
- self.discard()
- return self.cache()
-
- def cache(self): # pragma: no cover
- """Caches the data."""
- raise NotImplementedError()
-
- def discard(self): # pragma: no cover
- """Discards cached data.
-
- Pretty much everything is temporary except completed Build.
- """
- raise NotImplementedError()
-
- def read_non_json(self, suburl):
- """Returns raw data for a suburl.
-
- Contrary to self.read(), self.read_non_json() is always available since
- suburl is rooted at the base url. read() is only accessible for resources
- that have an URI.
- """
- return self.parent.read_non_json(suburl)
-
-
-class AddressableBaseDataNode(Node): # pylint: disable=W0223
- """A node that contains a dictionary of data that can be fetched with an url.
-
- The node is directly addressable. It also often can be fetched by the parent.
- """
- printable_attributes = Node.printable_attributes + ['data']
-
- def __init__(self, parent, url, data):
- super(AddressableBaseDataNode, self).__init__(parent, url)
- self._data = data
-
- @property
- def cached_data(self):
- return self._data
-
- @property
- def data(self):
- self.cache()
- return self._data
-
- def cache(self):
- if self._data is None:
- self._data = self._readall()
- return True
- return False
-
- def discard(self):
- self._data = None
-
- def read(self, suburl):
- assert self.url, self.__class__.__name__
- url = self.url
- if suburl:
- url = '%s/%s' % (self.url, suburl)
- return self.parent.read(url)
-
- def _readall(self):
- return self.read('')
-
-
-class AddressableDataNode(AddressableBaseDataNode): # pylint: disable=W0223
- """Automatically encodes the url."""
-
- def __init__(self, parent, url, data):
- super(AddressableDataNode, self).__init__(parent, urllib.quote(url), data)
-
-
-class NonAddressableDataNode(Node): # pylint: disable=W0223
- """A node that cannot be addressed by an unique url.
-
- The data comes directly from the parent.
- """
- def __init__(self, parent, subkey):
- super(NonAddressableDataNode, self).__init__(parent, None)
- self.subkey = subkey
-
- @property
- def cached_data(self):
- if self.parent.cached_data is None:
- return None
- return self.parent.cached_data[self.subkey]
-
- @property
- def data(self):
- return self.parent.data[self.subkey]
-
- def cache(self):
- self.parent.cache()
-
- def discard(self): # pragma: no cover
- """Avoid invalid state when parent recreate the object."""
- raise AttributeError('Call parent discard() instead')
-
-
-class VirtualNodeList(Node):
- """Base class for every node that has children.
-
- Adds partial supports for keys and iterator functionality. 'key' can be a
- string or a int. Not to be used directly.
- """
- printable_attributes = Node.printable_attributes + ['keys']
-
- def __init__(self, parent, url):
- super(VirtualNodeList, self).__init__(parent, url)
- # Keeps the keys independently when ordering is needed.
- self._is_cached = False
- self._has_keys_cached = False
-
- def __contains__(self, key):
- """Enables 'if i in obj:'."""
- return key in self.keys
-
- def __iter__(self):
- """Enables 'for i in obj:'. It returns children."""
- self.cache_keys()
- for key in self.keys:
- yield self[key]
-
- def __len__(self):
- """Enables 'len(obj)' to get the number of childs."""
- return len(self.keys)
-
- def discard(self):
- """Discards data.
-
- The default behavior is to not invalidate cached keys. The only place where
- keys need to be invalidated is with Builds.
- """
- self._is_cached = False
- self._has_keys_cached = False
-
- @property
- def cached_children(self): # pragma: no cover
- """Returns an iterator over the children that are cached."""
- raise NotImplementedError()
-
- @property
- def cached_keys(self): # pragma: no cover
- raise NotImplementedError()
-
- @property
- def keys(self): # pragma: no cover
- """Returns the keys for every children."""
- raise NotImplementedError()
-
- def __getitem__(self, key): # pragma: no cover
- """Returns a child, without fetching its data.
-
- The children could be invalid since no verification is done.
- """
- raise NotImplementedError()
-
- def cache(self): # pragma: no cover
- """Cache all the children."""
- raise NotImplementedError()
-
- def cache_keys(self): # pragma: no cover
- """Cache all children's keys."""
- raise NotImplementedError()
-
-
-class NodeList(VirtualNodeList): # pylint: disable=W0223
- """Adds a cache of the keys."""
- def __init__(self, parent, url):
- super(NodeList, self).__init__(parent, url)
- self._keys = []
-
- @property
- def cached_keys(self):
- return self._keys
-
- @property
- def keys(self):
- self.cache_keys()
- return self._keys
-
-
-class NonAddressableNodeList(VirtualNodeList): # pylint: disable=W0223
- """A node that contains children but retrieves all its data from its parent.
-
- I.e. there's no url to get directly this data.
- """
- # Child class object for children of this instance. For example, BuildSteps
- # has BuildStep children.
- _child_cls = None
-
- def __init__(self, parent, subkey):
- super(NonAddressableNodeList, self).__init__(parent, None)
- self.subkey = subkey
- assert (
- not isinstance(self._child_cls, NonAddressableDataNode) and
- issubclass(self._child_cls, NonAddressableDataNode)), (
- self._child_cls.__name__)
-
- @property
- def cached_children(self):
- if self.parent.cached_data is not None:
- for i in xrange(len(self.parent.cached_data[self.subkey])):
- yield self[i]
-
- @property
- def cached_data(self):
- if self.parent.cached_data is None:
- return None
- return self.parent.data.get(self.subkey, None)
-
- @property
- def cached_keys(self):
- if self.parent.cached_data is None:
- return None
- return range(len(self.parent.data.get(self.subkey, [])))
-
- @property
- def data(self):
- return self.parent.data[self.subkey]
-
- def cache(self):
- self.parent.cache()
-
- def cache_keys(self):
- self.parent.cache()
-
- def discard(self): # pragma: no cover
- """Avoid infinite recursion by having the caller calls the parent's
- discard() explicitely.
- """
- raise AttributeError('Call parent discard() instead')
-
- def __iter__(self):
- """Enables 'for i in obj:'. It returns children."""
- if self.data:
- for i in xrange(len(self.data)):
- yield self[i]
-
- def __getitem__(self, key):
- """Doesn't cache the value, it's not needed.
-
- TODO(maruel): Cache?
- """
- if isinstance(key, int) and key < 0:
- key = len(self.data) + key
- # pylint: disable=E1102
- return self._child_cls(self, key)
-
-
-class AddressableNodeList(NodeList):
- """A node that has children that can be addressed with an url."""
-
- # Child class object for children of this instance. For example, Builders has
- # Builder children and Builds has Build children.
- _child_cls = None
-
- def __init__(self, parent, url):
- super(AddressableNodeList, self).__init__(parent, url)
- self._cache = {}
- assert (
- not isinstance(self._child_cls, AddressableDataNode) and
- issubclass(self._child_cls, AddressableDataNode)), (
- self._child_cls.__name__)
-
- @property
- def cached_children(self):
- for item in self._cache.itervalues():
- if item.cached_data is not None:
- yield item
-
- @property
- def cached_keys(self):
- return self._cache.keys()
-
- def __getitem__(self, key):
- """Enables 'obj[i]'."""
- if self._has_keys_cached and not key in self._keys:
- raise KeyError(key)
-
- if not key in self._cache:
- # Create an empty object.
- self._create_obj(key, None)
- return self._cache[key]
-
- def cache(self):
- if not self._is_cached:
- data = self._readall()
- for key in sorted(data):
- self._create_obj(key, data[key])
- self._is_cached = True
- self._has_keys_cached = True
-
- def cache_partial(self, children):
- """Caches a partial number of children.
-
- This method is more efficient since it does a single request for all the
- children instead of one request per children.
-
- It only grab objects not already cached.
- """
- # pylint: disable=W0212
- if not self._is_cached:
- to_fetch = [
- child for child in children
- if not (child in self._cache and self._cache[child].cached_data)
- ]
- if to_fetch:
- # Similar to cache(). The only reason to sort is to simplify testing.
- params = '&'.join(
- 'select=%s' % urllib.quote(str(v)) for v in sorted(to_fetch))
- data = self.read('?' + params)
- for key in sorted(data):
- self._create_obj(key, data[key])
-
- def cache_keys(self):
- """Implement to speed up enumeration. Defaults to call cache()."""
- if not self._has_keys_cached:
- self.cache()
- assert self._has_keys_cached
-
- def discard(self):
- """Discards temporary children."""
- super(AddressableNodeList, self).discard()
- for v in self._cache.itervalues():
- v.discard()
-
- def read(self, suburl):
- assert self.url, self.__class__.__name__
- url = self.url
- if suburl:
- url = '%s/%s' % (self.url, suburl)
- return self.parent.read(url)
-
- def _create_obj(self, key, data):
- """Creates an object of type self._child_cls."""
- # pylint: disable=E1102
- obj = self._child_cls(self, key, data)
- # obj.key and key may be different.
- # No need to overide cached data with None.
- if data is not None or obj.key not in self._cache:
- self._cache[obj.key] = obj
- if obj.key not in self._keys:
- self._keys.append(obj.key)
-
- def _readall(self):
- return self.read('')
-
-
-class SubViewNodeList(VirtualNodeList): # pylint: disable=W0223
- """A node that shows a subset of children that comes from another structure.
-
- The node is not addressable.
-
- E.g. the keys are retrieved from parent but the actual data comes from
- virtual_parent.
- """
-
- def __init__(self, parent, virtual_parent, subkey):
- super(SubViewNodeList, self).__init__(parent, None)
- self.subkey = subkey
- self.virtual_parent = virtual_parent
- assert isinstance(self.parent, AddressableDataNode)
- assert isinstance(self.virtual_parent, NodeList)
-
- @property
- def cached_children(self):
- if self.parent.cached_data is not None:
- for item in self.keys:
- if item in self.virtual_parent.keys:
- child = self[item]
- if child.cached_data is not None:
- yield child
-
- @property
- def cached_keys(self):
- return (self.parent.cached_data or {}).get(self.subkey, [])
-
- @property
- def keys(self):
- self.cache_keys()
- return self.parent.data.get(self.subkey, [])
-
- def cache(self):
- """Batch request for each child in a single read request."""
- if not self._is_cached:
- self.virtual_parent.cache_partial(self.keys)
- self._is_cached = True
-
- def cache_keys(self):
- if not self._has_keys_cached:
- self.parent.cache()
- self._has_keys_cached = True
-
- def discard(self):
- if self.parent.cached_data is not None:
- for child in self.virtual_parent.cached_children:
- if child.key in self.keys:
- child.discard()
- self.parent.discard()
- super(SubViewNodeList, self).discard()
-
- def __getitem__(self, key):
- """Makes sure the key is in our key but grab it from the virtual parent."""
- return self.virtual_parent[key]
-
- def __iter__(self):
- self.cache()
- return super(SubViewNodeList, self).__iter__()
-
-
-###############################################################################
-## Buildbot-specific code
-
-
-class Slave(AddressableDataNode):
- printable_attributes = AddressableDataNode.printable_attributes + [
- 'name', 'key', 'connected', 'version',
- ]
-
- def __init__(self, parent, name, data):
- super(Slave, self).__init__(parent, name, data)
- self.name = name
- self.key = self.name
- # TODO(maruel): Add SlaveBuilders and a 'builders' property.
- # TODO(maruel): Add a 'running_builds' property.
-
- @property
- def connected(self):
- return self.data.get('connected', False)
-
- @property
- def version(self):
- return self.data.get('version')
-
-
-class Slaves(AddressableNodeList):
- _child_cls = Slave
- printable_attributes = AddressableNodeList.printable_attributes + ['names']
-
- def __init__(self, parent):
- super(Slaves, self).__init__(parent, 'slaves')
-
- @property
- def names(self):
- return self.keys
-
-
-class BuilderSlaves(SubViewNodeList):
- """Similar to Slaves but only list slaves connected to a specific builder.
- """
- printable_attributes = SubViewNodeList.printable_attributes + ['names']
-
- def __init__(self, parent):
- super(BuilderSlaves, self).__init__(
- parent, parent.parent.parent.slaves, 'slaves')
-
- @property
- def names(self):
- return self.keys
-
-
-class BuildStep(NonAddressableDataNode):
- printable_attributes = NonAddressableDataNode.printable_attributes + [
- 'name', 'number', 'start_time', 'end_time', 'duration', 'is_started',
- 'is_finished', 'is_running',
- 'result', 'simplified_result',
- ]
-
- def __init__(self, parent, number):
- """It's already pre-loaded by definition since the data is retrieve via the
- Build object.
- """
- assert isinstance(number, int)
- super(BuildStep, self).__init__(parent, number)
- self.number = number
-
- @property
- def build(self):
- """Returns the Build object for this BuildStep."""
- # Build.BuildSteps.BuildStep
- return self.parent.parent
-
- @property
- def start_time(self):
- if self.data.get('times'):
- return int(round(self.data['times'][0]))
-
- @property
- def end_time(self):
- times = self.data.get('times')
- if times and len(times) == 2 and times[1]:
- return int(round(times[1]))
-
- @property
- def duration(self):
- if self.start_time:
- return (self.end_time or int(round(time.time()))) - self.start_time
-
- @property
- def name(self):
- return self.data['name']
-
- @property
- def is_started(self):
- return self.data.get('isStarted', False)
-
- @property
- def is_finished(self):
- return self.data.get('isFinished', False)
-
- @property
- def is_running(self):
- return self.is_started and not self.is_finished
-
- @property
- def result(self):
- result = self.data.get('results')
- if result is None:
- # results may be 0, in that case with filter=1, the value won't be
- # present.
- if self.data.get('isFinished'):
- result = self.data.get('results', 0)
- while isinstance(result, list):
- result = result[0]
- return result
-
- @property
- def simplified_result(self):
- """Returns a simplified 3 state value, True, False or None."""
- result = self.result
- if result in (SUCCESS, WARNINGS):
- return True
- elif result in (FAILURE, EXCEPTION, RETRY):
- return False
- assert result in (None, SKIPPED), (result, self.data)
- return None
-
- @property
- def stdio(self):
- """Returns the stdio for this step or None if not available."""
- # Parents ordering is BuildSteps / Build / Builds / Builders
- # A bit hackish but works.
- build = self.build
- builder = build.builder
- suburl = 'builders/%s/builds/%d/steps/%s/logs/stdio/text' % (
- builder.name, build.number, self.name)
- return self.read_non_json(suburl)
-
-
-class BuildSteps(NonAddressableNodeList):
- """Duplicates keys to support lookup by both step number and step name."""
- printable_attributes = NonAddressableNodeList.printable_attributes + [
- 'failed',
- ]
- _child_cls = BuildStep
-
- def __init__(self, parent):
- """It's already pre-loaded by definition since the data is retrieve via the
- Build object.
- """
- super(BuildSteps, self).__init__(parent, 'steps')
-
- @property
- def keys(self):
- """Returns the steps name in order."""
- return [i['name'] for i in (self.data or [])]
-
- @property
- def failed(self):
- """Shortcuts that lists the step names of steps that failed."""
- return [step.name for step in self if step.simplified_result is False]
-
- def __getitem__(self, key):
- """Accept step name in addition to index number."""
- if isinstance(key, basestring):
- # It's a string, try to find the corresponding index.
- for i, step in enumerate(self.data):
- if step['name'] == key:
- key = i
- break
- else:
- raise KeyError(key)
- return super(BuildSteps, self).__getitem__(key)
-
-
-class Build(AddressableDataNode):
- printable_attributes = AddressableDataNode.printable_attributes + [
- 'key', 'number', 'steps', 'blame', 'reason', 'revision', 'result',
- 'simplified_result', 'start_time', 'end_time', 'duration', 'slave',
- 'properties', 'completed',
- ]
-
- def __init__(self, parent, key, data):
- super(Build, self).__init__(parent, str(key), data)
- self.number = int(key)
- self.key = self.number
- self.steps = BuildSteps(self)
-
- @property
- def blame(self):
- return self.data.get('blame', [])
-
- @property
- def builder(self):
- """Returns the Builder object.
-
- Goes up the hierarchy to find the Buildbot.builders[builder] instance.
- """
- return self.parent.parent.parent.parent.builders[self.data['builderName']]
-
- @property
- def start_time(self):
- if self.data.get('times'):
- return int(round(self.data['times'][0]))
-
- @property
- def end_time(self):
- times = self.data.get('times')
- if times and len(times) == 2 and times[1]:
- return int(round(times[1]))
-
- @property
- def duration(self):
- if self.start_time:
- return (self.end_time or int(round(time.time()))) - self.start_time
-
- @property
- def eta(self):
- return self.data.get('eta', 0)
-
- @property
- def completed(self):
- return self.data.get('currentStep') is None
-
- @property
- def properties(self):
- return self.data.get('properties', [])
-
- @property
- def properties_as_dict(self):
- """Converts the 3-tuple properties into a dict(p[0]: p[1]) and ignores the
- property's source.
- """
- return dict((p[0], p[1]) for p in self.properties)
-
- @property
- def reason(self):
- return self.data.get('reason')
-
- @property
- def result(self):
- result = self.data.get('results')
- while isinstance(result, list):
- result = result[0]
- if result is None and self.steps:
- # results may be 0, in that case with filter=1, the value won't be
- # present.
- result = self.steps[-1].result
- return result
-
- @property
- def revision(self):
- return self.data.get('sourceStamp', {}).get('revision')
-
- @property
- def simplified_result(self):
- """Returns a simplified 3 state value, True, False or None."""
- result = self.result
- if result in (SUCCESS, WARNINGS, SKIPPED):
- return True
- elif result in (FAILURE, EXCEPTION, RETRY):
- return False
- assert result is None, (result, self.data)
- return None
-
- @property
- def slave(self):
- """Returns the Slave object.
-
- Goes up the hierarchy to find the Buildbot.slaves[slave] instance.
- """
- return self.parent.parent.parent.parent.slaves[self.data['slave']]
-
- def discard(self):
- """Completed Build isn't discarded."""
- if self._data and self.result is None:
- assert not self.steps or not self.steps[-1].data.get('isFinished')
- self._data = None
-
-
-class CurrentBuilds(SubViewNodeList):
- """Lists of the current builds."""
- def __init__(self, parent):
- super(CurrentBuilds, self).__init__(
- parent, parent.builds, 'currentBuilds')
-
-
-class PendingBuilds(AddressableDataNode):
- def __init__(self, parent):
- super(PendingBuilds, self).__init__(parent, 'pendingBuilds', None)
-
-
-class Builds(AddressableNodeList):
- """Supports iteration.
-
- Recommends using .cache() to speed up if a significant number of builds are
- iterated over.
- """
- _child_cls = Build
-
- def __init__(self, parent):
- super(Builds, self).__init__(parent, 'builds')
-
- def __getitem__(self, key):
- """Adds supports for negative reference and enables retrieving non-cached
- builds.
-
- e.g. -1 is the last build, -2 is the previous build before the last one.
- """
- key = int(key)
- if key < 0:
- # Convert negative to positive build number.
- self.cache_keys()
- # Since the negative value can be outside of the cache keys range, use the
- # highest key value and calculate from it.
- key = max(self._keys) + key + 1
-
- if not key in self._cache:
- # Create an empty object.
- self._create_obj(key, None)
- return self._cache[key]
-
- def __iter__(self):
- """Returns cached Build objects in reversed order.
-
- The most recent build is returned first and then in reverse chronological
- order, up to the oldest cached build by the server. Older builds can be
- accessed but will trigger significantly more I/O so they are not included by
- default in the iteration.
-
- To access the older builds, use self.iterall() instead.
- """
- self.cache()
- return reversed(self._cache.values())
-
- def iterall(self):
- """Returns Build objects in decreasing order unbounded up to build 0.
-
- The most recent build is returned first and then in reverse chronological
- order. Older builds can be accessed and will trigger significantly more I/O
- so use this carefully.
- """
- # Only cache keys here.
- self.cache_keys()
- if self._keys:
- for i in xrange(max(self._keys), -1, -1):
- yield self[i]
-
- def cache_keys(self):
- """Grabs the keys (build numbers) from the builder."""
- if not self._has_keys_cached:
- for i in self.parent.data.get('cachedBuilds', []):
- i = int(i)
- self._cache.setdefault(i, Build(self, i, None))
- if i not in self._keys:
- self._keys.append(i)
- self._has_keys_cached = True
-
- def discard(self):
- super(Builds, self).discard()
- # Can't keep keys.
- self._has_keys_cached = False
-
- def _readall(self):
- return self.read('_all')
-
-
-class Builder(AddressableDataNode):
- printable_attributes = AddressableDataNode.printable_attributes + [
- 'name', 'key', 'builds', 'slaves', 'pending_builds', 'current_builds',
- ]
-
- def __init__(self, parent, name, data):
- super(Builder, self).__init__(parent, name, data)
- self.name = name
- self.key = name
- self.builds = Builds(self)
- self.slaves = BuilderSlaves(self)
- self.current_builds = CurrentBuilds(self)
- self.pending_builds = PendingBuilds(self)
-
- def discard(self):
- super(Builder, self).discard()
- self.builds.discard()
- self.slaves.discard()
- self.current_builds.discard()
-
-
-class Builders(AddressableNodeList):
- """Root list of builders."""
- _child_cls = Builder
-
- def __init__(self, parent):
- super(Builders, self).__init__(parent, 'builders')
-
-
-class Buildbot(AddressableBaseDataNode):
- """If a master restart occurs, this object should be recreated as it caches
- data.
- """
- # Throttle fetches to not kill the server.
- auto_throttle = None
- printable_attributes = AddressableDataNode.printable_attributes + [
- 'slaves', 'builders', 'last_fetch',
- ]
-
- def __init__(self, url):
- super(Buildbot, self).__init__(None, url, None)
- self._builders = Builders(self)
- self._slaves = Slaves(self)
- self.last_fetch = None
-
- @property
- def builders(self):
- return self._builders
-
- @property
- def slaves(self):
- return self._slaves
-
- def discard(self):
- """Discards information about Builders and Slaves."""
- super(Buildbot, self).discard()
- self._builders.discard()
- self._slaves.discard()
-
- def read(self, suburl):
- """Returns json decoded data for the suburl."""
- if self.auto_throttle:
- if self.last_fetch:
- delta = datetime.datetime.utcnow() - self.last_fetch
- remaining = (datetime.timedelta(seconds=self.auto_throttle) -
- delta)
- if remaining > datetime.timedelta(seconds=0):
- logging.debug('Sleeping for %ss' % remaining)
- time.sleep(remaining.seconds)
- self.last_fetch = datetime.datetime.utcnow()
- url = '%s/json/%s' % (self.url, suburl)
- if '?' in url:
- url += '&filter=1'
- else:
- url += '?filter=1'
- logging.debug('read(%s)' % suburl)
- try:
- channel = urllib.urlopen(url)
- data = channel.read()
- except IOError as e:
- logging.warning('caught %s while fetching "%s"; re-throwing' % (
- str(e), url))
- raise
- try:
- return json.loads(data)
- except ValueError:
- if channel.getcode() >= 400:
- # Convert it into an HTTPError for easier processing.
- raise urllib2.HTTPError(
- url, channel.getcode(), '%s:\n%s' % (url, data), channel.headers,
- None)
- raise
-
- def read_non_json(self, suburl):
- """Returns data for an arbitrary suburl outside of the /json/ path."""
- logging.debug('read_non_json(%s)' % suburl)
- return urllib.urlopen('%s/%s' % (self.url, suburl)).read()
-
- def _readall(self):
- return self.read('project')
-
-
-###############################################################################
-## Controller code
-
-
-def usage(more):
- def hook(fn):
- fn.func_usage_more = more
- return fn
- return hook
-
-
-def need_buildbot(fn):
- """Post-parse args to create a buildbot object."""
- @functools.wraps(fn)
- def hook(parser, args, *extra_args, **kwargs):
- old_parse_args = parser.parse_args
- def new_parse_args(args):
- options, args = old_parse_args(args)
- if len(args) < 1:
- parser.error('Need to pass the root url of the buildbot')
- url = args.pop(0)
- if not url.startswith('http'):
- url = 'http://' + url
- buildbot = Buildbot(url)
- buildbot.auto_throttle = options.throttle
- return options, args, buildbot
- parser.parse_args = new_parse_args
- # Call the original function with the modified parser.
- return fn(parser, args, *extra_args, **kwargs)
-
- hook.func_usage_more = '[options] <url>'
- return hook
-
-
-@need_buildbot
-def CMDpending(parser, args):
- """Lists pending jobs."""
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- if not options.builders:
- options.builders = buildbot.builders.keys
- for builder in options.builders:
- builder = buildbot.builders[builder]
- pending_builds = builder.data.get('pendingBuilds', 0)
- if not pending_builds:
- continue
- print 'Builder %s: %d' % (builder.name, pending_builds)
- if not options.quiet:
- for pending in builder.pending_builds.data:
- if 'revision' in pending['source']:
- print ' revision: %s' % pending['source']['revision']
- for change in pending['source']['changes']:
- print ' change:'
- print ' comment: %r' % unicode(change['comments'][:50])
- print ' who: %s' % change['who']
- return 0
-
-
-@usage('[options] <url> [commands] ...')
-@need_buildbot
-def CMDrun(parser, args):
- """Runs commands passed as parameters.
-
- When passing commands on the command line, each command will be run as if it
- was on its own line.
- """
- parser.add_option('-f', '--file', help='Read script from file')
- parser.add_option(
- '-i', dest='use_stdin', action='store_true', help='Read script on stdin')
- # Variable 'buildbot' is not used directly.
- # pylint: disable=W0612
- options, args, buildbot = parser.parse_args(args)
- if (bool(args) + bool(options.use_stdin) + bool(options.file)) != 1:
- parser.error('Need to pass only one of: <commands>, -f <file> or -i')
- if options.use_stdin:
- cmds = sys.stdin.read()
- elif options.file:
- cmds = open(options.file).read()
- else:
- cmds = '\n'.join(args)
- compiled = compile(cmds, '<cmd line>', 'exec')
- eval(compiled, globals(), locals())
- return 0
-
-
-@need_buildbot
-def CMDinteractive(parser, args):
- """Runs an interactive shell to run queries."""
- _, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- prompt = (
- 'Buildbot interactive console for "%s".\n'
- 'Hint: Start with typing: \'buildbot.printable_attributes\' or '
- '\'print str(buildbot)\' to explore.') % buildbot.url
- local_vars = {
- 'buildbot': buildbot,
- 'b': buildbot,
- }
- code.interact(prompt, None, local_vars)
-
-
-@need_buildbot
-def CMDidle(parser, args):
- """Lists idle slaves."""
- return find_idle_busy_slaves(parser, args, True)
-
-
-@need_buildbot
-def CMDbusy(parser, args):
- """Lists idle slaves."""
- return find_idle_busy_slaves(parser, args, False)
-
-
-@need_buildbot
-def CMDdisconnected(parser, args):
- """Lists disconnected slaves."""
- _, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- for slave in buildbot.slaves:
- if not slave.connected:
- print slave.name
- return 0
-
-
-def find_idle_busy_slaves(parser, args, show_idle):
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- parser.add_option(
- '-s', '--slave', dest='slaves', action='append', default=[],
- help='Slaves to filter on')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- if not options.builders:
- options.builders = buildbot.builders.keys
- for builder in options.builders:
- builder = buildbot.builders[builder]
- if options.slaves:
- # Only the subset of slaves connected to the builder.
- slaves = list(set(options.slaves).intersection(set(builder.slaves.names)))
- if not slaves:
- continue
- else:
- slaves = builder.slaves.names
- busy_slaves = [build.slave.name for build in builder.current_builds]
- if show_idle:
- slaves = natsorted(set(slaves) - set(busy_slaves))
- else:
- slaves = natsorted(set(slaves) & set(busy_slaves))
- if options.quiet:
- for slave in slaves:
- print slave
- else:
- if slaves:
- print 'Builder %s: %s' % (builder.name, ', '.join(slaves))
- return 0
-
-
-def last_failure(
- buildbot, builders=None, slaves=None, steps=None, no_cache=False):
- """Generator returning Build object that were the last failure with the
- specific filters.
- """
- builders = builders or buildbot.builders.keys
- for builder in builders:
- builder = buildbot.builders[builder]
- if slaves:
- # Only the subset of slaves connected to the builder.
- builder_slaves = list(set(slaves).intersection(set(builder.slaves.names)))
- if not builder_slaves:
- continue
- else:
- builder_slaves = builder.slaves.names
-
- if not no_cache and len(builder.slaves) > 2:
- # Unless you just want the last few builds, it's often faster to
- # fetch the whole thing at once, at the cost of a small hickup on
- # the buildbot.
- # TODO(maruel): Cache only N last builds or all builds since
- # datetime.
- builder.builds.cache()
-
- found = []
- for build in builder.builds:
- if build.slave.name not in builder_slaves or build.slave.name in found:
- continue
- # Only add the slave for the first completed build but still look for
- # incomplete builds.
- if build.completed:
- found.append(build.slave.name)
-
- if steps:
- if any(build.steps[step].simplified_result is False for step in steps):
- yield build
- elif build.simplified_result is False:
- yield build
-
- if len(found) == len(builder_slaves):
- # Found all the slaves, quit.
- break
-
-
-@need_buildbot
-def CMDlast_failure(parser, args):
- """Lists all slaves that failed on that step on their last build.
-
- Example: to find all slaves where their last build was a compile failure,
- run with --step compile"""
- parser.add_option(
- '-S', '--step', dest='steps', action='append', default=[],
- help='List all slaves that failed on that step on their last build')
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- parser.add_option(
- '-s', '--slave', dest='slaves', action='append', default=[],
- help='Slaves to filter on')
- parser.add_option(
- '-n', '--no_cache', action='store_true',
- help='Don\'t load all builds at once')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- print_builders = not options.quiet and len(options.builders) != 1
- last_builder = None
- for build in last_failure(
- buildbot, builders=options.builders,
- slaves=options.slaves, steps=options.steps,
- no_cache=options.no_cache):
-
- if print_builders and last_builder != build.builder:
- print build.builder.name
- last_builder = build.builder
-
- if options.quiet:
- if options.slaves:
- print '%s: %s' % (build.builder.name, build.slave.name)
- else:
- print build.slave.name
- else:
- out = '%d on %s: blame:%s' % (
- build.number, build.slave.name, ', '.join(build.blame))
- if print_builders:
- out = ' ' + out
- print out
-
- if len(options.steps) != 1:
- for step in build.steps:
- if step.simplified_result is False:
- # Assume the first line is the text name anyway.
- summary = ', '.join(step.data['text'][1:])[:40]
- out = ' %s: "%s"' % (step.data['name'], summary)
- if print_builders:
- out = ' ' + out
- print out
- return 0
-
-
-@need_buildbot
-def CMDcurrent(parser, args):
- """Lists current jobs."""
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- parser.add_option(
- '--blame', action='store_true', help='Only print the blame list')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- if not options.builders:
- options.builders = buildbot.builders.keys
-
- if options.blame:
- blame = set()
- for builder in options.builders:
- for build in buildbot.builders[builder].current_builds:
- if build.blame:
- for blamed in build.blame:
- blame.add(blamed)
- print '\n'.join(blame)
- return 0
-
- for builder in options.builders:
- builder = buildbot.builders[builder]
- if not options.quiet and builder.current_builds:
- print builder.name
- for build in builder.current_builds:
- if options.quiet:
- print build.slave.name
- else:
- out = '%4d: slave=%10s' % (build.number, build.slave.name)
- out += ' duration=%5d' % (build.duration or 0)
- if build.eta:
- out += ' eta=%5.0f' % build.eta
- else:
- out += ' '
- if build.blame:
- out += ' blame=' + ', '.join(build.blame)
- print out
-
- return 0
-
-
-@need_buildbot
-def CMDbuilds(parser, args):
- """Lists all builds.
-
- Example: to find all builds on a single slave, run with -b bar -s foo
- """
- parser.add_option(
- '-r', '--result', type='int', help='Build result to filter on')
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- parser.add_option(
- '-s', '--slave', dest='slaves', action='append', default=[],
- help='Slaves to filter on')
- parser.add_option(
- '-n', '--no_cache', action='store_true',
- help='Don\'t load all builds at once')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- builders = options.builders or buildbot.builders.keys
- for builder in builders:
- builder = buildbot.builders[builder]
- for build in builder.builds:
- if not options.slaves or build.slave.name in options.slaves:
- if options.quiet:
- out = ''
- if options.builders:
- out += '%s/' % builder.name
- if len(options.slaves) != 1:
- out += '%s/' % build.slave.name
- out += '%d revision:%s result:%s blame:%s' % (
- build.number, build.revision, build.result, ','.join(build.blame))
- print out
- else:
- print build
- return 0
-
-
-@need_buildbot
-def CMDcount(parser, args):
- """Count the number of builds that occured during a specific period.
- """
- parser.add_option(
- '-o', '--over', type='int', help='Number of seconds to look for')
- parser.add_option(
- '-b', '--builder', dest='builders', action='append', default=[],
- help='Builders to filter on')
- options, args, buildbot = parser.parse_args(args)
- if args:
- parser.error('Unrecognized parameters: %s' % ' '.join(args))
- if not options.over:
- parser.error(
- 'Specify the number of seconds, e.g. --over 86400 for the last 24 '
- 'hours')
- builders = options.builders or buildbot.builders.keys
- counts = {}
- since = time.time() - options.over
- for builder in builders:
- builder = buildbot.builders[builder]
- counts[builder.name] = 0
- if not options.quiet:
- print builder.name
- for build in builder.builds.iterall():
- try:
- start_time = build.start_time
- except urllib2.HTTPError:
- # The build was probably trimmed.
- print >> sys.stderr, (
- 'Failed to fetch build %s/%d' % (builder.name, build.number))
- continue
- if start_time >= since:
- counts[builder.name] += 1
- else:
- break
- if not options.quiet:
- print '.. %d' % counts[builder.name]
-
- align_name = max(len(b) for b in counts)
- align_number = max(len(str(c)) for c in counts.itervalues())
- for builder in sorted(counts):
- print '%*s: %*d' % (align_name, builder, align_number, counts[builder])
- print 'Total: %d' % sum(counts.itervalues())
- return 0
-
-
-class OptionParser(optparse.OptionParser):
- def parse_args(self, args=None, values=None):
- """Adds common parsing."""
- options, args = optparse.OptionParser.parse_args(self, args, values)
- levels = (logging.WARNING, logging.INFO, logging.DEBUG)
- logging.basicConfig(level=levels[min(options.verbose, len(levels)-1)])
- return options, args
-
- def format_description(self, _):
- """Removes description formatting."""
- return self.description
-
-
-def gen_parser():
- """Returns an OptionParser instance with default options.
-
- It should be then processed with gen_usage() before being used.
- """
- parser = OptionParser(version=__version__)
- parser.add_option(
- '-v', '--verbose', action='count', default=0,
- help='Use multiple times to increase logging leve')
- parser.add_option(
- '-q', '--quiet', action='store_true',
- help='Reduces the output to be parsed by scripts, independent of -v')
- parser.add_option(
- '--throttle', type='float',
- help='Minimum delay to sleep between requests')
- return parser
-
-
-###############################################################################
-## Generic subcommand handling code
-
-
-def Command(name):
- return getattr(sys.modules[__name__], 'CMD' + name, None)
-
-
-@usage('<command>')
-def CMDhelp(parser, args):
- """Print list of commands or use 'help <command>'."""
- _, args = parser.parse_args(args)
- if len(args) == 1:
- return main(args + ['--help'])
- parser.print_help()
- return 0
-
-
-def gen_usage(parser, command):
- """Modifies an OptionParser object with the command's documentation.
-
- The documentation is taken from the function's docstring.
- """
- obj = Command(command)
- more = getattr(obj, 'func_usage_more')
- # OptParser.description prefer nicely non-formatted strings.
- parser.description = obj.__doc__ + '\n'
- parser.set_usage('usage: %%prog %s %s' % (command, more))
-
-
-def main(args=None):
- # Do it late so all commands are listed.
- # pylint: disable=E1101
- CMDhelp.__doc__ += '\n\nCommands are:\n' + '\n'.join(
- ' %-12s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n', 1)[0])
- for fn in dir(sys.modules[__name__]) if fn.startswith('CMD'))
-
- parser = gen_parser()
- if args is None:
- args = sys.argv[1:]
- if args:
- command = Command(args[0])
- if command:
- # "fix" the usage and the description now that we know the subcommand.
- gen_usage(parser, args[0])
- return command(parser, args[1:])
-
- # Not a known command. Default to help.
- gen_usage(parser, 'help')
- return CMDhelp(parser, args)
-
-
-if __name__ == '__main__':
- sys.exit(main())
« no previous file with comments | « commit-queue/async_push.py ('k') | commit-queue/codereview.settings » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698