Package buildbot :: Package status :: Package web :: Module grid
[frames] | no frames]

Source Code for Module buildbot.status.web.grid

  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 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  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 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 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 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  from twisted.internet import defer 
 17  from buildbot.status.web.base import HtmlResource 
 18  from buildbot.status.web.base import build_get_class, path_to_builder, path_to_build 
 19  from buildbot.sourcestamp import SourceStamp 
20 21 -class ANYBRANCH: pass # a flag value, used below
22
23 -class GridStatusMixin(object):
24 - def getPageTitle(self, request):
25 status = self.getStatus(request) 26 p = status.getTitle() 27 if p: 28 return "BuildBot: %s" % p 29 else: 30 return "BuildBot"
31 32 # handle reloads through an http header 33 # TODO: send this as a real header, rather than a tag
34 - def get_reload_time(self, request):
35 if "reload" in request.args: 36 try: 37 reload_time = int(request.args["reload"][0]) 38 return max(reload_time, 15) 39 except ValueError: 40 pass 41 return None
42
43 - def build_cxt(self, request, build):
44 if not build: 45 return {} 46 47 if build.isFinished(): 48 # get the text and annotate the first line with a link 49 text = build.getText() 50 if not text: text = [ "(no information)" ] 51 if text == [ "build", "successful" ]: text = [ "OK" ] 52 else: 53 text = [ 'building' ] 54 55 name = build.getBuilder().getName() 56 57 cxt = {} 58 cxt['name'] = name 59 cxt['url'] = path_to_build(request, build) 60 cxt['text'] = text 61 cxt['class'] = build_get_class(build) 62 return cxt
63 64 @defer.inlineCallbacks
65 - def builder_cxt(self, request, builder):
66 state, builds = builder.getState() 67 68 # look for upcoming builds. We say the state is "waiting" if the 69 # builder is otherwise idle and there is a scheduler which tells us a 70 # build will be performed some time in the near future. TODO: this 71 # functionality used to be in BuilderStatus.. maybe this code should 72 # be merged back into it. 73 upcoming = [] 74 builderName = builder.getName() 75 for s in self.getStatus(request).getSchedulers(): 76 if builderName in s.listBuilderNames(): 77 upcoming.extend(s.getPendingBuildTimes()) 78 if state == "idle" and upcoming: 79 state = "waiting" 80 81 n_pending = len((yield builder.getPendingBuildRequestStatuses())) 82 83 cxt = { 'url': path_to_builder(request, builder), 84 'name': builder.getName(), 85 'state': state, 86 'n_pending': n_pending } 87 88 defer.returnValue(cxt)
89
90 - def getSourceStampKey(self, ss):
91 """Given two source stamps, we want to assign them to the same row if 92 they are the same version of code, even if they differ in minor detail. 93 94 This function returns an appropriate comparison key for that. 95 """ 96 return (ss.branch, ss.revision, ss.patch)
97
98 - def getRecentBuilds(self, builder, numBuilds, branch):
99 """ 100 get a list of most recent builds on given builder 101 """ 102 build = builder.getBuild(-1) 103 num = 0 104 while build and num < numBuilds: 105 start = build.getTimes()[0] 106 #TODO: support multiple sourcestamps 107 ss = build.getSourceStamps(absolute=True)[0] 108 109 okay_build = True 110 111 # skip un-started builds 112 if not start: 113 okay_build = False 114 115 # skip non-matching branches 116 if branch != ANYBRANCH and ss.branch != branch: 117 okay_build = False 118 119 if okay_build: 120 num += 1 121 yield build 122 123 build = build.getPreviousBuild() 124 return
125
126 - def getRecentSourcestamps(self, status, numBuilds, categories, branch):
127 """ 128 get a list of the most recent NUMBUILDS SourceStamp tuples, sorted 129 by the earliest start we've seen for them 130 """ 131 # TODO: use baseweb's getLastNBuilds? 132 sourcestamps = { } # { ss-tuple : earliest time } 133 for bn in status.getBuilderNames(): 134 builder = status.getBuilder(bn) 135 if categories and builder.category not in categories: 136 continue 137 for build in self.getRecentBuilds(builder, numBuilds, branch): 138 #TODO: support multiple sourcestamps 139 ss = build.getSourceStamps(absolute=True)[0] 140 key= self.getSourceStampKey(ss) 141 start = build.getTimes()[0] 142 if key not in sourcestamps or sourcestamps[key][1] > start: 143 sourcestamps[key] = (ss, start) 144 145 # now sort those and take the NUMBUILDS most recent 146 sourcestamps = sourcestamps.values() 147 sourcestamps.sort(lambda x, y: cmp(x[1], y[1])) 148 sourcestamps = map(lambda tup : tup[0], sourcestamps) 149 sourcestamps = sourcestamps[-numBuilds:] 150 151 return sourcestamps
152
153 -class GridStatusResource(HtmlResource, GridStatusMixin):
154 # TODO: docs 155 status = None 156 changemaster = None 157 158 @defer.inlineCallbacks
159 - def content(self, request, cxt):
160 """This method builds the regular grid display. 161 That is, build stamps across the top, build hosts down the left side 162 """ 163 164 # get url parameters 165 numBuilds = int(request.args.get("width", [5])[0]) 166 categories = request.args.get("category", []) 167 branch = request.args.get("branch", [ANYBRANCH])[0] 168 if branch == 'trunk': branch = None 169 170 # and the data we want to render 171 status = self.getStatus(request) 172 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 173 174 cxt['refresh'] = self.get_reload_time(request) 175 176 cxt.update({'categories': categories, 177 'branch': branch, 178 'ANYBRANCH': ANYBRANCH, 179 'stamps': map(SourceStamp.asDict, stamps) 180 }) 181 182 sortedBuilderNames = status.getBuilderNames()[:] 183 sortedBuilderNames.sort() 184 185 cxt['builders'] = [] 186 187 for bn in sortedBuilderNames: 188 builds = [None] * len(stamps) 189 190 builder = status.getBuilder(bn) 191 if categories and builder.category not in categories: 192 continue 193 194 for build in self.getRecentBuilds(builder, numBuilds, branch): 195 #TODO: support multiple sourcestamps 196 if len(build.getSourceStamps()) == 1: 197 ss = build.getSourceStamps(absolute=True)[0] 198 key= self.getSourceStampKey(ss) 199 for i in range(len(stamps)): 200 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 201 builds[i] = build 202 203 b = yield self.builder_cxt(request, builder) 204 205 b['builds'] = [] 206 for build in builds: 207 b['builds'].append(self.build_cxt(request, build)) 208 209 cxt['builders'].append(b) 210 211 template = request.site.buildbot_service.templates.get_template("grid.html") 212 defer.returnValue(template.render(**cxt))
213
214 215 -class TransposedGridStatusResource(HtmlResource, GridStatusMixin):
216 # TODO: docs 217 status = None 218 changemaster = None 219 default_rev_order = "asc" 220 221 @defer.inlineCallbacks
222 - def content(self, request, cxt):
223 """This method builds the transposed grid display. 224 That is, build hosts across the top, build stamps down the left side 225 """ 226 227 # get url parameters 228 numBuilds = int(request.args.get("length", [5])[0]) 229 categories = request.args.get("category", []) 230 branch = request.args.get("branch", [ANYBRANCH])[0] 231 if branch == 'trunk': branch = None 232 233 rev_order = request.args.get("rev_order", [self.default_rev_order])[0] 234 if rev_order not in ["asc", "desc"]: 235 rev_order = self.default_rev_order 236 237 cxt['refresh'] = self.get_reload_time(request) 238 239 # and the data we want to render 240 status = self.getStatus(request) 241 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 242 243 cxt.update({'categories': categories, 244 'branch': branch, 245 'ANYBRANCH': ANYBRANCH, 246 'stamps': map(SourceStamp.asDict, stamps), 247 }) 248 249 sortedBuilderNames = status.getBuilderNames()[:] 250 sortedBuilderNames.sort() 251 252 cxt['sorted_builder_names'] = sortedBuilderNames 253 cxt['builder_builds'] = builder_builds = [] 254 cxt['builders'] = builders = [] 255 cxt['range'] = range(len(stamps)) 256 if rev_order == "desc": 257 cxt['range'].reverse() 258 259 for bn in sortedBuilderNames: 260 builds = [None] * len(stamps) 261 262 builder = status.getBuilder(bn) 263 if categories and builder.category not in categories: 264 continue 265 266 for build in self.getRecentBuilds(builder, numBuilds, branch): 267 #TODO: support multiple sourcestamps 268 if len(build.getSourceStamps()) == 1: 269 ss = build.getSourceStamps(absolute=True)[0] 270 key = self.getSourceStampKey(ss) 271 for i in range(len(stamps)): 272 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 273 builds[i] = build 274 275 b = yield self.builder_cxt(request, builder) 276 builders.append(b) 277 278 builder_builds.append(map(lambda b: self.build_cxt(request, b), builds)) 279 280 template = request.site.buildbot_service.templates.get_template('grid_transposed.html') 281 defer.returnValue(template.render(**cxt))
282