diff options
author | Patrick Ohly <patrick.ohly@intel.com> | 2016-11-30 10:50:08 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-12-07 10:37:59 +0000 |
commit | 6b5037bf2cbf274ed3e8a7aee3fe9a1b2e039e84 (patch) | |
tree | 22f8ad9efa112da27d0e36a5452e36e9187704e8 /scripts/pybootchartgui | |
parent | 0cd48fcef4264f338e201079e68ddb2ed8d3d68a (diff) | |
download | poky-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.py | 62 | ||||
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/parsing.py | 26 | ||||
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/samples.py | 11 |
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 |
134 | TASK_COLOR_PACKAGE_WRITE = (0.0, 0.50, 0.50, 1.0) | 134 | TASK_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. | ||
138 | VOLUME_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 |
137 | STATE_UNDEFINED = 0 | 147 | STATE_UNDEFINED = 0 |
138 | STATE_RUNNING = 1 | 148 | STATE_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 | ||
510 | def _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 | ||
56 | class 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 | |||
56 | class ProcessSample: | 67 | class 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 |