+ def _find_overlap(self, events, start_key, end_key):
+ # given a list of event dicts, return a new list in which each event
+ # has an extra "row" key (an int, starting at 0), and if appropriate
+ # a "serverid" key (ascii-encoded server id), replacing the "server"
+ # key. This is a hint to our JS frontend about how to overlap the
+ # parts of the graph it is drawing.
+
+ # we must always make a copy, since we're going to be adding keys
+ # and don't want to change the original objects. If we're
+ # stringifying serverids, we'll also be changing the serverid keys.
+ new_events = []
+ rows = []
+ for ev in events:
+ ev = ev.copy()
+ if ev.has_key('server'):
+ ev["serverid"] = ev["server"].get_longname()
+ del ev["server"]
+ # find an empty slot in the rows
+ free_slot = None
+ for row,finished in enumerate(rows):
+ if finished is not None:
+ if ev[start_key] > finished:
+ free_slot = row
+ break
+ if free_slot is None:
+ free_slot = len(rows)
+ rows.append(ev[end_key])
+ else:
+ rows[free_slot] = ev[end_key]
+ ev["row"] = free_slot
+ new_events.append(ev)
+ return new_events
+
+ def _find_overlap_requests(self, events):
+ """We compute a three-element 'row tuple' for each event: (serverid,
+ shnum, row). All elements are ints. The first is a mapping from
+ serverid to group number, the second is a mapping from shnum to
+ subgroup number. The third is a row within the subgroup.
+
+ We also return a list of lists of rowcounts, so renderers can decide
+ how much vertical space to give to each row.
+ """
+
+ serverid_to_group = {}
+ groupnum_to_rows = {} # maps groupnum to a table of rows. Each table
+ # is a list with an element for each row number
+ # (int starting from 0) that contains a
+ # finish_time, indicating that the row is empty
+ # beyond that time. If finish_time is None, it
+ # indicate a response that has not yet
+ # completed, so the row cannot be reused.
+ new_events = []
+ for ev in events:
+ # DownloadStatus promises to give us events in temporal order
+ ev = ev.copy()
+ ev["serverid"] = ev["server"].get_longname()
+ del ev["server"]
+ if ev["serverid"] not in serverid_to_group:
+ groupnum = len(serverid_to_group)
+ serverid_to_group[ev["serverid"]] = groupnum
+ groupnum = serverid_to_group[ev["serverid"]]
+ if groupnum not in groupnum_to_rows:
+ groupnum_to_rows[groupnum] = []
+ rows = groupnum_to_rows[groupnum]
+ # find an empty slot in the rows
+ free_slot = None
+ for row,finished in enumerate(rows):
+ if finished is not None:
+ if ev["start_time"] > finished:
+ free_slot = row
+ break
+ if free_slot is None:
+ free_slot = len(rows)
+ rows.append(ev["finish_time"])
+ else:
+ rows[free_slot] = ev["finish_time"]
+ ev["row"] = (groupnum, free_slot)
+ new_events.append(ev)
+ del groupnum
+ # maybe also return serverid_to_group, groupnum_to_rows, and some
+ # indication of the highest finish_time
+ #
+ # actually, return the highest rownum for each groupnum
+ highest_rownums = [len(groupnum_to_rows[groupnum])
+ for groupnum in range(len(serverid_to_group))]
+ return new_events, highest_rownums
+
+ def child_event_json(self, ctx):
+ inevow.IRequest(ctx).setHeader("content-type", "text/plain")
+ data = { } # this will be returned to the GET
+ ds = self.download_status
+
+ data["misc"] = self._find_overlap(ds.misc_events,
+ "start_time", "finish_time")
+ data["read"] = self._find_overlap(ds.read_events,
+ "start_time", "finish_time")
+ data["segment"] = self._find_overlap(ds.segment_events,
+ "start_time", "finish_time")
+ # TODO: overlap on DYHB isn't very useful, and usually gets in the
+ # way. So don't do it.
+ data["dyhb"] = self._find_overlap(ds.dyhb_requests,
+ "start_time", "finish_time")
+ data["block"],data["block_rownums"] = self._find_overlap_requests(ds.block_requests)
+
+ server_info = {} # maps longname to {num,color,short}
+ server_shortnames = {} # maps servernum to shortname
+ for d_ev in ds.dyhb_requests:
+ s = d_ev["server"]
+ longname = s.get_longname()
+ if longname not in server_info:
+ num = len(server_info)
+ server_info[longname] = {"num": num,
+ "color": self.color(s),
+ "short": s.get_name() }
+ server_shortnames[str(num)] = s.get_name()
+
+ data["server_info"] = server_info
+ data["num_serverids"] = len(server_info)
+ # we'd prefer the keys of serverids[] to be ints, but this is JSON,
+ # so they get converted to strings. Stupid javascript.
+ data["serverids"] = server_shortnames
+ data["bounds"] = {"min": ds.first_timestamp, "max": ds.last_timestamp}