Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # The MIT License (MIT) | |
4 | # | |
a3fa57c0 | 5 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
39fec005 | 6 | # 2015 - Antoine Busque <abusque@efficios.com> |
4ed24f86 JD |
7 | # |
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
9 | # of this software and associated documentation files (the "Software"), to deal | |
10 | # in the Software without restriction, including without limitation the rights | |
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
12 | # copies of the Software, and to permit persons to whom the Software is | |
13 | # furnished to do so, subject to the following conditions: | |
14 | # | |
15 | # The above copyright notice and this permission notice shall be included in | |
16 | # all copies or substantial portions of the Software. | |
17 | # | |
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
24 | # SOFTWARE. | |
25 | ||
bd3cd7c5 | 26 | import socket |
44823c31 | 27 | from collections import OrderedDict |
bd3cd7c5 JD |
28 | |
29 | ||
30 | class StateVariable: | |
31 | pass | |
32 | ||
33 | ||
34 | class Process(): | |
35 | def __init__(self): | |
2b4a3c12 AB |
36 | self.tid = None |
37 | self.pid = None | |
3f52a605 | 38 | self.comm = '' |
bd3cd7c5 JD |
39 | # indexed by fd |
40 | self.fds = {} | |
41 | # indexed by filename | |
42 | self.closed_fds = {} | |
31d9c53b AB |
43 | # filenames (indexed by timestamp) associated with given fd (top-level |
44 | # index) at a given point in time | |
45 | self.chrono_fds = {} | |
bd3cd7c5 JD |
46 | self.current_syscall = {} |
47 | self.init_counts() | |
48 | ||
49 | def init_counts(self): | |
50 | self.cpu_ns = 0 | |
51 | self.migrate_count = 0 | |
52 | # network read/write | |
53 | self.net_read = 0 | |
54 | self.net_write = 0 | |
55 | # disk read/write (might be cached) | |
56 | self.disk_read = 0 | |
57 | self.disk_write = 0 | |
58 | # actual block access read/write | |
59 | self.block_read = 0 | |
60 | self.block_write = 0 | |
61 | # unclassified read/write (FD passing and statedump) | |
62 | self.unk_read = 0 | |
63 | self.unk_write = 0 | |
64 | # total I/O read/write | |
65 | self.read = 0 | |
66 | self.write = 0 | |
67 | # last TS where the process was scheduled in | |
6ddb1dd6 | 68 | self.last_sched = None |
bd3cd7c5 | 69 | # the process scheduled before this one |
2b4a3c12 | 70 | self.prev_tid = None |
bd3cd7c5 JD |
71 | # indexed by syscall_name |
72 | self.syscalls = {} | |
73 | self.perf = {} | |
74 | self.dirty = 0 | |
bd3cd7c5 JD |
75 | self.total_syscalls = 0 |
76 | # array of IORequest objects for freq analysis later (block and | |
77 | # syscalls with no FD like sys_sync) | |
78 | self.iorequests = [] | |
79 | ||
44823c31 AB |
80 | def track_chrono_fd(self, fd, filename, fdtype, timestamp): |
81 | chrono_metadata = {} | |
3f52a605 AB |
82 | chrono_metadata['filename'] = filename |
83 | chrono_metadata['fdtype'] = fdtype | |
44823c31 AB |
84 | |
85 | if fd not in self.chrono_fds: | |
86 | self.chrono_fds[fd] = OrderedDict() | |
87 | self.chrono_fds[fd][timestamp] = chrono_metadata | |
88 | else: | |
89 | chrono_fd = self.chrono_fds[fd] | |
90 | last_ts = next(reversed(chrono_fd)) | |
3f52a605 | 91 | if filename != chrono_fd[last_ts]['filename']: |
44823c31 AB |
92 | chrono_fd[timestamp] = chrono_metadata |
93 | ||
bd3cd7c5 JD |
94 | |
95 | class CPU(): | |
3c9bf3fa AB |
96 | def __init__(self, cpu_id): |
97 | self.cpu_id = cpu_id | |
bd3cd7c5 | 98 | self.cpu_ns = 0 |
2b4a3c12 | 99 | self.current_tid = None |
f4522f09 | 100 | self.current_hard_irq = None |
26facb3a AB |
101 | # softirqs use a dict because multiple ones can be raised before |
102 | # handling. They are indexed by vec, and each entry is a list, | |
103 | # ordered chronologically | |
104 | self.current_softirqs = {} | |
bd3cd7c5 JD |
105 | self.start_task_ns = 0 |
106 | self.perf = {} | |
107 | self.wakeup_queue = [] | |
108 | ||
109 | ||
994541c3 AB |
110 | class MemoryManagement(): |
111 | def __init__(self): | |
112 | self.page_count = 0 | |
113 | ||
114 | ||
bd3cd7c5 | 115 | class Syscall(): |
21167679 | 116 | # One instance for each unique syscall name per process |
bd3cd7c5 | 117 | def __init__(self): |
3f52a605 | 118 | self.name = '' |
bd3cd7c5 | 119 | self.count = 0 |
21167679 JD |
120 | # duration min/max |
121 | self.min = None | |
122 | self.max = 0 | |
123 | self.total_duration = 0 | |
124 | # list of all syscall events (SyscallEvent) | |
125 | self.rq = [] | |
126 | ||
127 | ||
128 | class SyscallEvent(): | |
129 | def __init__(self): | |
130 | self.entry_ts = None | |
131 | self.exit_ts = None | |
132 | self.ret = None | |
133 | self.duration = None | |
bd3cd7c5 JD |
134 | |
135 | ||
136 | class Disk(): | |
137 | def __init__(self): | |
3f52a605 AB |
138 | self.name = '' |
139 | self.prettyname = '' | |
bd3cd7c5 JD |
140 | self.init_counts() |
141 | ||
142 | def init_counts(self): | |
143 | self.nr_sector = 0 | |
144 | self.nr_requests = 0 | |
145 | self.completed_requests = 0 | |
146 | self.request_time = 0 | |
147 | self.pending_requests = {} | |
148 | self.rq_list = [] | |
149 | self.max = None | |
150 | self.min = None | |
151 | self.total = None | |
152 | self.count = None | |
153 | self.rq_values = None | |
154 | self.stdev = None | |
155 | ||
156 | ||
157 | class Iface(): | |
158 | def __init__(self): | |
3f52a605 | 159 | self.name = '' |
bd3cd7c5 JD |
160 | self.init_counts() |
161 | ||
162 | def init_counts(self): | |
163 | self.recv_bytes = 0 | |
164 | self.recv_packets = 0 | |
165 | self.send_bytes = 0 | |
166 | self.send_packets = 0 | |
167 | ||
168 | ||
169 | class FDType(): | |
170 | unknown = 0 | |
171 | disk = 1 | |
172 | net = 2 | |
173 | # not 100% sure they are network FDs (assumed when net_dev_xmit is | |
174 | # called during a write syscall and the type in unknown). | |
175 | maybe_net = 3 | |
176 | ||
177 | ||
178 | class FD(): | |
179 | def __init__(self): | |
3f52a605 | 180 | self.filename = '' |
2b4a3c12 | 181 | self.fd = None |
bd3cd7c5 JD |
182 | # address family |
183 | self.family = socket.AF_UNSPEC | |
184 | self.fdtype = FDType.unknown | |
185 | # if FD was inherited, parent PID | |
2b4a3c12 | 186 | self.parent = None |
bd3cd7c5 JD |
187 | self.init_counts() |
188 | ||
189 | def init_counts(self): | |
190 | # network read/write | |
191 | self.net_read = 0 | |
192 | self.net_write = 0 | |
193 | # disk read/write (might be cached) | |
194 | self.disk_read = 0 | |
195 | self.disk_write = 0 | |
196 | # unclassified read/write (FD passing and statedump) | |
197 | self.unk_read = 0 | |
198 | self.unk_write = 0 | |
199 | # total read/write | |
200 | self.read = 0 | |
201 | self.write = 0 | |
202 | self.open = 0 | |
203 | self.close = 0 | |
204 | self.cloexec = 0 | |
205 | # array of syscall IORequest objects for freq analysis later | |
206 | self.iorequests = [] | |
207 | ||
208 | ||
209 | class IRQ(): | |
9b94fe47 | 210 | def __init__(self, id, cpu_id, start_ts=None): |
f4522f09 | 211 | self.id = id |
9b94fe47 | 212 | self.cpu_id = cpu_id |
f4522f09 | 213 | self.start_ts = start_ts |
2b4a3c12 | 214 | self.stop_ts = None |
bd3cd7c5 | 215 | |
f4522f09 AB |
216 | |
217 | class HardIRQ(IRQ): | |
9b94fe47 AB |
218 | def __init__(self, id, cpu_id, start_ts): |
219 | super().__init__(id, cpu_id, start_ts) | |
f4522f09 AB |
220 | self.ret = None |
221 | ||
222 | @classmethod | |
223 | def new_from_irq_handler_entry(cls, event): | |
3f52a605 | 224 | id = event['irq'] |
9b94fe47 | 225 | cpu_id = event['cpu_id'] |
f4522f09 | 226 | start_ts = event.timestamp |
9b94fe47 | 227 | return cls(id, cpu_id, start_ts) |
f4522f09 AB |
228 | |
229 | ||
230 | class SoftIRQ(IRQ): | |
9b94fe47 | 231 | def __init__(self, id, cpu_id, raise_ts=None, start_ts=None): |
26facb3a | 232 | super().__init__(id, cpu_id, start_ts) |
f4522f09 AB |
233 | self.raise_ts = raise_ts |
234 | ||
235 | @classmethod | |
236 | def new_from_softirq_raise(cls, event): | |
3f52a605 | 237 | id = event['vec'] |
9b94fe47 | 238 | cpu_id = event['cpu_id'] |
f4522f09 | 239 | raise_ts = event.timestamp |
9b94fe47 | 240 | return cls(id, cpu_id, raise_ts) |
f4522f09 AB |
241 | |
242 | @classmethod | |
243 | def new_from_softirq_entry(cls, event): | |
3f52a605 | 244 | id = event['vec'] |
9b94fe47 | 245 | cpu_id = event['cpu_id'] |
f4522f09 | 246 | start_ts = event.timestamp |
9b94fe47 | 247 | return cls(id, cpu_id, start_ts=start_ts) |
ca95b1c3 | 248 | |
bd3cd7c5 JD |
249 | |
250 | class IORequest(): | |
3f52a605 | 251 | # I/O 'type' |
bd3cd7c5 JD |
252 | IO_SYSCALL = 1 |
253 | IO_BLOCK = 2 | |
254 | IO_NET = 3 | |
255 | # I/O operations | |
256 | OP_OPEN = 1 | |
257 | OP_READ = 2 | |
258 | OP_WRITE = 3 | |
259 | OP_CLOSE = 4 | |
260 | OP_SYNC = 5 | |
261 | ||
262 | def __init__(self): | |
263 | # IORequest.IO_* | |
264 | self.iotype = None | |
265 | # bytes for syscalls and net, sectors for block | |
266 | # FIXME: syscalls handling vectors (vector size missing) | |
267 | self.size = None | |
268 | # for syscalls and block: delay between issue and completion | |
269 | # of the request | |
270 | self.duration = None | |
271 | # IORequest.OP_* | |
272 | self.operation = None | |
273 | # syscall name | |
274 | self.name = None | |
275 | # begin syscall timestamp | |
276 | self.begin = None | |
277 | # end syscall timestamp | |
278 | self.end = None | |
279 | # current process | |
280 | self.proc = None | |
281 | # current FD (for syscalls) | |
282 | self.fd = None | |
283 | # buffers dirtied during the operation | |
284 | self.dirty = 0 | |
285 | # pages allocated during the operation | |
286 | self.page_alloc = 0 | |
287 | # pages freed during the operation | |
288 | self.page_free = 0 | |
289 | # pages written on disk during the operation | |
290 | self.page_written = 0 | |
291 | # kswapd was forced to wakeup during the operation | |
292 | self.woke_kswapd = False | |
293 | # estimated pages flushed during a sync operation | |
294 | self.page_cleared = 0 | |
295 | ||
296 | ||
297 | class Syscalls_stats(): | |
298 | def __init__(self): | |
299 | self.read_max = 0 | |
300 | self.read_min = None | |
301 | self.read_total = 0 | |
302 | self.read_count = 0 | |
303 | self.read_rq = [] | |
304 | self.all_read = [] | |
305 | ||
306 | self.write_max = 0 | |
307 | self.write_min = None | |
308 | self.write_total = 0 | |
309 | self.write_count = 0 | |
310 | self.write_rq = [] | |
311 | self.all_write = [] | |
312 | ||
313 | self.open_max = 0 | |
314 | self.open_min = None | |
315 | self.open_total = 0 | |
316 | self.open_count = 0 | |
317 | self.open_rq = [] | |
318 | self.all_open = [] | |
319 | ||
320 | self.sync_max = 0 | |
321 | self.sync_min = None | |
322 | self.sync_total = 0 | |
323 | self.sync_count = 0 | |
324 | self.sync_rq = [] | |
325 | self.all_sync = [] | |
326 | ||
327 | ||
328 | class SyscallConsts(): | |
329 | # TODO: decouple socket/family logic from this class | |
330 | INET_FAMILIES = [socket.AF_INET, socket.AF_INET6] | |
331 | DISK_FAMILIES = [socket.AF_UNIX] | |
332 | # list nof syscalls that open a FD on disk (in the exit_syscall event) | |
3f52a605 AB |
333 | DISK_OPEN_SYSCALLS = ['sys_open', 'syscall_entry_open', |
334 | 'sys_openat', 'syscall_entry_openat'] | |
bd3cd7c5 JD |
335 | # list of syscalls that open a FD on the network |
336 | # (in the exit_syscall event) | |
3f52a605 AB |
337 | NET_OPEN_SYSCALLS = ['sys_accept', 'syscall_entry_accept', |
338 | 'sys_accept4', 'syscall_entry_accept4', | |
339 | 'sys_socket', 'syscall_entry_socket'] | |
bd3cd7c5 | 340 | # list of syscalls that can duplicate a FD |
3f52a605 AB |
341 | DUP_OPEN_SYSCALLS = ['sys_fcntl', 'syscall_entry_fcntl', |
342 | 'sys_dup2', 'syscall_entry_dup2'] | |
343 | SYNC_SYSCALLS = ['sys_sync', 'syscall_entry_sync', | |
344 | 'sys_sync_file_range', 'syscall_entry_sync_file_range', | |
345 | 'sys_fsync', 'syscall_entry_fsync', | |
346 | 'sys_fdatasync', 'syscall_entry_fdatasync'] | |
bd3cd7c5 JD |
347 | # merge the 3 open lists |
348 | OPEN_SYSCALLS = DISK_OPEN_SYSCALLS + NET_OPEN_SYSCALLS + DUP_OPEN_SYSCALLS | |
3f52a605 AB |
349 | # list of syscalls that close a FD (in the 'fd =' field) |
350 | CLOSE_SYSCALLS = ['sys_close', 'syscall_entry_close'] | |
bd3cd7c5 | 351 | # list of syscall that read on a FD, value in the exit_syscall following |
3f52a605 AB |
352 | READ_SYSCALLS = ['sys_read', 'syscall_entry_read', |
353 | 'sys_recvmsg', 'syscall_entry_recvmsg', | |
354 | 'sys_recvfrom', 'syscall_entry_recvfrom', | |
355 | 'sys_splice', 'syscall_entry_splice', | |
356 | 'sys_readv', 'syscall_entry_readv', | |
357 | 'sys_sendfile64', 'syscall_entry_sendfile64'] | |
bd3cd7c5 | 358 | # list of syscall that write on a FD, value in the exit_syscall following |
3f52a605 AB |
359 | WRITE_SYSCALLS = ['sys_write', 'syscall_entry_write', |
360 | 'sys_sendmsg', 'syscall_entry_sendmsg', | |
361 | 'sys_sendto', 'syscall_entry_sendto', | |
362 | 'sys_writev', 'syscall_entry_writev'] | |
21167679 JD |
363 | # All I/O related syscalls |
364 | IO_SYSCALLS = OPEN_SYSCALLS + CLOSE_SYSCALLS + READ_SYSCALLS + \ | |
c437b3bd | 365 | WRITE_SYSCALLS + SYNC_SYSCALLS |
bd3cd7c5 JD |
366 | # generic names assigned to special FDs, don't try to match these in the |
367 | # closed_fds dict | |
3f52a605 | 368 | GENERIC_NAMES = ['unknown', 'socket'] |
bd3cd7c5 JD |
369 | |
370 | def __init__(): | |
371 | pass |