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