OLD | NEW |
1 URL: http://buildbot.net/trac | 1 URL: http://buildbot.net/trac |
2 Version: 0.8.4p1 | 2 Version: 0.8.4p1 |
3 License: GNU General Public License (GPL) Version 2 | 3 License: GNU General Public License (GPL) Version 2 |
4 | 4 |
5 This is a forked copy of buildbot v0.8.4p1. | 5 This is a forked copy of buildbot v0.8.4p1. |
6 | 6 |
7 | 7 |
8 Add extra parameters to HttpStatusPush as a very basic authentication mechanism. | 8 Add extra parameters to HttpStatusPush as a very basic authentication mechanism. |
9 | 9 |
10 diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_p
arty/buildbot_8_4p1/buildbot/status/status_push.py | 10 diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_p
arty/buildbot_8_4p1/buildbot/status/status_push.py |
(...skipping 1181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1192 + tmp = [] | 1192 + tmp = [] |
1193 + if isinstance(self.command, list): | 1193 + if isinstance(self.command, list): |
1194 + self._flattenList(tmp, self.command) | 1194 + self._flattenList(tmp, self.command) |
1195 + else: | 1195 + else: |
1196 + tmp = self.command | 1196 + tmp = self.command |
1197 + | 1197 + |
1198 + kwargs['command'] = tmp | 1198 + kwargs['command'] = tmp |
1199 kwargs['logfiles'] = self.logfiles | 1199 kwargs['logfiles'] = self.logfiles |
1200 | 1200 |
1201 # check for the usePTY flag | 1201 # check for the usePTY flag |
| 1202 |
| 1203 |
| 1204 Replace the simple (and very slow) LRU cache implementation in BuilderStatus |
| 1205 with a better one. Added SyncLRUCache to avoid the expensive concurrency |
| 1206 protection in AsyncLRUCache. |
| 1207 |
| 1208 Index: buildbot/status/builder.py |
| 1209 =================================================================== |
| 1210 --- buildbot/status/builder.py (revision 127129) |
| 1211 +++ buildbot/status/builder.py (working copy) |
| 1212 @@ -86,8 +86,8 @@ |
| 1213 self.currentBuilds = [] |
| 1214 self.nextBuild = None |
| 1215 self.watchers = [] |
| 1216 - self.buildCache = weakref.WeakValueDictionary() |
| 1217 - self.buildCache_LRU = [] |
| 1218 + self.buildCache = util.lru.SyncLRUCache(self.cacheMiss, |
| 1219 + self.buildCacheSize) |
| 1220 self.logCompressionLimit = False # default to no compression for tests |
| 1221 self.logCompressionMethod = "bz2" |
| 1222 self.logMaxSize = None # No default limit |
| 1223 @@ -103,7 +103,6 @@ |
| 1224 d = styles.Versioned.__getstate__(self) |
| 1225 d['watchers'] = [] |
| 1226 del d['buildCache'] |
| 1227 - del d['buildCache_LRU'] |
| 1228 for b in self.currentBuilds: |
| 1229 b.saveYourself() |
| 1230 # TODO: push a 'hey, build was interrupted' event |
| 1231 @@ -119,8 +118,8 @@ |
| 1232 # when loading, re-initialize the transient stuff. Remember that |
| 1233 # upgradeToVersion1 and such will be called after this finishes. |
| 1234 styles.Versioned.__setstate__(self, d) |
| 1235 - self.buildCache = weakref.WeakValueDictionary() |
| 1236 - self.buildCache_LRU = [] |
| 1237 + self.buildCache = util.lru.SyncLRUCache(self.cacheMiss, |
| 1238 + self.buildCacheSize) |
| 1239 self.currentBuilds = [] |
| 1240 self.watchers = [] |
| 1241 self.slavenames = [] |
| 1242 @@ -132,6 +131,7 @@ |
| 1243 # gets pickled and unpickled. |
| 1244 if buildmaster.buildCacheSize is not None: |
| 1245 self.buildCacheSize = buildmaster.buildCacheSize |
| 1246 + self.buildCache.set_max_size(buildmaster.buildCacheSize) |
| 1247 |
| 1248 def upgradeToVersion1(self): |
| 1249 if hasattr(self, 'slavename'): |
| 1250 @@ -186,33 +186,17 @@ |
| 1251 except: |
| 1252 log.msg("unable to save builder %s" % self.name) |
| 1253 log.err() |
| 1254 - |
| 1255 |
| 1256 + |
| 1257 # build cache management |
| 1258 |
| 1259 def makeBuildFilename(self, number): |
| 1260 return os.path.join(self.basedir, "%d" % number) |
| 1261 |
| 1262 - def touchBuildCache(self, build): |
| 1263 - self.buildCache[build.number] = build |
| 1264 - if build in self.buildCache_LRU: |
| 1265 - self.buildCache_LRU.remove(build) |
| 1266 - self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] +
[ build ] |
| 1267 - return build |
| 1268 - |
| 1269 def getBuildByNumber(self, number): |
| 1270 - # first look in currentBuilds |
| 1271 - for b in self.currentBuilds: |
| 1272 - if b.number == number: |
| 1273 - return self.touchBuildCache(b) |
| 1274 + return self.buildCache.get(number) |
| 1275 |
| 1276 - # then in the buildCache |
| 1277 - if number in self.buildCache: |
| 1278 - metrics.MetricCountEvent.log("buildCache.hits", 1) |
| 1279 - return self.touchBuildCache(self.buildCache[number]) |
| 1280 - metrics.MetricCountEvent.log("buildCache.misses", 1) |
| 1281 - |
| 1282 - # then fall back to loading it from disk |
| 1283 + def loadBuildFromFile(self, number): |
| 1284 filename = self.makeBuildFilename(number) |
| 1285 try: |
| 1286 log.msg("Loading builder %s's build %d from on-disk pickle" |
| 1287 @@ -235,12 +219,20 @@ |
| 1288 build.upgradeLogfiles() |
| 1289 # check that logfiles exist |
| 1290 build.checkLogfiles() |
| 1291 - return self.touchBuildCache(build) |
| 1292 + return build |
| 1293 except IOError: |
| 1294 raise IndexError("no such build %d" % number) |
| 1295 except EOFError: |
| 1296 raise IndexError("corrupted build pickle %d" % number) |
| 1297 |
| 1298 + def cacheMiss(self, number): |
| 1299 + # first look in currentBuilds |
| 1300 + for b in self.currentBuilds: |
| 1301 + if b.number == number: |
| 1302 + return b |
| 1303 + # then fall back to loading it from disk |
| 1304 + return self.loadBuildFromFile(number) |
| 1305 + |
| 1306 def prune(self, events_only=False): |
| 1307 # begin by pruning our own events |
| 1308 self.events = self.events[-self.eventHorizon:] |
| 1309 @@ -287,7 +279,7 @@ |
| 1310 is_logfile = True |
| 1311 |
| 1312 if num is None: continue |
| 1313 - if num in self.buildCache: continue |
| 1314 + if num in self.buildCache.cache: continue |
| 1315 |
| 1316 if (is_logfile and num < earliest_log) or num < earliest_build: |
| 1317 pathname = os.path.join(self.basedir, filename) |
| 1318 @@ -510,7 +502,7 @@ |
| 1319 assert s.number == self.nextBuildNumber - 1 |
| 1320 assert s not in self.currentBuilds |
| 1321 self.currentBuilds.append(s) |
| 1322 - self.touchBuildCache(s) |
| 1323 + self.buildCache.put(s.number, s) |
| 1324 |
| 1325 # now that the BuildStatus is prepared to answer queries, we can |
| 1326 # announce the new build to all our watchers |
| 1327 @@ -620,7 +612,7 @@ |
| 1328 # Collect build numbers. |
| 1329 # Important: Only grab the *cached* builds numbers to reduce I/O. |
| 1330 current_builds = [b.getNumber() for b in self.currentBuilds] |
| 1331 - cached_builds = list(set(self.buildCache.keys() + current_builds)) |
| 1332 + cached_builds = list(set(self.buildCache.cache.keys() + current_builds)
) |
| 1333 cached_builds.sort() |
| 1334 result['cachedBuilds'] = cached_builds |
| 1335 result['currentBuilds'] = current_builds |
| 1336 Index: buildbot/util/lru.py |
| 1337 =================================================================== |
| 1338 --- buildbot/util/lru.py (revision 127129) |
| 1339 +++ buildbot/util/lru.py (working copy) |
| 1340 @@ -244,5 +244,82 @@ |
| 1341 log.msg(" got:", sorted(self.refcount.items())) |
| 1342 inv_failed = True |
| 1343 |
| 1344 + |
| 1345 +class SyncLRUCache(AsyncLRUCache): |
| 1346 + """ |
| 1347 + |
| 1348 + A least-recently-used cache using the same strategy as AsyncLRUCache, |
| 1349 + minus the protections for concurrent access. The motivation for this |
| 1350 + class is to provide a speedier implementation for heavily-used caches |
| 1351 + that don't need the concurrency protections. |
| 1352 + |
| 1353 + The constructor takes the same arguments as the AsyncLRUCache |
| 1354 + constructor, except C{miss_fn} must return the missing value, I{not} a |
| 1355 + deferred. |
| 1356 + """ |
| 1357 + |
| 1358 + # utility function to record recent use of this key |
| 1359 + def _ref_key(key): |
| 1360 + refcount = self.refcount |
| 1361 + queue = self.queue |
| 1362 + |
| 1363 + queue.append(key) |
| 1364 + refcount[key] = refcount[key] + 1 |
| 1365 + |
| 1366 + # periodically compact the queue by eliminating duplicate keys |
| 1367 + # while preserving order of most recent access. Note that this |
| 1368 + # is only required when the cache does not exceed its maximum |
| 1369 + # size |
| 1370 + if len(queue) > self.max_queue: |
| 1371 + refcount.clear() |
| 1372 + queue_appendleft = queue.appendleft |
| 1373 + queue_appendleft(self.sentinel) |
| 1374 + for k in ifilterfalse(refcount.__contains__, |
| 1375 + iter(queue.pop, self.sentinel)): |
| 1376 + queue_appendleft(k) |
| 1377 + refcount[k] = 1 |
| 1378 + |
| 1379 + def get(self, key, **miss_fn_kwargs): |
| 1380 + """ |
| 1381 + Fetch a value from the cache by key, invoking C{self.miss_fn(key)} if |
| 1382 + the key is not in the cache. |
| 1383 + |
| 1384 + No protection is provided against concurrent access. |
| 1385 + |
| 1386 + @param key: cache key |
| 1387 + @param **miss_fn_kwargs: keyword arguments to the miss_fn |
| 1388 + @returns: cache value |
| 1389 + """ |
| 1390 + cache = self.cache |
| 1391 + weakrefs = self.weakrefs |
| 1392 + |
| 1393 + try: |
| 1394 + result = cache[key] |
| 1395 + self.hits += 1 |
| 1396 + self._ref_key(key) |
| 1397 + return result |
| 1398 + except KeyError: |
| 1399 + try: |
| 1400 + result = weakrefs[key] |
| 1401 + self.refhits += 1 |
| 1402 + cache[key] = result |
| 1403 + self._ref_key(key) |
| 1404 + return result |
| 1405 + except KeyError: |
| 1406 + pass |
| 1407 + |
| 1408 + # if we're here, we've missed and need to fetch |
| 1409 + self.misses += 1 |
| 1410 + |
| 1411 + result = self.miss_fn(key, **miss_fn_kwargs) |
| 1412 + if result is not None: |
| 1413 + cache[key] = result |
| 1414 + weakrefs[key] = result |
| 1415 + self._ref_key(key) |
| 1416 + self._purge() |
| 1417 + |
| 1418 + return result |
| 1419 + |
| 1420 + |
| 1421 # for tests |
| 1422 inv_failed = False |
| 1423 Index: buildbot/test/unit/test_status_builder_cache.py |
| 1424 =================================================================== |
| 1425 --- buildbot/test/unit/test_status_builder_cache.py (revision 0) |
| 1426 +++ buildbot/test/unit/test_status_builder_cache.py (revision 0) |
| 1427 @@ -0,0 +1,60 @@ |
| 1428 +# This file is part of Buildbot. Buildbot is free software: you can |
| 1429 +# redistribute it and/or modify it under the terms of the GNU General Public |
| 1430 +# License as published by the Free Software Foundation, version 2. |
| 1431 +# |
| 1432 +# This program is distributed in the hope that it will be useful, but WITHOUT |
| 1433 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 1434 +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| 1435 +# details. |
| 1436 +# |
| 1437 +# You should have received a copy of the GNU General Public License along with |
| 1438 +# this program; if not, write to the Free Software Foundation, Inc., 51 |
| 1439 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 1440 +# |
| 1441 +# Copyright Buildbot Team Members |
| 1442 + |
| 1443 +import os |
| 1444 +from mock import Mock |
| 1445 +from twisted.trial import unittest |
| 1446 +from buildbot.status import builder, master |
| 1447 + |
| 1448 +class TestBuildStatus(unittest.TestCase): |
| 1449 + |
| 1450 + # that buildstep.BuildStepStatus is never instantiated here should tell you |
| 1451 + # that these classes are not well isolated! |
| 1452 + |
| 1453 + def setupBuilder(self, buildername, category=None): |
| 1454 + b = builder.BuilderStatus(buildername=buildername, category=category) |
| 1455 + # Ackwardly, Status sets this member variable. |
| 1456 + b.basedir = os.path.abspath(self.mktemp()) |
| 1457 + os.mkdir(b.basedir) |
| 1458 + # Otherwise, builder.nextBuildNumber is not defined. |
| 1459 + b.determineNextBuildNumber() |
| 1460 + # Must initialize these fields before pickling. |
| 1461 + b.currentBigState = 'idle' |
| 1462 + b.status = 'idle' |
| 1463 + return b |
| 1464 + |
| 1465 + def setupStatus(self, b): |
| 1466 + m = Mock() |
| 1467 + m.buildbotURL = 'http://buildbot:8010/' |
| 1468 + m.basedir = '/basedir' |
| 1469 + s = master.Status(m) |
| 1470 + b.status = s |
| 1471 + return s |
| 1472 + |
| 1473 + def testBuildCache(self): |
| 1474 + b = self.setupBuilder('builder_1') |
| 1475 + builds = [] |
| 1476 + for i in xrange(5): |
| 1477 + build = b.newBuild() |
| 1478 + build.setProperty('propkey', 'propval%d' % i, 'test') |
| 1479 + builds.append(build) |
| 1480 + build.buildStarted(build) |
| 1481 + build.buildFinished() |
| 1482 + for build in builds: |
| 1483 + build2 = b.getBuild(build.number) |
| 1484 + self.assertTrue(build2) |
| 1485 + self.assertEqual(build2.number, build.number) |
| 1486 + self.assertEqual(build.getProperty('propkey'), |
| 1487 + 'propval%d' % build.number) |
OLD | NEW |