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

Side by Side Diff: third_party/buildbot_8_4p1/buildbot/status/builder.py

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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/buildbot_8_4p1/README.chromium ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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:
OLDNEW
« no previous file with comments | « third_party/buildbot_8_4p1/README.chromium ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698