summaryrefslogtreecommitdiffstats
path: root/meta/lib/buildstats.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/buildstats.py')
-rw-r--r--meta/lib/buildstats.py88
1 files changed, 71 insertions, 17 deletions
diff --git a/meta/lib/buildstats.py b/meta/lib/buildstats.py
index 8627ed3c31..1ffe679801 100644
--- a/meta/lib/buildstats.py
+++ b/meta/lib/buildstats.py
@@ -1,4 +1,6 @@
1# 1#
2# Copyright OpenEmbedded Contributors
3#
2# SPDX-License-Identifier: GPL-2.0-only 4# SPDX-License-Identifier: GPL-2.0-only
3# 5#
4# Implements system state sampling. Called by buildstats.bbclass. 6# Implements system state sampling. Called by buildstats.bbclass.
@@ -14,13 +16,27 @@ class SystemStats:
14 bn = d.getVar('BUILDNAME') 16 bn = d.getVar('BUILDNAME')
15 bsdir = os.path.join(d.getVar('BUILDSTATS_BASE'), bn) 17 bsdir = os.path.join(d.getVar('BUILDSTATS_BASE'), bn)
16 bb.utils.mkdirhier(bsdir) 18 bb.utils.mkdirhier(bsdir)
19 file_handlers = [('diskstats', self._reduce_diskstats),
20 ('meminfo', self._reduce_meminfo),
21 ('stat', self._reduce_stat)]
22
23 # Some hosts like openSUSE have readable /proc/pressure files
24 # but throw errors when these files are opened. Catch these error
25 # and ensure that the reduce_proc_pressure directory is not created.
26 if os.path.exists("/proc/pressure"):
27 try:
28 with open('/proc/pressure/cpu', 'rb') as source:
29 source.read()
30 pressuredir = os.path.join(bsdir, 'reduced_proc_pressure')
31 bb.utils.mkdirhier(pressuredir)
32 file_handlers.extend([('pressure/cpu', self._reduce_pressure),
33 ('pressure/io', self._reduce_pressure),
34 ('pressure/memory', self._reduce_pressure)])
35 except Exception:
36 pass
17 37
18 self.proc_files = [] 38 self.proc_files = []
19 for filename, handler in ( 39 for filename, handler in (file_handlers):
20 ('diskstats', self._reduce_diskstats),
21 ('meminfo', self._reduce_meminfo),
22 ('stat', self._reduce_stat),
23 ):
24 # The corresponding /proc files might not exist on the host. 40 # The corresponding /proc files might not exist on the host.
25 # For example, /proc/diskstats is not available in virtualized 41 # For example, /proc/diskstats is not available in virtualized
26 # environments like Linux-VServer. Silently skip collecting 42 # environments like Linux-VServer. Silently skip collecting
@@ -37,24 +53,32 @@ class SystemStats:
37 # Last time that we sampled /proc data resp. recorded disk monitoring data. 53 # Last time that we sampled /proc data resp. recorded disk monitoring data.
38 self.last_proc = 0 54 self.last_proc = 0
39 self.last_disk_monitor = 0 55 self.last_disk_monitor = 0
40 # Minimum number of seconds between recording a sample. This 56 # Minimum number of seconds between recording a sample. This becames relevant when we get
41 # becames relevant when we get called very often while many 57 # called very often while many short tasks get started. Sampling during quiet periods
42 # short tasks get started. Sampling during quiet periods
43 # depends on the heartbeat event, which fires less often. 58 # depends on the heartbeat event, which fires less often.
44 self.min_seconds = 1 59 # By default, the Heartbeat events occur roughly once every second but the actual time
45 60 # between these events deviates by a few milliseconds, in most cases. Hence
46 self.meminfo_regex = re.compile(b'^(MemTotal|MemFree|Buffers|Cached|SwapTotal|SwapFree):\s*(\d+)') 61 # pick a somewhat arbitary tolerance such that we sample a large majority
47 self.diskstats_regex = re.compile(b'^([hsv]d.|mtdblock\d|mmcblk\d|cciss/c\d+d\d+.*)$') 62 # of the Heartbeat events. This ignores rare events that fall outside the minimum
63 # and may lead an extra sample in a given second every so often. However, it allows for fairly
64 # consistent intervals between samples without missing many events.
65 self.tolerance = 0.01
66 self.min_seconds = 1.0 - self.tolerance
67
68 self.meminfo_regex = re.compile(rb'^(MemTotal|MemFree|Buffers|Cached|SwapTotal|SwapFree):\s*(\d+)')
69 self.diskstats_regex = re.compile(rb'^([hsv]d.|mtdblock\d|mmcblk\d|cciss/c\d+d\d+.*)$')
48 self.diskstats_ltime = None 70 self.diskstats_ltime = None
49 self.diskstats_data = None 71 self.diskstats_data = None
50 self.stat_ltimes = None 72 self.stat_ltimes = None
73 # Last time we sampled /proc/pressure. All resources stored in a single dict with the key as filename
74 self.last_pressure = {"pressure/cpu": None, "pressure/io": None, "pressure/memory": None}
51 75
52 def close(self): 76 def close(self):
53 self.monitor_disk.close() 77 self.monitor_disk.close()
54 for _, output, _ in self.proc_files: 78 for _, output, _ in self.proc_files:
55 output.close() 79 output.close()
56 80
57 def _reduce_meminfo(self, time, data): 81 def _reduce_meminfo(self, time, data, filename):
58 """ 82 """
59 Extracts 'MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree' 83 Extracts 'MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree'
60 and writes their values into a single line, in that order. 84 and writes their values into a single line, in that order.
@@ -75,7 +99,7 @@ class SystemStats:
75 disk = linetokens[2] 99 disk = linetokens[2]
76 return self.diskstats_regex.match(disk) 100 return self.diskstats_regex.match(disk)
77 101
78 def _reduce_diskstats(self, time, data): 102 def _reduce_diskstats(self, time, data, filename):
79 relevant_tokens = filter(self._diskstats_is_relevant_line, map(lambda x: x.split(), data.split(b'\n'))) 103 relevant_tokens = filter(self._diskstats_is_relevant_line, map(lambda x: x.split(), data.split(b'\n')))
80 diskdata = [0] * 3 104 diskdata = [0] * 3
81 reduced = None 105 reduced = None
@@ -104,10 +128,10 @@ class SystemStats:
104 return reduced 128 return reduced
105 129
106 130
107 def _reduce_nop(self, time, data): 131 def _reduce_nop(self, time, data, filename):
108 return (time, data) 132 return (time, data)
109 133
110 def _reduce_stat(self, time, data): 134 def _reduce_stat(self, time, data, filename):
111 if not data: 135 if not data:
112 return None 136 return None
113 # CPU times {user, nice, system, idle, io_wait, irq, softirq} from first line 137 # CPU times {user, nice, system, idle, io_wait, irq, softirq} from first line
@@ -126,14 +150,41 @@ class SystemStats:
126 self.stat_ltimes = times 150 self.stat_ltimes = times
127 return reduced 151 return reduced
128 152
153 def _reduce_pressure(self, time, data, filename):
154 """
155 Return reduced pressure: {avg10, avg60, avg300} and delta total compared to the previous sample
156 for the cpu, io and memory resources. A common function is used for all 3 resources since the
157 format of the /proc/pressure file is the same in each case.
158 """
159 if not data:
160 return None
161 tokens = data.split(b'\n', 1)[0].split()
162 avg10 = float(tokens[1].split(b'=')[1])
163 avg60 = float(tokens[2].split(b'=')[1])
164 avg300 = float(tokens[3].split(b'=')[1])
165 total = int(tokens[4].split(b'=')[1])
166
167 reduced = None
168 if self.last_pressure[filename]:
169 delta = total - self.last_pressure[filename]
170 reduced = (time, (avg10, avg60, avg300, delta))
171 self.last_pressure[filename] = total
172 return reduced
173
129 def sample(self, event, force): 174 def sample(self, event, force):
175 """
176 Collect and log proc or disk_monitor stats periodically.
177 Return True if a new sample is collected and hence the value last_proc or last_disk_monitor
178 is changed.
179 """
180 retval = False
130 now = time.time() 181 now = time.time()
131 if (now - self.last_proc > self.min_seconds) or force: 182 if (now - self.last_proc > self.min_seconds) or force:
132 for filename, output, handler in self.proc_files: 183 for filename, output, handler in self.proc_files:
133 with open(os.path.join('/proc', filename), 'rb') as input: 184 with open(os.path.join('/proc', filename), 'rb') as input:
134 data = input.read() 185 data = input.read()
135 if handler: 186 if handler:
136 reduced = handler(now, data) 187 reduced = handler(now, data, filename)
137 else: 188 else:
138 reduced = (now, data) 189 reduced = (now, data)
139 if reduced: 190 if reduced:
@@ -150,6 +201,7 @@ class SystemStats:
150 data + 201 data +
151 b'\n') 202 b'\n')
152 self.last_proc = now 203 self.last_proc = now
204 retval = True
153 205
154 if isinstance(event, bb.event.MonitorDiskEvent) and \ 206 if isinstance(event, bb.event.MonitorDiskEvent) and \
155 ((now - self.last_disk_monitor > self.min_seconds) or force): 207 ((now - self.last_disk_monitor > self.min_seconds) or force):
@@ -159,3 +211,5 @@ class SystemStats:
159 for dev, sample in event.disk_usage.items()]).encode('ascii') + 211 for dev, sample in event.disk_usage.items()]).encode('ascii') +
160 b'\n') 212 b'\n')
161 self.last_disk_monitor = now 213 self.last_disk_monitor = now
214 retval = True
215 return retval \ No newline at end of file