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 | ||
bd3cd7c5 | 25 | import socket |
44823c31 | 26 | from collections import OrderedDict |
bd3cd7c5 JD |
27 | |
28 | ||
29 | class StateVariable: | |
30 | pass | |
31 | ||
32 | ||
33 | class Process(): | |
34 | def __init__(self): | |
2b4a3c12 AB |
35 | self.tid = None |
36 | self.pid = None | |
bd3cd7c5 JD |
37 | self.comm = "" |
38 | # indexed by fd | |
39 | self.fds = {} | |
40 | # indexed by filename | |
41 | self.closed_fds = {} | |
31d9c53b AB |
42 | # filenames (indexed by timestamp) associated with given fd (top-level |
43 | # index) at a given point in time | |
44 | self.chrono_fds = {} | |
bd3cd7c5 JD |
45 | self.current_syscall = {} |
46 | self.init_counts() | |
47 | ||
48 | def init_counts(self): | |
49 | self.cpu_ns = 0 | |
50 | self.migrate_count = 0 | |
51 | # network read/write | |
52 | self.net_read = 0 | |
53 | self.net_write = 0 | |
54 | # disk read/write (might be cached) | |
55 | self.disk_read = 0 | |
56 | self.disk_write = 0 | |
57 | # actual block access read/write | |
58 | self.block_read = 0 | |
59 | self.block_write = 0 | |
60 | # unclassified read/write (FD passing and statedump) | |
61 | self.unk_read = 0 | |
62 | self.unk_write = 0 | |
63 | # total I/O read/write | |
64 | self.read = 0 | |
65 | self.write = 0 | |
66 | # last TS where the process was scheduled in | |
6ddb1dd6 | 67 | self.last_sched = None |
bd3cd7c5 | 68 | # the process scheduled before this one |
2b4a3c12 | 69 | self.prev_tid = None |
bd3cd7c5 JD |
70 | # indexed by syscall_name |
71 | self.syscalls = {} | |
72 | self.perf = {} | |
73 | self.dirty = 0 | |
74 | self.allocated_pages = 0 | |
75 | self.freed_pages = 0 | |
76 | self.total_syscalls = 0 | |
77 | # array of IORequest objects for freq analysis later (block and | |
78 | # syscalls with no FD like sys_sync) | |
79 | self.iorequests = [] | |
80 | ||
44823c31 AB |
81 | def track_chrono_fd(self, fd, filename, fdtype, timestamp): |
82 | chrono_metadata = {} | |
83 | chrono_metadata["filename"] = filename | |
84 | chrono_metadata["fdtype"] = fdtype | |
85 | ||
86 | if fd not in self.chrono_fds: | |
87 | self.chrono_fds[fd] = OrderedDict() | |
88 | self.chrono_fds[fd][timestamp] = chrono_metadata | |
89 | else: | |
90 | chrono_fd = self.chrono_fds[fd] | |
91 | last_ts = next(reversed(chrono_fd)) | |
92 | if filename != chrono_fd[last_ts]["filename"]: | |
93 | chrono_fd[timestamp] = chrono_metadata | |
94 | ||
bd3cd7c5 JD |
95 | |
96 | class CPU(): | |
97 | def __init__(self): | |
2b4a3c12 | 98 | self.cpu_id = None |
bd3cd7c5 | 99 | self.cpu_ns = 0 |
2b4a3c12 | 100 | self.current_tid = None |
f4522f09 AB |
101 | self.current_hard_irq = None |
102 | self.current_soft_irq = None | |
bd3cd7c5 JD |
103 | self.start_task_ns = 0 |
104 | self.perf = {} | |
105 | self.wakeup_queue = [] | |
106 | ||
107 | ||
994541c3 AB |
108 | class MemoryManagement(): |
109 | def __init__(self): | |
110 | self.page_count = 0 | |
111 | ||
112 | ||
bd3cd7c5 | 113 | class Syscall(): |
21167679 | 114 | # One instance for each unique syscall name per process |
bd3cd7c5 JD |
115 | def __init__(self): |
116 | self.name = "" | |
117 | self.count = 0 | |
21167679 JD |
118 | # duration min/max |
119 | self.min = None | |
120 | self.max = 0 | |
121 | self.total_duration = 0 | |
122 | # list of all syscall events (SyscallEvent) | |
123 | self.rq = [] | |
124 | ||
125 | ||
126 | class SyscallEvent(): | |
127 | def __init__(self): | |
128 | self.entry_ts = None | |
129 | self.exit_ts = None | |
130 | self.ret = None | |
131 | self.duration = None | |
bd3cd7c5 JD |
132 | |
133 | ||
134 | class Disk(): | |
135 | def __init__(self): | |
136 | self.name = "" | |
137 | self.prettyname = "" | |
138 | self.init_counts() | |
139 | ||
140 | def init_counts(self): | |
141 | self.nr_sector = 0 | |
142 | self.nr_requests = 0 | |
143 | self.completed_requests = 0 | |
144 | self.request_time = 0 | |
145 | self.pending_requests = {} | |
146 | self.rq_list = [] | |
147 | self.max = None | |
148 | self.min = None | |
149 | self.total = None | |
150 | self.count = None | |
151 | self.rq_values = None | |
152 | self.stdev = None | |
153 | ||
154 | ||
155 | class Iface(): | |
156 | def __init__(self): | |
157 | self.name = "" | |
158 | self.init_counts() | |
159 | ||
160 | def init_counts(self): | |
161 | self.recv_bytes = 0 | |
162 | self.recv_packets = 0 | |
163 | self.send_bytes = 0 | |
164 | self.send_packets = 0 | |
165 | ||
166 | ||
167 | class FDType(): | |
168 | unknown = 0 | |
169 | disk = 1 | |
170 | net = 2 | |
171 | # not 100% sure they are network FDs (assumed when net_dev_xmit is | |
172 | # called during a write syscall and the type in unknown). | |
173 | maybe_net = 3 | |
174 | ||
175 | ||
176 | class FD(): | |
177 | def __init__(self): | |
178 | self.filename = "" | |
2b4a3c12 | 179 | self.fd = None |
bd3cd7c5 JD |
180 | # address family |
181 | self.family = socket.AF_UNSPEC | |
182 | self.fdtype = FDType.unknown | |
183 | # if FD was inherited, parent PID | |
2b4a3c12 | 184 | self.parent = None |
bd3cd7c5 JD |
185 | self.init_counts() |
186 | ||
187 | def init_counts(self): | |
188 | # network read/write | |
189 | self.net_read = 0 | |
190 | self.net_write = 0 | |
191 | # disk read/write (might be cached) | |
192 | self.disk_read = 0 | |
193 | self.disk_write = 0 | |
194 | # unclassified read/write (FD passing and statedump) | |
195 | self.unk_read = 0 | |
196 | self.unk_write = 0 | |
197 | # total read/write | |
198 | self.read = 0 | |
199 | self.write = 0 | |
200 | self.open = 0 | |
201 | self.close = 0 | |
202 | self.cloexec = 0 | |
203 | # array of syscall IORequest objects for freq analysis later | |
204 | self.iorequests = [] | |
205 | ||
206 | ||
207 | class IRQ(): | |
bd3cd7c5 JD |
208 | # from include/linux/interrupt.h |
209 | soft_names = {0: "HI_SOFTIRQ", | |
210 | 1: "TIMER_SOFTIRQ", | |
211 | 2: "NET_TX_SOFTIRQ", | |
212 | 3: "NET_RX_SOFTIRQ", | |
213 | 4: "BLOCK_SOFTIRQ", | |
214 | 5: "BLOCK_IOPOLL_SOFTIRQ", | |
215 | 6: "TASKLET_SOFTIRQ", | |
216 | 7: "SCHED_SOFTIRQ", | |
217 | 8: "HRTIMER_SOFTIRQ", | |
218 | 9: "RCU_SOFTIRQ"} | |
219 | ||
f4522f09 AB |
220 | def __init__(self, id, start_ts=None): |
221 | self.id = id | |
222 | self.start_ts = start_ts | |
2b4a3c12 | 223 | self.stop_ts = None |
bd3cd7c5 | 224 | |
f4522f09 AB |
225 | |
226 | class HardIRQ(IRQ): | |
227 | def __init__(self, id, start_ts): | |
228 | super().__init__(id, start_ts) | |
229 | self.ret = None | |
230 | ||
231 | @classmethod | |
232 | def new_from_irq_handler_entry(cls, event): | |
233 | id = event["irq"] | |
234 | start_ts = event.timestamp | |
235 | return cls(id, start_ts) | |
236 | ||
237 | ||
238 | class SoftIRQ(IRQ): | |
239 | def __init__(self, id, raise_ts=None, start_ts=None): | |
240 | super().__init__(id) | |
241 | self.raise_ts = raise_ts | |
242 | ||
243 | @classmethod | |
244 | def new_from_softirq_raise(cls, event): | |
245 | id = event["vec"] | |
246 | raise_ts = event.timestamp | |
247 | return cls(id, raise_ts) | |
248 | ||
249 | @classmethod | |
250 | def new_from_softirq_entry(cls, event): | |
251 | id = event["vec"] | |
252 | start_ts = event.timestamp | |
253 | return cls(id, start_ts=start_ts) | |
ca95b1c3 | 254 | |
bd3cd7c5 JD |
255 | |
256 | class IORequest(): | |
257 | # I/O "type" | |
258 | IO_SYSCALL = 1 | |
259 | IO_BLOCK = 2 | |
260 | IO_NET = 3 | |
261 | # I/O operations | |
262 | OP_OPEN = 1 | |
263 | OP_READ = 2 | |
264 | OP_WRITE = 3 | |
265 | OP_CLOSE = 4 | |
266 | OP_SYNC = 5 | |
267 | ||
268 | def __init__(self): | |
269 | # IORequest.IO_* | |
270 | self.iotype = None | |
271 | # bytes for syscalls and net, sectors for block | |
272 | # FIXME: syscalls handling vectors (vector size missing) | |
273 | self.size = None | |
274 | # for syscalls and block: delay between issue and completion | |
275 | # of the request | |
276 | self.duration = None | |
277 | # IORequest.OP_* | |
278 | self.operation = None | |
279 | # syscall name | |
280 | self.name = None | |
281 | # begin syscall timestamp | |
282 | self.begin = None | |
283 | # end syscall timestamp | |
284 | self.end = None | |
285 | # current process | |
286 | self.proc = None | |
287 | # current FD (for syscalls) | |
288 | self.fd = None | |
289 | # buffers dirtied during the operation | |
290 | self.dirty = 0 | |
291 | # pages allocated during the operation | |
292 | self.page_alloc = 0 | |
293 | # pages freed during the operation | |
294 | self.page_free = 0 | |
295 | # pages written on disk during the operation | |
296 | self.page_written = 0 | |
297 | # kswapd was forced to wakeup during the operation | |
298 | self.woke_kswapd = False | |
299 | # estimated pages flushed during a sync operation | |
300 | self.page_cleared = 0 | |
301 | ||
302 | ||
303 | class Syscalls_stats(): | |
304 | def __init__(self): | |
305 | self.read_max = 0 | |
306 | self.read_min = None | |
307 | self.read_total = 0 | |
308 | self.read_count = 0 | |
309 | self.read_rq = [] | |
310 | self.all_read = [] | |
311 | ||
312 | self.write_max = 0 | |
313 | self.write_min = None | |
314 | self.write_total = 0 | |
315 | self.write_count = 0 | |
316 | self.write_rq = [] | |
317 | self.all_write = [] | |
318 | ||
319 | self.open_max = 0 | |
320 | self.open_min = None | |
321 | self.open_total = 0 | |
322 | self.open_count = 0 | |
323 | self.open_rq = [] | |
324 | self.all_open = [] | |
325 | ||
326 | self.sync_max = 0 | |
327 | self.sync_min = None | |
328 | self.sync_total = 0 | |
329 | self.sync_count = 0 | |
330 | self.sync_rq = [] | |
331 | self.all_sync = [] | |
332 | ||
333 | ||
334 | class SyscallConsts(): | |
335 | # TODO: decouple socket/family logic from this class | |
336 | INET_FAMILIES = [socket.AF_INET, socket.AF_INET6] | |
337 | DISK_FAMILIES = [socket.AF_UNIX] | |
338 | # list nof syscalls that open a FD on disk (in the exit_syscall event) | |
339 | DISK_OPEN_SYSCALLS = ["sys_open", "syscall_entry_open", | |
340 | "sys_openat", "syscall_entry_openat"] | |
341 | # list of syscalls that open a FD on the network | |
342 | # (in the exit_syscall event) | |
343 | NET_OPEN_SYSCALLS = ["sys_accept", "syscall_entry_accept", | |
af16b456 | 344 | "sys_accept4", "syscall_entry_accept4", |
bd3cd7c5 JD |
345 | "sys_socket", "syscall_entry_socket"] |
346 | # list of syscalls that can duplicate a FD | |
347 | DUP_OPEN_SYSCALLS = ["sys_fcntl", "syscall_entry_fcntl", | |
348 | "sys_dup2", "syscall_entry_dup2"] | |
349 | SYNC_SYSCALLS = ["sys_sync", "syscall_entry_sync", | |
350 | "sys_sync_file_range", "syscall_entry_sync_file_range", | |
351 | "sys_fsync", "syscall_entry_fsync", | |
352 | "sys_fdatasync", "syscall_entry_fdatasync"] | |
353 | # merge the 3 open lists | |
354 | OPEN_SYSCALLS = DISK_OPEN_SYSCALLS + NET_OPEN_SYSCALLS + DUP_OPEN_SYSCALLS | |
355 | # list of syscalls that close a FD (in the "fd =" field) | |
356 | CLOSE_SYSCALLS = ["sys_close", "syscall_entry_close"] | |
357 | # list of syscall that read on a FD, value in the exit_syscall following | |
358 | READ_SYSCALLS = ["sys_read", "syscall_entry_read", | |
359 | "sys_recvmsg", "syscall_entry_recvmsg", | |
360 | "sys_recvfrom", "syscall_entry_recvfrom", | |
361 | "sys_splice", "syscall_entry_splice", | |
362 | "sys_readv", "syscall_entry_readv", | |
363 | "sys_sendfile64", "syscall_entry_sendfile64"] | |
364 | # list of syscall that write on a FD, value in the exit_syscall following | |
365 | WRITE_SYSCALLS = ["sys_write", "syscall_entry_write", | |
366 | "sys_sendmsg", "syscall_entry_sendmsg", | |
367 | "sys_sendto", "syscall_entry_sendto", | |
368 | "sys_writev", "syscall_entry_writev"] | |
21167679 JD |
369 | # All I/O related syscalls |
370 | IO_SYSCALLS = OPEN_SYSCALLS + CLOSE_SYSCALLS + READ_SYSCALLS + \ | |
c437b3bd | 371 | WRITE_SYSCALLS + SYNC_SYSCALLS |
bd3cd7c5 JD |
372 | # generic names assigned to special FDs, don't try to match these in the |
373 | # closed_fds dict | |
374 | GENERIC_NAMES = ["unknown", "socket"] | |
375 | ||
376 | def __init__(): | |
377 | pass |