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.deferredGenerator
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 wfd = defer.waitForDeferred( 82 builder.getPendingBuildRequestStatuses()) 83 yield wfd 84 n_pending = len(wfd.getResult()) 85 86 cxt = { 'url': path_to_builder(request, builder), 87 'name': builder.getName(), 88 'state': state, 89 'n_pending': n_pending } 90 91 yield cxt
92
93 - def getSourceStampKey(self, ss):
94 """Given two source stamps, we want to assign them to the same row if 95 they are the same version of code, even if they differ in minor detail. 96 97 This function returns an appropriate comparison key for that. 98 """ 99 return (ss.branch, ss.revision, ss.patch)
100
101 - def getRecentBuilds(self, builder, numBuilds, branch):
102 """ 103 get a list of most recent builds on given builder 104 """ 105 build = builder.getBuild(-1) 106 num = 0 107 while build and num < numBuilds: 108 start = build.getTimes()[0] 109 ss = build.getSourceStamp(absolute=True) 110 111 okay_build = True 112 113 # skip un-started builds 114 if not start: 115 okay_build = False 116 117 # skip non-matching branches 118 if branch != ANYBRANCH and ss.branch != branch: 119 okay_build = False 120 121 if okay_build: 122 num += 1 123 yield build 124 125 build = build.getPreviousBuild() 126 return
127
128 - def getRecentSourcestamps(self, status, numBuilds, categories, branch):
129 """ 130 get a list of the most recent NUMBUILDS SourceStamp tuples, sorted 131 by the earliest start we've seen for them 132 """ 133 # TODO: use baseweb's getLastNBuilds? 134 sourcestamps = { } # { ss-tuple : earliest time } 135 for bn in status.getBuilderNames(): 136 builder = status.getBuilder(bn) 137 if categories and builder.category not in categories: 138 continue 139 for build in self.getRecentBuilds(builder, numBuilds, branch): 140 ss = build.getSourceStamp(absolute=True) 141 key= self.getSourceStampKey(ss) 142 start = build.getTimes()[0] 143 if key not in sourcestamps or sourcestamps[key][1] > start: 144 sourcestamps[key] = (ss, start) 145 146 # now sort those and take the NUMBUILDS most recent 147 sourcestamps = sourcestamps.values() 148 sourcestamps.sort(lambda x, y: cmp(x[1], y[1])) 149 sourcestamps = map(lambda tup : tup[0], sourcestamps) 150 sourcestamps = sourcestamps[-numBuilds:] 151 152 return sourcestamps
153
154 -class GridStatusResource(HtmlResource, GridStatusMixin):
155 # TODO: docs 156 status = None 157 changemaster = None 158 159 @defer.deferredGenerator
160 - def content(self, request, cxt):
161 """This method builds the regular grid display. 162 That is, build stamps across the top, build hosts down the left side 163 """ 164 165 # get url parameters 166 numBuilds = int(request.args.get("width", [5])[0]) 167 categories = request.args.get("category", []) 168 branch = request.args.get("branch", [ANYBRANCH])[0] 169 if branch == 'trunk': branch = None 170 171 # and the data we want to render 172 status = self.getStatus(request) 173 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 174 175 cxt['refresh'] = self.get_reload_time(request) 176 177 cxt.update({'categories': categories, 178 'branch': branch, 179 'ANYBRANCH': ANYBRANCH, 180 'stamps': map(SourceStamp.asDict, stamps) 181 }) 182 183 sortedBuilderNames = status.getBuilderNames()[:] 184 sortedBuilderNames.sort() 185 186 cxt['builders'] = [] 187 188 for bn in sortedBuilderNames: 189 builds = [None] * len(stamps) 190 191 builder = status.getBuilder(bn) 192 if categories and builder.category not in categories: 193 continue 194 195 for build in self.getRecentBuilds(builder, numBuilds, branch): 196 ss = build.getSourceStamp(absolute=True) 197 key= self.getSourceStampKey(ss) 198 for i in range(len(stamps)): 199 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 200 builds[i] = build 201 202 wfd = defer.waitForDeferred( 203 self.builder_cxt(request, builder)) 204 yield wfd 205 b = wfd.getResult() 206 207 b['builds'] = [] 208 for build in builds: 209 b['builds'].append(self.build_cxt(request, build)) 210 cxt['builders'].append(b) 211 212 template = request.site.buildbot_service.templates.get_template("grid.html") 213 yield template.render(**cxt)
214
215 216 -class TransposedGridStatusResource(HtmlResource, GridStatusMixin):
217 # TODO: docs 218 status = None 219 changemaster = None 220 default_rev_order = "asc" 221 222 @defer.deferredGenerator
223 - def content(self, request, cxt):
224 """This method builds the transposed grid display. 225 That is, build hosts across the top, build stamps down the left side 226 """ 227 228 # get url parameters 229 numBuilds = int(request.args.get("length", [5])[0]) 230 categories = request.args.get("category", []) 231 branch = request.args.get("branch", [ANYBRANCH])[0] 232 if branch == 'trunk': branch = None 233 234 rev_order = request.args.get("rev_order", [self.default_rev_order])[0] 235 if rev_order not in ["asc", "desc"]: 236 rev_order = self.default_rev_order 237 238 cxt['refresh'] = self.get_reload_time(request) 239 240 # and the data we want to render 241 status = self.getStatus(request) 242 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 243 244 cxt.update({'categories': categories, 245 'branch': branch, 246 'ANYBRANCH': ANYBRANCH, 247 'stamps': map(SourceStamp.asDict, stamps), 248 }) 249 250 sortedBuilderNames = status.getBuilderNames()[:] 251 sortedBuilderNames.sort() 252 253 cxt['sorted_builder_names'] = sortedBuilderNames 254 cxt['builder_builds'] = builder_builds = [] 255 cxt['builders'] = builders = [] 256 cxt['range'] = range(len(stamps)) 257 if rev_order == "desc": 258 cxt['range'].reverse() 259 260 for bn in sortedBuilderNames: 261 builds = [None] * len(stamps) 262 263 builder = status.getBuilder(bn) 264 if categories and builder.category not in categories: 265 continue 266 267 for build in self.getRecentBuilds(builder, numBuilds, branch): 268 ss = build.getSourceStamp(absolute=True) 269 key = self.getSourceStampKey(ss) 270 for i in range(len(stamps)): 271 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 272 builds[i] = build 273 274 wfd = defer.waitForDeferred( 275 self.builder_cxt(request, builder)) 276 yield wfd 277 builders.append(wfd.getResult()) 278 279 builder_builds.append(map(lambda b: self.build_cxt(request, b), builds)) 280 281 template = request.site.buildbot_service.templates.get_template('grid_transposed.html') 282 yield template.render(**cxt)
283