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

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
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
11 # this program; if not, write to the Free Software Foundation, Inc., 51 11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 # 13 #
14 # Copyright Buildbot Team Members 14 # Copyright Buildbot Team Members
15 15
16 16
17 import weakref 17 import weakref
18 import gc 18 import gc
19 import os, re, itertools 19 import os, re, itertools
20 from cPickle import load, dump 20 from cPickle import load, dump
21 21
22 from zope.interface import implements 22 from zope.interface import implements
23 from twisted.python import log, runtime 23 from twisted.python import log, runtime
24 from twisted.persisted import styles 24 from twisted.persisted import styles
25 from buildbot.process import metrics 25 from buildbot.process import metrics
26 from buildbot import interfaces, util 26 from buildbot import interfaces, util
27 from buildbot.util.lru import SyncLRUCache
27 from buildbot.status.event import Event 28 from buildbot.status.event import Event
28 from buildbot.status.build import BuildStatus 29 from buildbot.status.build import BuildStatus
29 from buildbot.status.buildrequest import BuildRequestStatus 30 from buildbot.status.buildrequest import BuildRequestStatus
30 31
31 # user modules expect these symbols to be present here 32 # user modules expect these symbols to be present here
32 from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED 33 from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED
33 from buildbot.status.results import EXCEPTION, RETRY, Results, worst_status 34 from buildbot.status.results import EXCEPTION, RETRY, Results, worst_status
34 _hush_pyflakes = [ SUCCESS, WARNINGS, FAILURE, SKIPPED, 35 _hush_pyflakes = [ SUCCESS, WARNINGS, FAILURE, SKIPPED,
35 EXCEPTION, RETRY, Results, worst_status ] 36 EXCEPTION, RETRY, Results, worst_status ]
36 37
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 self.slavenames = [] 80 self.slavenames = []
80 self.events = [] 81 self.events = []
81 # these three hold Events, and are used to retrieve the current 82 # these three hold Events, and are used to retrieve the current
82 # state of the boxes. 83 # state of the boxes.
83 self.lastBuildStatus = None 84 self.lastBuildStatus = None
84 #self.currentBig = None 85 #self.currentBig = None
85 #self.currentSmall = None 86 #self.currentSmall = None
86 self.currentBuilds = [] 87 self.currentBuilds = []
87 self.nextBuild = None 88 self.nextBuild = None
88 self.watchers = [] 89 self.watchers = []
89 self.buildCache = weakref.WeakValueDictionary() 90 self.buildCache = SyncLRUCache(self.cacheMiss, self.buildCacheSize)
90 self.buildCache_LRU = []
91 self.logCompressionLimit = False # default to no compression for tests 91 self.logCompressionLimit = False # default to no compression for tests
92 self.logCompressionMethod = "bz2" 92 self.logCompressionMethod = "bz2"
93 self.logMaxSize = None # No default limit 93 self.logMaxSize = None # No default limit
94 self.logMaxTailSize = None # No tail buffering 94 self.logMaxTailSize = None # No tail buffering
95 95
96 # persistence 96 # persistence
97 97
98 def __getstate__(self): 98 def __getstate__(self):
99 # when saving, don't record transient stuff like what builds are 99 # when saving, don't record transient stuff like what builds are
100 # currently running, because they won't be there when we start back 100 # 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 101 # up. Nor do we save self.watchers, nor anything that gets set by our
102 # parent like .basedir and .status 102 # parent like .basedir and .status
103 d = styles.Versioned.__getstate__(self) 103 d = styles.Versioned.__getstate__(self)
104 d['watchers'] = [] 104 d['watchers'] = []
105 del d['buildCache'] 105 del d['buildCache']
106 del d['buildCache_LRU']
107 for b in self.currentBuilds: 106 for b in self.currentBuilds:
108 b.saveYourself() 107 b.saveYourself()
109 # TODO: push a 'hey, build was interrupted' event 108 # TODO: push a 'hey, build was interrupted' event
110 del d['currentBuilds'] 109 del d['currentBuilds']
111 d.pop('pendingBuilds', None) 110 d.pop('pendingBuilds', None)
112 del d['currentBigState'] 111 del d['currentBigState']
113 del d['basedir'] 112 del d['basedir']
114 del d['status'] 113 del d['status']
115 del d['nextBuildNumber'] 114 del d['nextBuildNumber']
116 return d 115 return d
117 116
118 def __setstate__(self, d): 117 def __setstate__(self, d):
119 # when loading, re-initialize the transient stuff. Remember that 118 # when loading, re-initialize the transient stuff. Remember that
120 # upgradeToVersion1 and such will be called after this finishes. 119 # upgradeToVersion1 and such will be called after this finishes.
121 styles.Versioned.__setstate__(self, d) 120 styles.Versioned.__setstate__(self, d)
122 self.buildCache = weakref.WeakValueDictionary() 121 self.buildCache = SyncLRUCache(self.cacheMiss, self.buildCacheSize)
123 self.buildCache_LRU = []
124 self.currentBuilds = [] 122 self.currentBuilds = []
125 self.watchers = [] 123 self.watchers = []
126 self.slavenames = [] 124 self.slavenames = []
127 # self.basedir must be filled in by our parent 125 # self.basedir must be filled in by our parent
128 # self.status must be filled in by our parent 126 # self.status must be filled in by our parent
129 127
130 def reconfigFromBuildmaster(self, buildmaster): 128 def reconfigFromBuildmaster(self, buildmaster):
131 # Note that we do not hang onto the buildmaster, since this object 129 # Note that we do not hang onto the buildmaster, since this object
132 # gets pickled and unpickled. 130 # gets pickled and unpickled.
133 if buildmaster.buildCacheSize is not None: 131 if buildmaster.buildCacheSize is not None:
134 self.buildCacheSize = buildmaster.buildCacheSize 132 self.buildCacheSize = buildmaster.buildCacheSize
133 self.buildCache.set_max_size(buildmaster.buildCacheSize)
135 134
136 def upgradeToVersion1(self): 135 def upgradeToVersion1(self):
137 if hasattr(self, 'slavename'): 136 if hasattr(self, 'slavename'):
138 self.slavenames = [self.slavename] 137 self.slavenames = [self.slavename]
139 del self.slavename 138 del self.slavename
140 if hasattr(self, 'nextBuildNumber'): 139 if hasattr(self, 'nextBuildNumber'):
141 del self.nextBuildNumber # determineNextBuildNumber chooses this 140 del self.nextBuildNumber # determineNextBuildNumber chooses this
142 self.wasUpgraded = True 141 self.wasUpgraded = True
143 142
144 def determineNextBuildNumber(self): 143 def determineNextBuildNumber(self):
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
179 try: 178 try:
180 dump(self, open(tmpfilename, "wb"), -1) 179 dump(self, open(tmpfilename, "wb"), -1)
181 if runtime.platformType == 'win32': 180 if runtime.platformType == 'win32':
182 # windows cannot rename a file on top of an existing one 181 # windows cannot rename a file on top of an existing one
183 if os.path.exists(filename): 182 if os.path.exists(filename):
184 os.unlink(filename) 183 os.unlink(filename)
185 os.rename(tmpfilename, filename) 184 os.rename(tmpfilename, filename)
186 except: 185 except:
187 log.msg("unable to save builder %s" % self.name) 186 log.msg("unable to save builder %s" % self.name)
188 log.err() 187 log.err()
189 188
190 189
191 # build cache management 190 # build cache management
192 191
193 def makeBuildFilename(self, number): 192 def makeBuildFilename(self, number):
194 return os.path.join(self.basedir, "%d" % number) 193 return os.path.join(self.basedir, "%d" % number)
195 194
196 def touchBuildCache(self, build): 195 def getBuildByNumber(self, number):
197 self.buildCache[build.number] = build 196 return self.buildCache.get(number)
198 if build in self.buildCache_LRU:
199 self.buildCache_LRU.remove(build)
200 self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ]
201 return build
202 197
203 def getBuildByNumber(self, number): 198 def loadBuildFromFile(self, number):
204 # first look in currentBuilds
205 for b in self.currentBuilds:
206 if b.number == number:
207 return self.touchBuildCache(b)
208
209 # then in the buildCache
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) 199 filename = self.makeBuildFilename(number)
217 try: 200 try:
218 log.msg("Loading builder %s's build %d from on-disk pickle" 201 log.msg("Loading builder %s's build %d from on-disk pickle"
219 % (self.name, number)) 202 % (self.name, number))
220 build = load(open(filename, "rb")) 203 build = load(open(filename, "rb"))
221 build.builder = self 204 build.builder = self
222 205
223 # (bug #1068) if we need to upgrade, we probably need to rewrite 206 # (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 207 # this pickle, too. We determine this by looking at the list of
225 # Versioned objects that have been unpickled, and (after doUpgrade) 208 # Versioned objects that have been unpickled, and (after doUpgrade)
226 # checking to see if any of them set wasUpgraded. The Versioneds' 209 # checking to see if any of them set wasUpgraded. The Versioneds'
227 # upgradeToVersionNN methods all set this. 210 # upgradeToVersionNN methods all set this.
228 versioneds = styles.versionedsToUpgrade 211 versioneds = styles.versionedsToUpgrade
229 styles.doUpgrade() 212 styles.doUpgrade()
230 if True in [ hasattr(o, 'wasUpgraded') for o in versioneds.values() ]: 213 if True in [ hasattr(o, 'wasUpgraded') for o in versioneds.values() ]:
231 log.msg("re-writing upgraded build pickle") 214 log.msg("re-writing upgraded build pickle")
232 build.saveYourself() 215 build.saveYourself()
233 216
234 # handle LogFiles from after 0.5.0 and before 0.6.5 217 # handle LogFiles from after 0.5.0 and before 0.6.5
235 build.upgradeLogfiles() 218 build.upgradeLogfiles()
236 # check that logfiles exist 219 # check that logfiles exist
237 build.checkLogfiles() 220 build.checkLogfiles()
238 return self.touchBuildCache(build) 221 return build
239 except IOError: 222 except IOError:
240 raise IndexError("no such build %d" % number) 223 raise IndexError("no such build %d" % number)
241 except EOFError: 224 except EOFError:
242 raise IndexError("corrupted build pickle %d" % number) 225 raise IndexError("corrupted build pickle %d" % number)
243 226
227 def cacheMiss(self, number):
228 # first look in currentBuilds
229 for b in self.currentBuilds:
230 if b.number == number:
231 return b
232 # then fall back to loading it from disk
233 return self.loadBuildFromFile(number)
234
244 def prune(self, events_only=False): 235 def prune(self, events_only=False):
245 # begin by pruning our own events 236 # begin by pruning our own events
246 self.events = self.events[-self.eventHorizon:] 237 self.events = self.events[-self.eventHorizon:]
247 238
248 if events_only: 239 if events_only:
249 return 240 return
250 241
251 gc.collect() 242 gc.collect()
252 243
253 # get the horizons straight 244 # get the horizons straight
(...skipping 26 matching lines...) Expand all
280 is_logfile = False 271 is_logfile = False
281 if mo: 272 if mo:
282 num = int(mo.group(1)) 273 num = int(mo.group(1))
283 else: 274 else:
284 mo = build_log_re.match(filename) 275 mo = build_log_re.match(filename)
285 if mo: 276 if mo:
286 num = int(mo.group(1)) 277 num = int(mo.group(1))
287 is_logfile = True 278 is_logfile = True
288 279
289 if num is None: continue 280 if num is None: continue
290 if num in self.buildCache: continue 281 if num in self.buildCache.cache: continue
291 282
292 if (is_logfile and num < earliest_log) or num < earliest_build: 283 if (is_logfile and num < earliest_log) or num < earliest_build:
293 pathname = os.path.join(self.basedir, filename) 284 pathname = os.path.join(self.basedir, filename)
294 log.msg("pruning '%s'" % pathname) 285 log.msg("pruning '%s'" % pathname)
295 try: os.unlink(pathname) 286 try: os.unlink(pathname)
296 except OSError: pass 287 except OSError: pass
297 288
298 # IBuilderStatus methods 289 # IBuilderStatus methods
299 def getName(self): 290 def getName(self):
300 return self.name 291 return self.name
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after
503 494
504 # buildStarted is called by our child BuildStatus instances 495 # buildStarted is called by our child BuildStatus instances
505 def buildStarted(self, s): 496 def buildStarted(self, s):
506 """Now the BuildStatus object is ready to go (it knows all of its 497 """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.""" 498 Steps, its ETA, etc), so it is safe to notify our watchers."""
508 499
509 assert s.builder is self # paranoia 500 assert s.builder is self # paranoia
510 assert s.number == self.nextBuildNumber - 1 501 assert s.number == self.nextBuildNumber - 1
511 assert s not in self.currentBuilds 502 assert s not in self.currentBuilds
512 self.currentBuilds.append(s) 503 self.currentBuilds.append(s)
513 self.touchBuildCache(s) 504 self.buildCache.put(s.number, s)
514 505
515 # now that the BuildStatus is prepared to answer queries, we can 506 # now that the BuildStatus is prepared to answer queries, we can
516 # announce the new build to all our watchers 507 # announce the new build to all our watchers
517 508
518 for w in self.watchers: # TODO: maybe do this later? callLater(0)? 509 for w in self.watchers: # TODO: maybe do this later? callLater(0)?
519 try: 510 try:
520 receiver = w.buildStarted(self.getName(), s) 511 receiver = w.buildStarted(self.getName(), s)
521 if receiver: 512 if receiver:
522 if type(receiver) == type(()): 513 if type(receiver) == type(()):
523 s.subscribe(receiver[0], receiver[1]) 514 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) 604 result['basedir'] = os.path.basename(self.basedir)
614 result['category'] = self.category 605 result['category'] = self.category
615 result['slaves'] = self.slavenames 606 result['slaves'] = self.slavenames
616 #result['url'] = self.parent.getURLForThing(self) 607 #result['url'] = self.parent.getURLForThing(self)
617 # TODO(maruel): Add cache settings? Do we care? 608 # TODO(maruel): Add cache settings? Do we care?
618 609
619 # Transient 610 # Transient
620 # Collect build numbers. 611 # Collect build numbers.
621 # Important: Only grab the *cached* builds numbers to reduce I/O. 612 # Important: Only grab the *cached* builds numbers to reduce I/O.
622 current_builds = [b.getNumber() for b in self.currentBuilds] 613 current_builds = [b.getNumber() for b in self.currentBuilds]
623 cached_builds = list(set(self.buildCache.keys() + current_builds)) 614 cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
624 cached_builds.sort() 615 cached_builds.sort()
625 result['cachedBuilds'] = cached_builds 616 result['cachedBuilds'] = cached_builds
626 result['currentBuilds'] = current_builds 617 result['currentBuilds'] = current_builds
627 result['state'] = self.getState()[0] 618 result['state'] = self.getState()[0]
628 # lies, but we don't have synchronous access to this info; use 619 # lies, but we don't have synchronous access to this info; use
629 # asDict_async instead 620 # asDict_async instead
630 result['pendingBuilds'] = 0 621 result['pendingBuilds'] = 0
631 return result 622 return result
632 623
633 def asDict_async(self): 624 def asDict_async(self):
634 """Just like L{asDict}, but with a nonzero pendingBuilds.""" 625 """Just like L{asDict}, but with a nonzero pendingBuilds."""
635 result = self.asDict() 626 result = self.asDict()
636 d = self.getPendingBuildRequestStatuses() 627 d = self.getPendingBuildRequestStatuses()
637 def combine(statuses): 628 def combine(statuses):
638 result['pendingBuilds'] = len(statuses) 629 result['pendingBuilds'] = len(statuses)
639 return result 630 return result
640 d.addCallback(combine) 631 d.addCallback(combine)
641 return d 632 return d
642 633
643 def getMetrics(self): 634 def getMetrics(self):
644 return self.botmaster.parent.metrics 635 return self.botmaster.parent.metrics
645 636
646 # vim: set ts=4 sts=4 sw=4 et: 637 # vim: set ts=4 sts=4 sw=4 et:
OLDNEW
« no previous file with comments | « third_party/buildbot_8_4p1/README.chromium ('k') | third_party/buildbot_8_4p1/buildbot/test/unit/test_status_builder_cache.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698