summaryrefslogtreecommitdiffstats
path: root/scripts/pybootchartgui
diff options
context:
space:
mode:
authorPatrick Ohly <patrick.ohly@intel.com>2016-11-30 10:50:08 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-12-07 10:37:59 +0000
commit6b5037bf2cbf274ed3e8a7aee3fe9a1b2e039e84 (patch)
tree22f8ad9efa112da27d0e36a5452e36e9187704e8 /scripts/pybootchartgui
parent0cd48fcef4264f338e201079e68ddb2ed8d3d68a (diff)
downloadpoky-6b5037bf2cbf274ed3e8a7aee3fe9a1b2e039e84.tar.gz
pybootchartgui: render disk space usage
This adds a new, separate chart showing the amount of disk space used over time for each volume monitored during the build. The hight of the graph entries represents the delta between current usage and minimal usage during the build. That's more useful than showing just the current usage, because then a graph showing changes in the order of MBs in a volume that is several GB large would be just flat. The legend shows the maximum of those deltas, i.e. maximum amount of space needed for the build. Minor caveat: sampling of disk space usage starts a bit later than the initial task, so the displayed value may be slightly lower than the actual amount of space needed because sampling does not record the actual initial state. (From OE-Core rev: 263d189d066b578debf08b2bd07494a69b70f70d) Signed-off-by: Patrick Ohly <patrick.ohly@intel.com> Signed-off-by: Ross Burton <ross.burton@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts/pybootchartgui')
-rw-r--r--scripts/pybootchartgui/pybootchartgui/draw.py62
-rw-r--r--scripts/pybootchartgui/pybootchartgui/parsing.py26
-rw-r--r--scripts/pybootchartgui/pybootchartgui/samples.py11
3 files changed, 99 insertions, 0 deletions
diff --git a/scripts/pybootchartgui/pybootchartgui/draw.py b/scripts/pybootchartgui/pybootchartgui/draw.py
index ec5dd333a1..f0143ad0d6 100644
--- a/scripts/pybootchartgui/pybootchartgui/draw.py
+++ b/scripts/pybootchartgui/pybootchartgui/draw.py
@@ -133,6 +133,16 @@ TASK_COLOR_PACKAGE = (0.0, 1.00, 1.00, 1.0)
133# Package Write RPM/DEB/IPK task color 133# Package Write RPM/DEB/IPK task color
134TASK_COLOR_PACKAGE_WRITE = (0.0, 0.50, 0.50, 1.0) 134TASK_COLOR_PACKAGE_WRITE = (0.0, 0.50, 0.50, 1.0)
135 135
136# Distinct colors used for different disk volumnes.
137# If we have more volumns, colors get re-used.
138VOLUME_COLORS = [
139 (1.0, 1.0, 0.00, 1.0),
140 (0.0, 1.00, 0.00, 1.0),
141 (1.0, 0.00, 1.00, 1.0),
142 (0.0, 0.00, 1.00, 1.0),
143 (0.0, 1.00, 1.00, 1.0),
144]
145
136# Process states 146# Process states
137STATE_UNDEFINED = 0 147STATE_UNDEFINED = 0
138STATE_RUNNING = 1 148STATE_RUNNING = 1
@@ -397,6 +407,58 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w):
397 407
398 curr_y = curr_y + 30 + bar_h 408 curr_y = curr_y + 30 + bar_h
399 409
410 # render disk space usage
411 #
412 # Draws the amount of disk space used on each volume relative to the
413 # lowest recorded amount. The graphs for each volume are stacked above
414 # each other so that total disk usage is visible.
415 if trace.monitor_disk:
416 ctx.set_font_size(LEGEND_FONT_SIZE)
417 # Determine set of volumes for which we have
418 # information and the minimal amount of used disk
419 # space for each. Currently samples are allowed to
420 # not have a values for all volumes; drawing could be
421 # made more efficient if that wasn't the case.
422 volumes = set()
423 min_used = {}
424 for sample in trace.monitor_disk:
425 for volume, used in sample.records.items():
426 volumes.add(volume)
427 if volume not in min_used or min_used[volume] > used:
428 min_used[volume] = used
429 volumes = sorted(list(volumes))
430 disk_scale = 0
431 for i, volume in enumerate(volumes):
432 volume_scale = max([sample.records[volume] - min_used[volume]
433 for sample in trace.monitor_disk
434 if volume in sample.records])
435 # Does not take length of volume name into account, but fixed offset
436 # works okay in practice.
437 draw_legend_box(ctx, '%s (max: %u MiB)' % (volume, volume_scale / 1024 / 1024),
438 VOLUME_COLORS[i % len(VOLUME_COLORS)],
439 off_x + i * 250, curr_y+20, leg_s)
440 disk_scale += volume_scale
441
442 # render used amount of disk space
443 chart_rect = (off_x, curr_y+30, w, bar_h)
444 if clip_visible (clip, chart_rect):
445 draw_box_ticks (ctx, chart_rect, sec_w)
446 draw_annotations (ctx, proc_tree, trace.times, chart_rect)
447 for i in range(len(volumes), 0, -1):
448 draw_chart (ctx, VOLUME_COLORS[(i - 1) % len(VOLUME_COLORS)], True, chart_rect, \
449 [(sample.time,
450 # Sum up used space of all volumes including the current one
451 # so that the graphs appear as stacked on top of each other.
452 reduce(lambda x,y: x+y,
453 [sample.records[volume] - min_used[volume]
454 for volume in volumes[0:i]
455 if volume in sample.records],
456 0))
457 for sample in trace.monitor_disk], \
458 proc_tree, [0, disk_scale])
459
460 curr_y = curr_y + 30 + bar_h
461
400 # render mem usage 462 # render mem usage
401 chart_rect = (off_x, curr_y+30, w, meminfo_bar_h) 463 chart_rect = (off_x, curr_y+30, w, meminfo_bar_h)
402 mem_stats = trace.mem_stats 464 mem_stats = trace.mem_stats
diff --git a/scripts/pybootchartgui/pybootchartgui/parsing.py b/scripts/pybootchartgui/pybootchartgui/parsing.py
index 48eb048dae..301145ab67 100644
--- a/scripts/pybootchartgui/pybootchartgui/parsing.py
+++ b/scripts/pybootchartgui/pybootchartgui/parsing.py
@@ -48,6 +48,7 @@ class Trace:
48 self.filename = None 48 self.filename = None
49 self.parent_map = None 49 self.parent_map = None
50 self.mem_stats = [] 50 self.mem_stats = []
51 self.monitor_disk = None
51 self.times = [] # Always empty, but expected by draw.py when drawing system charts. 52 self.times = [] # Always empty, but expected by draw.py when drawing system charts.
52 53
53 if len(paths): 54 if len(paths):
@@ -506,6 +507,29 @@ def _parse_proc_meminfo_log(file):
506 507
507 return mem_stats 508 return mem_stats
508 509
510def _parse_monitor_disk_log(file):
511 """
512 Parse file with information about amount of diskspace used.
513 The format of relevant lines should be: ^volume path: number-of-bytes?
514 """
515 disk_stats = []
516 diskinfo_re = re.compile(r'^(.+):\s*(\d+)$')
517
518 for time, lines in _parse_timed_blocks(file):
519 sample = DiskSpaceSample(time)
520
521 for line in lines:
522 match = diskinfo_re.match(line)
523 if not match:
524 raise ParseError("Invalid monitor_disk line \"%s\"" % line)
525 sample.add_value(match.group(1), int(match.group(2)))
526
527 if sample.valid():
528 disk_stats.append(sample)
529
530 return disk_stats
531
532
509# if we boot the kernel with: initcall_debug printk.time=1 we can 533# if we boot the kernel with: initcall_debug printk.time=1 we can
510# get all manner of interesting data from the dmesg output 534# get all manner of interesting data from the dmesg output
511# We turn this into a pseudo-process tree: each event is 535# We turn this into a pseudo-process tree: each event is
@@ -684,6 +708,8 @@ def _do_parse(writer, state, filename, file):
684 state.mem_stats = _parse_proc_meminfo_log(file) 708 state.mem_stats = _parse_proc_meminfo_log(file)
685 elif name == "cmdline2.log": 709 elif name == "cmdline2.log":
686 state.cmdline = _parse_cmdline_log(writer, file) 710 state.cmdline = _parse_cmdline_log(writer, file)
711 elif name == "monitor_disk.log":
712 state.monitor_disk = _parse_monitor_disk_log(file)
687 elif not filename.endswith('.log'): 713 elif not filename.endswith('.log'):
688 _parse_bitbake_buildstats(writer, state, filename, file) 714 _parse_bitbake_buildstats(writer, state, filename, file)
689 t2 = clock() 715 t2 = clock()
diff --git a/scripts/pybootchartgui/pybootchartgui/samples.py b/scripts/pybootchartgui/pybootchartgui/samples.py
index 015d743aa0..bedca4165a 100644
--- a/scripts/pybootchartgui/pybootchartgui/samples.py
+++ b/scripts/pybootchartgui/pybootchartgui/samples.py
@@ -53,6 +53,17 @@ class MemSample:
53 # discard incomplete samples 53 # discard incomplete samples
54 return [v for v in MemSample.used_values if v not in keys] == [] 54 return [v for v in MemSample.used_values if v not in keys] == []
55 55
56class DiskSpaceSample:
57 def __init__(self, time):
58 self.time = time
59 self.records = {}
60
61 def add_value(self, name, value):
62 self.records[name] = value
63
64 def valid(self):
65 return bool(self.records)
66
56class ProcessSample: 67class ProcessSample:
57 def __init__(self, time, state, cpu_sample): 68 def __init__(self, time, state, cpu_sample):
58 self.time = time 69 self.time = time