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 __future__ import generators 
 17   
 18  from buildbot.status.web.base import HtmlResource 
 19  from buildbot.status.web.base import build_get_class, path_to_builder, path_to_build 
 20  from buildbot.sourcestamp import SourceStamp 
 21   
22 -class ANYBRANCH: pass # a flag value, used below
23
24 -class GridStatusMixin(object):
25 - def getTitle(self, request):
26 status = self.getStatus(request) 27 p = status.getProjectName() 28 if p: 29 return "BuildBot: %s" % p 30 else: 31 return "BuildBot"
32 33 # handle reloads through an http header 34 # TODO: send this as a real header, rather than a tag
35 - def get_reload_time(self, request):
36 if "reload" in request.args: 37 try: 38 reload_time = int(request.args["reload"][0]) 39 return max(reload_time, 15) 40 except ValueError: 41 pass 42 return None
43
44 - def build_cxt(self, request, build):
45 if not build: 46 return {} 47 48 if build.isFinished(): 49 # get the text and annotate the first line with a link 50 text = build.getText() 51 if not text: text = [ "(no information)" ] 52 if text == [ "build", "successful" ]: text = [ "OK" ] 53 else: 54 text = [ 'building' ] 55 56 name = build.getBuilder().getName() 57 58 cxt = {} 59 cxt['name'] = name 60 cxt['url'] = path_to_build(request, build) 61 cxt['text'] = text 62 cxt['class'] = build_get_class(build) 63 return cxt
64
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 # TODO: for now, this pending/upcoming stuff is in the "current 82 # activity" box, but really it should go into a "next activity" row 83 # instead. The only times it should show up in "current activity" is 84 # when the builder is otherwise idle. 85 86 cxt = { 'url': path_to_builder(request, builder), 87 'name': builder.getName(), 88 'state': state, 89 'n_pending': len(builder.getPendingBuilds()) } 90 91 return 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 getRecentSourcestamps(self, status, numBuilds, categories, branch):
102 """ 103 get a list of the most recent NUMBUILDS SourceStamp tuples, sorted 104 by the earliest start we've seen for them 105 """ 106 # TODO: use baseweb's getLastNBuilds? 107 sourcestamps = { } # { ss-tuple : earliest time } 108 for bn in status.getBuilderNames(): 109 builder = status.getBuilder(bn) 110 if categories and builder.category not in categories: 111 continue 112 build = builder.getBuild(-1) 113 while build: 114 ss = build.getSourceStamp(absolute=True) 115 start = build.getTimes()[0] 116 build = build.getPreviousBuild() 117 118 # skip un-started builds 119 if not start: continue 120 121 # skip non-matching branches 122 if branch != ANYBRANCH and ss.branch != branch: continue 123 124 key= self.getSourceStampKey(ss) 125 if key not in sourcestamps or sourcestamps[key][1] > start: 126 sourcestamps[key] = (ss, start) 127 128 # now sort those and take the NUMBUILDS most recent 129 sourcestamps = sourcestamps.values() 130 sourcestamps.sort(lambda x, y: cmp(x[1], y[1])) 131 sourcestamps = map(lambda tup : tup[0], sourcestamps) 132 sourcestamps = sourcestamps[-numBuilds:] 133 134 return sourcestamps
135
136 -class GridStatusResource(HtmlResource, GridStatusMixin):
137 # TODO: docs 138 status = None 139 changemaster = None 140
141 - def content(self, request, cxt):
142 """This method builds the regular grid display. 143 That is, build stamps across the top, build hosts down the left side 144 """ 145 146 # get url parameters 147 numBuilds = int(request.args.get("width", [5])[0]) 148 categories = request.args.get("category", []) 149 branch = request.args.get("branch", [ANYBRANCH])[0] 150 if branch == 'trunk': branch = None 151 152 # and the data we want to render 153 status = self.getStatus(request) 154 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 155 156 cxt['refresh'] = self.get_reload_time(request) 157 158 cxt.update({'categories': categories, 159 'branch': branch, 160 'ANYBRANCH': ANYBRANCH, 161 'stamps': map(SourceStamp.asDict, stamps) 162 }) 163 164 sortedBuilderNames = status.getBuilderNames()[:] 165 sortedBuilderNames.sort() 166 167 cxt['builders'] = [] 168 169 for bn in sortedBuilderNames: 170 builds = [None] * len(stamps) 171 172 builder = status.getBuilder(bn) 173 if categories and builder.category not in categories: 174 continue 175 176 build = builder.getBuild(-1) 177 while build and None in builds: 178 ss = build.getSourceStamp(absolute=True) 179 key= self.getSourceStampKey(ss) 180 for i in range(len(stamps)): 181 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 182 builds[i] = build 183 build = build.getPreviousBuild() 184 185 b = self.builder_cxt(request, builder) 186 b['builds'] = [] 187 for build in builds: 188 b['builds'].append(self.build_cxt(request, build)) 189 cxt['builders'].append(b) 190 191 template = request.site.buildbot_service.templates.get_template("grid.html") 192 return template.render(**cxt)
193 194
195 -class TransposedGridStatusResource(HtmlResource, GridStatusMixin):
196 # TODO: docs 197 status = None 198 changemaster = None 199 default_rev_order = "asc" 200
201 - def content(self, request, cxt):
202 """This method builds the transposed grid display. 203 That is, build hosts across the top, build stamps down the left side 204 """ 205 206 # get url parameters 207 numBuilds = int(request.args.get("length", [5])[0]) 208 categories = request.args.get("category", []) 209 branch = request.args.get("branch", [ANYBRANCH])[0] 210 if branch == 'trunk': branch = None 211 212 rev_order = request.args.get("rev_order", [self.default_rev_order])[0] 213 if rev_order not in ["asc", "desc"]: 214 rev_order = self.default_rev_order 215 216 cxt['refresh'] = self.get_reload_time(request) 217 218 # and the data we want to render 219 status = self.getStatus(request) 220 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 221 222 cxt.update({'categories': categories, 223 'branch': branch, 224 'ANYBRANCH': ANYBRANCH, 225 'stamps': map(SourceStamp.asDict, stamps), 226 }) 227 228 sortedBuilderNames = status.getBuilderNames()[:] 229 sortedBuilderNames.sort() 230 231 cxt['sorted_builder_names'] = sortedBuilderNames 232 cxt['builder_builds'] = builder_builds = [] 233 cxt['builders'] = builders = [] 234 cxt['range'] = range(len(stamps)) 235 if rev_order == "desc": 236 cxt['range'].reverse() 237 238 for bn in sortedBuilderNames: 239 builds = [None] * len(stamps) 240 241 builder = status.getBuilder(bn) 242 if categories and builder.category not in categories: 243 continue 244 245 build = builder.getBuild(-1) 246 while build and None in builds: 247 ss = build.getSourceStamp(absolute=True) 248 key = self.getSourceStampKey(ss) 249 for i in range(len(stamps)): 250 if key == self.getSourceStampKey(stamps[i]) and builds[i] is None: 251 builds[i] = build 252 build = build.getPreviousBuild() 253 254 builders.append(self.builder_cxt(request, builder)) 255 builder_builds.append(map(lambda b: self.build_cxt(request, b), builds)) 256 257 template = request.site.buildbot_service.templates.get_template('grid_transposed.html') 258 data = template.render(**cxt) 259 return data
260