1# This file is part of pybootchartgui. 2 3# pybootchartgui is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7 8# pybootchartgui is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12 13# You should have received a copy of the GNU General Public License 14# along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>. 15 16 17import cairo 18import math 19import re 20import random 21import colorsys 22import functools 23from operator import itemgetter 24 25class RenderOptions: 26 27 def __init__(self, app_options): 28 # should we render a cumulative CPU time chart 29 self.cumulative = True 30 self.charts = True 31 self.kernel_only = False 32 self.app_options = app_options 33 34 def proc_tree (self, trace): 35 if self.kernel_only: 36 return trace.kernel_tree 37 else: 38 return trace.proc_tree 39 40# Process tree background color. 41BACK_COLOR = (1.0, 1.0, 1.0, 1.0) 42 43WHITE = (1.0, 1.0, 1.0, 1.0) 44# Process tree border color. 45BORDER_COLOR = (0.63, 0.63, 0.63, 1.0) 46# Second tick line color. 47TICK_COLOR = (0.92, 0.92, 0.92, 1.0) 48# 5-second tick line color. 49TICK_COLOR_BOLD = (0.86, 0.86, 0.86, 1.0) 50# Annotation colour 51ANNOTATION_COLOR = (0.63, 0.0, 0.0, 0.5) 52# Text color. 53TEXT_COLOR = (0.0, 0.0, 0.0, 1.0) 54 55# Font family 56FONT_NAME = "Bitstream Vera Sans" 57# Title text font. 58TITLE_FONT_SIZE = 18 59# Default text font. 60TEXT_FONT_SIZE = 12 61# Axis label font. 62AXIS_FONT_SIZE = 11 63# Legend font. 64LEGEND_FONT_SIZE = 12 65 66# CPU load chart color. 67CPU_COLOR = (0.40, 0.55, 0.70, 1.0) 68# IO wait chart color. 69IO_COLOR = (0.76, 0.48, 0.48, 0.5) 70# Disk throughput color. 71DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) 72 73BYTES_RECEIVED_COLOR = (0.0, 0.0, 1.0, 1.0) 74BYTES_TRANSMITTED_COLOR = (1.0, 0.0, 0.0, 1.0) 75BYTES_RECEIVE_DIFF_COLOR = (0.0, 0.0, 1.0, 0.3) 76BYTES_TRANSMIT_DIFF_COLOR = (1.0, 0.0, 0.0, 0.3) 77# CPU load chart color. 78FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) 79# Mem cached color 80MEM_CACHED_COLOR = CPU_COLOR 81# Mem used color 82MEM_USED_COLOR = IO_COLOR 83# Buffers color 84MEM_BUFFERS_COLOR = (0.4, 0.4, 0.4, 0.3) 85# Swap color 86MEM_SWAP_COLOR = DISK_TPUT_COLOR 87 88# avg10 CPU pressure color 89CPU_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0) 90# delta total CPU pressure color 91CPU_PRESSURE_TOTAL_COLOR = CPU_COLOR 92# avg10 IO pressure color 93IO_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0) 94# delta total IO pressure color 95IO_PRESSURE_TOTAL_COLOR = IO_COLOR 96# avg10 memory pressure color 97MEM_PRESSURE_AVG10_COLOR = (0.0, 0.0, 0.0, 1.0) 98# delta total memory pressure color 99MEM_PRESSURE_TOTAL_COLOR = DISK_TPUT_COLOR 100 101 102 103 104# Process border color. 105PROC_BORDER_COLOR = (0.71, 0.71, 0.71, 1.0) 106# Waiting process color. 107PROC_COLOR_D = (0.76, 0.48, 0.48, 0.5) 108# Running process color. 109PROC_COLOR_R = CPU_COLOR 110# Sleeping process color. 111PROC_COLOR_S = (0.94, 0.94, 0.94, 1.0) 112# Stopped process color. 113PROC_COLOR_T = (0.94, 0.50, 0.50, 1.0) 114# Zombie process color. 115PROC_COLOR_Z = (0.71, 0.71, 0.71, 1.0) 116# Dead process color. 117PROC_COLOR_X = (0.71, 0.71, 0.71, 0.125) 118# Paging process color. 119PROC_COLOR_W = (0.71, 0.71, 0.71, 0.125) 120 121# Process label color. 122PROC_TEXT_COLOR = (0.19, 0.19, 0.19, 1.0) 123# Process label font. 124PROC_TEXT_FONT_SIZE = 12 125 126# Signature color. 127SIG_COLOR = (0.0, 0.0, 0.0, 0.3125) 128# Signature font. 129SIG_FONT_SIZE = 14 130# Signature text. 131SIGNATURE = "http://github.com/mmeeks/bootchart" 132 133# Process dependency line color. 134DEP_COLOR = (0.75, 0.75, 0.75, 1.0) 135# Process dependency line stroke. 136DEP_STROKE = 1.0 137 138# Process description date format. 139DESC_TIME_FORMAT = "mm:ss.SSS" 140 141# Cumulative coloring bits 142HSV_MAX_MOD = 31 143HSV_STEP = 7 144 145# Configure task color 146TASK_COLOR_CONFIGURE = (1.0, 1.0, 0.00, 1.0) 147# Compile task color. 148TASK_COLOR_COMPILE = (0.0, 1.00, 0.00, 1.0) 149# Install task color 150TASK_COLOR_INSTALL = (1.0, 0.00, 1.00, 1.0) 151# Sysroot task color 152TASK_COLOR_SYSROOT = (0.0, 0.00, 1.00, 1.0) 153# Package task color 154TASK_COLOR_PACKAGE = (0.0, 1.00, 1.00, 1.0) 155# Package Write RPM/DEB/IPK task color 156TASK_COLOR_PACKAGE_WRITE = (0.0, 0.50, 0.50, 1.0) 157 158# Distinct colors used for different disk volumnes. 159# If we have more volumns, colors get re-used. 160VOLUME_COLORS = [ 161 (1.0, 1.0, 0.00, 1.0), 162 (0.0, 1.00, 0.00, 1.0), 163 (1.0, 0.00, 1.00, 1.0), 164 (0.0, 0.00, 1.00, 1.0), 165 (0.0, 1.00, 1.00, 1.0), 166] 167 168# Process states 169STATE_UNDEFINED = 0 170STATE_RUNNING = 1 171STATE_SLEEPING = 2 172STATE_WAITING = 3 173STATE_STOPPED = 4 174STATE_ZOMBIE = 5 175 176STATE_COLORS = [(0, 0, 0, 0), PROC_COLOR_R, PROC_COLOR_S, PROC_COLOR_D, \ 177 PROC_COLOR_T, PROC_COLOR_Z, PROC_COLOR_X, PROC_COLOR_W] 178 179# CumulativeStats Types 180STAT_TYPE_CPU = 0 181STAT_TYPE_IO = 1 182 183# Convert ps process state to an int 184def get_proc_state(flag): 185 return "RSDTZXW".find(flag) + 1 186 187def draw_text(ctx, text, color, x, y): 188 ctx.set_source_rgba(*color) 189 ctx.move_to(x, y) 190 ctx.show_text(text) 191 192def draw_fill_rect(ctx, color, rect): 193 ctx.set_source_rgba(*color) 194 ctx.rectangle(*rect) 195 ctx.fill() 196 197def draw_rect(ctx, color, rect): 198 ctx.set_source_rgba(*color) 199 ctx.rectangle(*rect) 200 ctx.stroke() 201 202def draw_legend_box(ctx, label, fill_color, x, y, s): 203 draw_fill_rect(ctx, fill_color, (x, y - s, s, s)) 204 draw_rect(ctx, PROC_BORDER_COLOR, (x, y - s, s, s)) 205 draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) 206 207def draw_legend_line(ctx, label, fill_color, x, y, s): 208 draw_fill_rect(ctx, fill_color, (x, y - s/2, s + 1, 3)) 209 ctx.arc(x + (s + 1)/2.0, y - (s - 3)/2.0, 2.5, 0, 2.0 * math.pi) 210 ctx.fill() 211 draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) 212 213def draw_label_in_box(ctx, color, label, x, y, w, maxx): 214 label_w = ctx.text_extents(label)[2] 215 label_x = x + w / 2 - label_w / 2 216 if label_w + 10 > w: 217 label_x = x + w + 5 218 if label_x + label_w > maxx: 219 label_x = x - label_w - 5 220 draw_text(ctx, label, color, label_x, y) 221 222def draw_sec_labels(ctx, options, rect, sec_w, nsecs): 223 ctx.set_font_size(AXIS_FONT_SIZE) 224 prev_x = 0 225 for i in range(0, rect[2] + 1, sec_w): 226 if ((i / sec_w) % nsecs == 0) : 227 if options.app_options.as_minutes : 228 label = "%.1f" % (i / sec_w / 60.0) 229 else : 230 label = "%d" % (i / sec_w) 231 label_w = ctx.text_extents(label)[2] 232 x = rect[0] + i - label_w/2 233 if x >= prev_x: 234 draw_text(ctx, label, TEXT_COLOR, x, rect[1] - 2) 235 prev_x = x + label_w 236 237def draw_box_ticks(ctx, rect, sec_w): 238 draw_rect(ctx, BORDER_COLOR, tuple(rect)) 239 240 ctx.set_line_cap(cairo.LINE_CAP_SQUARE) 241 242 for i in range(sec_w, rect[2] + 1, sec_w): 243 if ((i / sec_w) % 10 == 0) : 244 ctx.set_line_width(1.5) 245 elif sec_w < 5 : 246 continue 247 else : 248 ctx.set_line_width(1.0) 249 if ((i / sec_w) % 30 == 0) : 250 ctx.set_source_rgba(*TICK_COLOR_BOLD) 251 else : 252 ctx.set_source_rgba(*TICK_COLOR) 253 ctx.move_to(rect[0] + i, rect[1] + 1) 254 ctx.line_to(rect[0] + i, rect[1] + rect[3] - 1) 255 ctx.stroke() 256 ctx.set_line_width(1.0) 257 258 ctx.set_line_cap(cairo.LINE_CAP_BUTT) 259 260def draw_annotations(ctx, proc_tree, times, rect): 261 ctx.set_line_cap(cairo.LINE_CAP_SQUARE) 262 ctx.set_source_rgba(*ANNOTATION_COLOR) 263 ctx.set_dash([4, 4]) 264 265 for time in times: 266 if time is not None: 267 x = ((time - proc_tree.start_time) * rect[2] / proc_tree.duration) 268 269 ctx.move_to(rect[0] + x, rect[1] + 1) 270 ctx.line_to(rect[0] + x, rect[1] + rect[3] - 1) 271 ctx.stroke() 272 273 ctx.set_line_cap(cairo.LINE_CAP_BUTT) 274 ctx.set_dash([]) 275 276def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree, data_range): 277 ctx.set_line_width(0.5) 278 x_shift = proc_tree.start_time 279 280 def transform_point_coords(point, x_base, y_base, \ 281 xscale, yscale, x_trans, y_trans): 282 x = (point[0] - x_base) * xscale + x_trans 283 y = (point[1] - y_base) * -yscale + y_trans + chart_bounds[3] 284 return x, y 285 286 max_x = max (x for (x, y) in data) 287 max_y = max (y for (x, y) in data) 288 # avoid divide by zero 289 if max_y == 0: 290 max_y = 1.0 291 if (max_x - x_shift): 292 xscale = float (chart_bounds[2]) / (max_x - x_shift) 293 else: 294 xscale = float (chart_bounds[2]) 295 # If data_range is given, scale the chart so that the value range in 296 # data_range matches the chart bounds exactly. 297 # Otherwise, scale so that the actual data matches the chart bounds. 298 if data_range and (data_range[1] - data_range[0]): 299 yscale = float(chart_bounds[3]) / (data_range[1] - data_range[0]) 300 ybase = data_range[0] 301 else: 302 yscale = float(chart_bounds[3]) / max_y 303 ybase = 0 304 305 first = transform_point_coords (data[0], x_shift, ybase, xscale, yscale, \ 306 chart_bounds[0], chart_bounds[1]) 307 last = transform_point_coords (data[-1], x_shift, ybase, xscale, yscale, \ 308 chart_bounds[0], chart_bounds[1]) 309 310 ctx.set_source_rgba(*color) 311 ctx.move_to(*first) 312 for point in data: 313 x, y = transform_point_coords (point, x_shift, ybase, xscale, yscale, \ 314 chart_bounds[0], chart_bounds[1]) 315 ctx.line_to(x, y) 316 if fill: 317 ctx.stroke_preserve() 318 ctx.line_to(last[0], chart_bounds[1]+chart_bounds[3]) 319 ctx.line_to(first[0], chart_bounds[1]+chart_bounds[3]) 320 ctx.line_to(first[0], first[1]) 321 ctx.fill() 322 else: 323 ctx.stroke() 324 ctx.set_line_width(1.0) 325 326bar_h = 55 327meminfo_bar_h = 2 * bar_h 328header_h = 60 329# offsets 330off_x, off_y = 220, 10 331sec_w_base = 1 # the width of a second 332proc_h = 16 # the height of a process 333leg_s = 10 334MIN_IMG_W = 800 335CUML_HEIGHT = 2000 # Increased value to accommodate CPU and I/O Graphs 336OPTIONS = None 337 338def extents(options, xscale, trace): 339 start = min(trace.start.keys()) 340 end = start 341 342 processes = 0 343 for proc in trace.processes: 344 if not options.app_options.show_all and \ 345 trace.processes[proc][1] - trace.processes[proc][0] < options.app_options.mintime: 346 continue 347 348 if trace.processes[proc][1] > end: 349 end = trace.processes[proc][1] 350 processes += 1 351 352 if trace.min is not None and trace.max is not None: 353 start = trace.min 354 end = trace.max 355 356 w = int ((end - start) * sec_w_base * xscale) + 2 * off_x 357 h = proc_h * processes + header_h + 2 * off_y 358 359 if options.charts: 360 if trace.cpu_stats: 361 h += 30 + bar_h 362 if trace.disk_stats: 363 h += 30 + bar_h 364 if trace.cpu_pressure: 365 h += 30 + bar_h 366 if trace.io_pressure: 367 h += 30 + bar_h 368 if trace.mem_pressure: 369 h += 30 + bar_h 370 if trace.monitor_disk: 371 h += 30 + bar_h 372 if trace.mem_stats: 373 h += meminfo_bar_h 374 375 # Allow for width of process legend and offset 376 if w < (720 + off_x): 377 w = 720 + off_x 378 379 return (w, h) 380 381def clip_visible(clip, rect): 382 xmax = max (clip[0], rect[0]) 383 ymax = max (clip[1], rect[1]) 384 xmin = min (clip[0] + clip[2], rect[0] + rect[2]) 385 ymin = min (clip[1] + clip[3], rect[1] + rect[3]) 386 return (xmin > xmax and ymin > ymax) 387 388def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): 389 proc_tree = options.proc_tree(trace) 390 391 # render bar legend 392 if trace.cpu_stats: 393 ctx.set_font_size(LEGEND_FONT_SIZE) 394 395 draw_legend_box(ctx, "CPU (user+sys)", CPU_COLOR, off_x, curr_y+20, leg_s) 396 draw_legend_box(ctx, "I/O (wait)", IO_COLOR, off_x + 120, curr_y+20, leg_s) 397 398 # render I/O wait 399 chart_rect = (off_x, curr_y+30, w, bar_h) 400 if clip_visible (clip, chart_rect): 401 draw_box_ticks (ctx, chart_rect, sec_w) 402 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 403 draw_chart (ctx, IO_COLOR, True, chart_rect, \ 404 [(sample.time, sample.user + sample.sys + sample.io) for sample in trace.cpu_stats], \ 405 proc_tree, None) 406 # render CPU load 407 draw_chart (ctx, CPU_COLOR, True, chart_rect, \ 408 [(sample.time, sample.user + sample.sys) for sample in trace.cpu_stats], \ 409 proc_tree, None) 410 411 curr_y = curr_y + 30 + bar_h 412 413 # render second chart 414 if trace.disk_stats: 415 draw_legend_line(ctx, "Disk throughput", DISK_TPUT_COLOR, off_x, curr_y+20, leg_s) 416 draw_legend_box(ctx, "Disk utilization", IO_COLOR, off_x + 120, curr_y+20, leg_s) 417 418 # render I/O utilization 419 chart_rect = (off_x, curr_y+30, w, bar_h) 420 if clip_visible (clip, chart_rect): 421 draw_box_ticks (ctx, chart_rect, sec_w) 422 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 423 draw_chart (ctx, IO_COLOR, True, chart_rect, \ 424 [(sample.time, sample.util) for sample in trace.disk_stats], \ 425 proc_tree, None) 426 427 # render disk throughput 428 max_sample = max (trace.disk_stats, key = lambda s: s.tput) 429 if clip_visible (clip, chart_rect): 430 draw_chart (ctx, DISK_TPUT_COLOR, False, chart_rect, \ 431 [(sample.time, sample.tput) for sample in trace.disk_stats], \ 432 proc_tree, None) 433 434 pos_x = off_x + ((max_sample.time - proc_tree.start_time) * w / proc_tree.duration) 435 436 shift_x, shift_y = -20, 20 437 if (pos_x < off_x + 245): 438 shift_x, shift_y = 5, 40 439 440 label = "%dMB/s" % round ((max_sample.tput) / 1024.0) 441 draw_text (ctx, label, DISK_TPUT_COLOR, pos_x + shift_x, curr_y + shift_y) 442 443 curr_y = curr_y + 30 + bar_h 444 445 if trace.net_stats: 446 for iface, samples in trace.net_stats.items(): 447 max_received_sample = max(samples, key=lambda s: s.received_bytes) 448 max_transmitted_sample = max(samples, key=lambda s: s.transmitted_bytes) 449 max_receive_diff_sample = max(samples, key=lambda s: s.receive_diff) 450 max_transmit_diff_sample = max(samples, key=lambda s: s.transmit_diff) 451 452 draw_text(ctx, "Iface: %s" % (iface), TEXT_COLOR, off_x, curr_y+20) 453 draw_legend_line(ctx, "Bytes received (max %d)" % (max_received_sample.received_bytes), 454 BYTES_RECEIVED_COLOR, off_x+150, curr_y+20, leg_s) 455 draw_legend_line(ctx, "Bytes transmitted (max %d)" % (max_transmitted_sample.transmitted_bytes), 456 BYTES_TRANSMITTED_COLOR, off_x+400, curr_y+20, leg_s) 457 draw_legend_box(ctx, "Bytes receive diff (max %d)" % (max_receive_diff_sample.receive_diff), 458 BYTES_RECEIVE_DIFF_COLOR, off_x+650, curr_y+20, leg_s) 459 draw_legend_box(ctx, "Bytes transmit diff (max %d)" % (max_transmit_diff_sample.transmit_diff), 460 BYTES_TRANSMIT_DIFF_COLOR, off_x+900, curr_y+20, leg_s) 461 462 463 chart_rect = (off_x, curr_y + 30, w, bar_h) 464 if clip_visible(clip, chart_rect): 465 draw_box_ticks(ctx, chart_rect, sec_w) 466 draw_annotations(ctx, proc_tree, trace.times, chart_rect) 467 468 if clip_visible (clip, chart_rect): 469 draw_chart (ctx, BYTES_RECEIVED_COLOR, False, chart_rect, \ 470 [(sample.time, sample.received_bytes) for sample in samples], \ 471 proc_tree, None) 472 473 draw_chart (ctx, BYTES_TRANSMITTED_COLOR, False, chart_rect, \ 474 [(sample.time, sample.transmitted_bytes) for sample in samples], \ 475 proc_tree, None) 476 477 if clip_visible (clip, chart_rect): 478 draw_chart (ctx, BYTES_RECEIVE_DIFF_COLOR, True, chart_rect, \ 479 [(sample.time, sample.receive_diff) for sample in samples], \ 480 proc_tree, None) 481 482 draw_chart (ctx, BYTES_TRANSMIT_DIFF_COLOR, True, chart_rect, \ 483 [(sample.time, sample.transmit_diff) for sample in samples], \ 484 proc_tree, None) 485 486 curr_y = curr_y + 30 + bar_h 487 488 # render CPU pressure chart 489 if trace.cpu_pressure: 490 max_sample_avg = max (trace.cpu_pressure, key = lambda s: s.avg10) 491 max_sample_total = max (trace.cpu_pressure, key = lambda s: s.deltaTotal) 492 draw_legend_line(ctx, "avg10 CPU Pressure (max %d%%)" % (max_sample_avg.avg10), CPU_PRESSURE_AVG10_COLOR, off_x, curr_y+20, leg_s) 493 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) 494 495 # render delta total cpu 496 chart_rect = (off_x, curr_y+30, w, bar_h) 497 if clip_visible (clip, chart_rect): 498 draw_box_ticks (ctx, chart_rect, sec_w) 499 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 500 draw_chart (ctx, CPU_PRESSURE_TOTAL_COLOR, True, chart_rect, \ 501 [(sample.time, sample.deltaTotal) for sample in trace.cpu_pressure], \ 502 proc_tree, None) 503 504 # render avg10 cpu 505 if clip_visible (clip, chart_rect): 506 draw_chart (ctx, CPU_PRESSURE_AVG10_COLOR, False, chart_rect, \ 507 [(sample.time, sample.avg10) for sample in trace.cpu_pressure], \ 508 proc_tree, None) 509 510 pos_x = off_x + ((max_sample_avg.time - proc_tree.start_time) * w / proc_tree.duration) 511 512 shift_x, shift_y = -20, 20 513 if (pos_x < off_x + 245): 514 shift_x, shift_y = 5, 40 515 516 517 label = "%d%%" % (max_sample_avg.avg10) 518 draw_text (ctx, label, CPU_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y) 519 520 curr_y = curr_y + 30 + bar_h 521 522 # render I/O pressure chart 523 if trace.io_pressure: 524 max_sample_avg = max (trace.io_pressure, key = lambda s: s.avg10) 525 max_sample_total = max (trace.io_pressure, key = lambda s: s.deltaTotal) 526 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) 527 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) 528 529 # render delta total io 530 chart_rect = (off_x, curr_y+30, w, bar_h) 531 if clip_visible (clip, chart_rect): 532 draw_box_ticks (ctx, chart_rect, sec_w) 533 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 534 draw_chart (ctx, IO_PRESSURE_TOTAL_COLOR, True, chart_rect, \ 535 [(sample.time, sample.deltaTotal) for sample in trace.io_pressure], \ 536 proc_tree, None) 537 538 # render avg10 io 539 if clip_visible (clip, chart_rect): 540 draw_chart (ctx, IO_PRESSURE_AVG10_COLOR, False, chart_rect, \ 541 [(sample.time, sample.avg10) for sample in trace.io_pressure], \ 542 proc_tree, None) 543 544 pos_x = off_x + ((max_sample_avg.time - proc_tree.start_time) * w / proc_tree.duration) 545 546 shift_x, shift_y = -20, 20 547 if (pos_x < off_x + 245): 548 shift_x, shift_y = 5, 40 549 550 551 label = "%d%%" % (max_sample_avg.avg10) 552 draw_text (ctx, label, IO_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y) 553 554 curr_y = curr_y + 30 + bar_h 555 556 # render MEM pressure chart 557 if trace.mem_pressure: 558 max_sample_avg = max (trace.mem_pressure, key = lambda s: s.avg10) 559 max_sample_total = max (trace.mem_pressure, key = lambda s: s.deltaTotal) 560 draw_legend_line(ctx, "avg10 MEM Pressure (max %d%%)" % (max_sample_avg.avg10), MEM_PRESSURE_AVG10_COLOR, off_x, curr_y+20, leg_s) 561 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) 562 563 # render delta total mem 564 chart_rect = (off_x, curr_y+30, w, bar_h) 565 if clip_visible (clip, chart_rect): 566 draw_box_ticks (ctx, chart_rect, sec_w) 567 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 568 draw_chart (ctx, MEM_PRESSURE_TOTAL_COLOR, True, chart_rect, \ 569 [(sample.time, sample.deltaTotal) for sample in trace.mem_pressure], \ 570 proc_tree, None) 571 572 # render avg10 mem 573 if clip_visible (clip, chart_rect): 574 draw_chart (ctx, MEM_PRESSURE_AVG10_COLOR, False, chart_rect, \ 575 [(sample.time, sample.avg10) for sample in trace.mem_pressure], \ 576 proc_tree, None) 577 578 pos_x = off_x + ((max_sample_avg.time - proc_tree.start_time) * w / proc_tree.duration) 579 580 shift_x, shift_y = -20, 20 581 if (pos_x < off_x + 245): 582 shift_x, shift_y = 5, 40 583 584 585 label = "%d%%" % (max_sample_avg.avg10) 586 draw_text (ctx, label, MEM_PRESSURE_AVG10_COLOR, pos_x + shift_x, curr_y + shift_y) 587 588 curr_y = curr_y + 30 + bar_h 589 590 # render disk space usage 591 # 592 # Draws the amount of disk space used on each volume relative to the 593 # lowest recorded amount. The graphs for each volume are stacked above 594 # each other so that total disk usage is visible. 595 if trace.monitor_disk: 596 ctx.set_font_size(LEGEND_FONT_SIZE) 597 # Determine set of volumes for which we have 598 # information and the minimal amount of used disk 599 # space for each. Currently samples are allowed to 600 # not have a values for all volumes; drawing could be 601 # made more efficient if that wasn't the case. 602 volumes = set() 603 min_used = {} 604 for sample in trace.monitor_disk: 605 for volume, used in sample.records.items(): 606 volumes.add(volume) 607 if volume not in min_used or min_used[volume] > used: 608 min_used[volume] = used 609 volumes = sorted(list(volumes)) 610 disk_scale = 0 611 for i, volume in enumerate(volumes): 612 volume_scale = max([sample.records[volume] - min_used[volume] 613 for sample in trace.monitor_disk 614 if volume in sample.records]) 615 # Does not take length of volume name into account, but fixed offset 616 # works okay in practice. 617 draw_legend_box(ctx, '%s (max: %u MiB)' % (volume, volume_scale / 1024 / 1024), 618 VOLUME_COLORS[i % len(VOLUME_COLORS)], 619 off_x + i * 250, curr_y+20, leg_s) 620 disk_scale += volume_scale 621 622 # render used amount of disk space 623 chart_rect = (off_x, curr_y+30, w, bar_h) 624 if clip_visible (clip, chart_rect): 625 draw_box_ticks (ctx, chart_rect, sec_w) 626 draw_annotations (ctx, proc_tree, trace.times, chart_rect) 627 for i in range(len(volumes), 0, -1): 628 draw_chart (ctx, VOLUME_COLORS[(i - 1) % len(VOLUME_COLORS)], True, chart_rect, \ 629 [(sample.time, 630 # Sum up used space of all volumes including the current one 631 # so that the graphs appear as stacked on top of each other. 632 functools.reduce(lambda x,y: x+y, 633 [sample.records[volume] - min_used[volume] 634 for volume in volumes[0:i] 635 if volume in sample.records], 636 0)) 637 for sample in trace.monitor_disk], \ 638 proc_tree, [0, disk_scale]) 639 640 curr_y = curr_y + 30 + bar_h 641 642 # render mem usage 643 chart_rect = (off_x, curr_y+30, w, meminfo_bar_h) 644 mem_stats = trace.mem_stats 645 if mem_stats and clip_visible (clip, chart_rect): 646 mem_scale = max(sample.buffers for sample in mem_stats) 647 draw_legend_box(ctx, "Mem cached (scale: %u MiB)" % (float(mem_scale) / 1024), MEM_CACHED_COLOR, off_x, curr_y+20, leg_s) 648 draw_legend_box(ctx, "Used", MEM_USED_COLOR, off_x + 240, curr_y+20, leg_s) 649 draw_legend_box(ctx, "Buffers", MEM_BUFFERS_COLOR, off_x + 360, curr_y+20, leg_s) 650 draw_legend_line(ctx, "Swap (scale: %u MiB)" % max([(sample.swap)/1024 for sample in mem_stats]), \ 651 MEM_SWAP_COLOR, off_x + 480, curr_y+20, leg_s) 652 draw_box_ticks(ctx, chart_rect, sec_w) 653 draw_annotations(ctx, proc_tree, trace.times, chart_rect) 654 draw_chart(ctx, MEM_BUFFERS_COLOR, True, chart_rect, \ 655 [(sample.time, sample.buffers) for sample in trace.mem_stats], \ 656 proc_tree, [0, mem_scale]) 657 draw_chart(ctx, MEM_USED_COLOR, True, chart_rect, \ 658 [(sample.time, sample.used) for sample in mem_stats], \ 659 proc_tree, [0, mem_scale]) 660 draw_chart(ctx, MEM_CACHED_COLOR, True, chart_rect, \ 661 [(sample.time, sample.cached) for sample in mem_stats], \ 662 proc_tree, [0, mem_scale]) 663 draw_chart(ctx, MEM_SWAP_COLOR, False, chart_rect, \ 664 [(sample.time, float(sample.swap)) for sample in mem_stats], \ 665 proc_tree, None) 666 667 curr_y = curr_y + meminfo_bar_h 668 669 return curr_y 670 671def render_processes_chart(ctx, options, trace, curr_y, width, h, sec_w): 672 chart_rect = [off_x, curr_y+header_h, width, h - curr_y - 1 * off_y - header_h ] 673 674 draw_legend_box (ctx, "Configure", \ 675 TASK_COLOR_CONFIGURE, off_x , curr_y + 45, leg_s) 676 draw_legend_box (ctx, "Compile", \ 677 TASK_COLOR_COMPILE, off_x+120, curr_y + 45, leg_s) 678 draw_legend_box (ctx, "Install", \ 679 TASK_COLOR_INSTALL, off_x+240, curr_y + 45, leg_s) 680 draw_legend_box (ctx, "Populate Sysroot", \ 681 TASK_COLOR_SYSROOT, off_x+360, curr_y + 45, leg_s) 682 draw_legend_box (ctx, "Package", \ 683 TASK_COLOR_PACKAGE, off_x+480, curr_y + 45, leg_s) 684 draw_legend_box (ctx, "Package Write", \ 685 TASK_COLOR_PACKAGE_WRITE, off_x+600, curr_y + 45, leg_s) 686 687 ctx.set_font_size(PROC_TEXT_FONT_SIZE) 688 689 draw_box_ticks(ctx, chart_rect, sec_w) 690 draw_sec_labels(ctx, options, chart_rect, sec_w, 30) 691 692 y = curr_y+header_h 693 694 offset = trace.min or min(trace.start.keys()) 695 for start in sorted(trace.start.keys()): 696 for process in sorted(trace.start[start]): 697 elapsed_time = trace.processes[process][1] - start 698 if not options.app_options.show_all and \ 699 elapsed_time < options.app_options.mintime: 700 continue 701 task = process.split(":")[1] 702 703 #print(process) 704 #print(trace.processes[process][1]) 705 #print(s) 706 707 x = chart_rect[0] + (start - offset) * sec_w 708 w = elapsed_time * sec_w 709 710 def set_alfa(color, alfa): 711 clist = list(color) 712 clist[-1] = alfa 713 return tuple(clist) 714 715 #print("proc at %s %s %s %s" % (x, y, w, proc_h)) 716 col = None 717 if task == "do_compile": 718 col = TASK_COLOR_COMPILE 719 elif "do_compile" in task: 720 col = set_alfa(TASK_COLOR_COMPILE, 0.25) 721 elif task == "do_configure": 722 col = TASK_COLOR_CONFIGURE 723 elif "do_configure" in task: 724 col = set_alfa(TASK_COLOR_CONFIGURE, 0.25) 725 elif task == "do_install": 726 col = TASK_COLOR_INSTALL 727 elif task == "do_populate_sysroot": 728 col = TASK_COLOR_SYSROOT 729 elif task == "do_package": 730 col = TASK_COLOR_PACKAGE 731 elif task == "do_package_write_rpm" or \ 732 task == "do_package_write_deb" or \ 733 task == "do_package_write_ipk": 734 col = TASK_COLOR_PACKAGE_WRITE 735 else: 736 col = WHITE 737 738 if col: 739 draw_fill_rect(ctx, col, (x, y, w, proc_h)) 740 draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h)) 741 742 # Show elapsed time for each task 743 process = "%ds %s" % (elapsed_time, process) 744 draw_label_in_box(ctx, PROC_TEXT_COLOR, process, x, y + proc_h - 4, w, width) 745 746 y = y + proc_h 747 748 return curr_y 749 750# 751# Render the chart. 752# 753def render(ctx, options, xscale, trace): 754 (w, h) = extents (options, xscale, trace) 755 global OPTIONS 756 OPTIONS = options.app_options 757 758 # x, y, w, h 759 clip = ctx.clip_extents() 760 761 sec_w = int (xscale * sec_w_base) 762 ctx.set_line_width(1.0) 763 ctx.select_font_face(FONT_NAME) 764 draw_fill_rect(ctx, WHITE, (0, 0, max(w, MIN_IMG_W), h)) 765 w -= 2*off_x 766 curr_y = off_y; 767 768 if options.charts: 769 curr_y = render_charts (ctx, options, clip, trace, curr_y, w, h, sec_w) 770 771 curr_y = render_processes_chart (ctx, options, trace, curr_y, w, h, sec_w) 772 773 return 774 775 proc_tree = options.proc_tree (trace) 776 777 # draw the title and headers 778 if proc_tree.idle: 779 duration = proc_tree.idle 780 else: 781 duration = proc_tree.duration 782 783 if not options.kernel_only: 784 curr_y = draw_header (ctx, trace.headers, duration) 785 else: 786 curr_y = off_y; 787 788 # draw process boxes 789 proc_height = h 790 if proc_tree.taskstats and options.cumulative: 791 proc_height -= CUML_HEIGHT 792 793 draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times, 794 curr_y, w, proc_height, sec_w) 795 796 curr_y = proc_height 797 ctx.set_font_size(SIG_FONT_SIZE) 798 draw_text(ctx, SIGNATURE, SIG_COLOR, off_x + 5, proc_height - 8) 799 800 # draw a cumulative CPU-time-per-process graph 801 if proc_tree.taskstats and options.cumulative: 802 cuml_rect = (off_x, curr_y + off_y, w, CUML_HEIGHT/2 - off_y * 2) 803 if clip_visible (clip, cuml_rect): 804 draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_CPU) 805 806 # draw a cumulative I/O-time-per-process graph 807 if proc_tree.taskstats and options.cumulative: 808 cuml_rect = (off_x, curr_y + off_y * 100, w, CUML_HEIGHT/2 - off_y * 2) 809 if clip_visible (clip, cuml_rect): 810 draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_IO) 811 812def draw_process_bar_chart(ctx, clip, options, proc_tree, times, curr_y, w, h, sec_w): 813 header_size = 0 814 if not options.kernel_only: 815 draw_legend_box (ctx, "Running (%cpu)", 816 PROC_COLOR_R, off_x , curr_y + 45, leg_s) 817 draw_legend_box (ctx, "Unint.sleep (I/O)", 818 PROC_COLOR_D, off_x+120, curr_y + 45, leg_s) 819 draw_legend_box (ctx, "Sleeping", 820 PROC_COLOR_S, off_x+240, curr_y + 45, leg_s) 821 draw_legend_box (ctx, "Zombie", 822 PROC_COLOR_Z, off_x+360, curr_y + 45, leg_s) 823 header_size = 45 824 825 chart_rect = [off_x, curr_y + header_size + 15, 826 w, h - 2 * off_y - (curr_y + header_size + 15) + proc_h] 827 ctx.set_font_size (PROC_TEXT_FONT_SIZE) 828 829 draw_box_ticks (ctx, chart_rect, sec_w) 830 if sec_w > 100: 831 nsec = 1 832 else: 833 nsec = 5 834 draw_sec_labels (ctx, options, chart_rect, sec_w, nsec) 835 draw_annotations (ctx, proc_tree, times, chart_rect) 836 837 y = curr_y + 60 838 for root in proc_tree.process_tree: 839 draw_processes_recursively(ctx, root, proc_tree, y, proc_h, chart_rect, clip) 840 y = y + proc_h * proc_tree.num_nodes([root]) 841 842 843def draw_header (ctx, headers, duration): 844 toshow = [ 845 ('system.uname', 'uname', lambda s: s), 846 ('system.release', 'release', lambda s: s), 847 ('system.cpu', 'CPU', lambda s: re.sub('model name\s*:\s*', '', s, 1)), 848 ('system.kernel.options', 'kernel options', lambda s: s), 849 ] 850 851 header_y = ctx.font_extents()[2] + 10 852 ctx.set_font_size(TITLE_FONT_SIZE) 853 draw_text(ctx, headers['title'], TEXT_COLOR, off_x, header_y) 854 ctx.set_font_size(TEXT_FONT_SIZE) 855 856 for (headerkey, headertitle, mangle) in toshow: 857 header_y += ctx.font_extents()[2] 858 if headerkey in headers: 859 value = headers.get(headerkey) 860 else: 861 value = "" 862 txt = headertitle + ': ' + mangle(value) 863 draw_text(ctx, txt, TEXT_COLOR, off_x, header_y) 864 865 dur = duration / 100.0 866 txt = 'time : %02d:%05.2f' % (math.floor(dur/60), dur - 60 * math.floor(dur/60)) 867 if headers.get('system.maxpid') is not None: 868 txt = txt + ' max pid: %s' % (headers.get('system.maxpid')) 869 870 header_y += ctx.font_extents()[2] 871 draw_text (ctx, txt, TEXT_COLOR, off_x, header_y) 872 873 return header_y 874 875def draw_processes_recursively(ctx, proc, proc_tree, y, proc_h, rect, clip) : 876 x = rect[0] + ((proc.start_time - proc_tree.start_time) * rect[2] / proc_tree.duration) 877 w = ((proc.duration) * rect[2] / proc_tree.duration) 878 879 draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip) 880 draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h)) 881 ipid = int(proc.pid) 882 if not OPTIONS.show_all: 883 cmdString = proc.cmd 884 else: 885 cmdString = '' 886 if (OPTIONS.show_pid or OPTIONS.show_all) and ipid != 0: 887 cmdString = cmdString + " [" + str(ipid // 1000) + "]" 888 if OPTIONS.show_all: 889 if proc.args: 890 cmdString = cmdString + " '" + "' '".join(proc.args) + "'" 891 else: 892 cmdString = cmdString + " " + proc.exe 893 894 draw_label_in_box(ctx, PROC_TEXT_COLOR, cmdString, x, y + proc_h - 4, w, rect[0] + rect[2]) 895 896 next_y = y + proc_h 897 for child in proc.child_list: 898 if next_y > clip[1] + clip[3]: 899 break 900 child_x, child_y = draw_processes_recursively(ctx, child, proc_tree, next_y, proc_h, rect, clip) 901 draw_process_connecting_lines(ctx, x, y, child_x, child_y, proc_h) 902 next_y = next_y + proc_h * proc_tree.num_nodes([child]) 903 904 return x, y 905 906 907def draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip): 908 909 if y > clip[1] + clip[3] or y + proc_h + 2 < clip[1]: 910 return 911 912 draw_fill_rect(ctx, PROC_COLOR_S, (x, y, w, proc_h)) 913 914 last_tx = -1 915 for sample in proc.samples : 916 tx = rect[0] + round(((sample.time - proc_tree.start_time) * rect[2] / proc_tree.duration)) 917 918 # samples are sorted chronologically 919 if tx < clip[0]: 920 continue 921 if tx > clip[0] + clip[2]: 922 break 923 924 tw = round(proc_tree.sample_period * rect[2] / float(proc_tree.duration)) 925 if last_tx != -1 and abs(last_tx - tx) <= tw: 926 tw -= last_tx - tx 927 tx = last_tx 928 tw = max (tw, 1) # nice to see at least something 929 930 last_tx = tx + tw 931 state = get_proc_state( sample.state ) 932 933 color = STATE_COLORS[state] 934 if state == STATE_RUNNING: 935 alpha = min (sample.cpu_sample.user + sample.cpu_sample.sys, 1.0) 936 color = tuple(list(PROC_COLOR_R[0:3]) + [alpha]) 937# print "render time %d [ tx %d tw %d ], sample state %s color %s alpha %g" % (sample.time, tx, tw, state, color, alpha) 938 elif state == STATE_SLEEPING: 939 continue 940 941 draw_fill_rect(ctx, color, (tx, y, tw, proc_h)) 942 943def draw_process_connecting_lines(ctx, px, py, x, y, proc_h): 944 ctx.set_source_rgba(*DEP_COLOR) 945 ctx.set_dash([2, 2]) 946 if abs(px - x) < 3: 947 dep_off_x = 3 948 dep_off_y = proc_h / 4 949 ctx.move_to(x, y + proc_h / 2) 950 ctx.line_to(px - dep_off_x, y + proc_h / 2) 951 ctx.line_to(px - dep_off_x, py - dep_off_y) 952 ctx.line_to(px, py - dep_off_y) 953 else: 954 ctx.move_to(x, y + proc_h / 2) 955 ctx.line_to(px, y + proc_h / 2) 956 ctx.line_to(px, py) 957 ctx.stroke() 958 ctx.set_dash([]) 959 960# elide the bootchart collector - it is quite distorting 961def elide_bootchart(proc): 962 return proc.cmd == 'bootchartd' or proc.cmd == 'bootchart-colle' 963 964class CumlSample: 965 def __init__(self, proc): 966 self.cmd = proc.cmd 967 self.samples = [] 968 self.merge_samples (proc) 969 self.color = None 970 971 def merge_samples(self, proc): 972 self.samples.extend (proc.samples) 973 self.samples.sort (key = lambda p: p.time) 974 975 def next(self): 976 global palette_idx 977 palette_idx += HSV_STEP 978 return palette_idx 979 980 def get_color(self): 981 if self.color is None: 982 i = self.next() % HSV_MAX_MOD 983 h = 0.0 984 if i != 0: 985 h = (1.0 * i) / HSV_MAX_MOD 986 s = 0.5 987 v = 1.0 988 c = colorsys.hsv_to_rgb (h, s, v) 989 self.color = (c[0], c[1], c[2], 1.0) 990 return self.color 991 992 993def draw_cuml_graph(ctx, proc_tree, chart_bounds, duration, sec_w, stat_type): 994 global palette_idx 995 palette_idx = 0 996 997 time_hash = {} 998 total_time = 0.0 999 m_proc_list = {} 1000 1001 if stat_type is STAT_TYPE_CPU: 1002 sample_value = 'cpu' 1003 else: 1004 sample_value = 'io' 1005 for proc in proc_tree.process_list: 1006 if elide_bootchart(proc): 1007 continue 1008 1009 for sample in proc.samples: 1010 total_time += getattr(sample.cpu_sample, sample_value) 1011 if not sample.time in time_hash: 1012 time_hash[sample.time] = 1 1013 1014 # merge pids with the same cmd 1015 if not proc.cmd in m_proc_list: 1016 m_proc_list[proc.cmd] = CumlSample (proc) 1017 continue 1018 s = m_proc_list[proc.cmd] 1019 s.merge_samples (proc) 1020 1021 # all the sample times 1022 times = sorted(time_hash) 1023 if len (times) < 2: 1024 print("degenerate boot chart") 1025 return 1026 1027 pix_per_ns = chart_bounds[3] / total_time 1028# print "total time: %g pix-per-ns %g" % (total_time, pix_per_ns) 1029 1030 # FIXME: we have duplicates in the process list too [!] - why !? 1031 1032 # Render bottom up, left to right 1033 below = {} 1034 for time in times: 1035 below[time] = chart_bounds[1] + chart_bounds[3] 1036 1037 # same colors each time we render 1038 random.seed (0) 1039 1040 ctx.set_line_width(1) 1041 1042 legends = [] 1043 labels = [] 1044 1045 # render each pid in order 1046 for cs in m_proc_list.values(): 1047 row = {} 1048 cuml = 0.0 1049 1050 # print "pid : %s -> %g samples %d" % (proc.cmd, cuml, len (cs.samples)) 1051 for sample in cs.samples: 1052 cuml += getattr(sample.cpu_sample, sample_value) 1053 row[sample.time] = cuml 1054 1055 process_total_time = cuml 1056 1057 # hide really tiny processes 1058 if cuml * pix_per_ns <= 2: 1059 continue 1060 1061 last_time = times[0] 1062 y = last_below = below[last_time] 1063 last_cuml = cuml = 0.0 1064 1065 ctx.set_source_rgba(*cs.get_color()) 1066 for time in times: 1067 render_seg = False 1068 1069 # did the underlying trend increase ? 1070 if below[time] != last_below: 1071 last_below = below[last_time] 1072 last_cuml = cuml 1073 render_seg = True 1074 1075 # did we move up a pixel increase ? 1076 if time in row: 1077 nc = round (row[time] * pix_per_ns) 1078 if nc != cuml: 1079 last_cuml = cuml 1080 cuml = nc 1081 render_seg = True 1082 1083# if last_cuml > cuml: 1084# assert fail ... - un-sorted process samples 1085 1086 # draw the trailing rectangle from the last time to 1087 # before now, at the height of the last segment. 1088 if render_seg: 1089 w = math.ceil ((time - last_time) * chart_bounds[2] / proc_tree.duration) + 1 1090 x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) 1091 ctx.rectangle (x, below[last_time] - last_cuml, w, last_cuml) 1092 ctx.fill() 1093# ctx.stroke() 1094 last_time = time 1095 y = below [time] - cuml 1096 1097 row[time] = y 1098 1099 # render the last segment 1100 x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) 1101 y = below[last_time] - cuml 1102 ctx.rectangle (x, y, chart_bounds[2] - x, cuml) 1103 ctx.fill() 1104# ctx.stroke() 1105 1106 # render legend if it will fit 1107 if cuml > 8: 1108 label = cs.cmd 1109 extnts = ctx.text_extents(label) 1110 label_w = extnts[2] 1111 label_h = extnts[3] 1112# print "Text extents %g by %g" % (label_w, label_h) 1113 labels.append((label, 1114 chart_bounds[0] + chart_bounds[2] - label_w - off_x * 2, 1115 y + (cuml + label_h) / 2)) 1116 if cs in legends: 1117 print("ARGH - duplicate process in list !") 1118 1119 legends.append ((cs, process_total_time)) 1120 1121 below = row 1122 1123 # render grid-lines over the top 1124 draw_box_ticks(ctx, chart_bounds, sec_w) 1125 1126 # render labels 1127 for l in labels: 1128 draw_text(ctx, l[0], TEXT_COLOR, l[1], l[2]) 1129 1130 # Render legends 1131 font_height = 20 1132 label_width = 300 1133 LEGENDS_PER_COL = 15 1134 LEGENDS_TOTAL = 45 1135 ctx.set_font_size (TITLE_FONT_SIZE) 1136 dur_secs = duration / 100 1137 cpu_secs = total_time / 1000000000 1138 1139 # misleading - with multiple CPUs ... 1140# idle = ((dur_secs - cpu_secs) / dur_secs) * 100.0 1141 if stat_type is STAT_TYPE_CPU: 1142 label = "Cumulative CPU usage, by process; total CPU: " \ 1143 " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) 1144 else: 1145 label = "Cumulative I/O usage, by process; total I/O: " \ 1146 " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) 1147 1148 draw_text(ctx, label, TEXT_COLOR, chart_bounds[0] + off_x, 1149 chart_bounds[1] + font_height) 1150 1151 i = 0 1152 legends = sorted(legends, key=itemgetter(1), reverse=True) 1153 ctx.set_font_size(TEXT_FONT_SIZE) 1154 for t in legends: 1155 cs = t[0] 1156 time = t[1] 1157 x = chart_bounds[0] + off_x + int (i/LEGENDS_PER_COL) * label_width 1158 y = chart_bounds[1] + font_height * ((i % LEGENDS_PER_COL) + 2) 1159 str = "%s - %.0f(ms) (%2.2f%%)" % (cs.cmd, time/1000000, (time/total_time) * 100.0) 1160 draw_legend_box(ctx, str, cs.color, x, y, leg_s) 1161 i = i + 1 1162 if i >= LEGENDS_TOTAL: 1163 break 1164