diff --git a/pybootchartgui/draw.py b/pybootchartgui/draw.py index 7c3f60c..75528e3 100644 --- a/pybootchartgui/draw.py +++ b/pybootchartgui/draw.py @@ -202,7 +202,7 @@ def draw_box_ticks(ctx, rect, sec_w): ctx.set_line_cap(cairo.LINE_CAP_BUTT) -def draw_annotations(ctx, proc_tree, times, rect): +def draw_annotations(ctx, proc_tree, times, notes, rect): ctx.set_line_cap(cairo.LINE_CAP_SQUARE) ctx.set_source_rgba(*ANNOTATION_COLOR) ctx.set_dash([4, 4]) @@ -214,6 +214,8 @@ def draw_annotations(ctx, proc_tree, times, rect): ctx.move_to(rect[0] + x, rect[1] + 1) ctx.line_to(rect[0] + x, rect[1] + rect[3] - 1) ctx.stroke() + if notes is not None and time in notes: + draw_text(ctx, notes[time], ANNOTATION_COLOR, x, rect[1] - 15) ctx.set_line_cap(cairo.LINE_CAP_BUTT) ctx.set_dash([]) @@ -307,7 +309,7 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): chart_rect = (off_x, curr_y+30, w, bar_h) if clip_visible (clip, chart_rect): draw_box_ticks (ctx, chart_rect, sec_w) - draw_annotations (ctx, proc_tree, trace.times, chart_rect) + draw_annotations (ctx, proc_tree, trace.times, None, chart_rect) draw_chart (ctx, IO_COLOR, True, chart_rect, \ [(sample.time, sample.user + sample.sys + sample.io) for sample in trace.cpu_stats], \ proc_tree, None) @@ -326,7 +328,7 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): chart_rect = (off_x, curr_y+30, w, bar_h) if clip_visible (clip, chart_rect): draw_box_ticks (ctx, chart_rect, sec_w) - draw_annotations (ctx, proc_tree, trace.times, chart_rect) + draw_annotations (ctx, proc_tree, trace.times, None, chart_rect) draw_chart (ctx, IO_COLOR, True, chart_rect, \ [(sample.time, sample.util) for sample in trace.disk_stats], \ proc_tree, None) @@ -360,7 +362,7 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): draw_legend_line(ctx, "Swap (scale: %u MiB)" % max([(sample.records['SwapTotal'] - sample.records['SwapFree'])/1024 for sample in mem_stats]), \ MEM_SWAP_COLOR, off_x + 480, curr_y+20, leg_s) draw_box_ticks(ctx, chart_rect, sec_w) - draw_annotations(ctx, proc_tree, trace.times, chart_rect) + draw_annotations(ctx, proc_tree, trace.times, None, chart_rect) draw_chart(ctx, MEM_BUFFERS_COLOR, True, chart_rect, \ [(sample.time, sample.records['MemTotal'] - sample.records['MemFree']) for sample in trace.mem_stats], \ proc_tree, [0, mem_scale]) @@ -415,7 +417,7 @@ def render(ctx, options, xscale, trace): if proc_tree.taskstats and options.cumulative: proc_height -= CUML_HEIGHT - draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times, + draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times, trace.notes, curr_y, w, proc_height, sec_w) curr_y = proc_height @@ -434,7 +436,7 @@ def render(ctx, options, xscale, trace): if clip_visible (clip, cuml_rect): draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_IO) -def draw_process_bar_chart(ctx, clip, options, proc_tree, times, curr_y, w, h, sec_w): +def draw_process_bar_chart(ctx, clip, options, proc_tree, times, notes, curr_y, w, h, sec_w): header_size = 0 if not options.kernel_only: draw_legend_box (ctx, "Running (%cpu)", @@ -457,7 +459,7 @@ def draw_process_bar_chart(ctx, clip, options, proc_tree, times, curr_y, w, h, s else: nsec = 5 draw_sec_labels (ctx, chart_rect, sec_w, nsec) - draw_annotations (ctx, proc_tree, times, chart_rect) + draw_annotations (ctx, proc_tree, times, notes, chart_rect) y = curr_y + 60 for root in proc_tree.process_tree: diff --git a/pybootchartgui/parsing.py b/pybootchartgui/parsing.py index 993d7bf..cbdfece 100644 --- a/pybootchartgui/parsing.py +++ b/pybootchartgui/parsing.py @@ -26,6 +26,7 @@ from time import clock from collections import defaultdict from functools import reduce +from datetime import datetime from .samples import * from .process_tree import ProcessTree @@ -48,6 +49,7 @@ def __init__(self, writer, paths, options): self.filename = None self.parent_map = None self.mem_stats = None + self.notes = {} parse_paths (writer, self, paths) if not self.valid(): @@ -64,29 +66,57 @@ def __init__(self, writer, paths, options): else: idle = None - # Annotate other times as the first start point of given process lists - self.times = [ idle ] - if options.annotate: - for procnames in options.annotate: - names = [x[:15] for x in procnames.split(",")] - for proc in self.ps_stats.process_map.values(): - if proc.cmd in names: - self.times.append(proc.start_time) - break - else: - self.times.append(None) - self.proc_tree = ProcessTree(writer, self.kernel, self.ps_stats, self.ps_stats.sample_period, self.headers.get("profile.process"), options.prune, idle, self.taskstats, self.parent_map is not None) + # Annotate other times as the first start point of given process lists + self.times = [ idle ] + if options.annotate: + if os.path.isfile(options.annotate[0]): + self.addAnnotationsFromFile(options) + else: + for procnames in options.annotate: + try: + self.times.append(self.convertTimeToCoord(procnames)) + except ValueError: + names = [x[:15] for x in procnames.split(",")] + for proc in self.ps_stats.process_map.values(): + if proc.cmd in names: + self.times.append(proc.start_time) + break + else: + self.times.append(None) + if self.kernel is not None: self.kernel_tree = ProcessTree(writer, self.kernel, None, 0, self.headers.get("profile.process"), False, None, None, True) + def addAnnotationsFromFile(self, options): + labels = [] + times = [] + with open(options.annotate[0]) as f: + for line in f: + labels.append(line.split('\t')[0].strip()) + times.append(line.split('\t')[1].strip()) + for (l, t) in zip(labels, times): + time = self.convertTimeToCoord(t) + if time not in self.times: + self.times.append(time) + if time not in self.notes: + self.notes[time] = l + else: + self.notes[time] = self.notes[time] + '/' + l + + def convertTimeToCoord(self, time): + title = re.search(r'\d{2}:\d{2}:\d{2}', self.headers.get("title")).group() + endCoord = datetime.strptime(title, '%H:%M:%S') + startCoord = datetime.strptime(time, '%H:%M:%S') + return self.proc_tree.duration - (endCoord - startCoord).seconds * 100 + def valid(self): return self.headers != None and self.disk_stats != None and \ self.ps_stats != None and self.cpu_stats != None