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 |