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

Unified Diff: master/webstatus/waterfall.py

Issue 648353002: Remove Skia's forked buildbot code (Closed) Base URL: https://skia.googlesource.com/buildbot.git@master
Patch Set: Address comment Created 6 years, 2 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « master/webstatus/console.py ('k') | run_unittests » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: master/webstatus/waterfall.py
diff --git a/master/webstatus/waterfall.py b/master/webstatus/waterfall.py
deleted file mode 100644
index b4ef5b9e2ff6dee2b93d48114e328bd92332caa1..0000000000000000000000000000000000000000
--- a/master/webstatus/waterfall.py
+++ /dev/null
@@ -1,527 +0,0 @@
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-""" Skia's override of buildbot.status.web.waterfall """
-
-
-from buildbot import interfaces, util
-from buildbot.status import builder as builder_status_module
-from buildbot.status import buildstep
-from buildbot.changes import changes as changes_module
-
-from buildbot.status.web.base import Box, HtmlResource, IBox, ICurrentBox, \
- ITopBox, path_to_root, \
- map_branches
-from buildbot.status.web.waterfall import earlier, \
- later, \
- insertGaps, \
- WaterfallHelp, \
- ChangeEventSource
-from twisted.python import log
-from twisted.internet import defer
-
-import builder_name_schema
-import locale
-import time
-import urllib
-
-
-class WaterfallStatusResource(HtmlResource):
- """This builds the main status page, with the waterfall display, and
- all child pages."""
-
- def __init__(self, categories=None, num_events=200, num_events_max=None,
- title='Waterfall', only_show_failures=False,
- builder_filter=lambda x: not builder_name_schema.IsTrybot(x)):
- HtmlResource.__init__(self)
- self.categories = categories
- self.num_events = num_events
- self.num_events_max = num_events_max
- self.putChild("help", WaterfallHelp(categories))
- self.BuilderFilter = builder_filter
- self.title = title
- self.only_show_failures = only_show_failures
-
- def getPageTitle(self, request):
- status = self.getStatus(request)
- p = status.getTitle()
- if p:
- return "BuildBot: %s" % p
- else:
- return "BuildBot"
-
- def getChangeManager(self, request):
- return request.site.buildbot_service.getChangeSvc()
-
- def get_reload_time(self, request):
- if "reload" in request.args:
- try:
- reload_time = int(request.args["reload"][0])
- return max(reload_time, 15)
- except ValueError:
- pass
- return None
-
- def isSuccess(self, builderStatus):
- # Helper function to return True if the builder is not failing.
- # The function will return false if the current state is "offline",
- # the last build was not successful, or if a step from the current
- # build(s) failed.
-
- # Make sure the builder is online.
- if builderStatus.getState()[0] == 'offline':
- return False
-
- # Look at the last finished build to see if it was success or not.
- last_build = builderStatus.getLastFinishedBuild()
- if last_build and last_build.getResults() != builder_status_module.SUCCESS:
- return False
-
- # Check all the current builds to see if one step is already
- # failing.
- current_builds = builderStatus.getCurrentBuilds()
- if current_builds:
- for build in current_builds:
- for step in build.getSteps():
- if step.getResults()[0] == builder_status_module.FAILURE:
- return False
-
- # The last finished build was successful, and all the current builds
- # don't have any failed steps.
- return True
-
- def content(self, request, ctx):
- status = self.getStatus(request)
- master = request.site.buildbot_service.master
-
- # before calling content_with_db_data, make a bunch of database
- # queries. This is a sick hack, but beats rewriting the entire
- # waterfall around asynchronous calls
-
- results = {}
-
- # recent changes
- changes_d = master.db.changes.getRecentChanges(40)
- def to_changes(chdicts):
- return defer.gatherResults([
- changes_module.Change.fromChdict(master, chdict)
- for chdict in chdicts ])
- changes_d.addCallback(to_changes)
- def keep_changes(changes):
- results['changes'] = changes
- changes_d.addCallback(keep_changes)
-
- # build request counts for each builder
- all_builder_names = status.getBuilderNames(categories=self.categories)
- brstatus_ds = []
- brcounts = {}
- def keep_count(statuses, builder_name):
- brcounts[builder_name] = len(statuses)
- for builder_name in all_builder_names:
- builder_status = status.getBuilder(builder_name)
- d = builder_status.getPendingBuildRequestStatuses()
- d.addCallback(keep_count, builder_name)
- brstatus_ds.append(d)
-
- # wait for it all to finish
- d = defer.gatherResults([ changes_d ] + brstatus_ds)
- def call_content(_):
- return self.content_with_db_data(results['changes'],
- brcounts, request, ctx)
- d.addCallback(call_content)
- return d
-
- def content_with_db_data(self, changes, brcounts, request, ctx):
- ctx['title'] = self.title
- status = self.getStatus(request)
- ctx['refresh'] = self.get_reload_time(request)
-
- # we start with all Builders available to this Waterfall: this is
- # limited by the config-file -time categories= argument, and defaults
- # to all defined Builders.
- all_builder_names = status.getBuilderNames(categories=self.categories)
- builders = [status.getBuilder(name) for name in all_builder_names]
-
- # Apply a filter to the builders.
- builders = [b for b in builders if self.BuilderFilter(b.name)]
-
- # but if the URL has one or more builder= arguments (or the old show=
- # argument, which is still accepted for backwards compatibility), we
- # use that set of builders instead. We still don't show anything
- # outside the config-file time set limited by categories=.
- show_builders = request.args.get("show", [])
- show_builders.extend(request.args.get("builder", []))
- if show_builders:
- builders = [b for b in builders if b.name in show_builders]
-
- # now, if the URL has one or category= arguments, use them as a
- # filter: only show those builders which belong to one of the given
- # categories.
- show_categories = request.args.get("category", [])
- if show_categories:
- builders = [b for b in builders if b.category in show_categories]
-
- # If we are only showing failures, we remove all the builders that are not
- # currently red or won't be turning red at the end of their current run.
- if self.only_show_failures:
- builders = [b for b in builders if not self.isSuccess(b)]
-
- (change_names, builder_names, timestamps, event_grid, source_events) = \
- self.buildGrid(request, builders, changes)
-
- # start the table: top-header material
- locale_enc = locale.getdefaultlocale()[1]
- if locale_enc is not None:
- locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc)
- else:
- locale_tz = unicode(time.tzname[time.localtime()[-1]])
- ctx['tz'] = locale_tz
- ctx['changes_url'] = request.childLink("../changes")
-
- bn = ctx['builders'] = []
-
- for name in builder_names:
- builder = status.getBuilder(name)
- top_box = ITopBox(builder).getBox(request)
- current_box = ICurrentBox(builder).getBox(status, brcounts)
- bn.append({'name': name,
- 'url': request.childLink("../builders/%s" %
- urllib.quote(name, safe='')),
- 'top': top_box.text,
- 'top_class': top_box.class_,
- 'status': current_box.text,
- 'status_class': current_box.class_,
- })
-
- ctx.update(self.phase2(request, change_names + builder_names, timestamps,
- event_grid, source_events))
-
- def with_args(req, remove_args=None, new_args=None, new_path=None):
- if not remove_args:
- remove_args = []
- if not new_args:
- new_args = []
- newargs = req.args.copy()
- for argname in remove_args:
- newargs[argname] = []
- if "branch" in newargs:
- newargs["branch"] = [b for b in newargs["branch"] if b]
- for k, v in new_args:
- if k in newargs:
- newargs[k].append(v)
- else:
- newargs[k] = [v]
- newquery = "&".join(["%s=%s" % (urllib.quote(k), urllib.quote(v))
- for k in newargs
- for v in newargs[k]
- ])
- if new_path:
- new_url = new_path
- elif req.prepath:
- new_url = req.prepath[-1]
- else:
- new_url = ''
- if newquery:
- new_url += "?" + newquery
- return new_url
-
- if timestamps:
- bottom = timestamps[-1]
- ctx['nextpage'] = with_args(request, ["last_time"],
- [("last_time", str(int(bottom)))])
-
-
- helpurl = path_to_root(request) + "waterfall/help"
- ctx['help_url'] = with_args(request, new_path=helpurl)
-
- if self.get_reload_time(request) is not None:
- ctx['no_reload_page'] = with_args(request, remove_args=["reload"])
-
- template = request.site.buildbot_service.templates.get_template(
- "waterfall.html")
- data = template.render(**ctx)
- return data
-
- def buildGrid(self, request, builders, changes):
- debug = False
- show_events = False
- if request.args.get("show_events", ["false"])[0].lower() == "true":
- show_events = True
- filter_categories = request.args.get('category', [])
- filter_branches = [b for b in request.args.get("branch", []) if b]
- filter_branches = map_branches(filter_branches)
- filter_committers = [c for c in request.args.get("committer", []) if c]
- max_time = int(request.args.get("last_time", [util.now()])[0])
- if "show_time" in request.args:
- min_time = max_time - int(request.args["show_time"][0])
- elif "first_time" in request.args:
- min_time = int(request.args["first_time"][0])
- elif filter_branches or filter_committers:
- min_time = util.now() - 24 * 60 * 60
- else:
- min_time = 0
- span_length = 10 # ten-second chunks
- req_events = int(request.args.get("num_events", [self.num_events])[0])
- if self.num_events_max and req_events > self.num_events_max:
- max_page_len = self.num_events_max
- else:
- max_page_len = req_events
-
- # first step is to walk backwards in time, asking each column
- # (commit, all builders) if they have any events there. Build up the
- # array of events, and stop when we have a reasonable number.
-
- commit_source = ChangeEventSource(changes)
-
- last_event_time = util.now()
- sources = [commit_source] + builders
- change_names = ["changes"]
- builder_names = map(lambda builder: builder.getName(), builders)
- source_names = change_names + builder_names
- source_events = []
- source_generators = []
-
- def get_event_from(g):
- try:
- while True:
- e = g.next()
- # e might be buildstep.BuildStepStatus,
- # builder.BuildStatus, builder.Event,
- # waterfall.Spacer(builder.Event), or changes.Change .
- # The show_events=False flag means we should hide
- # builder.Event .
- if not show_events and isinstance(e, builder_status_module.Event):
- continue
-
- if isinstance(e, buildstep.BuildStepStatus):
- # unfinished steps are always shown
- if e.isFinished() and e.isHidden():
- continue
-
- break
- event = interfaces.IStatusEvent(e)
- if debug:
- log.msg("gen %s gave1 %s" % (g, event.getText()))
- except StopIteration:
- event = None
- return event
-
- for s in sources:
- gen = insertGaps(s.eventGenerator(filter_branches,
- filter_categories,
- filter_committers,
- min_time),
- show_events,
- last_event_time)
- source_generators.append(gen)
- # get the first event
- source_events.append(get_event_from(gen))
- event_grid = []
- timestamps = []
-
- last_event_time = 0
- for e in source_events:
- if e and e.getTimes()[0] > last_event_time:
- last_event_time = e.getTimes()[0]
- if last_event_time == 0:
- last_event_time = util.now()
-
- span_start = last_event_time - span_length
- debug_gather = 0
-
- while 1:
- if debug_gather:
- log.msg("checking (%s,]" % span_start)
- # the tableau of potential events is in source_events[]. The
- # window crawls backwards, and we examine one source at a time.
- # If the source's top-most event is in the window, is it pushed
- # onto the events[] array and the tableau is refilled. This
- # continues until the tableau event is not in the window (or is
- # missing).
-
- span_events = [] # for all sources, in this span. row of event_grid
- first_timestamp = None # timestamp of first event in the span
- last_timestamp = None # last pre-span event, for next span
-
- for c in range(len(source_generators)):
- events = [] # for this source, in this span. cell of event_grid
- event = source_events[c]
- while event and span_start < event.getTimes()[0]:
- # to look at windows that don't end with the present,
- # condition the .append on event.time <= spanFinish
- if not IBox(event, None):
- log.msg("BAD EVENT", event, event.getText())
- assert 0
- if debug:
- log.msg("pushing", event.getText(), event)
- events.append(event)
- starts, _ = event.getTimes()
- first_timestamp = earlier(first_timestamp, starts)
- event = get_event_from(source_generators[c])
- if debug:
- log.msg("finished span")
-
- if event:
- # this is the last pre-span event for this source
- last_timestamp = later(last_timestamp, event.getTimes()[0])
- if debug_gather:
- log.msg(" got %s from %s" % (events, source_names[c]))
- source_events[c] = event # refill the tableau
- span_events.append(events)
-
- # only show events older than max_time. This makes it possible to
- # visit a page that shows what it would be like to scroll off the
- # bottom of this one.
- if first_timestamp is not None and first_timestamp <= max_time:
- event_grid.append(span_events)
- timestamps.append(first_timestamp)
-
- if last_timestamp:
- span_start = last_timestamp - span_length
- else:
- # no more events
- break
- if min_time is not None and last_timestamp < min_time:
- break
-
- if len(timestamps) > max_page_len:
- break
-
- # now loop
-
- # loop is finished. now we have event_grid[] and timestamps[]
- if debug_gather:
- log.msg("finished loop")
- assert(len(timestamps) == len(event_grid))
- return (change_names, builder_names, timestamps, event_grid, source_events)
-
- def phase2(self, request, source_names, timestamps, event_grid,
- source_events):
-
- if not timestamps:
- return dict(grid=[], gridlen=0)
-
- # first pass: figure out the height of the chunks, populate grid
- grid = []
- for i in range(1+len(source_names)):
- grid.append([])
- # grid is a list of columns, one for the timestamps, and one per
- # event source. Each column is exactly the same height. Each element
- # of the list is a single <td> box.
- last_date = time.strftime("%d %b %Y", time.localtime(util.now()))
- for r in range(0, len(timestamps)):
- chunkstrip = event_grid[r]
- # chunkstrip is a horizontal strip of event blocks. Each block
- # is a vertical list of events, all for the same source.
- assert(len(chunkstrip) == len(source_names))
- max_rows = reduce(max, map(len, chunkstrip))
- for i in range(max_rows):
- if i != max_rows - 1:
- grid[0].append(None)
- else:
- # timestamp goes at the bottom of the chunk
- stuff = []
- # add the date at the beginning (if it is not the same as
- # today's date), and each time it changes
- todayday = time.strftime("%a", time.localtime(timestamps[r]))
- today = time.strftime("%d %b %Y", time.localtime(timestamps[r]))
- if today != last_date:
- stuff.append(todayday)
- stuff.append(today)
- last_date = today
- stuff.append(time.strftime("%H:%M:%S", time.localtime(timestamps[r])))
- grid[0].append(Box(text=stuff, class_="Time", valign="bottom",
- align="center"))
-
- # at this point the timestamp column has been populated with
- # max_rows boxes, most None but the last one has the time string
- for c in range(0, len(chunkstrip)):
- block = chunkstrip[c]
- assert(block != None) # should be [] instead
- for i in range(max_rows - len(block)):
- # fill top of chunk with blank space
- grid[c+1].append(None)
- for i in range(len(block)):
- # so the events are bottom-justified
- b = IBox(block[i]).getBox(request)
- b.parms['valign'] = "top"
- b.parms['align'] = "center"
- grid[c+1].append(b)
- # now all the other columns have max_rows new boxes too
- # populate the last row, if empty
- gridlen = len(grid[0])
- for i in range(len(grid)):
- strip = grid[i]
- assert(len(strip) == gridlen)
- if strip[-1] == None:
- if source_events[i - 1]:
- filler = IBox(source_events[i - 1]).getBox(request)
- else:
- # this can happen if you delete part of the build history
- filler = Box(text=["?"], align="center")
- strip[-1] = filler
- strip[-1].parms['rowspan'] = 1
- # second pass: bubble the events upwards to un-occupied locations
- # Every square of the grid that has a None in it needs to have
- # something else take its place.
- no_bubble = request.args.get("nobubble", ['0'])
- no_bubble = int(no_bubble[0])
- if not no_bubble:
- for col in range(len(grid)):
- strip = grid[col]
- if col == 1: # changes are handled differently
- for i in range(2, len(strip) + 1):
- # only merge empty boxes. Don't bubble commit boxes.
- if strip[-i] == None:
- next_box = strip[-i + 1]
- assert(next_box)
- if next_box:
- #if not next_box.event:
- if next_box.spacer:
- # bubble the empty box up
- strip[-i] = next_box
- strip[-i].parms['rowspan'] += 1
- strip[-i + 1] = None
- else:
- # we are above a commit box. Leave it
- # be, and turn the current box into an
- # empty one
- strip[-i] = Box([], rowspan=1,
- comment="commit bubble")
- strip[-i].spacer = True
- else:
- # we are above another empty box, which
- # somehow wasn't already converted.
- # Shouldn't happen
- pass
- else:
- for i in range(2, len(strip) + 1):
- # strip[-i] will go from next-to-last back to first
- if strip[-i] == None:
- # bubble previous item up
- assert(strip[-i + 1] != None)
- strip[-i] = strip[-i + 1]
- strip[-i].parms['rowspan'] += 1
- strip[-i + 1] = None
- else:
- strip[-i].parms['rowspan'] = 1
-
- # convert to dicts
- for i in range(gridlen):
- for strip in grid:
- if strip[i]:
- strip[i] = strip[i].td()
-
- return dict(grid=grid, gridlen=gridlen, no_bubble=no_bubble, time=last_date)
-
-
-class TrybotStatusResource(WaterfallStatusResource):
- def __init__(self, **kwargs):
- WaterfallStatusResource.__init__(self, title='Trybot Waterfall',
- builder_filter=builder_name_schema.IsTrybot, **kwargs)
-
-class FailureWaterfallStatusResource(WaterfallStatusResource):
- def __init__(self, **kwargs):
- WaterfallStatusResource.__init__(self, title='Currently Failing',
- only_show_failures=True, **kwargs)
« no previous file with comments | « master/webstatus/console.py ('k') | run_unittests » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698