xref: /openbmc/openbmc/poky/scripts/pybootchartgui/pybootchartgui/samples.py (revision c9537f57ab488bf5d90132917b0184e2527970a5)
1#  This file is part of pybootchartgui.
2
3#  pybootchartgui is free software: you can redistribute it and/or modify
4#  it under the terms of the GNU General Public License as published by
5#  the Free Software Foundation, either version 3 of the License, or
6#  (at your option) any later version.
7
8#  pybootchartgui is distributed in the hope that it will be useful,
9#  but WITHOUT ANY WARRANTY; without even the implied warranty of
10#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#  GNU General Public License for more details.
12
13#  You should have received a copy of the GNU General Public License
14#  along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>.
15
16
17class DiskStatSample:
18    def __init__(self, time):
19        self.time = time
20        self.diskdata = [0, 0, 0]
21    def add_diskdata(self, new_diskdata):
22        self.diskdata = [ a + b for a, b in zip(self.diskdata, new_diskdata) ]
23
24class CPUSample:
25    def __init__(self, time, user, sys, io = 0.0, swap = 0.0):
26        self.time = time
27        self.user = user
28        self.sys = sys
29        self.io = io
30        self.swap = swap
31
32    @property
33    def cpu(self):
34        return self.user + self.sys
35
36    def __str__(self):
37        return str(self.time) + "\t" + str(self.user) + "\t" + \
38               str(self.sys) + "\t" + str(self.io) + "\t" + str (self.swap)
39
40
41class 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
50class CPUPressureSample:
51    def __init__(self, time, avg10, avg60, avg300, deltaTotal):
52        self.time = time
53        self.avg10 = avg10
54        self.avg60 = avg60
55        self.avg300 = avg300
56        self.deltaTotal = deltaTotal
57
58class IOPressureSample:
59    def __init__(self, time, avg10, avg60, avg300, deltaTotal):
60        self.time = time
61        self.avg10 = avg10
62        self.avg60 = avg60
63        self.avg300 = avg300
64        self.deltaTotal = deltaTotal
65
66class MemPressureSample:
67    def __init__(self, time, avg10, avg60, avg300, deltaTotal):
68        self.time = time
69        self.avg10 = avg10
70        self.avg60 = avg60
71        self.avg300 = avg300
72        self.deltaTotal = deltaTotal
73
74
75class MemSample:
76    used_values = ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree',)
77
78    def __init__(self, time):
79        self.time = time
80        self.records = {}
81
82    def add_value(self, name, value):
83        if name in MemSample.used_values:
84            self.records[name] = value
85
86    def valid(self):
87        keys = self.records.keys()
88        # discard incomplete samples
89        return [v for v in MemSample.used_values if v not in keys] == []
90
91class DrawMemSample:
92    """
93    Condensed version of a MemSample with exactly the values used by the drawing code.
94    Initialized either from a valid MemSample or
95    a tuple/list of buffer/used/cached/swap values.
96    """
97    def __init__(self, mem_sample):
98        self.time = mem_sample.time
99        if isinstance(mem_sample, MemSample):
100            self.buffers = mem_sample.records['MemTotal'] - mem_sample.records['MemFree']
101            self.used = mem_sample.records['MemTotal'] - mem_sample.records['MemFree'] - mem_sample.records['Buffers']
102            self.cached = mem_sample.records['Cached']
103            self.swap = mem_sample.records['SwapTotal'] - mem_sample.records['SwapFree']
104        else:
105            self.buffers, self.used, self.cached, self.swap = mem_sample
106
107class DiskSpaceSample:
108    def __init__(self, time):
109        self.time = time
110        self.records = {}
111
112    def add_value(self, name, value):
113        self.records[name] = value
114
115    def valid(self):
116        return bool(self.records)
117
118class ProcessSample:
119    def __init__(self, time, state, cpu_sample):
120        self.time = time
121        self.state = state
122        self.cpu_sample = cpu_sample
123
124    def __str__(self):
125        return str(self.time) + "\t" + str(self.state) + "\t" + str(self.cpu_sample)
126
127class ProcessStats:
128    def __init__(self, writer, process_map, sample_count, sample_period, start_time, end_time):
129        self.process_map = process_map
130        self.sample_count = sample_count
131        self.sample_period = sample_period
132        self.start_time = start_time
133        self.end_time = end_time
134        writer.info ("%d samples, avg. sample length %f" % (self.sample_count, self.sample_period))
135        writer.info ("process list size: %d" % len (self.process_map.values()))
136
137class Process:
138    def __init__(self, writer, pid, cmd, ppid, start_time):
139        self.writer = writer
140        self.pid = pid
141        self.cmd = cmd
142        self.exe = cmd
143        self.args = []
144        self.ppid = ppid
145        self.start_time = start_time
146        self.duration = 0
147        self.samples = []
148        self.parent = None
149        self.child_list = []
150
151        self.active = None
152        self.last_user_cpu_time = None
153        self.last_sys_cpu_time = None
154
155        self.last_cpu_ns = 0
156        self.last_blkio_delay_ns = 0
157        self.last_swapin_delay_ns = 0
158
159    # split this process' run - triggered by a name change
160    def split(self, writer, pid, cmd, ppid, start_time):
161        split = Process (writer, pid, cmd, ppid, start_time)
162
163        split.last_cpu_ns = self.last_cpu_ns
164        split.last_blkio_delay_ns = self.last_blkio_delay_ns
165        split.last_swapin_delay_ns = self.last_swapin_delay_ns
166
167        return split
168
169    def __str__(self):
170        return " ".join([str(self.pid), self.cmd, str(self.ppid), '[ ' + str(len(self.samples)) + ' samples ]' ])
171
172    def calc_stats(self, samplePeriod):
173        if self.samples:
174            firstSample = self.samples[0]
175            lastSample = self.samples[-1]
176            self.start_time = min(firstSample.time, self.start_time)
177            self.duration = lastSample.time - self.start_time + samplePeriod
178
179        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] )
180        activeCount = activeCount + sum( [1 for sample in self.samples if sample.state == 'D'] )
181        self.active = (activeCount>2)
182
183    def calc_load(self, userCpu, sysCpu, interval):
184        userCpuLoad = float(userCpu - self.last_user_cpu_time) / interval
185        sysCpuLoad = float(sysCpu - self.last_sys_cpu_time) / interval
186        cpuLoad = userCpuLoad + sysCpuLoad
187        # normalize
188        if cpuLoad > 1.0:
189            userCpuLoad = userCpuLoad / cpuLoad
190            sysCpuLoad = sysCpuLoad / cpuLoad
191        return (userCpuLoad, sysCpuLoad)
192
193    def set_parent(self, processMap):
194        if self.ppid != None:
195            self.parent = processMap.get (self.ppid)
196            if self.parent == None and self.pid // 1000 > 1 and \
197                not (self.ppid == 2000 or self.pid == 2000): # kernel threads: ppid=2
198                self.writer.warn("Missing CONFIG_PROC_EVENTS: no parent for pid '%i' ('%s') with ppid '%i'" \
199                                 % (self.pid,self.cmd,self.ppid))
200
201    def get_end_time(self):
202        return self.start_time + self.duration
203
204class DiskSample:
205    def __init__(self, time, read, write, util):
206        self.time = time
207        self.read = read
208        self.write = write
209        self.util = util
210        self.tput = read + write
211
212    def __str__(self):
213        return "\t".join([str(self.time), str(self.read), str(self.write), str(self.util)])
214