fix: stats with 0 requests
[deliverable/lttng-analyses.git] / linuxautomaton / linuxautomaton / io.py
1 #!/usr/bin/env python3
2 #
3 # The MIT License (MIT)
4 #
5 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
6 # 2015 - Antoine Busque <abusque@efficios.com>
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining a copy
9 # of this software and associated documentation files (the "Software"), to deal
10 # in the Software without restriction, including without limitation the rights
11 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 # copies of the Software, and to permit persons to whom the Software is
13 # furnished to do so, subject to the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 # SOFTWARE.
25
26 import socket
27 from linuxautomaton import sp, sv, common
28 from babeltrace import CTFScope
29
30
31 class IoStateProvider(sp.StateProvider):
32 def __init__(self, state):
33 cbs = {
34 'syscall_entry': self._process_syscall_entry,
35 'syscall_exit': self._process_syscall_exit,
36 'syscall_entry_connect': self._process_connect,
37 'writeback_pages_written': self._process_writeback_pages_written,
38 'mm_vmscan_wakeup_kswapd': self._process_mm_vmscan_wakeup_kswapd,
39 'mm_page_free': self._process_mm_page_free
40 }
41
42 self._state = state
43 self._register_cbs(cbs)
44
45 def process_event(self, ev):
46 self._process_event_cb(ev)
47
48 def _process_syscall_entry(self, event):
49 # Only handle IO Syscalls
50 name = common.get_syscall_name(event)
51 if name not in sv.SyscallConsts.IO_SYSCALLS:
52 return
53
54 cpu_id = event['cpu_id']
55 if cpu_id not in self._state.cpus:
56 return
57
58 cpu = self._state.cpus[cpu_id]
59 if cpu.current_tid is None:
60 return
61
62 proc = self._state.tids[cpu.current_tid]
63
64 # check if we can fix the pid from a context
65 self._fix_context_pid(event, proc)
66
67 if name in sv.SyscallConsts.OPEN_SYSCALLS:
68 self._track_open(event, name, proc)
69 elif name in sv.SyscallConsts.CLOSE_SYSCALLS:
70 self._track_close(event, name, proc)
71 elif name in sv.SyscallConsts.READ_SYSCALLS or \
72 name in sv.SyscallConsts.WRITE_SYSCALLS:
73 self._track_read_write(event, name, proc)
74 elif name in sv.SyscallConsts.SYNC_SYSCALLS:
75 self._track_sync(event, name, proc)
76
77 def _process_syscall_exit(self, event):
78 cpu_id = event['cpu_id']
79 if cpu_id not in self._state.cpus:
80 return
81
82 cpu = self._state.cpus[cpu_id]
83 if cpu.current_tid is None:
84 return
85
86 proc = self._state.tids[cpu.current_tid]
87 current_syscall = proc.current_syscall
88 if current_syscall is None:
89 return
90
91 name = current_syscall.name
92 if name not in sv.SyscallConsts.IO_SYSCALLS:
93 return
94
95 self._track_io_rq_exit(event, proc)
96
97 proc.current_syscall = None
98
99 def _process_connect(self, event):
100 cpu_id = event['cpu_id']
101 if cpu_id not in self._state.cpus:
102 return
103
104 cpu = self._state.cpus[cpu_id]
105 if cpu.current_tid is None:
106 return
107
108 proc = self._state.tids[cpu.current_tid]
109 parent_proc = self._get_parent_proc(proc)
110
111 # FIXME: handle on syscall_exit_connect only when succesful
112 if 'family' in event and event['family'] == socket.AF_INET:
113 fd = event['fd']
114 if fd in parent_proc.fds:
115 parent_proc.fds[fd].filename = (
116 '%s:%d' % (common.get_v4_addr_str(event['v4addr']),
117 event['dport']))
118
119 def _process_writeback_pages_written(self, event):
120 for cpu in self._state.cpus.values():
121 if cpu.current_tid is None:
122 continue
123
124 current_syscall = self._state.tids[cpu.current_tid].current_syscall
125 if current_syscall is None:
126 continue
127
128 if current_syscall.io_rq:
129 current_syscall.io_rq.pages_written += event['pages']
130
131 def _process_mm_vmscan_wakeup_kswapd(self, event):
132 cpu_id = event['cpu_id']
133 if cpu_id not in self._state.cpus:
134 return
135
136 cpu = self._state.cpus[cpu_id]
137 if cpu.current_tid is None:
138 return
139
140 current_syscall = self._state.tids[cpu.current_tid].current_syscall
141 if current_syscall is None:
142 return
143
144 if current_syscall.io_rq:
145 current_syscall.io_rq.woke_kswapd = True
146
147 def _process_mm_page_free(self, event):
148 for cpu in self._state.cpus.values():
149 if cpu.current_tid is None:
150 continue
151
152 proc = self._state.tids[cpu.current_tid]
153
154 # if the current process is kswapd0, we need to
155 # attribute the page freed to the process that
156 # woke it up.
157 if proc.comm == 'kswapd0' and proc.prev_tid > 0:
158 proc = self._state.tids[proc.prev_tid]
159
160 current_syscall = proc.current_syscall
161 if current_syscall is None:
162 continue
163
164 if current_syscall.io_rq and current_syscall.io_rq.woke_kswapd:
165 current_syscall.io_rq.pages_freed += 1
166
167 def _track_open(self, event, name, proc):
168 current_syscall = proc.current_syscall
169 if name in sv.SyscallConsts.DISK_OPEN_SYSCALLS:
170 current_syscall.io_rq = sv.OpenIORequest.new_from_disk_open(
171 event, proc.tid)
172 elif name in ['accept', 'accept4']:
173 current_syscall.io_rq = sv.OpenIORequest.new_from_accept(
174 event, proc.tid)
175 elif name == 'socket':
176 current_syscall.io_rq = sv.OpenIORequest.new_from_socket(
177 event, proc.tid)
178 elif name in sv.SyscallConsts.DUP_OPEN_SYSCALLS:
179 self._track_dup(event, name, proc)
180
181 def _track_dup(self, event, name, proc):
182 current_syscall = proc.current_syscall
183
184 # If the process that triggered the io_rq is a thread,
185 # its FDs are that of the parent process
186 parent_proc = self._get_parent_proc(proc)
187 fds = parent_proc.fds
188
189 if name == 'dup':
190 oldfd = event['fildes']
191 elif name in ['dup2', 'dup3']:
192 oldfd = event['oldfd']
193 newfd = event['newfd']
194 if newfd in fds:
195 self._close_fd(parent_proc, newfd, event.timestamp)
196 elif name == 'fcntl':
197 # Only handle if cmd == F_DUPFD (0)
198 if event['cmd'] != 0:
199 return
200
201 oldfd = event['fd']
202
203 old_file = None
204 if oldfd in fds:
205 old_file = fds[oldfd]
206
207 current_syscall.io_rq = sv.OpenIORequest.new_from_old_fd(
208 event, proc.tid, old_file)
209
210 if name == 'dup3':
211 cloexec = event['flags'] & common.O_CLOEXEC == common.O_CLOEXEC
212 current_syscall.io_rq.cloexec = cloexec
213
214 def _track_close(self, event, name, proc):
215 proc.current_syscall.io_rq = sv.CloseIORequest(
216 event.timestamp, proc.tid, event['fd'])
217
218 def _track_read_write(self, event, name, proc):
219 current_syscall = proc.current_syscall
220
221 if name == 'splice':
222 current_syscall.io_rq = sv.ReadWriteIORequest.new_from_splice(
223 event, proc.tid)
224 return
225 elif name == 'sendfile64':
226 current_syscall.io_rq = sv.ReadWriteIORequest.new_from_sendfile64(
227 event, proc.tid)
228 return
229
230 if name in ['writev', 'pwritev', 'readv', 'preadv']:
231 size_key = 'vlen'
232 elif name == 'recvfrom':
233 size_key = 'size'
234 elif name == 'sendto':
235 size_key = 'len'
236 elif name in ['recvmsg', 'sendmsg']:
237 size_key = None
238 else:
239 size_key = 'count'
240
241 current_syscall.io_rq = sv.ReadWriteIORequest.new_from_fd_event(
242 event, proc.tid, size_key)
243
244 def _track_sync(self, event, name, proc):
245 current_syscall = proc.current_syscall
246
247 if name == 'sync':
248 current_syscall.io_rq = sv.SyncIORequest.new_from_sync(
249 event, proc.tid)
250 elif name in ['fsync', 'fdatasync']:
251 current_syscall.io_rq = sv.SyncIORequest.new_from_fsync(
252 event, proc.tid)
253 elif name == 'sync_file_range':
254 current_syscall.io_rq = sv.SyncIORequest.new_from_sync_file_range(
255 event, proc.tid)
256
257 def _track_io_rq_exit(self, event, proc):
258 ret = event['ret']
259 io_rq = proc.current_syscall.io_rq
260 # io_rq can be None in the case of fcntl when cmd is not
261 # F_DUPFD, in which case we disregard the syscall as it did
262 # not open any FD
263 if io_rq is None:
264 return
265
266 io_rq.update_from_exit(event)
267
268 if ret >= 0:
269 self._create_fd(proc, io_rq)
270
271 parent_proc = self._get_parent_proc(proc)
272 self._state.send_notification_cb('io_rq_exit',
273 io_rq=io_rq,
274 proc=proc,
275 parent_proc=parent_proc)
276
277 if isinstance(io_rq, sv.CloseIORequest) and ret == 0:
278 self._close_fd(proc, io_rq.fd, io_rq.end_ts)
279
280 def _create_fd(self, proc, io_rq):
281 parent_proc = self._get_parent_proc(proc)
282
283 if io_rq.fd is not None and io_rq.fd not in parent_proc.fds:
284 if isinstance(io_rq, sv.OpenIORequest):
285 parent_proc.fds[io_rq.fd] = sv.FD.new_from_open_rq(io_rq)
286 else:
287 parent_proc.fds[io_rq.fd] = sv.FD(io_rq.fd)
288
289 self._state.send_notification_cb('create_fd',
290 fd=io_rq.fd,
291 parent_proc=parent_proc,
292 timestamp=io_rq.end_ts)
293 elif isinstance(io_rq, sv.ReadWriteIORequest):
294 if io_rq.fd_in is not None and io_rq.fd_in not in parent_proc.fds:
295 parent_proc.fds[io_rq.fd_in] = sv.FD(io_rq.fd_in)
296 self._state.send_notification_cb('create_fd',
297 fd=io_rq.fd_in,
298 parent_proc=parent_proc,
299 timestamp=io_rq.end_ts)
300
301 if io_rq.fd_out is not None and \
302 io_rq.fd_out not in parent_proc.fds:
303 parent_proc.fds[io_rq.fd_out] = sv.FD(io_rq.fd_out)
304 self._state.send_notification_cb('create_fd',
305 fd=io_rq.fd_out,
306 parent_proc=parent_proc,
307 timestamp=io_rq.end_ts)
308
309 def _close_fd(self, proc, fd, timestamp):
310 parent_proc = self._get_parent_proc(proc)
311 self._state.send_notification_cb('close_fd',
312 fd=fd,
313 parent_proc=parent_proc,
314 timestamp=timestamp)
315 del parent_proc.fds[fd]
316
317 def _get_parent_proc(self, proc):
318 if proc.pid is not None and proc.tid != proc.pid:
319 parent_proc = self._state.tids[proc.pid]
320 else:
321 parent_proc = proc
322
323 return parent_proc
324
325 def _fix_context_pid(self, event, proc):
326 for context in event.field_list_with_scope(
327 CTFScope.STREAM_EVENT_CONTEXT):
328 if context != 'pid':
329 continue
330 # make sure the 'pid' field is not also in the event
331 # payload, otherwise we might clash
332 for context in event.field_list_with_scope(
333 CTFScope.EVENT_FIELDS):
334 if context == 'pid':
335 return
336
337 if proc.pid is None:
338 proc.pid = event['pid']
339 if event['pid'] != proc.tid:
340 proc.pid = event['pid']
341 parent_proc = sv.Process(proc.pid, proc.pid, proc.comm)
342 self._state.tids[parent_proc.pid] = parent_proc
This page took 0.074305 seconds and 5 git commands to generate.