Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | # The MIT License (MIT) |
2 | # | |
a3fa57c0 | 3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
39fec005 | 4 | # 2015 - Antoine Busque <abusque@efficios.com> |
4ed24f86 JD |
5 | # |
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | # of this software and associated documentation files (the "Software"), to deal | |
8 | # in the Software without restriction, including without limitation the rights | |
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | # copies of the Software, and to permit persons to whom the Software is | |
11 | # furnished to do so, subject to the following conditions: | |
12 | # | |
13 | # The above copyright notice and this permission notice shall be included in | |
14 | # all copies or substantial portions of the Software. | |
15 | # | |
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
22 | # SOFTWARE. | |
23 | ||
bd3cd7c5 | 24 | import socket |
56936af2 | 25 | from . import common |
bd3cd7c5 JD |
26 | |
27 | ||
28 | class StateVariable: | |
29 | pass | |
30 | ||
31 | ||
32 | class Process(): | |
f5ab2566 | 33 | def __init__(self, tid=None, pid=None, comm='', prio=None): |
3ce8423c AB |
34 | self.tid = tid |
35 | self.pid = pid | |
36 | self.comm = comm | |
f5ab2566 | 37 | self.prio = prio |
bd3cd7c5 JD |
38 | # indexed by fd |
39 | self.fds = {} | |
b5f11254 | 40 | self.current_syscall = None |
bd3cd7c5 | 41 | # the process scheduled before this one |
2b4a3c12 | 42 | self.prev_tid = None |
b9f05f8d AB |
43 | self.last_wakeup = None |
44 | self.last_waker = None | |
bd3cd7c5 JD |
45 | |
46 | ||
47 | class CPU(): | |
3c9bf3fa AB |
48 | def __init__(self, cpu_id): |
49 | self.cpu_id = cpu_id | |
2b4a3c12 | 50 | self.current_tid = None |
f4522f09 | 51 | self.current_hard_irq = None |
26facb3a AB |
52 | # softirqs use a dict because multiple ones can be raised before |
53 | # handling. They are indexed by vec, and each entry is a list, | |
54 | # ordered chronologically | |
55 | self.current_softirqs = {} | |
bd3cd7c5 JD |
56 | |
57 | ||
994541c3 AB |
58 | class MemoryManagement(): |
59 | def __init__(self): | |
60 | self.page_count = 0 | |
61 | ||
5ea5dc05 | 62 | |
21167679 | 63 | class SyscallEvent(): |
3b1dda96 AB |
64 | def __init__(self, name, begin_ts): |
65 | self.name = name | |
66 | self.begin_ts = begin_ts | |
67 | self.end_ts = None | |
21167679 JD |
68 | self.ret = None |
69 | self.duration = None | |
a4a9fb7b AB |
70 | # Only applicable to I/O syscalls |
71 | self.io_rq = None | |
bd3cd7c5 | 72 | |
3b1dda96 AB |
73 | def process_exit(self, event): |
74 | self.end_ts = event.timestamp | |
ef2f32f3 JD |
75 | try: |
76 | self.ret = event['ret'] | |
77 | except: | |
78 | print("[warning] syscall %s does not have a return code, that " | |
79 | "should not happen" % event.name) | |
80 | self.ret = -1 | |
3b1dda96 AB |
81 | self.duration = self.end_ts - self.begin_ts |
82 | ||
83 | @classmethod | |
84 | def new_from_entry(cls, event): | |
5ea5dc05 AB |
85 | name = common.get_syscall_name(event) |
86 | return cls(name, event.timestamp) | |
3b1dda96 | 87 | |
bd3cd7c5 JD |
88 | |
89 | class Disk(): | |
90 | def __init__(self): | |
3b1dda96 | 91 | # pending block IO Requests, indexed by sector |
bd3cd7c5 | 92 | self.pending_requests = {} |
bd3cd7c5 JD |
93 | |
94 | ||
bd3cd7c5 JD |
95 | class FDType(): |
96 | unknown = 0 | |
97 | disk = 1 | |
98 | net = 2 | |
99 | # not 100% sure they are network FDs (assumed when net_dev_xmit is | |
100 | # called during a write syscall and the type in unknown). | |
101 | maybe_net = 3 | |
102 | ||
8f9a5260 AB |
103 | @staticmethod |
104 | def get_fd_type(name, family): | |
105 | if name in SyscallConsts.NET_OPEN_SYSCALLS: | |
106 | if family in SyscallConsts.INET_FAMILIES: | |
107 | return FDType.net | |
108 | if family in SyscallConsts.DISK_FAMILIES: | |
109 | return FDType.disk | |
110 | ||
111 | if name in SyscallConsts.DISK_OPEN_SYSCALLS: | |
112 | return FDType.disk | |
113 | ||
114 | return FDType.unknown | |
115 | ||
bd3cd7c5 JD |
116 | |
117 | class FD(): | |
ccfc666b AB |
118 | def __init__(self, fd, filename='unknown', fd_type=FDType.unknown, |
119 | cloexec=False, family=None): | |
120 | self.fd = fd | |
121 | self.filename = filename | |
ccfc666b AB |
122 | self.fd_type = fd_type |
123 | self.cloexec = cloexec | |
124 | self.family = family | |
bd3cd7c5 | 125 | |
6f2c9e92 AB |
126 | @classmethod |
127 | def new_from_fd(cls, fd): | |
ccfc666b AB |
128 | return cls(fd.fd, fd.filename, fd.fd_type, fd.cloexec, fd.family) |
129 | ||
130 | @classmethod | |
131 | def new_from_open_rq(cls, io_rq): | |
132 | return cls(io_rq.fd, io_rq.filename, io_rq.fd_type, io_rq.cloexec, | |
133 | io_rq.family) | |
bd3cd7c5 JD |
134 | |
135 | ||
136 | class IRQ(): | |
0297cb98 | 137 | def __init__(self, id, cpu_id, begin_ts=None): |
f4522f09 | 138 | self.id = id |
9b94fe47 | 139 | self.cpu_id = cpu_id |
0297cb98 AB |
140 | self.begin_ts = begin_ts |
141 | self.end_ts = None | |
bd3cd7c5 | 142 | |
4a2c7d60 AB |
143 | @property |
144 | def duration(self): | |
145 | if not self.end_ts or not self.begin_ts: | |
146 | return None | |
147 | ||
148 | return self.end_ts - self.begin_ts | |
149 | ||
f4522f09 AB |
150 | |
151 | class HardIRQ(IRQ): | |
0297cb98 AB |
152 | def __init__(self, id, cpu_id, begin_ts): |
153 | super().__init__(id, cpu_id, begin_ts) | |
f4522f09 AB |
154 | self.ret = None |
155 | ||
156 | @classmethod | |
157 | def new_from_irq_handler_entry(cls, event): | |
3f52a605 | 158 | id = event['irq'] |
9b94fe47 | 159 | cpu_id = event['cpu_id'] |
0297cb98 AB |
160 | begin_ts = event.timestamp |
161 | return cls(id, cpu_id, begin_ts) | |
f4522f09 AB |
162 | |
163 | ||
164 | class SoftIRQ(IRQ): | |
0297cb98 AB |
165 | def __init__(self, id, cpu_id, raise_ts=None, begin_ts=None): |
166 | super().__init__(id, cpu_id, begin_ts) | |
f4522f09 AB |
167 | self.raise_ts = raise_ts |
168 | ||
169 | @classmethod | |
170 | def new_from_softirq_raise(cls, event): | |
3f52a605 | 171 | id = event['vec'] |
9b94fe47 | 172 | cpu_id = event['cpu_id'] |
f4522f09 | 173 | raise_ts = event.timestamp |
9b94fe47 | 174 | return cls(id, cpu_id, raise_ts) |
f4522f09 AB |
175 | |
176 | @classmethod | |
177 | def new_from_softirq_entry(cls, event): | |
3f52a605 | 178 | id = event['vec'] |
9b94fe47 | 179 | cpu_id = event['cpu_id'] |
0297cb98 AB |
180 | begin_ts = event.timestamp |
181 | return cls(id, cpu_id, begin_ts=begin_ts) | |
ca95b1c3 | 182 | |
bd3cd7c5 JD |
183 | |
184 | class IORequest(): | |
bd3cd7c5 JD |
185 | # I/O operations |
186 | OP_OPEN = 1 | |
187 | OP_READ = 2 | |
188 | OP_WRITE = 3 | |
189 | OP_CLOSE = 4 | |
190 | OP_SYNC = 5 | |
54106800 AB |
191 | # Operation used for requests that both read and write, |
192 | # e.g. splice and sendfile | |
193 | OP_READ_WRITE = 6 | |
bd3cd7c5 | 194 | |
3b1dda96 AB |
195 | def __init__(self, begin_ts, size, tid, operation): |
196 | self.begin_ts = begin_ts | |
197 | self.end_ts = None | |
bd3cd7c5 | 198 | self.duration = None |
3b1dda96 AB |
199 | # request size in bytes |
200 | self.size = size | |
201 | self.operation = operation | |
202 | # tid of process that triggered the rq | |
203 | self.tid = tid | |
a4a9fb7b AB |
204 | # Error number if request failed |
205 | self.errno = None | |
3b1dda96 | 206 | |
5157d702 AB |
207 | @staticmethod |
208 | def is_equivalent_operation(left_op, right_op): | |
209 | """Predicate used to compare equivalence of IO_OPERATION. | |
210 | ||
211 | This method is employed because OP_READ_WRITE behaves like a | |
212 | set containing both OP_READ and OP_WRITE and is therefore | |
213 | equivalent to these operations as well as itself | |
214 | """ | |
215 | if left_op == IORequest.OP_READ_WRITE: | |
216 | return right_op in [IORequest.OP_READ, IORequest.OP_WRITE, | |
217 | IORequest.OP_READ_WRITE] | |
218 | if left_op == IORequest.OP_READ: | |
219 | return right_op in [IORequest.OP_READ, IORequest.OP_READ_WRITE] | |
220 | if left_op == IORequest.OP_WRITE: | |
221 | return right_op in [IORequest.OP_WRITE, IORequest.OP_READ_WRITE] | |
222 | ||
223 | return left_op == right_op | |
3b1dda96 | 224 | |
320e15ef | 225 | |
3b1dda96 | 226 | class SyscallIORequest(IORequest): |
a4a9fb7b AB |
227 | def __init__(self, begin_ts, size, tid, operation, syscall_name): |
228 | super().__init__(begin_ts, None, tid, operation) | |
bd3cd7c5 | 229 | self.fd = None |
a4a9fb7b | 230 | self.syscall_name = syscall_name |
3b1dda96 AB |
231 | # Number of pages alloc'd/freed/written to disk during the rq |
232 | self.pages_allocated = 0 | |
233 | self.pages_freed = 0 | |
234 | self.pages_written = 0 | |
235 | # Whether kswapd was forced to wakeup during the rq | |
bd3cd7c5 | 236 | self.woke_kswapd = False |
3b1dda96 | 237 | |
a4a9fb7b AB |
238 | def update_from_exit(self, event): |
239 | self.end_ts = event.timestamp | |
240 | self.duration = self.end_ts - self.begin_ts | |
241 | if event['ret'] < 0: | |
242 | self.errno = -event['ret'] | |
243 | ||
244 | ||
245 | class OpenIORequest(SyscallIORequest): | |
246 | def __init__(self, begin_ts, tid, syscall_name, filename, | |
247 | fd_type): | |
248 | super().__init__(begin_ts, None, tid, IORequest.OP_OPEN, syscall_name) | |
249 | # FD set on syscall exit | |
250 | self.fd = None | |
251 | self.filename = filename | |
252 | self.fd_type = fd_type | |
253 | self.family = None | |
254 | self.cloexec = False | |
255 | ||
256 | def update_from_exit(self, event): | |
257 | super().update_from_exit(event) | |
258 | if event['ret'] >= 0: | |
259 | self.fd = event['ret'] | |
260 | ||
261 | @classmethod | |
262 | def new_from_disk_open(cls, event, tid): | |
263 | begin_ts = event.timestamp | |
264 | name = common.get_syscall_name(event) | |
265 | filename = event['filename'] | |
266 | ||
267 | req = cls(begin_ts, tid, name, filename, FDType.disk) | |
268 | req.cloexec = event['flags'] & common.O_CLOEXEC == common.O_CLOEXEC | |
269 | ||
270 | return req | |
271 | ||
272 | @classmethod | |
273 | def new_from_accept(cls, event, tid): | |
274 | # Handle both accept and accept4 | |
275 | begin_ts = event.timestamp | |
276 | name = common.get_syscall_name(event) | |
277 | req = cls(begin_ts, tid, name, 'socket', FDType.net) | |
278 | ||
279 | if 'family' in event: | |
280 | req.family = event['family'] | |
281 | # Set filename to ip:port if INET socket | |
282 | if req.family == socket.AF_INET: | |
283 | req.filename = '%s:%d' % (common.get_v4_addr_str( | |
284 | event['v4addr']), event['sport']) | |
285 | ||
286 | return req | |
287 | ||
288 | @classmethod | |
289 | def new_from_socket(cls, event, tid): | |
290 | begin_ts = event.timestamp | |
291 | req = cls(begin_ts, tid, 'socket', 'socket', FDType.net) | |
292 | ||
293 | if 'family' in event: | |
294 | req.family = event['family'] | |
295 | ||
296 | return req | |
297 | ||
298 | @classmethod | |
299 | def new_from_old_fd(cls, event, tid, old_fd): | |
300 | begin_ts = event.timestamp | |
301 | name = common.get_syscall_name(event) | |
302 | if old_fd is None: | |
303 | filename = 'unknown' | |
304 | fd_type = FDType.unknown | |
305 | else: | |
306 | filename = old_fd.filename | |
307 | fd_type = old_fd.fd_type | |
308 | ||
309 | return cls(begin_ts, tid, name, filename, fd_type) | |
310 | ||
311 | ||
312 | class CloseIORequest(SyscallIORequest): | |
313 | def __init__(self, begin_ts, tid, fd): | |
314 | super().__init__(begin_ts, None, tid, IORequest.OP_CLOSE, 'close') | |
315 | self.fd = fd | |
316 | ||
317 | ||
318 | class ReadWriteIORequest(SyscallIORequest): | |
319 | def __init__(self, begin_ts, size, tid, operation, syscall_name): | |
320 | super().__init__(begin_ts, size, tid, operation, syscall_name) | |
1b645a61 AB |
321 | # The size returned on syscall exit, in bytes. May differ from |
322 | # the size initially requested | |
323 | self.returned_size = None | |
a4a9fb7b AB |
324 | # Unused if fd is set |
325 | self.fd_in = None | |
326 | self.fd_out = None | |
327 | ||
328 | def update_from_exit(self, event): | |
329 | super().update_from_exit(event) | |
330 | ret = event['ret'] | |
331 | if ret >= 0: | |
332 | self.returned_size = ret | |
333 | # Set the size to the returned one if none was set at | |
334 | # entry, as with recvmsg or sendmsg | |
335 | if self.size is None: | |
336 | self.size = ret | |
337 | ||
338 | @classmethod | |
339 | def new_from_splice(cls, event, tid): | |
340 | begin_ts = event.timestamp | |
341 | size = event['len'] | |
342 | ||
54106800 | 343 | req = cls(begin_ts, size, tid, IORequest.OP_READ_WRITE, 'splice') |
a4a9fb7b AB |
344 | req.fd_in = event['fd_in'] |
345 | req.fd_out = event['fd_out'] | |
346 | ||
347 | return req | |
348 | ||
349 | @classmethod | |
350 | def new_from_sendfile64(cls, event, tid): | |
351 | begin_ts = event.timestamp | |
352 | size = event['count'] | |
353 | ||
54106800 | 354 | req = cls(begin_ts, size, tid, IORequest.OP_READ_WRITE, 'sendfile64') |
a4a9fb7b AB |
355 | req.fd_in = event['in_fd'] |
356 | req.fd_out = event['out_fd'] | |
357 | ||
358 | return req | |
359 | ||
360 | @classmethod | |
361 | def new_from_fd_event(cls, event, tid, size_key): | |
362 | begin_ts = event.timestamp | |
363 | # Some events, like recvmsg or sendmsg, only have size info on return | |
364 | if size_key is not None: | |
365 | size = event[size_key] | |
366 | else: | |
367 | size = None | |
368 | ||
369 | syscall_name = common.get_syscall_name(event) | |
370 | if syscall_name in SyscallConsts.READ_SYSCALLS: | |
371 | operation = IORequest.OP_READ | |
372 | else: | |
373 | operation = IORequest.OP_WRITE | |
374 | ||
375 | req = cls(begin_ts, size, tid, operation, syscall_name) | |
376 | req.fd = event['fd'] | |
377 | ||
378 | return req | |
379 | ||
380 | ||
381 | class SyncIORequest(SyscallIORequest): | |
382 | def __init__(self, begin_ts, size, tid, syscall_name): | |
383 | super().__init__(begin_ts, size, tid, IORequest.OP_SYNC, syscall_name) | |
384 | ||
a4a9fb7b AB |
385 | @classmethod |
386 | def new_from_sync(cls, event, tid): | |
387 | begin_ts = event.timestamp | |
388 | size = None | |
389 | ||
390 | return cls(begin_ts, size, tid, 'sync') | |
391 | ||
392 | @classmethod | |
393 | def new_from_fsync(cls, event, tid): | |
394 | # Also handle fdatasync | |
395 | begin_ts = event.timestamp | |
396 | size = None | |
397 | syscall_name = common.get_syscall_name(event) | |
398 | ||
399 | req = cls(begin_ts, size, tid, syscall_name) | |
400 | req.fd = event['fd'] | |
401 | ||
402 | return req | |
403 | ||
404 | @classmethod | |
405 | def new_from_sync_file_range(cls, event, tid): | |
406 | begin_ts = event.timestamp | |
407 | size = event['nbytes'] | |
408 | ||
409 | req = cls(begin_ts, size, tid, 'sync_file_range') | |
410 | req.fd = event['fd'] | |
411 | ||
412 | return req | |
413 | ||
3b1dda96 AB |
414 | |
415 | class BlockIORequest(IORequest): | |
416 | # Logical sector size in bytes, according to the kernel | |
417 | SECTOR_SIZE = 512 | |
418 | ||
419 | def __init__(self, begin_ts, tid, operation, dev, sector, nr_sector): | |
420 | size = nr_sector * BlockIORequest.SECTOR_SIZE | |
421 | super().__init__(begin_ts, size, tid, operation) | |
422 | self.dev = dev | |
423 | self.sector = sector | |
424 | self.nr_sector = nr_sector | |
425 | ||
426 | def update_from_rq_complete(self, event): | |
427 | self.end_ts = event.timestamp | |
428 | self.duration = self.end_ts - self.begin_ts | |
429 | ||
430 | @classmethod | |
431 | def new_from_rq_issue(cls, event): | |
432 | begin_ts = event.timestamp | |
433 | dev = event['dev'] | |
434 | sector = event['sector'] | |
435 | nr_sector = event['nr_sector'] | |
436 | tid = event['tid'] | |
437 | # An even rwbs indicates read operation, odd indicates write | |
438 | if event['rwbs'] % 2 == 0: | |
439 | operation = IORequest.OP_READ | |
440 | else: | |
441 | operation = IORequest.OP_WRITE | |
442 | ||
443 | return cls(begin_ts, tid, operation, dev, sector, nr_sector) | |
444 | ||
445 | ||
446 | class BlockRemapRequest(): | |
447 | def __init__(self, dev, sector, old_dev, old_sector): | |
448 | self.dev = dev | |
449 | self.sector = sector | |
450 | self.old_dev = old_dev | |
451 | self.old_sector = old_sector | |
bd3cd7c5 JD |
452 | |
453 | ||
bd3cd7c5 JD |
454 | class SyscallConsts(): |
455 | # TODO: decouple socket/family logic from this class | |
456 | INET_FAMILIES = [socket.AF_INET, socket.AF_INET6] | |
457 | DISK_FAMILIES = [socket.AF_UNIX] | |
458 | # list nof syscalls that open a FD on disk (in the exit_syscall event) | |
baa4cfe9 | 459 | DISK_OPEN_SYSCALLS = ['open', 'openat'] |
bd3cd7c5 JD |
460 | # list of syscalls that open a FD on the network |
461 | # (in the exit_syscall event) | |
a31d2d56 | 462 | NET_OPEN_SYSCALLS = ['socket'] |
bd3cd7c5 | 463 | # list of syscalls that can duplicate a FD |
9110f542 | 464 | DUP_OPEN_SYSCALLS = ['fcntl', 'dup', 'dup2', 'dup3'] |
baa4cfe9 | 465 | SYNC_SYSCALLS = ['sync', 'sync_file_range', 'fsync', 'fdatasync'] |
bd3cd7c5 JD |
466 | # merge the 3 open lists |
467 | OPEN_SYSCALLS = DISK_OPEN_SYSCALLS + NET_OPEN_SYSCALLS + DUP_OPEN_SYSCALLS | |
3f52a605 | 468 | # list of syscalls that close a FD (in the 'fd =' field) |
baa4cfe9 | 469 | CLOSE_SYSCALLS = ['close'] |
bd3cd7c5 | 470 | # list of syscall that read on a FD, value in the exit_syscall following |
a692ae7b AB |
471 | READ_SYSCALLS = ['read', 'recvmsg', 'recvfrom', 'readv', 'pread', |
472 | 'pread64', 'preadv'] | |
bd3cd7c5 | 473 | # list of syscall that write on a FD, value in the exit_syscall following |
a692ae7b AB |
474 | WRITE_SYSCALLS = ['write', 'sendmsg', 'sendto', 'writev', 'pwrite', |
475 | 'pwrite64', 'pwritev'] | |
54106800 AB |
476 | # list of syscalls that both read and write on two FDs |
477 | READ_WRITE_SYSCALLS = ['splice', 'sendfile64'] | |
21167679 JD |
478 | # All I/O related syscalls |
479 | IO_SYSCALLS = OPEN_SYSCALLS + CLOSE_SYSCALLS + READ_SYSCALLS + \ | |
54106800 | 480 | WRITE_SYSCALLS + SYNC_SYSCALLS + READ_WRITE_SYSCALLS |