]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core-contrib.git/commitdiff
pybootchartgui: visualize /proc/net/dev network stats in graphs
authorOlga Denisova <denisova.olga.k@yandex.ru>
Tue, 15 Apr 2025 16:35:27 +0000 (19:35 +0300)
committerRichard Purdie <richard.purdie@linuxfoundation.org>
Thu, 24 Apr 2025 10:25:00 +0000 (11:25 +0100)
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.

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>
scripts/pybootchartgui/pybootchartgui/draw.py
scripts/pybootchartgui/pybootchartgui/parsing.py
scripts/pybootchartgui/pybootchartgui/samples.py

index c6e67833abf35656d03e858226fe39816deead19..16739a0fa1951b00fbb4fb09540735c3699f6bb7 100644 (file)
@@ -69,6 +69,11 @@ CPU_COLOR = (0.40, 0.55, 0.70, 1.0)
 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
@@ -437,6 +442,49 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w):
 
         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)
index 144a16c723b802b1e34685fc6a6b363439855646..72a54c6ba5746c61bce8e2dfaea8f49685bb01ac 100644 (file)
@@ -48,6 +48,7 @@ class Trace:
         self.filename = None
         self.parent_map = None
         self.mem_stats = []
+        self.net_stats = []
         self.monitor_disk = None
         self.cpu_pressure = []
         self.io_pressure = []
@@ -557,6 +558,21 @@ def _parse_monitor_disk_log(file):
 
     return disk_stats
 
+
+def _parse_reduced_net_log(file):
+    net_stats = {}
+    for time, lines in _parse_timed_blocks(file):
+
+        for line in lines:
+            parts = line.split()
+            iface = parts[0][:-1]
+            if iface not in net_stats:
+                net_stats[iface] = [NetSample(time, iface, int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4]))]
+            else:
+                net_stats[iface].append(NetSample(time, iface, int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4])))
+    return net_stats
+
+
 def _parse_pressure_logs(file, filename):
     """
     Parse file for "some" pressure with 'avg10', 'avg60' 'avg300' and delta total values
@@ -767,6 +783,8 @@ def _do_parse(writer, state, filename, file):
         state.cmdline = _parse_cmdline_log(writer, file)
     elif name == "monitor_disk.log":
         state.monitor_disk = _parse_monitor_disk_log(file)
+    elif name == "reduced_proc_net.log":
+        state.net_stats = _parse_reduced_net_log(file)
     #pressure logs are in a subdirectory
     elif name == "cpu.log":
         state.cpu_pressure = _parse_pressure_logs(file, name)
index a70d8a5a2832c51d3c9d82e7c8a3e0558dc97df3..7c92d2ce6af3d7bd4f6a26acd3d41eebf6b19465 100644 (file)
@@ -37,6 +37,16 @@ class CPUSample:
         return str(self.time) + "\t" + str(self.user) + "\t" + \
                str(self.sys) + "\t" + str(self.io) + "\t" + str (self.swap)
 
+
+class NetSample:
+    def __init__(self, time, iface, received_bytes, transmitted_bytes, receive_diff, transmit_diff):
+        self.time = time
+        self.iface = iface
+        self.received_bytes = received_bytes
+        self.transmitted_bytes = transmitted_bytes
+        self.receive_diff = receive_diff
+        self.transmit_diff = transmit_diff
+
 class CPUPressureSample:
     def __init__(self, time, avg10, avg60, avg300, deltaTotal):
         self.time = time