From 6f1568e54a7808b2ab568618fc5bb244249579f1 Mon Sep 17 00:00:00 2001 From: Peter Kjellerstedt Date: Fri, 15 Nov 2013 18:08:50 +0100 Subject: pybootchartgui: Import pybootchartgui 0.14.5 This update the pybootchartgui code to the latest release from its new location at "https://github.com/mmeeks/bootchart". This only imports the relevant parts, and not all of bootchart2. Signed-off-by: Peter Kjellerstedt Signed-off-by: Richard Purdie --- scripts/pybootchartgui/pybootchartgui/draw.py | 710 +++++++++++++++++++++----- 1 file changed, 579 insertions(+), 131 deletions(-) (limited to 'scripts/pybootchartgui/pybootchartgui/draw.py') diff --git a/scripts/pybootchartgui/pybootchartgui/draw.py b/scripts/pybootchartgui/pybootchartgui/draw.py index 1b872de75e..c3492c7cb9 100644 --- a/scripts/pybootchartgui/pybootchartgui/draw.py +++ b/scripts/pybootchartgui/pybootchartgui/draw.py @@ -1,6 +1,40 @@ +# This file is part of pybootchartgui. + +# pybootchartgui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# pybootchartgui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with pybootchartgui. If not, see . + + import cairo import math import re +import random +import colorsys +from operator import itemgetter + +class RenderOptions: + + def __init__(self, app_options): + # should we render a cumulative CPU time chart + self.cumulative = True + self.charts = True + self.kernel_only = False + self.app_options = app_options + + def proc_tree (self, trace): + if self.kernel_only: + return trace.kernel_tree + else: + return trace.proc_tree # Process tree background color. BACK_COLOR = (1.0, 1.0, 1.0, 1.0) @@ -12,11 +46,13 @@ BORDER_COLOR = (0.63, 0.63, 0.63, 1.0) TICK_COLOR = (0.92, 0.92, 0.92, 1.0) # 5-second tick line color. TICK_COLOR_BOLD = (0.86, 0.86, 0.86, 1.0) +# Annotation colour +ANNOTATION_COLOR = (0.63, 0.0, 0.0, 0.5) # Text color. TEXT_COLOR = (0.0, 0.0, 0.0, 1.0) # Font family -FONT_NAME = "Bitstream Vera Sans" +FONT_NAME = "Bitstream Vera Sans" # Title text font. TITLE_FONT_SIZE = 18 # Default text font. @@ -25,7 +61,7 @@ TEXT_FONT_SIZE = 12 AXIS_FONT_SIZE = 11 # Legend font. LEGEND_FONT_SIZE = 12 - + # CPU load chart color. CPU_COLOR = (0.40, 0.55, 0.70, 1.0) # IO wait chart color. @@ -34,11 +70,19 @@ IO_COLOR = (0.76, 0.48, 0.48, 0.5) DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) # CPU load chart color. FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) - +# Mem cached color +MEM_CACHED_COLOR = CPU_COLOR +# Mem used color +MEM_USED_COLOR = IO_COLOR +# Buffers color +MEM_BUFFERS_COLOR = (0.4, 0.4, 0.4, 0.3) +# Swap color +MEM_SWAP_COLOR = DISK_TPUT_COLOR + # Process border color. PROC_BORDER_COLOR = (0.71, 0.71, 0.71, 1.0) - -PROC_COLOR_D = (0.76, 0.48, 0.48, 0.125) +# Waiting process color. +PROC_COLOR_D = (0.76, 0.48, 0.48, 0.5) # Running process color. PROC_COLOR_R = CPU_COLOR # Sleeping process color. @@ -62,8 +106,8 @@ SIG_COLOR = (0.0, 0.0, 0.0, 0.3125) # Signature font. SIG_FONT_SIZE = 14 # Signature text. -SIGNATURE = "http://code.google.com/p/pybootchartgui" - +SIGNATURE = "http://github.com/mmeeks/bootchart" + # Process dependency line color. DEP_COLOR = (0.75, 0.75, 0.75, 1.0) # Process dependency line stroke. @@ -72,6 +116,10 @@ DEP_STROKE = 1.0 # Process description date format. DESC_TIME_FORMAT = "mm:ss.SSS" +# Cumulative coloring bits +HSV_MAX_MOD = 31 +HSV_STEP = 7 + # Configure task color TASK_COLOR_CONFIGURE = (1.0, 1.0, 0.00, 1.0) # Compile task color. @@ -91,66 +139,67 @@ STATE_WAITING = 3 STATE_STOPPED = 4 STATE_ZOMBIE = 5 -STATE_COLORS = [(0,0,0,0), PROC_COLOR_R, PROC_COLOR_S, PROC_COLOR_D, PROC_COLOR_T, PROC_COLOR_Z, PROC_COLOR_X, PROC_COLOR_W] +STATE_COLORS = [(0, 0, 0, 0), PROC_COLOR_R, PROC_COLOR_S, PROC_COLOR_D, \ + PROC_COLOR_T, PROC_COLOR_Z, PROC_COLOR_X, PROC_COLOR_W] + +# CumulativeStats Types +STAT_TYPE_CPU = 0 +STAT_TYPE_IO = 1 # Convert ps process state to an int def get_proc_state(flag): - return "RSDTZXW".index(flag) + 1 - + return "RSDTZXW".find(flag) + 1 def draw_text(ctx, text, color, x, y): ctx.set_source_rgba(*color) ctx.move_to(x, y) ctx.show_text(text) - - + def draw_fill_rect(ctx, color, rect): ctx.set_source_rgba(*color) ctx.rectangle(*rect) ctx.fill() - def draw_rect(ctx, color, rect): ctx.set_source_rgba(*color) ctx.rectangle(*rect) ctx.stroke() - - + def draw_legend_box(ctx, label, fill_color, x, y, s): draw_fill_rect(ctx, fill_color, (x, y - s, s, s)) draw_rect(ctx, PROC_BORDER_COLOR, (x, y - s, s, s)) draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) - - + def draw_legend_line(ctx, label, fill_color, x, y, s): - draw_fill_rect(ctx, fill_color, (x, y - s/2, s + 1, 3)) + draw_fill_rect(ctx, fill_color, (x, y - s/2, s + 1, 3)) ctx.arc(x + (s + 1)/2.0, y - (s - 3)/2.0, 2.5, 0, 2.0 * math.pi) ctx.fill() draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) - def draw_label_in_box(ctx, color, label, x, y, w, maxx): label_w = ctx.text_extents(label)[2] label_x = x + w / 2 - label_w / 2 - if label_w + 10 > w: - label_x = x + w + 5 - if label_x + label_w > maxx: - label_x = x - label_w - 5 + if label_w + 10 > w: + label_x = x + w + 5 + if label_x + label_w > maxx: + label_x = x - label_w - 5 draw_text(ctx, label, color, label_x, y) - -def draw_5sec_labels(ctx, rect, sec_w): - ctx.set_font_size(AXIS_FONT_SIZE) +def draw_sec_labels(ctx, rect, sec_w, nsecs): + ctx.set_font_size(AXIS_FONT_SIZE) + prev_x = 0 for i in range(0, rect[2] + 1, sec_w): - if ((i / sec_w) % 30 == 0) : + if ((i / sec_w) % nsecs == 0) : label = "%ds" % (i / sec_w) label_w = ctx.text_extents(label)[2] - draw_text(ctx, label, TEXT_COLOR, rect[0] + i - label_w/2, rect[1] - 2) - + x = rect[0] + i - label_w/2 + if x >= prev_x: + draw_text(ctx, label, TEXT_COLOR, x, rect[1] - 2) + prev_x = x + label_w def draw_box_ticks(ctx, rect, sec_w): draw_rect(ctx, BORDER_COLOR, tuple(rect)) - + ctx.set_line_cap(cairo.LINE_CAP_SQUARE) for i in range(sec_w, rect[2] + 1, sec_w): @@ -164,136 +213,211 @@ def draw_box_ticks(ctx, rect, sec_w): ctx.set_line_cap(cairo.LINE_CAP_BUTT) -def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree): +def draw_annotations(ctx, proc_tree, times, rect): + ctx.set_line_cap(cairo.LINE_CAP_SQUARE) + ctx.set_source_rgba(*ANNOTATION_COLOR) + ctx.set_dash([4, 4]) + + for time in times: + if time is not None: + x = ((time - proc_tree.start_time) * rect[2] / proc_tree.duration) + + ctx.move_to(rect[0] + x, rect[1] + 1) + ctx.line_to(rect[0] + x, rect[1] + rect[3] - 1) + ctx.stroke() + + ctx.set_line_cap(cairo.LINE_CAP_BUTT) + ctx.set_dash([]) + +def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree, data_range): ctx.set_line_width(0.5) x_shift = proc_tree.start_time - x_scale = proc_tree.duration - - def transform_point_coords(point, x_base, y_base, xscale, yscale, x_trans, y_trans): + + def transform_point_coords(point, x_base, y_base, \ + xscale, yscale, x_trans, y_trans): x = (point[0] - x_base) * xscale + x_trans - y = (point[1] - y_base) * -yscale + y_trans + bar_h + y = (point[1] - y_base) * -yscale + y_trans + chart_bounds[3] return x, y - xscale = float(chart_bounds[2]) / max(x for (x,y) in data) - yscale = float(chart_bounds[3]) / max(y for (x,y) in data) - - first = transform_point_coords(data[0], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) - last = transform_point_coords(data[-1], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) - + max_x = max (x for (x, y) in data) + max_y = max (y for (x, y) in data) + # avoid divide by zero + if max_y == 0: + max_y = 1.0 + xscale = float (chart_bounds[2]) / max_x + # If data_range is given, scale the chart so that the value range in + # data_range matches the chart bounds exactly. + # Otherwise, scale so that the actual data matches the chart bounds. + if data_range: + yscale = float(chart_bounds[3]) / (data_range[1] - data_range[0]) + ybase = data_range[0] + else: + yscale = float(chart_bounds[3]) / max_y + ybase = 0 + + first = transform_point_coords (data[0], x_shift, ybase, xscale, yscale, \ + chart_bounds[0], chart_bounds[1]) + last = transform_point_coords (data[-1], x_shift, ybase, xscale, yscale, \ + chart_bounds[0], chart_bounds[1]) + ctx.set_source_rgba(*color) ctx.move_to(*first) for point in data: - x, y = transform_point_coords(point, x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) + x, y = transform_point_coords (point, x_shift, ybase, xscale, yscale, \ + chart_bounds[0], chart_bounds[1]) ctx.line_to(x, y) if fill: ctx.stroke_preserve() - ctx.line_to(last[0], chart_bounds[1]+bar_h) - ctx.line_to(first[0], chart_bounds[1]+bar_h) + ctx.line_to(last[0], chart_bounds[1]+chart_bounds[3]) + ctx.line_to(first[0], chart_bounds[1]+chart_bounds[3]) ctx.line_to(first[0], first[1]) ctx.fill() else: ctx.stroke() ctx.set_line_width(1.0) -header_h = 280 bar_h = 55 +meminfo_bar_h = 2 * bar_h +header_h = 110 + 2 * (30 + bar_h) + 1 * (30 + meminfo_bar_h) # offsets off_x, off_y = 10, 10 -sec_w = 1 # the width of a second +sec_w_base = 1 # the width of a second proc_h = 16 # the height of a process leg_s = 10 MIN_IMG_W = 800 +CUML_HEIGHT = 2000 # Increased value to accomodate CPU and I/O Graphs +OPTIONS = None +def extents(options, xscale, trace): + start = min(trace.start.keys()) + end = max(trace.end.keys()) -def extents(res): - start = min(res.start.keys()) - end = max(res.end.keys()) + w = int ((end - start) * sec_w_base * xscale) + 2*off_x + h = proc_h * len(trace.processes) + header_h + 2 * off_y - w = ((end - start) * sec_w) + 2*off_x - h = proc_h * len(res.processes) + header_h + 2*off_y + return (w, h) - return (w,h) +def clip_visible(clip, rect): + xmax = max (clip[0], rect[0]) + ymax = max (clip[1], rect[1]) + xmin = min (clip[0] + clip[2], rect[0] + rect[2]) + ymin = min (clip[1] + clip[3], rect[1] + rect[3]) + return (xmin > xmax and ymin > ymax) -# -# Render the chart. -# -def render(ctx, res): - (w, h) = extents(res) +def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): + proc_tree = options.proc_tree(trace) - ctx.set_line_width(1.0) - ctx.select_font_face(FONT_NAME) - draw_fill_rect(ctx, WHITE, (0, 0, max(w, MIN_IMG_W), h)) - w -= 2*off_x - # draw the title and headers - #curr_y = draw_header(ctx, headers, off_x, proc_tree.duration) - curr_y = 0 - # render bar legend ctx.set_font_size(LEGEND_FONT_SIZE) - #print "w, h %s %s" % (w, h) - - #draw_legend_box(ctx, "CPU (user+sys)", CPU_COLOR, off_x, curr_y+20, leg_s) - #draw_legend_box(ctx, "I/O (wait)", IO_COLOR, off_x + 120, curr_y+20, leg_s) + draw_legend_box(ctx, "CPU (user+sys)", CPU_COLOR, off_x, curr_y+20, leg_s) + draw_legend_box(ctx, "I/O (wait)", IO_COLOR, off_x + 120, curr_y+20, leg_s) # render I/O wait - #chart_rect = (off_x, curr_y+30, w, bar_h) - #draw_box_ticks(ctx, chart_rect, sec_w) - #draw_chart(ctx, IO_COLOR, True, chart_rect, [(sample.time, sample.user + sample.sys + sample.io) for sample in cpu_stats], proc_tree) - # render CPU load - #draw_chart(ctx, CPU_COLOR, True, chart_rect, [(sample.time, sample.user + sample.sys) for sample in cpu_stats], proc_tree) - - #curr_y = curr_y + 30 + bar_h + 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_chart (ctx, IO_COLOR, True, chart_rect, \ + [(sample.time, sample.user + sample.sys + sample.io) for sample in trace.cpu_stats], \ + proc_tree, None) + # render CPU load + draw_chart (ctx, CPU_COLOR, True, chart_rect, \ + [(sample.time, sample.user + sample.sys) for sample in trace.cpu_stats], \ + proc_tree, None) + + curr_y = curr_y + 30 + bar_h # render second chart - #draw_legend_line(ctx, "Disk throughput", DISK_TPUT_COLOR, off_x, curr_y+20, leg_s) - #draw_legend_box(ctx, "Disk utilization", IO_COLOR, off_x + 120, curr_y+20, leg_s) + draw_legend_line(ctx, "Disk throughput", DISK_TPUT_COLOR, off_x, curr_y+20, leg_s) + draw_legend_box(ctx, "Disk utilization", IO_COLOR, off_x + 120, curr_y+20, leg_s) # render I/O utilization - #chart_rect = (off_x, curr_y+30, w, bar_h) - #draw_box_ticks(ctx, chart_rect, sec_w) - #draw_chart(ctx, IO_COLOR, True, chart_rect, [(sample.time, sample.util) for sample in disk_stats], proc_tree) - + 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_chart (ctx, IO_COLOR, True, chart_rect, \ + [(sample.time, sample.util) for sample in trace.disk_stats], \ + proc_tree, None) + # render disk throughput - #max_sample = max(disk_stats, key=lambda s: s.tput) - #draw_chart(ctx, DISK_TPUT_COLOR, False, chart_rect, [(sample.time, sample.tput) for sample in disk_stats], proc_tree) - - #pos_x = off_x + ((max_sample.time - proc_tree.start_time) * w / proc_tree.duration) - pos_x = off_x + max_sample = max (trace.disk_stats, key = lambda s: s.tput) + if clip_visible (clip, chart_rect): + draw_chart (ctx, DISK_TPUT_COLOR, False, chart_rect, \ + [(sample.time, sample.tput) for sample in trace.disk_stats], \ + proc_tree, None) + + pos_x = off_x + ((max_sample.time - proc_tree.start_time) * w / proc_tree.duration) shift_x, shift_y = -20, 20 if (pos_x < off_x + 245): shift_x, shift_y = 5, 40 - - #label = "%dMB/s" % round((max_sample.tput) / 1024.0) - #draw_text(ctx, label, DISK_TPUT_COLOR, pos_x + shift_x, curr_y + shift_y) - - + label = "%dMB/s" % round ((max_sample.tput) / 1024.0) + draw_text (ctx, label, DISK_TPUT_COLOR, pos_x + shift_x, curr_y + shift_y) + + curr_y = curr_y + 30 + bar_h + + # render mem usage + chart_rect = (off_x, curr_y+30, w, meminfo_bar_h) + mem_stats = trace.mem_stats + if mem_stats and clip_visible (clip, chart_rect): + mem_scale = max(sample.records['MemTotal'] - sample.records['MemFree'] for sample in mem_stats) + draw_legend_box(ctx, "Mem cached (scale: %u MiB)" % (float(mem_scale) / 1024), MEM_CACHED_COLOR, off_x, curr_y+20, leg_s) + draw_legend_box(ctx, "Used", MEM_USED_COLOR, off_x + 240, curr_y+20, leg_s) + draw_legend_box(ctx, "Buffers", MEM_BUFFERS_COLOR, off_x + 360, curr_y+20, leg_s) + 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_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]) + draw_chart(ctx, MEM_USED_COLOR, True, chart_rect, \ + [(sample.time, sample.records['MemTotal'] - sample.records['MemFree'] - sample.records['Buffers']) for sample in mem_stats], \ + proc_tree, [0, mem_scale]) + draw_chart(ctx, MEM_CACHED_COLOR, True, chart_rect, \ + [(sample.time, sample.records['Cached']) for sample in mem_stats], \ + proc_tree, [0, mem_scale]) + draw_chart(ctx, MEM_SWAP_COLOR, False, chart_rect, \ + [(sample.time, float(sample.records['SwapTotal'] - sample.records['SwapFree'])) for sample in mem_stats], \ + proc_tree, None) + + curr_y = curr_y + meminfo_bar_h + + return curr_y + +def render_processes_chart(ctx, options, trace, curr_y, w, h, sec_w): chart_rect = [off_x, curr_y+60, w, h - 2 * off_y - (curr_y+60) + proc_h] - draw_legend_box(ctx, "Configure", TASK_COLOR_CONFIGURE, off_x , curr_y + 45, leg_s) - draw_legend_box(ctx, "Compile", TASK_COLOR_COMPILE, off_x+120, curr_y + 45, leg_s) - draw_legend_box(ctx, "Install", TASK_COLOR_INSTALL, off_x+240, curr_y + 45, leg_s) - draw_legend_box(ctx, "Package", TASK_COLOR_PACKAGE, off_x+360, curr_y + 45, leg_s) - draw_legend_box(ctx, "Populate Sysroot", TASK_COLOR_SYSROOT, off_x+480, curr_y + 45, leg_s) - + draw_legend_box (ctx, "Configure", \ + TASK_COLOR_CONFIGURE, off_x , curr_y + 45, leg_s) + draw_legend_box (ctx, "Compile", \ + TASK_COLOR_COMPILE, off_x+120, curr_y + 45, leg_s) + draw_legend_box (ctx, "Install", \ + TASK_COLOR_INSTALL, off_x+240, curr_y + 45, leg_s) + draw_legend_box (ctx, "Package", \ + TASK_COLOR_PACKAGE, off_x+360, curr_y + 45, leg_s) + draw_legend_box (ctx, "Populate Sysroot", \ + TASK_COLOR_SYSROOT, off_x+480, curr_y + 45, leg_s) + ctx.set_font_size(PROC_TEXT_FONT_SIZE) - + draw_box_ticks(ctx, chart_rect, sec_w) - draw_5sec_labels(ctx, chart_rect, sec_w) + draw_sec_labels(ctx, chart_rect, sec_w, 30) y = curr_y+60 - offset = min(res.start.keys()) - for s in sorted(res.start.keys()): - for val in sorted(res.start[s]): + offset = min(trace.start.keys()) + for s in sorted(trace.start.keys()): + for val in sorted(trace.start[s]): task = val.split(":")[1] #print val - #print res.processes[val][1] + #print trace.processes[val][1] #print s x = (s - offset) * sec_w - w = ((res.processes[val][1] - s) * sec_w) + w = ((trace.processes[val][1] - s) * sec_w) #print "proc at %s %s %s %s" % (x, y, w, proc_h) col = None @@ -315,95 +439,419 @@ def render(ctx, res): draw_label_in_box(ctx, PROC_TEXT_COLOR, val, x, y + proc_h - 4, w, proc_h) y = y + proc_h + return curr_y + +# +# Render the chart. +# +def render(ctx, options, xscale, trace): + (w, h) = extents (options, xscale, trace) + global OPTIONS + OPTIONS = options.app_options + + # x, y, w, h + clip = ctx.clip_extents() + + sec_w = int (xscale * sec_w_base) + ctx.set_line_width(1.0) + ctx.select_font_face(FONT_NAME) + draw_fill_rect(ctx, WHITE, (0, 0, max(w, MIN_IMG_W), h)) + w -= 2*off_x + curr_y = off_y; + + curr_y = render_processes_chart (ctx, options, trace, curr_y, w, h, sec_w) + + return + + proc_tree = options.proc_tree (trace) + + # draw the title and headers + if proc_tree.idle: + duration = proc_tree.idle + else: + duration = proc_tree.duration + + if not options.kernel_only: + curr_y = draw_header (ctx, trace.headers, duration) + else: + curr_y = off_y; + + if options.charts: + curr_y = render_charts (ctx, options, clip, trace, curr_y, w, h, sec_w) + # draw process boxes - #draw_process_bar_chart(ctx, proc_tree, curr_y + bar_h, w, h) + proc_height = h + if proc_tree.taskstats and options.cumulative: + proc_height -= CUML_HEIGHT - ctx.set_font_size(SIG_FONT_SIZE) - draw_text(ctx, SIGNATURE, SIG_COLOR, off_x + 5, h - off_y - 5) + draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times, + curr_y, w, proc_height, sec_w) -def draw_process_bar_chart(ctx, proc_tree, curr_y, w, h): + curr_y = proc_height + ctx.set_font_size(SIG_FONT_SIZE) + draw_text(ctx, SIGNATURE, SIG_COLOR, off_x + 5, proc_height - 8) + + # draw a cumulative CPU-time-per-process graph + if proc_tree.taskstats and options.cumulative: + cuml_rect = (off_x, curr_y + off_y, w, CUML_HEIGHT/2 - off_y * 2) + if clip_visible (clip, cuml_rect): + draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_CPU) + + # draw a cumulative I/O-time-per-process graph + if proc_tree.taskstats and options.cumulative: + cuml_rect = (off_x, curr_y + off_y * 100, w, CUML_HEIGHT/2 - off_y * 2) + 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): + header_size = 0 + if not options.kernel_only: + draw_legend_box (ctx, "Running (%cpu)", + PROC_COLOR_R, off_x , curr_y + 45, leg_s) + draw_legend_box (ctx, "Unint.sleep (I/O)", + PROC_COLOR_D, off_x+120, curr_y + 45, leg_s) + draw_legend_box (ctx, "Sleeping", + PROC_COLOR_S, off_x+240, curr_y + 45, leg_s) + draw_legend_box (ctx, "Zombie", + PROC_COLOR_Z, off_x+360, curr_y + 45, leg_s) + header_size = 45 + + chart_rect = [off_x, curr_y + header_size + 15, + w, h - 2 * off_y - (curr_y + header_size + 15) + proc_h] + ctx.set_font_size (PROC_TEXT_FONT_SIZE) + + draw_box_ticks (ctx, chart_rect, sec_w) + if sec_w > 100: + nsec = 1 + else: + nsec = 5 + draw_sec_labels (ctx, chart_rect, sec_w, nsec) + draw_annotations (ctx, proc_tree, times, chart_rect) - for root in proc_tree.process_tree: - draw_processes_recursively(ctx, root, proc_tree, y, proc_h, chart_rect) - y = y + proc_h * proc_tree.num_nodes([root]) + y = curr_y + 60 + for root in proc_tree.process_tree: + draw_processes_recursively(ctx, root, proc_tree, y, proc_h, chart_rect, clip) + y = y + proc_h * proc_tree.num_nodes([root]) -def draw_header(ctx, headers, off_x, duration): - dur = duration / 100.0 +def draw_header (ctx, headers, duration): toshow = [ ('system.uname', 'uname', lambda s: s), ('system.release', 'release', lambda s: s), ('system.cpu', 'CPU', lambda s: re.sub('model name\s*:\s*', '', s, 1)), ('system.kernel.options', 'kernel options', lambda s: s), - ('pseudo.header', 'time', lambda s: '%02d:%05.2f' % (math.floor(dur/60), dur - 60 * math.floor(dur/60))) ] header_y = ctx.font_extents()[2] + 10 ctx.set_font_size(TITLE_FONT_SIZE) draw_text(ctx, headers['title'], TEXT_COLOR, off_x, header_y) ctx.set_font_size(TEXT_FONT_SIZE) - + for (headerkey, headertitle, mangle) in toshow: header_y += ctx.font_extents()[2] - txt = headertitle + ': ' + mangle(headers.get(headerkey)) + if headerkey in headers: + value = headers.get(headerkey) + else: + value = "" + txt = headertitle + ': ' + mangle(value) draw_text(ctx, txt, TEXT_COLOR, off_x, header_y) + dur = duration / 100.0 + txt = 'time : %02d:%05.2f' % (math.floor(dur/60), dur - 60 * math.floor(dur/60)) + if headers.get('system.maxpid') is not None: + txt = txt + ' max pid: %s' % (headers.get('system.maxpid')) + + header_y += ctx.font_extents()[2] + draw_text (ctx, txt, TEXT_COLOR, off_x, header_y) + return header_y -def draw_processes_recursively(ctx, proc, proc_tree, y, proc_h, rect) : +def draw_processes_recursively(ctx, proc, proc_tree, y, proc_h, rect, clip) : x = rect[0] + ((proc.start_time - proc_tree.start_time) * rect[2] / proc_tree.duration) w = ((proc.duration) * rect[2] / proc_tree.duration) - draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect) + draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip) draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h)) - draw_label_in_box(ctx, PROC_TEXT_COLOR, proc.cmd, x, y + proc_h - 4, w, rect[0] + rect[2]) + ipid = int(proc.pid) + if not OPTIONS.show_all: + cmdString = proc.cmd + else: + cmdString = '' + if (OPTIONS.show_pid or OPTIONS.show_all) and ipid is not 0: + cmdString = cmdString + " [" + str(ipid // 1000) + "]" + if OPTIONS.show_all: + if proc.args: + cmdString = cmdString + " '" + "' '".join(proc.args) + "'" + else: + cmdString = cmdString + " " + proc.exe + + draw_label_in_box(ctx, PROC_TEXT_COLOR, cmdString, x, y + proc_h - 4, w, rect[0] + rect[2]) next_y = y + proc_h for child in proc.child_list: - child_x, child_y = draw_processes_recursively(ctx, child, proc_tree, next_y, proc_h, rect) + if next_y > clip[1] + clip[3]: + break + child_x, child_y = draw_processes_recursively(ctx, child, proc_tree, next_y, proc_h, rect, clip) draw_process_connecting_lines(ctx, x, y, child_x, child_y, proc_h) next_y = next_y + proc_h * proc_tree.num_nodes([child]) - + return x, y -def draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect): +def draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip): + + if y > clip[1] + clip[3] or y + proc_h + 2 < clip[1]: + return + draw_fill_rect(ctx, PROC_COLOR_S, (x, y, w, proc_h)) last_tx = -1 - for sample in proc.samples : + for sample in proc.samples : tx = rect[0] + round(((sample.time - proc_tree.start_time) * rect[2] / proc_tree.duration)) + + # samples are sorted chronologically + if tx < clip[0]: + continue + if tx > clip[0] + clip[2]: + break + tw = round(proc_tree.sample_period * rect[2] / float(proc_tree.duration)) if last_tx != -1 and abs(last_tx - tx) <= tw: tw -= last_tx - tx tx = last_tx - + tw = max (tw, 1) # nice to see at least something + last_tx = tx + tw state = get_proc_state( sample.state ) - color = STATE_COLORS[state] + color = STATE_COLORS[state] if state == STATE_RUNNING: - alpha = sample.cpu_sample.user + sample.cpu_sample.sys + alpha = min (sample.cpu_sample.user + sample.cpu_sample.sys, 1.0) color = tuple(list(PROC_COLOR_R[0:3]) + [alpha]) +# print "render time %d [ tx %d tw %d ], sample state %s color %s alpha %g" % (sample.time, tx, tw, state, color, alpha) elif state == STATE_SLEEPING: continue draw_fill_rect(ctx, color, (tx, y, tw, proc_h)) - def draw_process_connecting_lines(ctx, px, py, x, y, proc_h): ctx.set_source_rgba(*DEP_COLOR) - ctx.set_dash([2,2]) + ctx.set_dash([2, 2]) if abs(px - x) < 3: dep_off_x = 3 dep_off_y = proc_h / 4 ctx.move_to(x, y + proc_h / 2) ctx.line_to(px - dep_off_x, y + proc_h / 2) ctx.line_to(px - dep_off_x, py - dep_off_y) - ctx.line_to(px, py - dep_off_y) + ctx.line_to(px, py - dep_off_y) else: ctx.move_to(x, y + proc_h / 2) ctx.line_to(px, y + proc_h / 2) ctx.line_to(px, py) ctx.stroke() - ctx.set_dash([]) + ctx.set_dash([]) + +# elide the bootchart collector - it is quite distorting +def elide_bootchart(proc): + return proc.cmd == 'bootchartd' or proc.cmd == 'bootchart-colle' + +class CumlSample: + def __init__(self, proc): + self.cmd = proc.cmd + self.samples = [] + self.merge_samples (proc) + self.color = None + + def merge_samples(self, proc): + self.samples.extend (proc.samples) + self.samples.sort (key = lambda p: p.time) + + def next(self): + global palette_idx + palette_idx += HSV_STEP + return palette_idx + + def get_color(self): + if self.color is None: + i = self.next() % HSV_MAX_MOD + h = 0.0 + if i is not 0: + h = (1.0 * i) / HSV_MAX_MOD + s = 0.5 + v = 1.0 + c = colorsys.hsv_to_rgb (h, s, v) + self.color = (c[0], c[1], c[2], 1.0) + return self.color + + +def draw_cuml_graph(ctx, proc_tree, chart_bounds, duration, sec_w, stat_type): + global palette_idx + palette_idx = 0 + + time_hash = {} + total_time = 0.0 + m_proc_list = {} + + if stat_type is STAT_TYPE_CPU: + sample_value = 'cpu' + else: + sample_value = 'io' + for proc in proc_tree.process_list: + if elide_bootchart(proc): + continue + + for sample in proc.samples: + total_time += getattr(sample.cpu_sample, sample_value) + if not sample.time in time_hash: + time_hash[sample.time] = 1 + + # merge pids with the same cmd + if not proc.cmd in m_proc_list: + m_proc_list[proc.cmd] = CumlSample (proc) + continue + s = m_proc_list[proc.cmd] + s.merge_samples (proc) + + # all the sample times + times = sorted(time_hash) + if len (times) < 2: + print("degenerate boot chart") + return + + pix_per_ns = chart_bounds[3] / total_time +# print "total time: %g pix-per-ns %g" % (total_time, pix_per_ns) + + # FIXME: we have duplicates in the process list too [!] - why !? + + # Render bottom up, left to right + below = {} + for time in times: + below[time] = chart_bounds[1] + chart_bounds[3] + + # same colors each time we render + random.seed (0) + + ctx.set_line_width(1) + + legends = [] + labels = [] + + # render each pid in order + for cs in m_proc_list.values(): + row = {} + cuml = 0.0 + + # print "pid : %s -> %g samples %d" % (proc.cmd, cuml, len (cs.samples)) + for sample in cs.samples: + cuml += getattr(sample.cpu_sample, sample_value) + row[sample.time] = cuml + + process_total_time = cuml + + # hide really tiny processes + if cuml * pix_per_ns <= 2: + continue + + last_time = times[0] + y = last_below = below[last_time] + last_cuml = cuml = 0.0 + + ctx.set_source_rgba(*cs.get_color()) + for time in times: + render_seg = False + + # did the underlying trend increase ? + if below[time] != last_below: + last_below = below[last_time] + last_cuml = cuml + render_seg = True + + # did we move up a pixel increase ? + if time in row: + nc = round (row[time] * pix_per_ns) + if nc != cuml: + last_cuml = cuml + cuml = nc + render_seg = True + +# if last_cuml > cuml: +# assert fail ... - un-sorted process samples + + # draw the trailing rectangle from the last time to + # before now, at the height of the last segment. + if render_seg: + w = math.ceil ((time - last_time) * chart_bounds[2] / proc_tree.duration) + 1 + x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) + ctx.rectangle (x, below[last_time] - last_cuml, w, last_cuml) + ctx.fill() +# ctx.stroke() + last_time = time + y = below [time] - cuml + + row[time] = y + + # render the last segment + x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) + y = below[last_time] - cuml + ctx.rectangle (x, y, chart_bounds[2] - x, cuml) + ctx.fill() +# ctx.stroke() + + # render legend if it will fit + if cuml > 8: + label = cs.cmd + extnts = ctx.text_extents(label) + label_w = extnts[2] + label_h = extnts[3] +# print "Text extents %g by %g" % (label_w, label_h) + labels.append((label, + chart_bounds[0] + chart_bounds[2] - label_w - off_x * 2, + y + (cuml + label_h) / 2)) + if cs in legends: + print("ARGH - duplicate process in list !") + + legends.append ((cs, process_total_time)) + + below = row + + # render grid-lines over the top + draw_box_ticks(ctx, chart_bounds, sec_w) + + # render labels + for l in labels: + draw_text(ctx, l[0], TEXT_COLOR, l[1], l[2]) + + # Render legends + font_height = 20 + label_width = 300 + LEGENDS_PER_COL = 15 + LEGENDS_TOTAL = 45 + ctx.set_font_size (TITLE_FONT_SIZE) + dur_secs = duration / 100 + cpu_secs = total_time / 1000000000 + + # misleading - with multiple CPUs ... +# idle = ((dur_secs - cpu_secs) / dur_secs) * 100.0 + if stat_type is STAT_TYPE_CPU: + label = "Cumulative CPU usage, by process; total CPU: " \ + " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) + else: + label = "Cumulative I/O usage, by process; total I/O: " \ + " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) + + draw_text(ctx, label, TEXT_COLOR, chart_bounds[0] + off_x, + chart_bounds[1] + font_height) + + i = 0 + legends = sorted(legends, key=itemgetter(1), reverse=True) + ctx.set_font_size(TEXT_FONT_SIZE) + for t in legends: + cs = t[0] + time = t[1] + x = chart_bounds[0] + off_x + int (i/LEGENDS_PER_COL) * label_width + y = chart_bounds[1] + font_height * ((i % LEGENDS_PER_COL) + 2) + str = "%s - %.0f(ms) (%2.2f%%)" % (cs.cmd, time/1000000, (time/total_time) * 100.0) + draw_legend_box(ctx, str, cs.color, x, y, leg_s) + i = i + 1 + if i >= LEGENDS_TOTAL: + break -- cgit v1.2.3