mirror of
https://git.yoctoproject.org/git/poky
synced 2026-01-01 13:58:04 +00:00
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>
214 lines
7.4 KiB
Python
214 lines
7.4 KiB
Python
# This file is part of pybootchartgui.
|
|
|
|
# pybootchartgui is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# pybootchartgui is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
class DiskStatSample:
|
|
def __init__(self, time):
|
|
self.time = time
|
|
self.diskdata = [0, 0, 0]
|
|
def add_diskdata(self, new_diskdata):
|
|
self.diskdata = [ a + b for a, b in zip(self.diskdata, new_diskdata) ]
|
|
|
|
class CPUSample:
|
|
def __init__(self, time, user, sys, io = 0.0, swap = 0.0):
|
|
self.time = time
|
|
self.user = user
|
|
self.sys = sys
|
|
self.io = io
|
|
self.swap = swap
|
|
|
|
@property
|
|
def cpu(self):
|
|
return self.user + self.sys
|
|
|
|
def __str__(self):
|
|
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
|
|
self.avg10 = avg10
|
|
self.avg60 = avg60
|
|
self.avg300 = avg300
|
|
self.deltaTotal = deltaTotal
|
|
|
|
class IOPressureSample:
|
|
def __init__(self, time, avg10, avg60, avg300, deltaTotal):
|
|
self.time = time
|
|
self.avg10 = avg10
|
|
self.avg60 = avg60
|
|
self.avg300 = avg300
|
|
self.deltaTotal = deltaTotal
|
|
|
|
class MemPressureSample:
|
|
def __init__(self, time, avg10, avg60, avg300, deltaTotal):
|
|
self.time = time
|
|
self.avg10 = avg10
|
|
self.avg60 = avg60
|
|
self.avg300 = avg300
|
|
self.deltaTotal = deltaTotal
|
|
|
|
|
|
class MemSample:
|
|
used_values = ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree',)
|
|
|
|
def __init__(self, time):
|
|
self.time = time
|
|
self.records = {}
|
|
|
|
def add_value(self, name, value):
|
|
if name in MemSample.used_values:
|
|
self.records[name] = value
|
|
|
|
def valid(self):
|
|
keys = self.records.keys()
|
|
# discard incomplete samples
|
|
return [v for v in MemSample.used_values if v not in keys] == []
|
|
|
|
class DrawMemSample:
|
|
"""
|
|
Condensed version of a MemSample with exactly the values used by the drawing code.
|
|
Initialized either from a valid MemSample or
|
|
a tuple/list of buffer/used/cached/swap values.
|
|
"""
|
|
def __init__(self, mem_sample):
|
|
self.time = mem_sample.time
|
|
if isinstance(mem_sample, MemSample):
|
|
self.buffers = mem_sample.records['MemTotal'] - mem_sample.records['MemFree']
|
|
self.used = mem_sample.records['MemTotal'] - mem_sample.records['MemFree'] - mem_sample.records['Buffers']
|
|
self.cached = mem_sample.records['Cached']
|
|
self.swap = mem_sample.records['SwapTotal'] - mem_sample.records['SwapFree']
|
|
else:
|
|
self.buffers, self.used, self.cached, self.swap = mem_sample
|
|
|
|
class DiskSpaceSample:
|
|
def __init__(self, time):
|
|
self.time = time
|
|
self.records = {}
|
|
|
|
def add_value(self, name, value):
|
|
self.records[name] = value
|
|
|
|
def valid(self):
|
|
return bool(self.records)
|
|
|
|
class ProcessSample:
|
|
def __init__(self, time, state, cpu_sample):
|
|
self.time = time
|
|
self.state = state
|
|
self.cpu_sample = cpu_sample
|
|
|
|
def __str__(self):
|
|
return str(self.time) + "\t" + str(self.state) + "\t" + str(self.cpu_sample)
|
|
|
|
class ProcessStats:
|
|
def __init__(self, writer, process_map, sample_count, sample_period, start_time, end_time):
|
|
self.process_map = process_map
|
|
self.sample_count = sample_count
|
|
self.sample_period = sample_period
|
|
self.start_time = start_time
|
|
self.end_time = end_time
|
|
writer.info ("%d samples, avg. sample length %f" % (self.sample_count, self.sample_period))
|
|
writer.info ("process list size: %d" % len (self.process_map.values()))
|
|
|
|
class Process:
|
|
def __init__(self, writer, pid, cmd, ppid, start_time):
|
|
self.writer = writer
|
|
self.pid = pid
|
|
self.cmd = cmd
|
|
self.exe = cmd
|
|
self.args = []
|
|
self.ppid = ppid
|
|
self.start_time = start_time
|
|
self.duration = 0
|
|
self.samples = []
|
|
self.parent = None
|
|
self.child_list = []
|
|
|
|
self.active = None
|
|
self.last_user_cpu_time = None
|
|
self.last_sys_cpu_time = None
|
|
|
|
self.last_cpu_ns = 0
|
|
self.last_blkio_delay_ns = 0
|
|
self.last_swapin_delay_ns = 0
|
|
|
|
# split this process' run - triggered by a name change
|
|
def split(self, writer, pid, cmd, ppid, start_time):
|
|
split = Process (writer, pid, cmd, ppid, start_time)
|
|
|
|
split.last_cpu_ns = self.last_cpu_ns
|
|
split.last_blkio_delay_ns = self.last_blkio_delay_ns
|
|
split.last_swapin_delay_ns = self.last_swapin_delay_ns
|
|
|
|
return split
|
|
|
|
def __str__(self):
|
|
return " ".join([str(self.pid), self.cmd, str(self.ppid), '[ ' + str(len(self.samples)) + ' samples ]' ])
|
|
|
|
def calc_stats(self, samplePeriod):
|
|
if self.samples:
|
|
firstSample = self.samples[0]
|
|
lastSample = self.samples[-1]
|
|
self.start_time = min(firstSample.time, self.start_time)
|
|
self.duration = lastSample.time - self.start_time + samplePeriod
|
|
|
|
activeCount = sum( [1 for sample in self.samples if sample.cpu_sample and sample.cpu_sample.sys + sample.cpu_sample.user + sample.cpu_sample.io > 0.0] )
|
|
activeCount = activeCount + sum( [1 for sample in self.samples if sample.state == 'D'] )
|
|
self.active = (activeCount>2)
|
|
|
|
def calc_load(self, userCpu, sysCpu, interval):
|
|
userCpuLoad = float(userCpu - self.last_user_cpu_time) / interval
|
|
sysCpuLoad = float(sysCpu - self.last_sys_cpu_time) / interval
|
|
cpuLoad = userCpuLoad + sysCpuLoad
|
|
# normalize
|
|
if cpuLoad > 1.0:
|
|
userCpuLoad = userCpuLoad / cpuLoad
|
|
sysCpuLoad = sysCpuLoad / cpuLoad
|
|
return (userCpuLoad, sysCpuLoad)
|
|
|
|
def set_parent(self, processMap):
|
|
if self.ppid != None:
|
|
self.parent = processMap.get (self.ppid)
|
|
if self.parent == None and self.pid // 1000 > 1 and \
|
|
not (self.ppid == 2000 or self.pid == 2000): # kernel threads: ppid=2
|
|
self.writer.warn("Missing CONFIG_PROC_EVENTS: no parent for pid '%i' ('%s') with ppid '%i'" \
|
|
% (self.pid,self.cmd,self.ppid))
|
|
|
|
def get_end_time(self):
|
|
return self.start_time + self.duration
|
|
|
|
class DiskSample:
|
|
def __init__(self, time, read, write, util):
|
|
self.time = time
|
|
self.read = read
|
|
self.write = write
|
|
self.util = util
|
|
self.tput = read + write
|
|
|
|
def __str__(self):
|
|
return "\t".join([str(self.time), str(self.read), str(self.write), str(self.util)])
|