c18d6ded4632f5815da36a1e3747b1073e251c21
[deliverable/lttng-analyses.git] / lttnganalyses / linuxautomaton / sv.py
1 # The MIT License (MIT)
2 #
3 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
4 # 2015 - Antoine Busque <abusque@efficios.com>
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
24 import socket
25 from . import common
26
27
28 class StateVariable:
29 pass
30
31
32 class Process():
33 def __init__(self, tid=None, pid=None, comm='', prio=None):
34 self.tid = tid
35 self.pid = pid
36 self.comm = comm
37 self.prio = prio
38 # indexed by fd
39 self.fds = {}
40 self.current_syscall = None
41 # the process scheduled before this one
42 self.prev_tid = None
43 self.last_wakeup = None
44 self.last_waker = None
45
46
47 class CPU():
48 def __init__(self, cpu_id):
49 self.cpu_id = cpu_id
50 self.current_tid = None
51 self.current_hard_irq = None
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 = {}
56
57
58 class MemoryManagement():
59 def __init__(self):
60 self.page_count = 0
61
62
63 class SyscallEvent():
64 def __init__(self, name, begin_ts):
65 self.name = name
66 self.begin_ts = begin_ts
67 self.end_ts = None
68 self.ret = None
69 self.duration = None
70 # Only applicable to I/O syscalls
71 self.io_rq = None
72
73 def process_exit(self, event):
74 self.end_ts = event.timestamp
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
81 self.duration = self.end_ts - self.begin_ts
82
83 @classmethod
84 def new_from_entry(cls, event):
85 name = common.get_syscall_name(event)
86 return cls(name, event.timestamp)
87
88
89 class Disk():
90 def __init__(self):
91 # pending block IO Requests, indexed by sector
92 self.pending_requests = {}
93
94
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
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
116
117 class FD():
118 def __init__(self, fd, filename='unknown', fd_type=FDType.unknown,
119 cloexec=False, family=None):
120 self.fd = fd
121 self.filename = filename
122 self.fd_type = fd_type
123 self.cloexec = cloexec
124 self.family = family
125
126 @classmethod
127 def new_from_fd(cls, fd):
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)
134
135
136 class IRQ():
137 def __init__(self, id, cpu_id, begin_ts=None):
138 self.id = id
139 self.cpu_id = cpu_id
140 self.begin_ts = begin_ts
141 self.end_ts = None
142
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
150
151 class HardIRQ(IRQ):
152 def __init__(self, id, cpu_id, begin_ts):
153 super().__init__(id, cpu_id, begin_ts)
154 self.ret = None
155
156 @classmethod
157 def new_from_irq_handler_entry(cls, event):
158 id = event['irq']
159 cpu_id = event['cpu_id']
160 begin_ts = event.timestamp
161 return cls(id, cpu_id, begin_ts)
162
163
164 class SoftIRQ(IRQ):
165 def __init__(self, id, cpu_id, raise_ts=None, begin_ts=None):
166 super().__init__(id, cpu_id, begin_ts)
167 self.raise_ts = raise_ts
168
169 @classmethod
170 def new_from_softirq_raise(cls, event):
171 id = event['vec']
172 cpu_id = event['cpu_id']
173 raise_ts = event.timestamp
174 return cls(id, cpu_id, raise_ts)
175
176 @classmethod
177 def new_from_softirq_entry(cls, event):
178 id = event['vec']
179 cpu_id = event['cpu_id']
180 begin_ts = event.timestamp
181 return cls(id, cpu_id, begin_ts=begin_ts)
182
183
184 class IORequest():
185 # I/O operations
186 OP_OPEN = 1
187 OP_READ = 2
188 OP_WRITE = 3
189 OP_CLOSE = 4
190 OP_SYNC = 5
191 # Operation used for requests that both read and write,
192 # e.g. splice and sendfile
193 OP_READ_WRITE = 6
194
195 def __init__(self, begin_ts, size, tid, operation):
196 self.begin_ts = begin_ts
197 self.end_ts = None
198 self.duration = None
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
204 # Error number if request failed
205 self.errno = None
206
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
224
225
226 class SyscallIORequest(IORequest):
227 def __init__(self, begin_ts, size, tid, operation, syscall_name):
228 super().__init__(begin_ts, None, tid, operation)
229 self.fd = None
230 self.syscall_name = syscall_name
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
236 self.woke_kswapd = False
237
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)
321 # The size returned on syscall exit, in bytes. May differ from
322 # the size initially requested
323 self.returned_size = None
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
343 req = cls(begin_ts, size, tid, IORequest.OP_READ_WRITE, 'splice')
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
354 req = cls(begin_ts, size, tid, IORequest.OP_READ_WRITE, 'sendfile64')
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
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
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
452
453
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)
459 DISK_OPEN_SYSCALLS = ['open', 'openat']
460 # list of syscalls that open a FD on the network
461 # (in the exit_syscall event)
462 NET_OPEN_SYSCALLS = ['socket']
463 # list of syscalls that can duplicate a FD
464 DUP_OPEN_SYSCALLS = ['fcntl', 'dup', 'dup2', 'dup3']
465 SYNC_SYSCALLS = ['sync', 'sync_file_range', 'fsync', 'fdatasync']
466 # merge the 3 open lists
467 OPEN_SYSCALLS = DISK_OPEN_SYSCALLS + NET_OPEN_SYSCALLS + DUP_OPEN_SYSCALLS
468 # list of syscalls that close a FD (in the 'fd =' field)
469 CLOSE_SYSCALLS = ['close']
470 # list of syscall that read on a FD, value in the exit_syscall following
471 READ_SYSCALLS = ['read', 'recvmsg', 'recvfrom', 'readv', 'pread',
472 'pread64', 'preadv']
473 # list of syscall that write on a FD, value in the exit_syscall following
474 WRITE_SYSCALLS = ['write', 'sendmsg', 'sendto', 'writev', 'pwrite',
475 'pwrite64', 'pwritev']
476 # list of syscalls that both read and write on two FDs
477 READ_WRITE_SYSCALLS = ['splice', 'sendfile64']
478 # All I/O related syscalls
479 IO_SYSCALLS = OPEN_SYSCALLS + CLOSE_SYSCALLS + READ_SYSCALLS + \
480 WRITE_SYSCALLS + SYNC_SYSCALLS + READ_WRITE_SYSCALLS
This page took 0.040153 seconds and 4 git commands to generate.