mirror of
https://git.yoctoproject.org/poky
synced 2026-04-06 02:02:21 +02:00
Add two new, separate charts showing the avg10 and delta total pressure over time for the CPU and IO resources. The height of the avg10 data in each chart represents the percentage of time "some" task was delayed over the specific resource during the last 10 seconds of the build. The height of the delta total data in each chart represents the total time "some" task was delayed since the last sample was collected. If the reduced_proc_pressure data is not present in the buildstats log, then the new charts are not shown at all rather than being present but unpopulated. Note that the delta total graphs may appear "spikey", oscillating from high values to low. This behaviour is fixed in a subsequent commit. (From OE-Core rev: fb9ff46dc3059cb3f4c8df8e4654184c3eab1571) Signed-off-by: Aryaman Gupta <aryaman.gupta@windriver.com> Signed-off-by: Randy MacLeod <randy.macleod@windriver.com> Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
196 lines
6.9 KiB
Python
196 lines
6.9 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 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 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)])
|