Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # This file is part of Buildbot. Buildbot is free software: you can | 1 # This file is part of Buildbot. Buildbot is free software: you can |
| 2 # redistribute it and/or modify it under the terms of the GNU General Public | 2 # redistribute it and/or modify it under the terms of the GNU General Public |
| 3 # License as published by the Free Software Foundation, version 2. | 3 # License as published by the Free Software Foundation, version 2. |
| 4 # | 4 # |
| 5 # This program is distributed in the hope that it will be useful, but WITHOUT | 5 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | 7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| 8 # details. | 8 # details. |
| 9 # | 9 # |
| 10 # You should have received a copy of the GNU General Public License along with | 10 # You should have received a copy of the GNU General Public License along with |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 79 self.slavenames = [] | 79 self.slavenames = [] |
| 80 self.events = [] | 80 self.events = [] |
| 81 # these three hold Events, and are used to retrieve the current | 81 # these three hold Events, and are used to retrieve the current |
| 82 # state of the boxes. | 82 # state of the boxes. |
| 83 self.lastBuildStatus = None | 83 self.lastBuildStatus = None |
| 84 #self.currentBig = None | 84 #self.currentBig = None |
| 85 #self.currentSmall = None | 85 #self.currentSmall = None |
| 86 self.currentBuilds = [] | 86 self.currentBuilds = [] |
| 87 self.nextBuild = None | 87 self.nextBuild = None |
| 88 self.watchers = [] | 88 self.watchers = [] |
| 89 self.buildCache = weakref.WeakValueDictionary() | 89 #self.buildCache = weakref.WeakValueDictionary() |
|
M-A Ruel
2012/03/16 13:02:06
I'd simply remove the old lines instead of comment
cmp
2012/03/16 17:40:51
Agree.
szager
2012/03/16 20:52:04
Done.
| |
| 90 self.buildCache_LRU = [] | 90 #self.buildCache_LRU = [] |
| 91 self.buildCache = util.lru.AsyncLRUCache(self.cacheMiss, | |
| 92 self.buildCacheSize) | |
| 91 self.logCompressionLimit = False # default to no compression for tests | 93 self.logCompressionLimit = False # default to no compression for tests |
| 92 self.logCompressionMethod = "bz2" | 94 self.logCompressionMethod = "bz2" |
| 93 self.logMaxSize = None # No default limit | 95 self.logMaxSize = None # No default limit |
| 94 self.logMaxTailSize = None # No tail buffering | 96 self.logMaxTailSize = None # No tail buffering |
| 95 | 97 |
| 96 # persistence | 98 # persistence |
| 97 | 99 |
| 98 def __getstate__(self): | 100 def __getstate__(self): |
| 99 # when saving, don't record transient stuff like what builds are | 101 # when saving, don't record transient stuff like what builds are |
| 100 # currently running, because they won't be there when we start back | 102 # currently running, because they won't be there when we start back |
| 101 # up. Nor do we save self.watchers, nor anything that gets set by our | 103 # up. Nor do we save self.watchers, nor anything that gets set by our |
| 102 # parent like .basedir and .status | 104 # parent like .basedir and .status |
| 103 d = styles.Versioned.__getstate__(self) | 105 d = styles.Versioned.__getstate__(self) |
| 104 d['watchers'] = [] | 106 d['watchers'] = [] |
| 105 del d['buildCache'] | 107 del d['buildCache'] |
| 106 del d['buildCache_LRU'] | 108 #del d['buildCache_LRU'] |
| 107 for b in self.currentBuilds: | 109 for b in self.currentBuilds: |
| 108 b.saveYourself() | 110 b.saveYourself() |
| 109 # TODO: push a 'hey, build was interrupted' event | 111 # TODO: push a 'hey, build was interrupted' event |
| 110 del d['currentBuilds'] | 112 del d['currentBuilds'] |
| 111 d.pop('pendingBuilds', None) | 113 d.pop('pendingBuilds', None) |
| 112 del d['currentBigState'] | 114 del d['currentBigState'] |
| 113 del d['basedir'] | 115 del d['basedir'] |
| 114 del d['status'] | 116 del d['status'] |
| 115 del d['nextBuildNumber'] | 117 del d['nextBuildNumber'] |
| 116 return d | 118 return d |
| 117 | 119 |
| 118 def __setstate__(self, d): | 120 def __setstate__(self, d): |
| 119 # when loading, re-initialize the transient stuff. Remember that | 121 # when loading, re-initialize the transient stuff. Remember that |
| 120 # upgradeToVersion1 and such will be called after this finishes. | 122 # upgradeToVersion1 and such will be called after this finishes. |
| 121 styles.Versioned.__setstate__(self, d) | 123 styles.Versioned.__setstate__(self, d) |
| 122 self.buildCache = weakref.WeakValueDictionary() | 124 #self.buildCache = weakref.WeakValueDictionary() |
| 123 self.buildCache_LRU = [] | 125 #self.buildCache_LRU = [] |
| 126 self.buildCache = util.lru.AsyncLRUCache(self.cacheMiss, | |
| 127 self.buildCacheSize) | |
| 124 self.currentBuilds = [] | 128 self.currentBuilds = [] |
| 125 self.watchers = [] | 129 self.watchers = [] |
| 126 self.slavenames = [] | 130 self.slavenames = [] |
| 127 # self.basedir must be filled in by our parent | 131 # self.basedir must be filled in by our parent |
| 128 # self.status must be filled in by our parent | 132 # self.status must be filled in by our parent |
| 129 | 133 |
| 130 def reconfigFromBuildmaster(self, buildmaster): | 134 def reconfigFromBuildmaster(self, buildmaster): |
| 131 # Note that we do not hang onto the buildmaster, since this object | 135 # Note that we do not hang onto the buildmaster, since this object |
| 132 # gets pickled and unpickled. | 136 # gets pickled and unpickled. |
| 133 if buildmaster.buildCacheSize is not None: | 137 if buildmaster.buildCacheSize is not None: |
| 134 self.buildCacheSize = buildmaster.buildCacheSize | 138 self.buildCacheSize = buildmaster.buildCacheSize |
| 139 self.buildCache.set_max_size(buildmaster.buildCacheSize) | |
| 135 | 140 |
| 136 def upgradeToVersion1(self): | 141 def upgradeToVersion1(self): |
| 137 if hasattr(self, 'slavename'): | 142 if hasattr(self, 'slavename'): |
| 138 self.slavenames = [self.slavename] | 143 self.slavenames = [self.slavename] |
| 139 del self.slavename | 144 del self.slavename |
| 140 if hasattr(self, 'nextBuildNumber'): | 145 if hasattr(self, 'nextBuildNumber'): |
| 141 del self.nextBuildNumber # determineNextBuildNumber chooses this | 146 del self.nextBuildNumber # determineNextBuildNumber chooses this |
| 142 self.wasUpgraded = True | 147 self.wasUpgraded = True |
| 143 | 148 |
| 144 def determineNextBuildNumber(self): | 149 def determineNextBuildNumber(self): |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 179 try: | 184 try: |
| 180 dump(self, open(tmpfilename, "wb"), -1) | 185 dump(self, open(tmpfilename, "wb"), -1) |
| 181 if runtime.platformType == 'win32': | 186 if runtime.platformType == 'win32': |
| 182 # windows cannot rename a file on top of an existing one | 187 # windows cannot rename a file on top of an existing one |
| 183 if os.path.exists(filename): | 188 if os.path.exists(filename): |
| 184 os.unlink(filename) | 189 os.unlink(filename) |
| 185 os.rename(tmpfilename, filename) | 190 os.rename(tmpfilename, filename) |
| 186 except: | 191 except: |
| 187 log.msg("unable to save builder %s" % self.name) | 192 log.msg("unable to save builder %s" % self.name) |
| 188 log.err() | 193 log.err() |
| 189 | 194 |
| 190 | 195 |
| 191 # build cache management | 196 # build cache management |
| 192 | 197 |
| 193 def makeBuildFilename(self, number): | 198 def makeBuildFilename(self, number): |
| 194 return os.path.join(self.basedir, "%d" % number) | 199 return os.path.join(self.basedir, "%d" % number) |
| 195 | 200 |
| 196 def touchBuildCache(self, build): | 201 # def touchBuildCache(self, build): |
| 197 self.buildCache[build.number] = build | 202 # self.buildCache[build.number] = build |
| 198 if build in self.buildCache_LRU: | 203 # if build in self.buildCache_LRU: |
| 199 self.buildCache_LRU.remove(build) | 204 # self.buildCache_LRU.remove(build) |
| 200 self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ] | 205 # self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ] |
| 201 return build | 206 # return build |
| 202 | 207 |
| 203 def getBuildByNumber(self, number): | 208 def getBuildByNumber(self, number): |
| 204 # first look in currentBuilds | 209 return self.buildCache.get(number) |
| 205 for b in self.currentBuilds: | |
| 206 if b.number == number: | |
| 207 return self.touchBuildCache(b) | |
| 208 | 210 |
| 209 # then in the buildCache | 211 def loadBuildFromFile(self, number): |
| 210 if number in self.buildCache: | |
| 211 metrics.MetricCountEvent.log("buildCache.hits", 1) | |
| 212 return self.touchBuildCache(self.buildCache[number]) | |
| 213 metrics.MetricCountEvent.log("buildCache.misses", 1) | |
| 214 | |
| 215 # then fall back to loading it from disk | |
| 216 filename = self.makeBuildFilename(number) | 212 filename = self.makeBuildFilename(number) |
| 217 try: | 213 try: |
| 218 log.msg("Loading builder %s's build %d from on-disk pickle" | 214 log.msg("Loading builder %s's build %d from on-disk pickle" |
| 219 % (self.name, number)) | 215 % (self.name, number)) |
| 220 build = load(open(filename, "rb")) | 216 build = load(open(filename, "rb")) |
| 221 build.builder = self | 217 build.builder = self |
| 222 | 218 |
| 223 # (bug #1068) if we need to upgrade, we probably need to rewrite | 219 # (bug #1068) if we need to upgrade, we probably need to rewrite |
| 224 # this pickle, too. We determine this by looking at the list of | 220 # this pickle, too. We determine this by looking at the list of |
| 225 # Versioned objects that have been unpickled, and (after doUpgrade) | 221 # Versioned objects that have been unpickled, and (after doUpgrade) |
| 226 # checking to see if any of them set wasUpgraded. The Versioneds' | 222 # checking to see if any of them set wasUpgraded. The Versioneds' |
| 227 # upgradeToVersionNN methods all set this. | 223 # upgradeToVersionNN methods all set this. |
| 228 versioneds = styles.versionedsToUpgrade | 224 versioneds = styles.versionedsToUpgrade |
| 229 styles.doUpgrade() | 225 styles.doUpgrade() |
| 230 if True in [ hasattr(o, 'wasUpgraded') for o in versioneds.values() ]: | 226 if True in [ hasattr(o, 'wasUpgraded') for o in versioneds.values() ]: |
| 231 log.msg("re-writing upgraded build pickle") | 227 log.msg("re-writing upgraded build pickle") |
| 232 build.saveYourself() | 228 build.saveYourself() |
| 233 | 229 |
| 234 # handle LogFiles from after 0.5.0 and before 0.6.5 | 230 # handle LogFiles from after 0.5.0 and before 0.6.5 |
| 235 build.upgradeLogfiles() | 231 build.upgradeLogfiles() |
| 236 # check that logfiles exist | 232 # check that logfiles exist |
| 237 build.checkLogfiles() | 233 build.checkLogfiles() |
| 238 return self.touchBuildCache(build) | 234 #return self.touchBuildCache(build) |
| 235 return build | |
| 239 except IOError: | 236 except IOError: |
| 240 raise IndexError("no such build %d" % number) | 237 raise IndexError("no such build %d" % number) |
| 241 except EOFError: | 238 except EOFError: |
| 242 raise IndexError("corrupted build pickle %d" % number) | 239 raise IndexError("corrupted build pickle %d" % number) |
| 243 | 240 |
| 241 def cacheMiss(self, number): | |
| 242 # first look in currentBuilds | |
| 243 for b in self.currentBuilds: | |
|
M-A Ruel
2012/03/16 13:02:06
if any(b.number == number for b in self.currentBui
szager
2012/03/16 20:52:04
I don't like that because it requires two linear s
| |
| 244 if b.number == number: | |
| 245 #return self.touchBuildCache(b) | |
| 246 return defer.succeed(b) | |
| 247 | |
| 248 # then fall back to loading it from disk | |
| 249 return threads.deferToThread(self.loadBuildFromFile, number) | |
| 250 | |
| 244 def prune(self, events_only=False): | 251 def prune(self, events_only=False): |
| 245 # begin by pruning our own events | 252 # begin by pruning our own events |
| 246 self.events = self.events[-self.eventHorizon:] | 253 self.events = self.events[-self.eventHorizon:] |
| 247 | 254 |
| 248 if events_only: | 255 if events_only: |
| 249 return | 256 return |
| 250 | 257 |
| 251 gc.collect() | 258 gc.collect() |
| 252 | 259 |
| 253 # get the horizons straight | 260 # get the horizons straight |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 280 is_logfile = False | 287 is_logfile = False |
| 281 if mo: | 288 if mo: |
| 282 num = int(mo.group(1)) | 289 num = int(mo.group(1)) |
| 283 else: | 290 else: |
| 284 mo = build_log_re.match(filename) | 291 mo = build_log_re.match(filename) |
| 285 if mo: | 292 if mo: |
| 286 num = int(mo.group(1)) | 293 num = int(mo.group(1)) |
| 287 is_logfile = True | 294 is_logfile = True |
| 288 | 295 |
| 289 if num is None: continue | 296 if num is None: continue |
| 290 if num in self.buildCache: continue | 297 if num in self.buildCache.cache: continue |
| 291 | 298 |
| 292 if (is_logfile and num < earliest_log) or num < earliest_build: | 299 if (is_logfile and num < earliest_log) or num < earliest_build: |
| 293 pathname = os.path.join(self.basedir, filename) | 300 pathname = os.path.join(self.basedir, filename) |
| 294 log.msg("pruning '%s'" % pathname) | 301 log.msg("pruning '%s'" % pathname) |
| 295 try: os.unlink(pathname) | 302 try: os.unlink(pathname) |
| 296 except OSError: pass | 303 except OSError: pass |
| 297 | 304 |
| 298 # IBuilderStatus methods | 305 # IBuilderStatus methods |
| 299 def getName(self): | 306 def getName(self): |
| 300 return self.name | 307 return self.name |
| (...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 503 | 510 |
| 504 # buildStarted is called by our child BuildStatus instances | 511 # buildStarted is called by our child BuildStatus instances |
| 505 def buildStarted(self, s): | 512 def buildStarted(self, s): |
| 506 """Now the BuildStatus object is ready to go (it knows all of its | 513 """Now the BuildStatus object is ready to go (it knows all of its |
| 507 Steps, its ETA, etc), so it is safe to notify our watchers.""" | 514 Steps, its ETA, etc), so it is safe to notify our watchers.""" |
| 508 | 515 |
| 509 assert s.builder is self # paranoia | 516 assert s.builder is self # paranoia |
| 510 assert s.number == self.nextBuildNumber - 1 | 517 assert s.number == self.nextBuildNumber - 1 |
| 511 assert s not in self.currentBuilds | 518 assert s not in self.currentBuilds |
| 512 self.currentBuilds.append(s) | 519 self.currentBuilds.append(s) |
| 513 self.touchBuildCache(s) | 520 #self.touchBuildCache(s) |
| 521 self.buildCache.put(s.number, s) | |
| 514 | 522 |
| 515 # now that the BuildStatus is prepared to answer queries, we can | 523 # now that the BuildStatus is prepared to answer queries, we can |
| 516 # announce the new build to all our watchers | 524 # announce the new build to all our watchers |
| 517 | 525 |
| 518 for w in self.watchers: # TODO: maybe do this later? callLater(0)? | 526 for w in self.watchers: # TODO: maybe do this later? callLater(0)? |
| 519 try: | 527 try: |
| 520 receiver = w.buildStarted(self.getName(), s) | 528 receiver = w.buildStarted(self.getName(), s) |
| 521 if receiver: | 529 if receiver: |
| 522 if type(receiver) == type(()): | 530 if type(receiver) == type(()): |
| 523 s.subscribe(receiver[0], receiver[1]) | 531 s.subscribe(receiver[0], receiver[1]) |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 613 result['basedir'] = os.path.basename(self.basedir) | 621 result['basedir'] = os.path.basename(self.basedir) |
| 614 result['category'] = self.category | 622 result['category'] = self.category |
| 615 result['slaves'] = self.slavenames | 623 result['slaves'] = self.slavenames |
| 616 #result['url'] = self.parent.getURLForThing(self) | 624 #result['url'] = self.parent.getURLForThing(self) |
| 617 # TODO(maruel): Add cache settings? Do we care? | 625 # TODO(maruel): Add cache settings? Do we care? |
| 618 | 626 |
| 619 # Transient | 627 # Transient |
| 620 # Collect build numbers. | 628 # Collect build numbers. |
| 621 # Important: Only grab the *cached* builds numbers to reduce I/O. | 629 # Important: Only grab the *cached* builds numbers to reduce I/O. |
| 622 current_builds = [b.getNumber() for b in self.currentBuilds] | 630 current_builds = [b.getNumber() for b in self.currentBuilds] |
| 623 cached_builds = list(set(self.buildCache.keys() + current_builds)) | 631 cached_builds = list(set(self.buildCache.cache.keys() + current_builds)) |
| 624 cached_builds.sort() | 632 cached_builds.sort() |
| 625 result['cachedBuilds'] = cached_builds | 633 result['cachedBuilds'] = cached_builds |
| 626 result['currentBuilds'] = current_builds | 634 result['currentBuilds'] = current_builds |
| 627 result['state'] = self.getState()[0] | 635 result['state'] = self.getState()[0] |
| 628 # lies, but we don't have synchronous access to this info; use | 636 # lies, but we don't have synchronous access to this info; use |
| 629 # asDict_async instead | 637 # asDict_async instead |
| 630 result['pendingBuilds'] = 0 | 638 result['pendingBuilds'] = 0 |
| 631 return result | 639 return result |
| 632 | 640 |
| 633 def asDict_async(self): | 641 def asDict_async(self): |
| 634 """Just like L{asDict}, but with a nonzero pendingBuilds.""" | 642 """Just like L{asDict}, but with a nonzero pendingBuilds.""" |
| 635 result = self.asDict() | 643 result = self.asDict() |
| 636 d = self.getPendingBuildRequestStatuses() | 644 d = self.getPendingBuildRequestStatuses() |
| 637 def combine(statuses): | 645 def combine(statuses): |
| 638 result['pendingBuilds'] = len(statuses) | 646 result['pendingBuilds'] = len(statuses) |
| 639 return result | 647 return result |
| 640 d.addCallback(combine) | 648 d.addCallback(combine) |
| 641 return d | 649 return d |
| 642 | 650 |
| 643 def getMetrics(self): | 651 def getMetrics(self): |
| 644 return self.botmaster.parent.metrics | 652 return self.botmaster.parent.metrics |
| 645 | 653 |
| 646 # vim: set ts=4 sts=4 sw=4 et: | 654 # vim: set ts=4 sts=4 sw=4 et: |
| OLD | NEW |