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