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

Unified Diff: third_party/buildbot_8_4p1/README.chromium

Issue 9703108: Switch to the good LRU implementation. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: Created 8 years, 9 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 | « no previous file | third_party/buildbot_8_4p1/buildbot/status/builder.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/buildbot_8_4p1/README.chromium
===================================================================
--- third_party/buildbot_8_4p1/README.chromium (revision 127129)
+++ third_party/buildbot_8_4p1/README.chromium (working copy)
@@ -1199,3 +1199,289 @@
kwargs['logfiles'] = self.logfiles
# check for the usePTY flag
+
+
+Replace the simple (and very slow) LRU cache implementation in BuilderStatus
+with a better one. Added SyncLRUCache to avoid the expensive concurrency
+protection in AsyncLRUCache.
+
+Index: buildbot/status/builder.py
+===================================================================
+--- buildbot/status/builder.py (revision 127129)
++++ buildbot/status/builder.py (working copy)
+@@ -86,8 +86,8 @@
+ self.currentBuilds = []
+ self.nextBuild = None
+ self.watchers = []
+- self.buildCache = weakref.WeakValueDictionary()
+- self.buildCache_LRU = []
++ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
++ self.buildCacheSize)
+ self.logCompressionLimit = False # default to no compression for tests
+ self.logCompressionMethod = "bz2"
+ self.logMaxSize = None # No default limit
+@@ -103,7 +103,6 @@
+ d = styles.Versioned.__getstate__(self)
+ d['watchers'] = []
+ del d['buildCache']
+- del d['buildCache_LRU']
+ for b in self.currentBuilds:
+ b.saveYourself()
+ # TODO: push a 'hey, build was interrupted' event
+@@ -119,8 +118,8 @@
+ # when loading, re-initialize the transient stuff. Remember that
+ # upgradeToVersion1 and such will be called after this finishes.
+ styles.Versioned.__setstate__(self, d)
+- self.buildCache = weakref.WeakValueDictionary()
+- self.buildCache_LRU = []
++ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
++ self.buildCacheSize)
+ self.currentBuilds = []
+ self.watchers = []
+ self.slavenames = []
+@@ -132,6 +131,7 @@
+ # gets pickled and unpickled.
+ if buildmaster.buildCacheSize is not None:
+ self.buildCacheSize = buildmaster.buildCacheSize
++ self.buildCache.set_max_size(buildmaster.buildCacheSize)
+
+ def upgradeToVersion1(self):
+ if hasattr(self, 'slavename'):
+@@ -186,33 +186,17 @@
+ except:
+ log.msg("unable to save builder %s" % self.name)
+ log.err()
+-
+
++
+ # build cache management
+
+ def makeBuildFilename(self, number):
+ return os.path.join(self.basedir, "%d" % number)
+
+- def touchBuildCache(self, build):
+- self.buildCache[build.number] = build
+- if build in self.buildCache_LRU:
+- self.buildCache_LRU.remove(build)
+- self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ]
+- return build
+-
+ def getBuildByNumber(self, number):
+- # first look in currentBuilds
+- for b in self.currentBuilds:
+- if b.number == number:
+- return self.touchBuildCache(b)
++ return self.buildCache.get(number)
+
+- # then in the buildCache
+- if number in self.buildCache:
+- metrics.MetricCountEvent.log("buildCache.hits", 1)
+- return self.touchBuildCache(self.buildCache[number])
+- metrics.MetricCountEvent.log("buildCache.misses", 1)
+-
+- # then fall back to loading it from disk
++ def loadBuildFromFile(self, number):
+ filename = self.makeBuildFilename(number)
+ try:
+ log.msg("Loading builder %s's build %d from on-disk pickle"
+@@ -235,12 +219,20 @@
+ build.upgradeLogfiles()
+ # check that logfiles exist
+ build.checkLogfiles()
+- return self.touchBuildCache(build)
++ return build
+ except IOError:
+ raise IndexError("no such build %d" % number)
+ except EOFError:
+ raise IndexError("corrupted build pickle %d" % number)
+
++ def cacheMiss(self, number):
++ # first look in currentBuilds
++ for b in self.currentBuilds:
++ if b.number == number:
++ return b
++ # then fall back to loading it from disk
++ return self.loadBuildFromFile(number)
++
+ def prune(self, events_only=False):
+ # begin by pruning our own events
+ self.events = self.events[-self.eventHorizon:]
+@@ -287,7 +279,7 @@
+ is_logfile = True
+
+ if num is None: continue
+- if num in self.buildCache: continue
++ if num in self.buildCache.cache: continue
+
+ if (is_logfile and num < earliest_log) or num < earliest_build:
+ pathname = os.path.join(self.basedir, filename)
+@@ -510,7 +502,7 @@
+ assert s.number == self.nextBuildNumber - 1
+ assert s not in self.currentBuilds
+ self.currentBuilds.append(s)
+- self.touchBuildCache(s)
++ self.buildCache.put(s.number, s)
+
+ # now that the BuildStatus is prepared to answer queries, we can
+ # announce the new build to all our watchers
+@@ -620,7 +612,7 @@
+ # Collect build numbers.
+ # Important: Only grab the *cached* builds numbers to reduce I/O.
+ current_builds = [b.getNumber() for b in self.currentBuilds]
+- cached_builds = list(set(self.buildCache.keys() + current_builds))
++ cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
+ cached_builds.sort()
+ result['cachedBuilds'] = cached_builds
+ result['currentBuilds'] = current_builds
+Index: buildbot/util/lru.py
+===================================================================
+--- buildbot/util/lru.py (revision 127129)
++++ buildbot/util/lru.py (working copy)
+@@ -244,5 +244,82 @@
+ log.msg(" got:", sorted(self.refcount.items()))
+ inv_failed = True
+
++
++class SyncLRUCache(AsyncLRUCache):
++ """
++
++ A least-recently-used cache using the same strategy as AsyncLRUCache,
++ minus the protections for concurrent access. The motivation for this
++ class is to provide a speedier implementation for heavily-used caches
++ that don't need the concurrency protections.
++
++ The constructor takes the same arguments as the AsyncLRUCache
++ constructor, except C{miss_fn} must return the missing value, I{not} a
++ deferred.
++ """
++
++ # utility function to record recent use of this key
++ def _ref_key(key):
++ refcount = self.refcount
++ queue = self.queue
++
++ queue.append(key)
++ refcount[key] = refcount[key] + 1
++
++ # periodically compact the queue by eliminating duplicate keys
++ # while preserving order of most recent access. Note that this
++ # is only required when the cache does not exceed its maximum
++ # size
++ if len(queue) > self.max_queue:
++ refcount.clear()
++ queue_appendleft = queue.appendleft
++ queue_appendleft(self.sentinel)
++ for k in ifilterfalse(refcount.__contains__,
++ iter(queue.pop, self.sentinel)):
++ queue_appendleft(k)
++ refcount[k] = 1
++
++ def get(self, key, **miss_fn_kwargs):
++ """
++ Fetch a value from the cache by key, invoking C{self.miss_fn(key)} if
++ the key is not in the cache.
++
++ No protection is provided against concurrent access.
++
++ @param key: cache key
++ @param **miss_fn_kwargs: keyword arguments to the miss_fn
++ @returns: cache value
++ """
++ cache = self.cache
++ weakrefs = self.weakrefs
++
++ try:
++ result = cache[key]
++ self.hits += 1
++ self._ref_key(key)
++ return result
++ except KeyError:
++ try:
++ result = weakrefs[key]
++ self.refhits += 1
++ cache[key] = result
++ self._ref_key(key)
++ return result
++ except KeyError:
++ pass
++
++ # if we're here, we've missed and need to fetch
++ self.misses += 1
++
++ result = self.miss_fn(key, **miss_fn_kwargs)
++ if result is not None:
++ cache[key] = result
++ weakrefs[key] = result
++ self._ref_key(key)
++ self._purge()
++
++ return result
++
++
+ # for tests
+ inv_failed = False
+Index: buildbot/test/unit/test_status_builder_cache.py
+===================================================================
+--- buildbot/test/unit/test_status_builder_cache.py (revision 0)
++++ buildbot/test/unit/test_status_builder_cache.py (revision 0)
+@@ -0,0 +1,60 @@
++# This file is part of Buildbot. Buildbot is free software: you can
++# redistribute it and/or modify it under the terms of the GNU General Public
++# License as published by the Free Software Foundation, version 2.
++#
++# This program is distributed in the hope that it will be useful, but WITHOUT
++# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
++# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
++# details.
++#
++# You should have received a copy of the GNU General Public License along with
++# this program; if not, write to the Free Software Foundation, Inc., 51
++# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++#
++# Copyright Buildbot Team Members
++
++import os
++from mock import Mock
++from twisted.trial import unittest
++from buildbot.status import builder, master
++
++class TestBuildStatus(unittest.TestCase):
++
++ # that buildstep.BuildStepStatus is never instantiated here should tell you
++ # that these classes are not well isolated!
++
++ def setupBuilder(self, buildername, category=None):
++ b = builder.BuilderStatus(buildername=buildername, category=category)
++ # Ackwardly, Status sets this member variable.
++ b.basedir = os.path.abspath(self.mktemp())
++ os.mkdir(b.basedir)
++ # Otherwise, builder.nextBuildNumber is not defined.
++ b.determineNextBuildNumber()
++ # Must initialize these fields before pickling.
++ b.currentBigState = 'idle'
++ b.status = 'idle'
++ return b
++
++ def setupStatus(self, b):
++ m = Mock()
++ m.buildbotURL = 'http://buildbot:8010/'
++ m.basedir = '/basedir'
++ s = master.Status(m)
++ b.status = s
++ return s
++
++ def testBuildCache(self):
++ b = self.setupBuilder('builder_1')
++ builds = []
++ for i in xrange(5):
++ build = b.newBuild()
++ build.setProperty('propkey', 'propval%d' % i, 'test')
++ builds.append(build)
++ build.buildStarted(build)
++ build.buildFinished()
++ for build in builds:
++ build2 = b.getBuild(build.number)
++ self.assertTrue(build2)
++ self.assertEqual(build2.number, build.number)
++ self.assertEqual(build.getProperty('propkey'),
++ 'propval%d' % build.number)
« no previous file with comments | « no previous file | third_party/buildbot_8_4p1/buildbot/status/builder.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698