diff options
author | Olga Denisova <denisova.olga.k@yandex.ru> | 2025-04-15 19:35:27 +0300 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2025-04-24 11:27:06 +0100 |
commit | f68e3e49d4f55e7c451450ffa3e33eb111ec5249 (patch) | |
tree | a397c3a1d3a1b06f8f4bce01258ea1f3d8e8cabb | |
parent | a0c83d90690763991d6445305e74a3994f1e47e8 (diff) | |
download | poky-f68e3e49d4f55e7c451450ffa3e33eb111ec5249.tar.gz |
pybootchartgui: visualize /proc/net/dev network stats in graphs
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>
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/draw.py | 48 | ||||
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/parsing.py | 18 | ||||
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/samples.py | 10 |
3 files changed, 76 insertions, 0 deletions
diff --git a/scripts/pybootchartgui/pybootchartgui/draw.py b/scripts/pybootchartgui/pybootchartgui/draw.py index c6e67833ab..16739a0fa1 100644 --- a/scripts/pybootchartgui/pybootchartgui/draw.py +++ b/scripts/pybootchartgui/pybootchartgui/draw.py | |||
@@ -69,6 +69,11 @@ CPU_COLOR = (0.40, 0.55, 0.70, 1.0) | |||
69 | IO_COLOR = (0.76, 0.48, 0.48, 0.5) | 69 | IO_COLOR = (0.76, 0.48, 0.48, 0.5) |
70 | # Disk throughput color. | 70 | # Disk throughput color. |
71 | DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) | 71 | DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) |
72 | |||
73 | BYTES_RECEIVED_COLOR = (0.0, 0.0, 1.0, 1.0) | ||
74 | BYTES_TRANSMITTED_COLOR = (1.0, 0.0, 0.0, 1.0) | ||
75 | BYTES_RECEIVE_DIFF_COLOR = (0.0, 0.0, 1.0, 0.3) | ||
76 | BYTES_TRANSMIT_DIFF_COLOR = (1.0, 0.0, 0.0, 0.3) | ||
72 | # CPU load chart color. | 77 | # CPU load chart color. |
73 | FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) | 78 | FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) |
74 | # Mem cached color | 79 | # Mem cached color |
@@ -437,6 +442,49 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): | |||
437 | 442 | ||
438 | curr_y = curr_y + 30 + bar_h | 443 | curr_y = curr_y + 30 + bar_h |
439 | 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 | |||
440 | # render CPU pressure chart | 488 | # render CPU pressure chart |
441 | if trace.cpu_pressure: | 489 | if trace.cpu_pressure: |
442 | max_sample_avg = max (trace.cpu_pressure, key = lambda s: s.avg10) | 490 | max_sample_avg = max (trace.cpu_pressure, key = lambda s: s.avg10) |
diff --git a/scripts/pybootchartgui/pybootchartgui/parsing.py b/scripts/pybootchartgui/pybootchartgui/parsing.py index 144a16c723..72a54c6ba5 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.net_stats = [] | ||
51 | self.monitor_disk = None | 52 | self.monitor_disk = None |
52 | self.cpu_pressure = [] | 53 | self.cpu_pressure = [] |
53 | self.io_pressure = [] | 54 | self.io_pressure = [] |
@@ -557,6 +558,21 @@ def _parse_monitor_disk_log(file): | |||
557 | 558 | ||
558 | return disk_stats | 559 | return disk_stats |
559 | 560 | ||
561 | |||
562 | def _parse_reduced_net_log(file): | ||
563 | net_stats = {} | ||
564 | for time, lines in _parse_timed_blocks(file): | ||
565 | |||
566 | for line in lines: | ||
567 | parts = line.split() | ||
568 | iface = parts[0][:-1] | ||
569 | if iface not in net_stats: | ||
570 | net_stats[iface] = [NetSample(time, iface, int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4]))] | ||
571 | else: | ||
572 | net_stats[iface].append(NetSample(time, iface, int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4]))) | ||
573 | return net_stats | ||
574 | |||
575 | |||
560 | def _parse_pressure_logs(file, filename): | 576 | def _parse_pressure_logs(file, filename): |
561 | """ | 577 | """ |
562 | Parse file for "some" pressure with 'avg10', 'avg60' 'avg300' and delta total values | 578 | Parse file for "some" pressure with 'avg10', 'avg60' 'avg300' and delta total values |
@@ -767,6 +783,8 @@ def _do_parse(writer, state, filename, file): | |||
767 | state.cmdline = _parse_cmdline_log(writer, file) | 783 | state.cmdline = _parse_cmdline_log(writer, file) |
768 | elif name == "monitor_disk.log": | 784 | elif name == "monitor_disk.log": |
769 | state.monitor_disk = _parse_monitor_disk_log(file) | 785 | state.monitor_disk = _parse_monitor_disk_log(file) |
786 | elif name == "reduced_proc_net.log": | ||
787 | state.net_stats = _parse_reduced_net_log(file) | ||
770 | #pressure logs are in a subdirectory | 788 | #pressure logs are in a subdirectory |
771 | elif name == "cpu.log": | 789 | elif name == "cpu.log": |
772 | state.cpu_pressure = _parse_pressure_logs(file, name) | 790 | state.cpu_pressure = _parse_pressure_logs(file, name) |
diff --git a/scripts/pybootchartgui/pybootchartgui/samples.py b/scripts/pybootchartgui/pybootchartgui/samples.py index a70d8a5a28..7c92d2ce6a 100644 --- a/scripts/pybootchartgui/pybootchartgui/samples.py +++ b/scripts/pybootchartgui/pybootchartgui/samples.py | |||
@@ -37,6 +37,16 @@ class CPUSample: | |||
37 | return str(self.time) + "\t" + str(self.user) + "\t" + \ | 37 | return str(self.time) + "\t" + str(self.user) + "\t" + \ |
38 | str(self.sys) + "\t" + str(self.io) + "\t" + str (self.swap) | 38 | str(self.sys) + "\t" + str(self.io) + "\t" + str (self.swap) |
39 | 39 | ||
40 | |||
41 | class NetSample: | ||
42 | def __init__(self, time, iface, received_bytes, transmitted_bytes, receive_diff, transmit_diff): | ||
43 | self.time = time | ||
44 | self.iface = iface | ||
45 | self.received_bytes = received_bytes | ||
46 | self.transmitted_bytes = transmitted_bytes | ||
47 | self.receive_diff = receive_diff | ||
48 | self.transmit_diff = transmit_diff | ||
49 | |||
40 | class CPUPressureSample: | 50 | class CPUPressureSample: |
41 | def __init__(self, time, avg10, avg60, avg300, deltaTotal): | 51 | def __init__(self, time, avg10, avg60, avg300, deltaTotal): |
42 | self.time = time | 52 | self.time = time |