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