Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # The MIT License (MIT) | |
4 | # | |
a3fa57c0 | 5 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
4ed24f86 JD |
6 | # |
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | # of this software and associated documentation files (the "Software"), to deal | |
9 | # in the Software without restriction, including without limitation the rights | |
10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | # copies of the Software, and to permit persons to whom the Software is | |
12 | # furnished to do so, subject to the following conditions: | |
13 | # | |
14 | # The above copyright notice and this permission notice shall be included in | |
15 | # all copies or substantial portions of the Software. | |
16 | # | |
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
23 | # SOFTWARE. | |
24 | ||
a9c9a2a6 JD |
25 | import socket |
26 | import operator | |
27 | from linuxautomaton import sp, sv, common | |
49fcdb4f | 28 | from babeltrace import CTFScope |
a9c9a2a6 JD |
29 | |
30 | ||
31 | class IOCategory(): | |
32 | """Defines an enumeration mapping IO categories to integer values. | |
33 | Used mainly to export syscall metadata (to JSON).""" | |
34 | ||
35 | invalid = 0 | |
36 | # Can't use open as a name given that is is a built-in function | |
37 | # TODO: find less stupid name | |
38 | opn = 1 | |
39 | close = 2 | |
40 | read = 3 | |
41 | write = 4 | |
42 | ||
43 | ||
44 | class SyscallsStateProvider(sp.StateProvider): | |
45 | def __init__(self, state): | |
46 | self.state = state | |
47 | self.cpus = state.cpus | |
48 | self.tids = state.tids | |
49 | self.syscalls = state.syscalls | |
50 | self.pending_syscalls = state.pending_syscalls | |
51 | self.syscalls["total"] = 0 | |
52 | self.dirty_pages = state.dirty_pages | |
53 | cbs = { | |
54 | 'syscall_entry': self._process_syscall_entry, | |
55 | 'syscall_exit': self._process_syscall_exit, | |
12117527 | 56 | 'writeback_pages_written': self._process_writeback_pages_written, |
30059169 | 57 | 'mm_vmscan_wakeup_kswapd': self._process_mm_vmscan_wakeup_kswapd, |
2f163c78 | 58 | 'mm_page_free': self._process_mm_page_free, |
a9c9a2a6 JD |
59 | } |
60 | self._register_cbs(cbs) | |
61 | ||
62 | def process_event(self, ev): | |
63 | self._process_event_cb(ev) | |
64 | ||
65 | def get_syscall_category(self, name): | |
66 | """Receives a syscall name and returns an enum value | |
67 | representing its IO category (open, close, read, or write)" | |
68 | ||
69 | This is used to produce json data for visualization""" | |
70 | ||
71 | if name in sv.SyscallConsts.OPEN_SYSCALLS: | |
72 | return IOCategory.opn | |
73 | if name in sv.SyscallConsts.CLOSE_SYSCALLS: | |
74 | return IOCategory.close | |
75 | if name in sv.SyscallConsts.READ_SYSCALLS: | |
76 | return IOCategory.read | |
77 | if name in sv.SyscallConsts.WRITE_SYSCALLS: | |
78 | return IOCategory.write | |
79 | ||
80 | return IOCategory.invalid | |
81 | ||
82 | def get_fd_type(self, name, family): | |
83 | if name in sv.SyscallConsts.NET_OPEN_SYSCALLS: | |
84 | if family in sv.SyscallConsts.INET_FAMILIES: | |
85 | return sv.FDType.net | |
86 | if family in sv.SyscallConsts.DISK_FAMILIES: | |
87 | return sv.FDType.disk | |
88 | ||
89 | if name in sv.SyscallConsts.DISK_OPEN_SYSCALLS: | |
90 | return sv.FDType.disk | |
91 | ||
92 | return sv.FDType.unknown | |
93 | ||
94 | def global_syscall_entry(self, name): | |
95 | if name not in self.syscalls: | |
96 | s = sv.Syscall() | |
97 | s.name = name | |
98 | s.count = 0 | |
99 | self.syscalls[name] = s | |
100 | else: | |
101 | s = self.syscalls[name] | |
102 | s.count += 1 | |
103 | self.syscalls["total"] += 1 | |
104 | ||
7cc161dc JD |
105 | def override_name(self, name, event): |
106 | if name in ["syscall_entry_epoll_ctl"]: | |
107 | if event["op"] == 1: | |
108 | name = "%s-ADD" % (name) | |
109 | elif event["op"] == 2: | |
110 | name = "%s-DEL" % (name) | |
111 | elif event["op"] == 3: | |
112 | name = "%s-MODE" % (name) | |
113 | return name | |
114 | ||
21167679 | 115 | def per_tid_syscall_entry(self, name, cpu_id, event): |
a9c9a2a6 JD |
116 | # we don't know which process is currently on this CPU |
117 | if cpu_id not in self.cpus: | |
118 | return | |
119 | c = self.cpus[cpu_id] | |
120 | if c.current_tid == -1: | |
121 | return | |
122 | t = self.tids[c.current_tid] | |
123 | t.total_syscalls += 1 | |
595f478e | 124 | name = self.override_name(name, event) |
a9c9a2a6 JD |
125 | if name not in t.syscalls: |
126 | s = sv.Syscall() | |
127 | s.name = name | |
128 | t.syscalls[name] = s | |
129 | else: | |
130 | s = t.syscalls[name] | |
131 | s.count += 1 | |
21167679 JD |
132 | current_syscall = t.current_syscall |
133 | current_syscall["name"] = name | |
134 | current_syscall["start"] = event.timestamp | |
595f478e | 135 | self.global_syscall_entry(name) |
a9c9a2a6 JD |
136 | |
137 | def track_open(self, name, proc, event, cpu): | |
138 | self.tids[cpu.current_tid].current_syscall = {} | |
139 | current_syscall = self.tids[cpu.current_tid].current_syscall | |
140 | if name in sv.SyscallConsts.DISK_OPEN_SYSCALLS: | |
141 | current_syscall["filename"] = event["filename"] | |
142 | if event["flags"] & common.O_CLOEXEC == common.O_CLOEXEC: | |
143 | current_syscall["cloexec"] = 1 | |
144 | elif name in ["sys_accept", "syscall_entry_accept"]: | |
145 | if "family" in event.keys() and event["family"] == socket.AF_INET: | |
146 | ipport = "%s:%d" % (common.get_v4_addr_str(event["v4addr"]), | |
147 | event["sport"]) | |
148 | current_syscall["filename"] = ipport | |
149 | else: | |
150 | current_syscall["filename"] = "socket" | |
151 | elif name in sv.SyscallConsts.NET_OPEN_SYSCALLS: | |
152 | current_syscall["filename"] = "socket" | |
153 | elif name in ["sys_dup2", "syscall_entry_dup2"]: | |
154 | newfd = event["newfd"] | |
155 | oldfd = event["oldfd"] | |
156 | if newfd in proc.fds.keys(): | |
157 | self.close_fd(proc, newfd) | |
158 | if oldfd in proc.fds.keys(): | |
159 | current_syscall["filename"] = proc.fds[oldfd].filename | |
160 | current_syscall["fdtype"] = proc.fds[oldfd].fdtype | |
161 | else: | |
162 | current_syscall["filename"] = "" | |
163 | elif name in ["sys_fcntl", "syscall_entry_fcntl"]: | |
164 | # F_DUPsv.FD | |
165 | if event["cmd"] != 0: | |
166 | return | |
167 | oldfd = event["fd"] | |
168 | if oldfd in proc.fds.keys(): | |
169 | current_syscall["filename"] = proc.fds[oldfd].filename | |
170 | current_syscall["fdtype"] = proc.fds[oldfd].fdtype | |
171 | else: | |
172 | current_syscall["filename"] = "" | |
173 | ||
174 | if name in sv.SyscallConsts.NET_OPEN_SYSCALLS and \ | |
175 | "family" in event.keys(): | |
176 | family = event["family"] | |
177 | current_syscall["family"] = family | |
178 | else: | |
179 | family = socket.AF_UNSPEC | |
180 | current_syscall["family"] = family | |
181 | ||
182 | current_syscall["name"] = name | |
183 | current_syscall["start"] = event.timestamp | |
184 | current_syscall["fdtype"] = self.get_fd_type(name, family) | |
185 | ||
186 | def close_fd(self, proc, fd): | |
187 | filename = proc.fds[fd].filename | |
188 | if filename not in sv.SyscallConsts.GENERIC_NAMES \ | |
189 | and filename in proc.closed_fds.keys(): | |
190 | f = proc.closed_fds[filename] | |
191 | f.close += 1 | |
192 | f.net_read += proc.fds[fd].net_read | |
193 | f.disk_read += proc.fds[fd].disk_read | |
194 | f.net_write += proc.fds[fd].net_write | |
195 | f.disk_write += proc.fds[fd].disk_write | |
196 | else: | |
197 | proc.closed_fds[filename] = proc.fds[fd] | |
198 | proc.closed_fds[filename].close = 1 | |
199 | # print("Close sv.FD %s in %d (%d, %d, %d, %d)" % | |
200 | # (filename, proc.tid, proc.fds[fd].read, proc.fds[fd].write, | |
201 | # proc.fds[fd].open, proc.fds[fd].close)) | |
202 | proc.fds.pop(fd, None) | |
203 | ||
204 | def track_close(self, name, proc, event, cpu): | |
205 | fd = event["fd"] | |
206 | if fd not in proc.fds.keys(): | |
207 | return | |
208 | ||
209 | tid = self.tids[cpu.current_tid] | |
210 | tid.current_syscall = {} | |
211 | current_syscall = tid.current_syscall | |
212 | current_syscall["filename"] = proc.fds[fd].filename | |
213 | current_syscall["name"] = name | |
214 | current_syscall["start"] = event.timestamp | |
215 | ||
216 | self.close_fd(proc, fd) | |
217 | ||
49fcdb4f JD |
218 | def _fix_context_pid(self, event, t): |
219 | for context in event.field_list_with_scope( | |
220 | CTFScope.STREAM_EVENT_CONTEXT): | |
221 | if context != "pid": | |
222 | continue | |
223 | # make sure the "pid" field is not also in the event | |
224 | # payload, otherwise we might clash | |
225 | for context in event.field_list_with_scope( | |
226 | CTFScope.EVENT_FIELDS): | |
227 | if context == "pid": | |
228 | return | |
229 | if t.pid == -1: | |
230 | t.pid == event["pid"] | |
231 | if event["pid"] != t.tid: | |
232 | t.pid = event["pid"] | |
233 | p = sv.Process() | |
234 | p.tid = t.pid | |
235 | p.pid = t.pid | |
236 | p.comm = t.comm | |
237 | self.tids[p.pid] = p | |
238 | ||
a9c9a2a6 JD |
239 | def track_fds(self, name, event, cpu_id): |
240 | # we don't know which process is currently on this CPU | |
241 | ret_string = "" | |
242 | if cpu_id not in self.cpus: | |
243 | return | |
244 | c = self.cpus[cpu_id] | |
245 | if c.current_tid == -1: | |
246 | return | |
247 | t = self.tids[c.current_tid] | |
248 | # check if we can fix the pid from a context | |
49fcdb4f | 249 | self._fix_context_pid(event, t) |
a9c9a2a6 JD |
250 | # if it's a thread, we want the parent |
251 | if t.pid != -1 and t.tid != t.pid: | |
252 | t = self.tids[t.pid] | |
253 | if name in sv.SyscallConsts.OPEN_SYSCALLS: | |
254 | self.track_open(name, t, event, c) | |
255 | elif name in sv.SyscallConsts.CLOSE_SYSCALLS: | |
256 | ret_string = "%s %s(%d)" % \ | |
257 | (common.ns_to_hour_nsec(event.timestamp), | |
258 | name, event["fd"]) | |
259 | self.track_close(name, t, event, c) | |
260 | # when a connect occurs, no new sv.FD is returned, but we can fix | |
261 | # the "filename" if we have the destination info | |
262 | elif name in ["sys_connect", "syscall_entry_connect"] \ | |
263 | and "family" in event.keys(): | |
264 | if event["family"] == socket.AF_INET: | |
69502021 | 265 | fd = self.get_fd(t, event["fd"], event) |
a9c9a2a6 JD |
266 | ipport = "%s:%d" % (common.get_v4_addr_str(event["v4addr"]), |
267 | event["dport"]) | |
268 | fd.filename = ipport | |
269 | return ret_string | |
270 | ||
69502021 | 271 | def get_fd(self, proc, fd, event): |
a9c9a2a6 JD |
272 | if fd not in proc.fds.keys(): |
273 | f = sv.FD() | |
274 | f.fd = fd | |
275 | f.filename = "unknown (origin not found)" | |
276 | proc.fds[fd] = f | |
277 | else: | |
278 | f = proc.fds[fd] | |
44823c31 AB |
279 | |
280 | proc.track_chrono_fd(fd, f.filename, f.fdtype, event.timestamp) | |
281 | ||
a9c9a2a6 JD |
282 | return f |
283 | ||
284 | def track_sync(self, name, event, cpu_id): | |
285 | # we don't know which process is currently on this CPU | |
286 | if cpu_id not in self.cpus: | |
287 | return | |
288 | c = self.cpus[cpu_id] | |
289 | if c.current_tid == -1: | |
290 | return | |
291 | t = self.tids[c.current_tid] | |
292 | self.pending_syscalls.append(t) | |
293 | # if it's a thread, we want the parent | |
294 | if t.pid != -1 and t.tid != t.pid: | |
295 | t = self.tids[t.pid] | |
296 | current_syscall = self.tids[c.current_tid].current_syscall | |
297 | current_syscall["name"] = name | |
298 | current_syscall["start"] = event.timestamp | |
299 | if name not in ["sys_sync", "syscall_entry_sync"]: | |
300 | fd = event["fd"] | |
69502021 | 301 | f = self.get_fd(t, fd, event) |
a9c9a2a6 JD |
302 | current_syscall["fd"] = f |
303 | current_syscall["filename"] = f.filename | |
304 | ||
305 | def track_read_write(self, name, event, cpu_id): | |
306 | # we don't know which process is currently on this CPU | |
307 | if cpu_id not in self.cpus: | |
308 | return | |
309 | c = self.cpus[cpu_id] | |
310 | if c.current_tid == -1: | |
311 | return | |
312 | t = self.tids[c.current_tid] | |
313 | self.pending_syscalls.append(t) | |
314 | # if it's a thread, we want the parent | |
315 | if t.pid != -1 and t.tid != t.pid: | |
316 | t = self.tids[t.pid] | |
317 | current_syscall = self.tids[c.current_tid].current_syscall | |
318 | current_syscall["name"] = name | |
319 | current_syscall["start"] = event.timestamp | |
320 | if name in ["sys_splice", "syscall_entry_splice"]: | |
69502021 AB |
321 | current_syscall["fd_in"] = self.get_fd(t, event["fd_in"], event) |
322 | current_syscall["fd_out"] = self.get_fd(t, event["fd_out"], event) | |
a9c9a2a6 JD |
323 | current_syscall["count"] = event["len"] |
324 | current_syscall["filename"] = current_syscall["fd_in"].filename | |
325 | return | |
326 | elif name in ["sys_sendfile64", "syscall_entry_sendfile64"]: | |
69502021 AB |
327 | current_syscall["fd_in"] = self.get_fd(t, event["in_fd"], event) |
328 | current_syscall["fd_out"] = self.get_fd(t, event["out_fd"], event) | |
a9c9a2a6 JD |
329 | current_syscall["count"] = event["count"] |
330 | current_syscall["filename"] = current_syscall["fd_in"].filename | |
331 | return | |
332 | fd = event["fd"] | |
69502021 | 333 | f = self.get_fd(t, fd, event) |
a9c9a2a6 JD |
334 | current_syscall["fd"] = f |
335 | if name in ["sys_writev", "syscall_entry_writev", | |
336 | "sys_readv", "syscall_entry_readv"]: | |
337 | current_syscall["count"] = event["vlen"] | |
338 | elif name in ["sys_recvfrom", "syscall_entry_recvfrom"]: | |
339 | current_syscall["count"] = event["size"] | |
340 | elif name in ["sys_recvmsg", "syscall_entry_recvmsg", | |
341 | "sys_sendmsg", "syscall_entry_sendmsg"]: | |
342 | current_syscall["count"] = "" | |
343 | elif name in ["sys_sendto", "syscall_entry_sendto"]: | |
344 | current_syscall["count"] = event["len"] | |
345 | else: | |
346 | try: | |
347 | current_syscall["count"] = event["count"] | |
348 | except: | |
349 | print("Missing count argument for syscall", | |
350 | current_syscall["name"]) | |
351 | current_syscall["count"] = 0 | |
352 | ||
353 | current_syscall["filename"] = f.filename | |
354 | ||
355 | def add_tid_fd(self, event, cpu): | |
356 | ret = event["ret"] | |
357 | t = self.tids[cpu.current_tid] | |
358 | # if it's a thread, we want the parent | |
359 | if t.pid != -1 and t.tid != t.pid: | |
360 | t = self.tids[t.pid] | |
361 | current_syscall = self.tids[cpu.current_tid].current_syscall | |
362 | ||
363 | name = current_syscall["filename"] | |
364 | if name not in sv.SyscallConsts.GENERIC_NAMES \ | |
365 | and name in t.closed_fds.keys(): | |
366 | fd = t.closed_fds[name] | |
367 | fd.open += 1 | |
368 | else: | |
369 | fd = sv.FD() | |
370 | fd.filename = name | |
371 | if current_syscall["name"] in sv.SyscallConsts.NET_OPEN_SYSCALLS: | |
372 | fd.family = current_syscall["family"] | |
373 | if fd.family in sv.SyscallConsts.INET_FAMILIES: | |
374 | fd.fdtype = sv.FDType.net | |
375 | fd.open = 1 | |
376 | if ret >= 0: | |
377 | fd.fd = ret | |
378 | else: | |
379 | return | |
380 | if "cloexec" in current_syscall.keys(): | |
381 | fd.cloexec = 1 | |
382 | t.fds[fd.fd] = fd | |
383 | ||
44823c31 | 384 | t.track_chrono_fd(fd.fd, fd.filename, fd.fdtype, event.timestamp) |
31d9c53b | 385 | |
a9c9a2a6 JD |
386 | def read_append(self, fd, proc, count, rq): |
387 | rq.operation = sv.IORequest.OP_READ | |
388 | rq.size = count | |
389 | if fd.fdtype in [sv.FDType.net, sv.FDType.maybe_net]: | |
390 | fd.net_read += count | |
391 | proc.net_read += count | |
392 | elif fd.fdtype == sv.FDType.disk: | |
393 | fd.disk_read += count | |
394 | proc.disk_read += count | |
395 | else: | |
396 | fd.unk_read += count | |
397 | proc.unk_read += count | |
398 | fd.read += count | |
399 | proc.read += count | |
400 | ||
401 | def write_append(self, fd, proc, count, rq): | |
402 | rq.operation = sv.IORequest.OP_WRITE | |
403 | rq.size = count | |
404 | if fd.fdtype in [sv.FDType.net, sv.FDType.maybe_net]: | |
405 | fd.net_write += count | |
406 | proc.net_write += count | |
407 | elif fd.fdtype == sv.FDType.disk: | |
408 | fd.disk_write += count | |
409 | proc.disk_write += count | |
410 | else: | |
411 | fd.unk_write += count | |
412 | proc.unk_write += count | |
413 | fd.write += count | |
414 | proc.write += count | |
415 | ||
416 | def track_read_write_return(self, name, ret, cpu): | |
417 | if ret < 0: | |
418 | # TODO: track errors | |
419 | return | |
420 | proc = self.tids[cpu.current_tid] | |
421 | # if it's a thread, we want the parent | |
422 | if proc.pid != -1 and proc.tid != proc.pid: | |
423 | proc = self.tids[proc.pid] | |
424 | current_syscall = self.tids[cpu.current_tid].current_syscall | |
425 | if name in ["sys_splice", "syscall_entry_splice", | |
426 | "sys_sendfile64", "syscall_entry_sendfile64"]: | |
427 | self.read_append(current_syscall["fd_in"], proc, ret, | |
428 | current_syscall["iorequest"]) | |
429 | self.write_append(current_syscall["fd_out"], proc, ret, | |
430 | current_syscall["iorequest"]) | |
431 | elif name in sv.SyscallConsts.READ_SYSCALLS: | |
432 | if ret > 0: | |
433 | self.read_append(current_syscall["fd"], proc, ret, | |
434 | current_syscall["iorequest"]) | |
435 | elif name in sv.SyscallConsts.WRITE_SYSCALLS: | |
436 | if ret > 0: | |
437 | self.write_append(current_syscall["fd"], proc, ret, | |
438 | current_syscall["iorequest"]) | |
439 | ||
440 | def get_page_queue_stats(self, page_list): | |
441 | processes = {} | |
442 | for i in page_list: | |
443 | procname = i[0].comm | |
444 | tid = i[0].tid | |
445 | filename = i[2] | |
446 | if tid not in processes.keys(): | |
447 | processes[tid] = {} | |
448 | processes[tid]["procname"] = procname | |
449 | processes[tid]["count"] = 1 | |
450 | processes[tid]["files"] = {} | |
451 | processes[tid]["files"][filename] = 1 | |
452 | else: | |
453 | processes[tid]["count"] += 1 | |
454 | if filename not in processes[tid]["files"].keys(): | |
455 | processes[tid]["files"][filename] = 1 | |
456 | else: | |
457 | processes[tid]["files"][filename] += 1 | |
458 | return processes | |
459 | ||
460 | def print_page_table(self, event, pages): | |
461 | spaces = (41 + 6) * " " | |
462 | for i in pages.keys(): | |
463 | p = pages[i] | |
464 | print("%s %s (%d): %d pages" % (spaces, p["procname"], | |
465 | i, p["count"])) | |
466 | files = sorted(p["files"].items(), key=operator.itemgetter(1), | |
467 | reverse=True) | |
468 | for f in files: | |
469 | print("%s - %s : %d pages" % (spaces, f[0], f[1])) | |
470 | ||
471 | def syscall_clear_pages(self, event, name, fd, current_syscall, tid): | |
472 | cleaned = [] | |
473 | if name in ["sys_sync", "syscall_entry_sync"]: | |
474 | # remove all the pages | |
475 | for i in range(len(self.dirty_pages["pages"])): | |
476 | cleaned.append(self.dirty_pages["pages"].pop(0)) | |
477 | else: | |
478 | # remove only the pages that belong to a specific proc/fd | |
479 | for i in range(len(self.dirty_pages["pages"])): | |
480 | proc = self.dirty_pages["pages"][i][0] | |
481 | page_fd = self.dirty_pages["pages"][i][3] | |
482 | if page_fd == fd and (tid.tid == proc.tid or | |
483 | tid.pid == proc.pid): | |
484 | cleaned.append(self.dirty_pages["pages"][i]) | |
485 | for i in cleaned: | |
486 | self.dirty_pages["pages"].remove(i) | |
487 | if len(cleaned) > 0: | |
488 | current_syscall["pages_cleared"] = cleaned | |
489 | ||
490 | def track_rw_latency(self, name, ret, c, ts, event): | |
491 | current_syscall = self.tids[c.current_tid].current_syscall | |
492 | rq = current_syscall["iorequest"] | |
493 | # FIXME: useless ? | |
494 | # if "start" not in current_syscall.keys(): | |
495 | # return | |
496 | rq.duration = (event.timestamp - current_syscall["start"]) | |
497 | rq.begin = current_syscall["start"] | |
498 | rq.end = event.timestamp | |
499 | rq.proc = self.tids[c.current_tid] | |
500 | if "fd" in current_syscall.keys(): | |
501 | rq.fd = current_syscall["fd"] | |
502 | r = current_syscall["fd"].iorequests | |
503 | r.append(current_syscall["iorequest"]) | |
504 | elif "fd_in" in current_syscall.keys(): | |
505 | rq.fd = current_syscall["fd_in"] | |
506 | # pages written during the latency | |
507 | if "pages_written" in current_syscall.keys(): | |
508 | rq.page_written = current_syscall["pages_written"] | |
509 | # dirty buffers during the latency | |
510 | if "dirty" in current_syscall.keys(): | |
511 | rq.dirty = current_syscall["dirty"] | |
512 | # alloc pages during the latency | |
513 | if "alloc" in current_syscall.keys(): | |
514 | rq.page_alloc = current_syscall["alloc"] | |
515 | # wakeup_kswapd during the latency | |
516 | if "page_free" in current_syscall.keys(): | |
517 | rq.page_free = current_syscall["page_free"] | |
518 | if "wakeup_kswapd" in current_syscall.keys(): | |
519 | rq.woke_kswapd = True | |
520 | if name in sv.SyscallConsts.SYNC_SYSCALLS: | |
521 | # self.syscall_clear_pages(event, name, fd, current_syscall, | |
522 | # self.tids[c.current_tid]) | |
523 | if "pages_cleared" in current_syscall.keys(): | |
524 | rq.page_cleared = len(current_syscall["pages_cleared"]) | |
525 | ||
21167679 JD |
526 | def _per_tid_syscall_exit(self, name, ret, event, c): |
527 | t = self.tids[c.current_tid] | |
3f195205 JD |
528 | if not name in t.syscalls: |
529 | return | |
21167679 JD |
530 | s = sv.SyscallEvent() |
531 | s.ret = ret | |
532 | s.entry_ts = t.current_syscall["start"] | |
533 | s.exit_ts = event.timestamp | |
534 | s.duration = s.exit_ts - s.entry_ts | |
535 | t_syscall = t.syscalls[name] | |
536 | if t_syscall.min is None or t_syscall.min > s.duration: | |
537 | t_syscall.min = s.duration | |
538 | if t_syscall.max < s.duration: | |
539 | t_syscall.max = s.duration | |
540 | t_syscall.total_duration += s.duration | |
541 | t_syscall.rq.append(s) | |
542 | ||
a9c9a2a6 JD |
543 | def _process_syscall_entry(self, event): |
544 | name = event.name | |
545 | ret_string = "" | |
546 | cpu_id = event["cpu_id"] | |
21167679 | 547 | self.per_tid_syscall_entry(name, cpu_id, event) |
a9c9a2a6 JD |
548 | ret_string = self.track_fds(name, event, cpu_id) |
549 | if name in sv.SyscallConsts.READ_SYSCALLS or \ | |
550 | name in sv.SyscallConsts.WRITE_SYSCALLS: | |
551 | self.track_read_write(name, event, cpu_id) | |
552 | if name in sv.SyscallConsts.SYNC_SYSCALLS: | |
553 | self.track_sync(name, event, cpu_id) | |
554 | return ret_string | |
555 | ||
556 | def _process_syscall_exit(self, event): | |
557 | cpu_id = event["cpu_id"] | |
558 | ret_string = "" | |
559 | if cpu_id not in self.cpus: | |
560 | return | |
561 | c = self.cpus[cpu_id] | |
562 | if c.current_tid == -1: | |
563 | return | |
564 | current_syscall = self.tids[c.current_tid].current_syscall | |
565 | if len(current_syscall.keys()) == 0: | |
566 | return | |
567 | name = current_syscall["name"] | |
568 | ret = event["ret"] | |
21167679 JD |
569 | self._per_tid_syscall_exit(name, ret, event, c) |
570 | ||
571 | if name not in sv.SyscallConsts.IO_SYSCALLS: | |
572 | return | |
573 | ||
a9c9a2a6 JD |
574 | current_syscall["iorequest"] = sv.IORequest() |
575 | current_syscall["iorequest"].iotype = sv.IORequest.IO_SYSCALL | |
576 | current_syscall["iorequest"].name = name | |
577 | if name in sv.SyscallConsts.OPEN_SYSCALLS: | |
578 | self.add_tid_fd(event, c) | |
579 | ret_string = "%s %s(%s, fd = %d)" % ( | |
580 | common.ns_to_hour_nsec(current_syscall["start"]), | |
581 | name, current_syscall["filename"], ret) | |
582 | if ret < 0: | |
583 | return ret_string | |
584 | t = self.tids[c.current_tid] | |
69502021 | 585 | current_syscall["fd"] = self.get_fd(t, ret, event) |
a9c9a2a6 JD |
586 | current_syscall["count"] = 0 |
587 | current_syscall["fd"].fdtype = current_syscall["fdtype"] | |
588 | current_syscall["iorequest"].operation = sv.IORequest.OP_OPEN | |
589 | self.track_rw_latency(name, ret, c, | |
590 | event.timestamp, event) | |
591 | elif name in sv.SyscallConsts.READ_SYSCALLS or \ | |
592 | name in sv.SyscallConsts.WRITE_SYSCALLS: | |
593 | self.track_read_write_return(name, ret, c) | |
594 | self.track_rw_latency(name, ret, c, event.timestamp, event) | |
595 | elif name in sv.SyscallConsts.SYNC_SYSCALLS: | |
596 | current_syscall["iorequest"].operation = sv.IORequest.OP_SYNC | |
597 | self.track_rw_latency(name, ret, c, event.timestamp, event) | |
598 | if name in ["sys_sync", "syscall_entry_sync"]: | |
599 | t = self.tids[c.current_tid] | |
600 | t.iorequests.append(current_syscall["iorequest"]) | |
601 | self.tids[c.current_tid].current_syscall = {} | |
602 | if self.tids[c.current_tid] in self.pending_syscalls: | |
603 | self.pending_syscalls.remove(self.tids[c.current_tid]) | |
604 | return ret_string | |
605 | ||
12117527 | 606 | def _process_writeback_pages_written(self, event): |
a9c9a2a6 JD |
607 | """writeback_pages_written""" |
608 | for c in self.cpus.values(): | |
609 | if c.current_tid <= 0: | |
610 | continue | |
611 | current_syscall = self.tids[c.current_tid].current_syscall | |
612 | if len(current_syscall.keys()) == 0: | |
613 | continue | |
614 | current_syscall["pages_written"] = event["pages"] | |
615 | ||
30059169 | 616 | def _process_mm_vmscan_wakeup_kswapd(self, event): |
a9c9a2a6 JD |
617 | """mm_vmscan_wakeup_kswapd""" |
618 | cpu_id = event["cpu_id"] | |
619 | if cpu_id not in self.cpus: | |
620 | return | |
621 | c = self.cpus[cpu_id] | |
622 | if c.current_tid == -1: | |
623 | return | |
624 | current_syscall = self.tids[c.current_tid].current_syscall | |
625 | if len(current_syscall.keys()) == 0: | |
626 | return | |
627 | current_syscall["wakeup_kswapd"] = 1 | |
628 | ||
2f163c78 | 629 | def _process_mm_page_free(self, event): |
a9c9a2a6 JD |
630 | """mm_page_free""" |
631 | for c in self.cpus.values(): | |
632 | if c.current_tid <= 0: | |
633 | continue | |
634 | p = self.tids[c.current_tid] | |
635 | # if the current process is kswapd0, we need to | |
636 | # attribute the page freed to the process that | |
637 | # woke it up. | |
638 | if p.comm == "kswapd0" and p.prev_tid > 0: | |
639 | p = self.tids[p.prev_tid] | |
640 | current_syscall = p.current_syscall | |
641 | if len(current_syscall.keys()) == 0: | |
642 | continue | |
643 | if "wakeup_kswapd" in current_syscall.keys(): | |
644 | if "page_free" in current_syscall.keys(): | |
645 | current_syscall["page_free"] += 1 | |
646 | else: | |
647 | current_syscall["page_free"] = 1 |