Refactor utils from linuxautomaton/common into the common package
[deliverable/lttng-analyses.git] / lttnganalyses / linuxautomaton / sv.py
CommitLineData
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 24import os
bd3cd7c5 25import socket
9079847d 26from ..common import format_utils, trace_utils
bd3cd7c5
JD
27
28
bd3cd7c5 29class 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
44class 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
55class MemoryManagement():
56 def __init__(self):
57 self.page_count = 0
58
5ea5dc05 59
21167679 60class 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
87class 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
93class 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
115class 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
134class 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
149class 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
162class 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
182class 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 224class 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
243class 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
311class 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
317class 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
380class 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
414class 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
445class 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
453class 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
This page took 0.047133 seconds and 5 git commands to generate.