diff options
Diffstat (limited to 'meta/lib/buildstats.py')
-rw-r--r-- | meta/lib/buildstats.py | 161 |
1 files changed, 0 insertions, 161 deletions
diff --git a/meta/lib/buildstats.py b/meta/lib/buildstats.py deleted file mode 100644 index 8627ed3c31..0000000000 --- a/meta/lib/buildstats.py +++ /dev/null | |||
@@ -1,161 +0,0 @@ | |||
1 | # | ||
2 | # SPDX-License-Identifier: GPL-2.0-only | ||
3 | # | ||
4 | # Implements system state sampling. Called by buildstats.bbclass. | ||
5 | # Because it is a real Python module, it can hold persistent state, | ||
6 | # like open log files and the time of the last sampling. | ||
7 | |||
8 | import time | ||
9 | import re | ||
10 | import bb.event | ||
11 | |||
12 | class SystemStats: | ||
13 | def __init__(self, d): | ||
14 | bn = d.getVar('BUILDNAME') | ||
15 | bsdir = os.path.join(d.getVar('BUILDSTATS_BASE'), bn) | ||
16 | bb.utils.mkdirhier(bsdir) | ||
17 | |||
18 | self.proc_files = [] | ||
19 | for filename, handler in ( | ||
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. | ||
25 | # For example, /proc/diskstats is not available in virtualized | ||
26 | # environments like Linux-VServer. Silently skip collecting | ||
27 | # the data. | ||
28 | if os.path.exists(os.path.join('/proc', filename)): | ||
29 | # In practice, this class gets instantiated only once in | ||
30 | # the bitbake cooker process. Therefore 'append' mode is | ||
31 | # not strictly necessary, but using it makes the class | ||
32 | # more robust should two processes ever write | ||
33 | # concurrently. | ||
34 | destfile = os.path.join(bsdir, '%sproc_%s.log' % ('reduced_' if handler else '', filename)) | ||
35 | self.proc_files.append((filename, open(destfile, 'ab'), handler)) | ||
36 | self.monitor_disk = open(os.path.join(bsdir, 'monitor_disk.log'), 'ab') | ||
37 | # Last time that we sampled /proc data resp. recorded disk monitoring data. | ||
38 | self.last_proc = 0 | ||
39 | self.last_disk_monitor = 0 | ||
40 | # Minimum number of seconds between recording a sample. This | ||
41 | # becames relevant when we get called very often while many | ||
42 | # short tasks get started. Sampling during quiet periods | ||
43 | # depends on the heartbeat event, which fires less often. | ||
44 | self.min_seconds = 1 | ||
45 | |||
46 | self.meminfo_regex = re.compile(b'^(MemTotal|MemFree|Buffers|Cached|SwapTotal|SwapFree):\s*(\d+)') | ||
47 | self.diskstats_regex = re.compile(b'^([hsv]d.|mtdblock\d|mmcblk\d|cciss/c\d+d\d+.*)$') | ||
48 | self.diskstats_ltime = None | ||
49 | self.diskstats_data = None | ||
50 | self.stat_ltimes = None | ||
51 | |||
52 | def close(self): | ||
53 | self.monitor_disk.close() | ||
54 | for _, output, _ in self.proc_files: | ||
55 | output.close() | ||
56 | |||
57 | def _reduce_meminfo(self, time, data): | ||
58 | """ | ||
59 | Extracts 'MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree' | ||
60 | and writes their values into a single line, in that order. | ||
61 | """ | ||
62 | values = {} | ||
63 | for line in data.split(b'\n'): | ||
64 | m = self.meminfo_regex.match(line) | ||
65 | if m: | ||
66 | values[m.group(1)] = m.group(2) | ||
67 | if len(values) == 6: | ||
68 | return (time, | ||
69 | b' '.join([values[x] for x in | ||
70 | (b'MemTotal', b'MemFree', b'Buffers', b'Cached', b'SwapTotal', b'SwapFree')]) + b'\n') | ||
71 | |||
72 | def _diskstats_is_relevant_line(self, linetokens): | ||
73 | if len(linetokens) != 14: | ||
74 | return False | ||
75 | disk = linetokens[2] | ||
76 | return self.diskstats_regex.match(disk) | ||
77 | |||
78 | def _reduce_diskstats(self, time, data): | ||
79 | relevant_tokens = filter(self._diskstats_is_relevant_line, map(lambda x: x.split(), data.split(b'\n'))) | ||
80 | diskdata = [0] * 3 | ||
81 | reduced = None | ||
82 | for tokens in relevant_tokens: | ||
83 | # rsect | ||
84 | diskdata[0] += int(tokens[5]) | ||
85 | # wsect | ||
86 | diskdata[1] += int(tokens[9]) | ||
87 | # use | ||
88 | diskdata[2] += int(tokens[12]) | ||
89 | if self.diskstats_ltime: | ||
90 | # We need to compute information about the time interval | ||
91 | # since the last sampling and record the result as sample | ||
92 | # for that point in the past. | ||
93 | interval = time - self.diskstats_ltime | ||
94 | if interval > 0: | ||
95 | sums = [ a - b for a, b in zip(diskdata, self.diskstats_data) ] | ||
96 | readTput = sums[0] / 2.0 * 100.0 / interval | ||
97 | writeTput = sums[1] / 2.0 * 100.0 / interval | ||
98 | util = float( sums[2] ) / 10 / interval | ||
99 | util = max(0.0, min(1.0, util)) | ||
100 | reduced = (self.diskstats_ltime, (readTput, writeTput, util)) | ||
101 | |||
102 | self.diskstats_ltime = time | ||
103 | self.diskstats_data = diskdata | ||
104 | return reduced | ||
105 | |||
106 | |||
107 | def _reduce_nop(self, time, data): | ||
108 | return (time, data) | ||
109 | |||
110 | def _reduce_stat(self, time, data): | ||
111 | if not data: | ||
112 | return None | ||
113 | # CPU times {user, nice, system, idle, io_wait, irq, softirq} from first line | ||
114 | tokens = data.split(b'\n', 1)[0].split() | ||
115 | times = [ int(token) for token in tokens[1:] ] | ||
116 | reduced = None | ||
117 | if self.stat_ltimes: | ||
118 | user = float((times[0] + times[1]) - (self.stat_ltimes[0] + self.stat_ltimes[1])) | ||
119 | system = float((times[2] + times[5] + times[6]) - (self.stat_ltimes[2] + self.stat_ltimes[5] + self.stat_ltimes[6])) | ||
120 | idle = float(times[3] - self.stat_ltimes[3]) | ||
121 | iowait = float(times[4] - self.stat_ltimes[4]) | ||
122 | |||
123 | aSum = max(user + system + idle + iowait, 1) | ||
124 | reduced = (time, (user/aSum, system/aSum, iowait/aSum)) | ||
125 | |||
126 | self.stat_ltimes = times | ||
127 | return reduced | ||
128 | |||
129 | def sample(self, event, force): | ||
130 | now = time.time() | ||
131 | if (now - self.last_proc > self.min_seconds) or force: | ||
132 | for filename, output, handler in self.proc_files: | ||
133 | with open(os.path.join('/proc', filename), 'rb') as input: | ||
134 | data = input.read() | ||
135 | if handler: | ||
136 | reduced = handler(now, data) | ||
137 | else: | ||
138 | reduced = (now, data) | ||
139 | if reduced: | ||
140 | if isinstance(reduced[1], bytes): | ||
141 | # Use as it is. | ||
142 | data = reduced[1] | ||
143 | else: | ||
144 | # Convert to a single line. | ||
145 | data = (' '.join([str(x) for x in reduced[1]]) + '\n').encode('ascii') | ||
146 | # Unbuffered raw write, less overhead and useful | ||
147 | # in case that we end up with concurrent writes. | ||
148 | os.write(output.fileno(), | ||
149 | ('%.0f\n' % reduced[0]).encode('ascii') + | ||
150 | data + | ||
151 | b'\n') | ||
152 | self.last_proc = now | ||
153 | |||
154 | if isinstance(event, bb.event.MonitorDiskEvent) and \ | ||
155 | ((now - self.last_disk_monitor > self.min_seconds) or force): | ||
156 | os.write(self.monitor_disk.fileno(), | ||
157 | ('%.0f\n' % now).encode('ascii') + | ||
158 | ''.join(['%s: %d\n' % (dev, sample.total_bytes - sample.free_bytes) | ||
159 | for dev, sample in event.disk_usage.items()]).encode('ascii') + | ||
160 | b'\n') | ||
161 | self.last_disk_monitor = now | ||