mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 21:09:03 +02:00

This patch adds support for parsing and visualizing network interface statistics from /proc/net/dev in pybootchartgui. It introduces a new NetSample class to hold per-interface metrics, including received/transmitted bytes and their deltas over time. The data is drawn using line and box charts in draw.py and helps to monitor network usage during the boot process for each interface individually. (From OE-Core rev: 9e640022c83a627bd05c23b66b658bd644b2f0d7) Signed-off-by: denisova-ok <denisova.olga.k@yandex.ru> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
1164 lines
41 KiB
Python
1164 lines
41 KiB
Python
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import cairo
|
|
import math
|
|
import re
|
|
import random
|
|
import colorsys
|
|
import functools
|
|
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)
|
|
|
|
WHITE = (1.0, 1.0, 1.0, 1.0)
|
|
# Process tree border color.
|
|
BORDER_COLOR = (0.63, 0.63, 0.63, 1.0)
|
|
# Second tick line color.
|
|
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"
|
|
# Title text font.
|
|
TITLE_FONT_SIZE = 18
|
|
# Default text font.
|
|
TEXT_FONT_SIZE = 12
|
|
# Axis label font.
|
|
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.
|
|
IO_COLOR = (0.76, 0.48, 0.48, 0.5)
|
|
# Disk throughput color.
|
|
DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0)
|
|
|
|
BYTES_RECEIVED_COLOR = (0.0, 0.0, 1.0, 1.0)
|
|
BYTES_TRANSMITTED_COLOR = (1.0, 0.0, 0.0, 1.0)
|
|
BYTES_RECEIVE_DIFF_COLOR = (0.0, 0.0, 1.0, 0.3)
|
|
BYTES_TRANSMIT_DIFF_COLOR = (1.0, 0.0, 0.0, 0.3)
|
|
# 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
|
|
|
|
# avg10 CPU pressure color
|
|
CPU_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0)
|
|
# delta total CPU pressure color
|
|
CPU_PRESSURE_TOTAL_COLOR = CPU_COLOR
|
|
# avg10 IO pressure color
|
|
IO_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0)
|
|
# delta total IO pressure color
|
|
IO_PRESSURE_TOTAL_COLOR = IO_COLOR
|
|
# avg10 memory pressure color
|
|
MEM_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0)
|
|
# delta total memory pressure color
|
|
MEM_PRESSURE_TOTAL_COLOR = DISK_TPUT_COLOR
|
|
|
|
|
|
|
|
|
|
# Process border color.
|
|
PROC_BORDER_COLOR = (0.71, 0.71, 0.71, 1.0)
|
|
# 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.
|
|
PROC_COLOR_S = (0.94, 0.94, 0.94, 1.0)
|
|
# Stopped process color.
|
|
PROC_COLOR_T = (0.94, 0.50, 0.50, 1.0)
|
|
# Zombie process color.
|
|
PROC_COLOR_Z = (0.71, 0.71, 0.71, 1.0)
|
|
# Dead process color.
|
|
PROC_COLOR_X = (0.71, 0.71, 0.71, 0.125)
|
|
# Paging process color.
|
|
PROC_COLOR_W = (0.71, 0.71, 0.71, 0.125)
|
|
|
|
# Process label color.
|
|
PROC_TEXT_COLOR = (0.19, 0.19, 0.19, 1.0)
|
|
# Process label font.
|
|
PROC_TEXT_FONT_SIZE = 12
|
|
|
|
# Signature color.
|
|
SIG_COLOR = (0.0, 0.0, 0.0, 0.3125)
|
|
# Signature font.
|
|
SIG_FONT_SIZE = 14
|
|
# Signature text.
|
|
SIGNATURE = "http://github.com/mmeeks/bootchart"
|
|
|
|
# Process dependency line color.
|
|
DEP_COLOR = (0.75, 0.75, 0.75, 1.0)
|
|
# Process dependency line stroke.
|
|
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.
|
|
TASK_COLOR_COMPILE = (0.0, 1.00, 0.00, 1.0)
|
|
# Install task color
|
|
TASK_COLOR_INSTALL = (1.0, 0.00, 1.00, 1.0)
|
|
# Sysroot task color
|
|
TASK_COLOR_SYSROOT = (0.0, 0.00, 1.00, 1.0)
|
|
# Package task color
|
|
TASK_COLOR_PACKAGE = (0.0, 1.00, 1.00, 1.0)
|
|
# Package Write RPM/DEB/IPK task color
|
|
TASK_COLOR_PACKAGE_WRITE = (0.0, 0.50, 0.50, 1.0)
|
|
|
|
# Distinct colors used for different disk volumnes.
|
|
# If we have more volumns, colors get re-used.
|
|
VOLUME_COLORS = [
|
|
(1.0, 1.0, 0.00, 1.0),
|
|
(0.0, 1.00, 0.00, 1.0),
|
|
(1.0, 0.00, 1.00, 1.0),
|
|
(0.0, 0.00, 1.00, 1.0),
|
|
(0.0, 1.00, 1.00, 1.0),
|
|
]
|
|
|
|
# Process states
|
|
STATE_UNDEFINED = 0
|
|
STATE_RUNNING = 1
|
|
STATE_SLEEPING = 2
|
|
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]
|
|
|
|
# CumulativeStats Types
|
|
STAT_TYPE_CPU = 0
|
|
STAT_TYPE_IO = 1
|
|
|
|
# Convert ps process state to an int
|
|
def get_proc_state(flag):
|
|
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))
|
|
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
|
|
draw_text(ctx, label, color, label_x, y)
|
|
|
|
def draw_sec_labels(ctx, options, 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) % nsecs == 0) :
|
|
if options.app_options.as_minutes :
|
|
label = "%.1f" % (i / sec_w / 60.0)
|
|
else :
|
|
label = "%d" % (i / sec_w)
|
|
label_w = ctx.text_extents(label)[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):
|
|
if ((i / sec_w) % 10 == 0) :
|
|
ctx.set_line_width(1.5)
|
|
elif sec_w < 5 :
|
|
continue
|
|
else :
|
|
ctx.set_line_width(1.0)
|
|
if ((i / sec_w) % 30 == 0) :
|
|
ctx.set_source_rgba(*TICK_COLOR_BOLD)
|
|
else :
|
|
ctx.set_source_rgba(*TICK_COLOR)
|
|
ctx.move_to(rect[0] + i, rect[1] + 1)
|
|
ctx.line_to(rect[0] + i, rect[1] + rect[3] - 1)
|
|
ctx.stroke()
|
|
ctx.set_line_width(1.0)
|
|
|
|
ctx.set_line_cap(cairo.LINE_CAP_BUTT)
|
|
|
|
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
|
|
|
|
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 + chart_bounds[3]
|
|
return x, y
|
|
|
|
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
|
|
if (max_x - x_shift):
|
|
xscale = float (chart_bounds[2]) / (max_x - x_shift)
|
|
else:
|
|
xscale = float (chart_bounds[2])
|
|
# 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 and (data_range[1] - data_range[0]):
|
|
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, 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]+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)
|
|
|
|
bar_h = 55
|
|
meminfo_bar_h = 2 * bar_h
|
|
header_h = 60
|
|
# offsets
|
|
off_x, off_y = 220, 10
|
|
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 accommodate CPU and I/O Graphs
|
|
OPTIONS = None
|
|
|
|
def extents(options, xscale, trace):
|
|
start = min(trace.start.keys())
|
|
end = start
|
|
|
|
processes = 0
|
|
for proc in trace.processes:
|
|
if not options.app_options.show_all and \
|
|
trace.processes[proc][1] - trace.processes[proc][0] < options.app_options.mintime:
|
|
continue
|
|
|
|
if trace.processes[proc][1] > end:
|
|
end = trace.processes[proc][1]
|
|
processes += 1
|
|
|
|
if trace.min is not None and trace.max is not None:
|
|
start = trace.min
|
|
end = trace.max
|
|
|
|
w = int ((end - start) * sec_w_base * xscale) + 2 * off_x
|
|
h = proc_h * processes + header_h + 2 * off_y
|
|
|
|
if options.charts:
|
|
if trace.cpu_stats:
|
|
h += 30 + bar_h
|
|
if trace.disk_stats:
|
|
h += 30 + bar_h
|
|
if trace.cpu_pressure:
|
|
h += 30 + bar_h
|
|
if trace.io_pressure:
|
|
h += 30 + bar_h
|
|
if trace.mem_pressure:
|
|
h += 30 + bar_h
|
|
if trace.monitor_disk:
|
|
h += 30 + bar_h
|
|
if trace.mem_stats:
|
|
h += meminfo_bar_h
|
|
|
|
# Allow for width of process legend and offset
|
|
if w < (720 + off_x):
|
|
w = 720 + off_x
|
|
|
|
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)
|
|
|
|
def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w):
|
|
proc_tree = options.proc_tree(trace)
|
|
|
|
# render bar legend
|
|
if trace.cpu_stats:
|
|
ctx.set_font_size(LEGEND_FONT_SIZE)
|
|
|
|
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)
|
|
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
|
|
if trace.disk_stats:
|
|
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)
|
|
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 (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)
|
|
|
|
curr_y = curr_y + 30 + bar_h
|
|
|
|
if trace.net_stats:
|
|
for iface, samples in trace.net_stats.items():
|
|
max_received_sample = max(samples, key=lambda s: s.received_bytes)
|
|
max_transmitted_sample = max(samples, key=lambda s: s.transmitted_bytes)
|
|
max_receive_diff_sample = max(samples, key=lambda s: s.receive_diff)
|
|
max_transmit_diff_sample = max(samples, key=lambda s: s.transmit_diff)
|
|
|
|
draw_text(ctx, "Iface: %s" % (iface), TEXT_COLOR, off_x, curr_y+20)
|
|
draw_legend_line(ctx, "Bytes received (max %d)" % (max_received_sample.received_bytes),
|
|
BYTES_RECEIVED_COLOR, off_x+150, curr_y+20, leg_s)
|
|
draw_legend_line(ctx, "Bytes transmitted (max %d)" % (max_transmitted_sample.transmitted_bytes),
|
|
BYTES_TRANSMITTED_COLOR, off_x+400, curr_y+20, leg_s)
|
|
draw_legend_box(ctx, "Bytes receive diff (max %d)" % (max_receive_diff_sample.receive_diff),
|
|
BYTES_RECEIVE_DIFF_COLOR, off_x+650, curr_y+20, leg_s)
|
|
draw_legend_box(ctx, "Bytes transmit diff (max %d)" % (max_transmit_diff_sample.transmit_diff),
|
|
BYTES_TRANSMIT_DIFF_COLOR, off_x+900, curr_y+20, leg_s)
|
|
|
|
|
|
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)
|
|
|
|
if clip_visible (clip, chart_rect):
|
|
draw_chart (ctx, BYTES_RECEIVED_COLOR, False, chart_rect, \
|
|
[(sample.time, sample.received_bytes) for sample in samples], \
|
|
proc_tree, None)
|
|
|
|
draw_chart (ctx, BYTES_TRANSMITTED_COLOR, False, chart_rect, \
|
|
[(sample.time, sample.transmitted_bytes) for sample in samples], \
|
|
proc_tree, None)
|
|
|
|
if clip_visible (clip, chart_rect):
|
|
draw_chart (ctx, BYTES_RECEIVE_DIFF_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.receive_diff) for sample in samples], \
|
|
proc_tree, None)
|
|
|
|
draw_chart (ctx, BYTES_TRANSMIT_DIFF_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.transmit_diff) for sample in samples], \
|
|
proc_tree, None)
|
|
|
|
curr_y = curr_y + 30 + bar_h
|
|
|
|
# render CPU pressure chart
|
|
if trace.cpu_pressure:
|
|
max_sample_avg = max (trace.cpu_pressure, key = lambda s: s.avg10)
|
|
max_sample_total = max (trace.cpu_pressure, key = lambda s: s.deltaTotal)
|
|
draw_legend_line(ctx, "avg10 CPU Pressure (max %d%%)" % (max_sample_avg.avg10), CPU_PRESSURE_AVG10_COLOR, off_x, curr_y+20, leg_s)
|
|
draw_legend_box(ctx, "delta total CPU Pressure (max %d)" % (max_sample_total.deltaTotal), CPU_PRESSURE_TOTAL_COLOR, off_x + 240, curr_y+20, leg_s)
|
|
|
|
# render delta total cpu
|
|
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, CPU_PRESSURE_TOTAL_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.deltaTotal) for sample in trace.cpu_pressure], \
|
|
proc_tree, None)
|
|
|
|
# render avg10 cpu
|
|
if clip_visible (clip, chart_rect):
|
|
draw_chart (ctx, CPU_PRESSURE_AVG10_COLOR, False, chart_rect, \
|
|
[(sample.time, sample.avg10) for sample in trace.cpu_pressure], \
|
|
proc_tree, None)
|
|
|
|
pos_x = off_x + ((max_sample_avg.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 = "%d%%" % (max_sample_avg.avg10)
|
|
draw_text (ctx, label, CPU_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y)
|
|
|
|
curr_y = curr_y + 30 + bar_h
|
|
|
|
# render I/O pressure chart
|
|
if trace.io_pressure:
|
|
max_sample_avg = max (trace.io_pressure, key = lambda s: s.avg10)
|
|
max_sample_total = max (trace.io_pressure, key = lambda s: s.deltaTotal)
|
|
draw_legend_line(ctx, "avg10 I/O Pressure (max %d%%)" % (max_sample_avg.avg10), IO_PRESSURE_AVG10_COLOR, off_x, curr_y+20, leg_s)
|
|
draw_legend_box(ctx, "delta total I/O Pressure (max %d)" % (max_sample_total.deltaTotal), IO_PRESSURE_TOTAL_COLOR, off_x + 240, curr_y+20, leg_s)
|
|
|
|
# render delta total io
|
|
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_PRESSURE_TOTAL_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.deltaTotal) for sample in trace.io_pressure], \
|
|
proc_tree, None)
|
|
|
|
# render avg10 io
|
|
if clip_visible (clip, chart_rect):
|
|
draw_chart (ctx, IO_PRESSURE_AVG10_COLOR, False, chart_rect, \
|
|
[(sample.time, sample.avg10) for sample in trace.io_pressure], \
|
|
proc_tree, None)
|
|
|
|
pos_x = off_x + ((max_sample_avg.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 = "%d%%" % (max_sample_avg.avg10)
|
|
draw_text (ctx, label, IO_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y)
|
|
|
|
curr_y = curr_y + 30 + bar_h
|
|
|
|
# render MEM pressure chart
|
|
if trace.mem_pressure:
|
|
max_sample_avg = max (trace.mem_pressure, key = lambda s: s.avg10)
|
|
max_sample_total = max (trace.mem_pressure, key = lambda s: s.deltaTotal)
|
|
draw_legend_line(ctx, "avg10 MEM Pressure (max %d%%)" % (max_sample_avg.avg10), MEM_PRESSURE_AVG10_COLOR, off_x, curr_y+20, leg_s)
|
|
draw_legend_box(ctx, "delta total MEM Pressure (max %d)" % (max_sample_total.deltaTotal), MEM_PRESSURE_TOTAL_COLOR, off_x + 240, curr_y+20, leg_s)
|
|
|
|
# render delta total mem
|
|
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, MEM_PRESSURE_TOTAL_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.deltaTotal) for sample in trace.mem_pressure], \
|
|
proc_tree, None)
|
|
|
|
# render avg10 mem
|
|
if clip_visible (clip, chart_rect):
|
|
draw_chart (ctx, MEM_PRESSURE_AVG10_COLOR, False, chart_rect, \
|
|
[(sample.time, sample.avg10) for sample in trace.mem_pressure], \
|
|
proc_tree, None)
|
|
|
|
pos_x = off_x + ((max_sample_avg.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 = "%d%%" % (max_sample_avg.avg10)
|
|
draw_text (ctx, label, MEM_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y)
|
|
|
|
curr_y = curr_y + 30 + bar_h
|
|
|
|
# render disk space usage
|
|
#
|
|
# Draws the amount of disk space used on each volume relative to the
|
|
# lowest recorded amount. The graphs for each volume are stacked above
|
|
# each other so that total disk usage is visible.
|
|
if trace.monitor_disk:
|
|
ctx.set_font_size(LEGEND_FONT_SIZE)
|
|
# Determine set of volumes for which we have
|
|
# information and the minimal amount of used disk
|
|
# space for each. Currently samples are allowed to
|
|
# not have a values for all volumes; drawing could be
|
|
# made more efficient if that wasn't the case.
|
|
volumes = set()
|
|
min_used = {}
|
|
for sample in trace.monitor_disk:
|
|
for volume, used in sample.records.items():
|
|
volumes.add(volume)
|
|
if volume not in min_used or min_used[volume] > used:
|
|
min_used[volume] = used
|
|
volumes = sorted(list(volumes))
|
|
disk_scale = 0
|
|
for i, volume in enumerate(volumes):
|
|
volume_scale = max([sample.records[volume] - min_used[volume]
|
|
for sample in trace.monitor_disk
|
|
if volume in sample.records])
|
|
# Does not take length of volume name into account, but fixed offset
|
|
# works okay in practice.
|
|
draw_legend_box(ctx, '%s (max: %u MiB)' % (volume, volume_scale / 1024 / 1024),
|
|
VOLUME_COLORS[i % len(VOLUME_COLORS)],
|
|
off_x + i * 250, curr_y+20, leg_s)
|
|
disk_scale += volume_scale
|
|
|
|
# render used amount of disk space
|
|
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)
|
|
for i in range(len(volumes), 0, -1):
|
|
draw_chart (ctx, VOLUME_COLORS[(i - 1) % len(VOLUME_COLORS)], True, chart_rect, \
|
|
[(sample.time,
|
|
# Sum up used space of all volumes including the current one
|
|
# so that the graphs appear as stacked on top of each other.
|
|
functools.reduce(lambda x,y: x+y,
|
|
[sample.records[volume] - min_used[volume]
|
|
for volume in volumes[0:i]
|
|
if volume in sample.records],
|
|
0))
|
|
for sample in trace.monitor_disk], \
|
|
proc_tree, [0, disk_scale])
|
|
|
|
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.buffers 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.swap)/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.buffers) for sample in trace.mem_stats], \
|
|
proc_tree, [0, mem_scale])
|
|
draw_chart(ctx, MEM_USED_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.used) for sample in mem_stats], \
|
|
proc_tree, [0, mem_scale])
|
|
draw_chart(ctx, MEM_CACHED_COLOR, True, chart_rect, \
|
|
[(sample.time, sample.cached) for sample in mem_stats], \
|
|
proc_tree, [0, mem_scale])
|
|
draw_chart(ctx, MEM_SWAP_COLOR, False, chart_rect, \
|
|
[(sample.time, float(sample.swap)) 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, width, h, sec_w):
|
|
chart_rect = [off_x, curr_y+header_h, width, h - curr_y - 1 * off_y - header_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, "Populate Sysroot", \
|
|
TASK_COLOR_SYSROOT, off_x+360, curr_y + 45, leg_s)
|
|
draw_legend_box (ctx, "Package", \
|
|
TASK_COLOR_PACKAGE, off_x+480, curr_y + 45, leg_s)
|
|
draw_legend_box (ctx, "Package Write", \
|
|
TASK_COLOR_PACKAGE_WRITE, off_x+600, curr_y + 45, leg_s)
|
|
|
|
ctx.set_font_size(PROC_TEXT_FONT_SIZE)
|
|
|
|
draw_box_ticks(ctx, chart_rect, sec_w)
|
|
draw_sec_labels(ctx, options, chart_rect, sec_w, 30)
|
|
|
|
y = curr_y+header_h
|
|
|
|
offset = trace.min or min(trace.start.keys())
|
|
for start in sorted(trace.start.keys()):
|
|
for process in sorted(trace.start[start]):
|
|
elapsed_time = trace.processes[process][1] - start
|
|
if not options.app_options.show_all and \
|
|
elapsed_time < options.app_options.mintime:
|
|
continue
|
|
task = process.split(":")[1]
|
|
|
|
#print(process)
|
|
#print(trace.processes[process][1])
|
|
#print(s)
|
|
|
|
x = chart_rect[0] + (start - offset) * sec_w
|
|
w = elapsed_time * sec_w
|
|
|
|
def set_alfa(color, alfa):
|
|
clist = list(color)
|
|
clist[-1] = alfa
|
|
return tuple(clist)
|
|
|
|
#print("proc at %s %s %s %s" % (x, y, w, proc_h))
|
|
col = None
|
|
if task == "do_compile":
|
|
col = TASK_COLOR_COMPILE
|
|
elif "do_compile" in task:
|
|
col = set_alfa(TASK_COLOR_COMPILE, 0.25)
|
|
elif task == "do_configure":
|
|
col = TASK_COLOR_CONFIGURE
|
|
elif "do_configure" in task:
|
|
col = set_alfa(TASK_COLOR_CONFIGURE, 0.25)
|
|
elif task == "do_install":
|
|
col = TASK_COLOR_INSTALL
|
|
elif task == "do_populate_sysroot":
|
|
col = TASK_COLOR_SYSROOT
|
|
elif task == "do_package":
|
|
col = TASK_COLOR_PACKAGE
|
|
elif task == "do_package_write_rpm" or \
|
|
task == "do_package_write_deb" or \
|
|
task == "do_package_write_ipk":
|
|
col = TASK_COLOR_PACKAGE_WRITE
|
|
else:
|
|
col = WHITE
|
|
|
|
if col:
|
|
draw_fill_rect(ctx, col, (x, y, w, proc_h))
|
|
draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h))
|
|
|
|
# Show elapsed time for each task
|
|
process = "%ds %s" % (elapsed_time, process)
|
|
draw_label_in_box(ctx, PROC_TEXT_COLOR, process, x, y + proc_h - 4, w, width)
|
|
|
|
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;
|
|
|
|
if options.charts:
|
|
curr_y = render_charts (ctx, options, clip, trace, curr_y, w, h, sec_w)
|
|
|
|
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;
|
|
|
|
# draw process boxes
|
|
proc_height = h
|
|
if proc_tree.taskstats and options.cumulative:
|
|
proc_height -= CUML_HEIGHT
|
|
|
|
draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times,
|
|
curr_y, w, proc_height, sec_w)
|
|
|
|
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, options, chart_rect, sec_w, nsec)
|
|
draw_annotations (ctx, proc_tree, times, chart_rect)
|
|
|
|
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, 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),
|
|
]
|
|
|
|
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]
|
|
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, 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, clip)
|
|
draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h))
|
|
ipid = int(proc.pid)
|
|
if not OPTIONS.show_all:
|
|
cmdString = proc.cmd
|
|
else:
|
|
cmdString = ''
|
|
if (OPTIONS.show_pid or OPTIONS.show_all) and ipid != 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:
|
|
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, 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 :
|
|
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]
|
|
if state == STATE_RUNNING:
|
|
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])
|
|
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)
|
|
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([])
|
|
|
|
# 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 != 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
|