📄 waterfall.py
字号:
spanLength = 10 # ten-second chunks maxPageLen = int(request.args.get("num_events", [200])[0]) # 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 = self.getChangemaster(request) lastEventTime = util.now() sources = [commit_source] + builders changeNames = ["changes"] builderNames = map(lambda builder: builder.getName(), builders) sourceNames = changeNames + builderNames sourceEvents = [] sourceGenerators = [] def get_event_from(g): try: while True: e = g.next() # e might be builder.BuildStepStatus, # builder.BuildStatus, builder.Event, # waterfall.Spacer(builder.Event), or changes.Change . # The showEvents=False flag means we should hide # builder.Event . if not showEvents and isinstance(e, builder.Event): 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(filterBranches), lastEventTime) sourceGenerators.append(gen) # get the first event sourceEvents.append(get_event_from(gen)) eventGrid = [] timestamps = [] lastEventTime = 0 for e in sourceEvents: if e and e.getTimes()[0] > lastEventTime: lastEventTime = e.getTimes()[0] if lastEventTime == 0: lastEventTime = util.now() spanStart = lastEventTime - spanLength debugGather = 0 while 1: if debugGather: log.msg("checking (%s,]" % spanStart) # the tableau of potential events is in sourceEvents[]. 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). spanEvents = [] # for all sources, in this span. row of eventGrid firstTimestamp = None # timestamp of first event in the span lastTimestamp = None # last pre-span event, for next span for c in range(len(sourceGenerators)): events = [] # for this source, in this span. cell of eventGrid event = sourceEvents[c] while event and spanStart < 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, finishes = event.getTimes() firstTimestamp = util.earlier(firstTimestamp, starts) event = get_event_from(sourceGenerators[c]) if debug: log.msg("finished span") if event: # this is the last pre-span event for this source lastTimestamp = util.later(lastTimestamp, event.getTimes()[0]) if debugGather: log.msg(" got %s from %s" % (events, sourceNames[c])) sourceEvents[c] = event # refill the tableau spanEvents.append(events) # only show events older than maxTime. This makes it possible to # visit a page that shows what it would be like to scroll off the # bottom of this one. if firstTimestamp is not None and firstTimestamp <= maxTime: eventGrid.append(spanEvents) timestamps.append(firstTimestamp) if lastTimestamp: spanStart = lastTimestamp - spanLength else: # no more events break if minTime is not None and lastTimestamp < minTime: break if len(timestamps) > maxPageLen: break # now loop # loop is finished. now we have eventGrid[] and timestamps[] if debugGather: log.msg("finished loop") assert(len(timestamps) == len(eventGrid)) return (changeNames, builderNames, timestamps, eventGrid, sourceEvents) def phase0(self, request, sourceNames, timestamps, eventGrid): # phase0 rendering if not timestamps: return "no events" data = "" for r in range(0, len(timestamps)): data += "<p>\n" data += "[%s]<br />" % timestamps[r] row = eventGrid[r] assert(len(row) == len(sourceNames)) for c in range(0, len(row)): if row[c]: data += "<b>%s</b><br />\n" % sourceNames[c] for e in row[c]: log.msg("Event", r, c, sourceNames[c], e.getText()) lognames = [loog.getName() for loog in e.getLogs()] data += "%s: %s: %s<br />" % (e.getText(), e.getTimes()[0], lognames) else: data += "<b>%s</b> [none]<br />\n" % sourceNames[c] return data def phase1(self, request, sourceNames, timestamps, eventGrid, sourceEvents): # phase1 rendering: table, but boxes do not overlap data = "" if not timestamps: return data lastDate = None for r in range(0, len(timestamps)): chunkstrip = eventGrid[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(sourceNames)) maxRows = reduce(lambda x,y: max(x,y), map(lambda x: len(x), chunkstrip)) for i in range(maxRows): data += " <tr>\n"; if i == 0: stuff = [] # add the date at the beginning, and each time it changes today = time.strftime("<b>%d %b %Y</b>", time.localtime(timestamps[r])) todayday = time.strftime("<b>%a</b>", time.localtime(timestamps[r])) if today != lastDate: stuff.append(todayday) stuff.append(today) lastDate = today stuff.append( time.strftime("%H:%M:%S", time.localtime(timestamps[r]))) data += td(stuff, valign="bottom", align="center", rowspan=maxRows, class_="Time") for c in range(0, len(chunkstrip)): block = chunkstrip[c] assert(block != None) # should be [] instead # bottom-justify offset = maxRows - len(block) if i < offset: data += td("") else: e = block[i-offset] box = IBox(e).getBox(request) box.parms["show_idle"] = 1 data += box.td(valign="top", align="center") data += " </tr>\n" return data def phase2(self, request, sourceNames, timestamps, eventGrid, sourceEvents): data = "" if not timestamps: return data # first pass: figure out the height of the chunks, populate grid grid = [] for i in range(1+len(sourceNames)): 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. lastDate = time.strftime("<b>%d %b %Y</b>", time.localtime(util.now())) for r in range(0, len(timestamps)): chunkstrip = eventGrid[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(sourceNames)) maxRows = reduce(lambda x,y: max(x,y), map(lambda x: len(x), chunkstrip)) for i in range(maxRows): if i != maxRows-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("<b>%a</b>", time.localtime(timestamps[r])) today = time.strftime("<b>%d %b %Y</b>", time.localtime(timestamps[r])) if today != lastDate: stuff.append(todayday) stuff.append(today) lastDate = 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 # maxRows 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(maxRows - 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 maxRows 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 sourceEvents[i-1]: filler = IBox(sourceEvents[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. noBubble = request.args.get("nobubble",['0']) noBubble = int(noBubble[0]) if not noBubble: 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 = strip[-i+1] assert(next) if next: #if not next.event: if next.spacer: # bubble the empty box up strip[-i] = next 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 # third pass: render the HTML table for i in range(gridlen): data += " <tr>\n"; for strip in grid: b = strip[i] if b: data += b.td() else: if noBubble: data += td([]) # Nones are left empty, rowspan should make it all fit data += " </tr>\n" return data
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -