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> |
39fec005 | 6 | # 2015 - Antoine Busque <abusque@efficios.com> |
4ed24f86 JD |
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 | ||
bd3cd7c5 | 26 | import socket |
44823c31 | 27 | from collections import OrderedDict |
bd3cd7c5 JD |
28 | |
29 | ||
30 | class StateVariable: | |
31 | pass | |
32 | ||
33 | ||
34 | class Process(): | |
3ce8423c AB |
35 | def __init__(self, tid=None, pid=None, comm=''): |
36 | self.tid = tid | |
37 | self.pid = pid | |
38 | self.comm = comm | |
bd3cd7c5 JD |
39 | # indexed by fd |
40 | self.fds = {} | |
41 | # indexed by filename | |
42 | self.closed_fds = {} | |
31d9c53b AB |
43 | # filenames (indexed by timestamp) associated with given fd (top-level |
44 | # index) at a given point in time | |
45 | self.chrono_fds = {} | |
bd3cd7c5 | 46 | self.current_syscall = {} |
bd3cd7c5 | 47 | # the process scheduled before this one |
2b4a3c12 | 48 | self.prev_tid = None |
bd3cd7c5 | 49 | |
44823c31 AB |
50 | def track_chrono_fd(self, fd, filename, fdtype, timestamp): |
51 | chrono_metadata = {} | |
3f52a605 AB |
52 | chrono_metadata['filename'] = filename |
53 | chrono_metadata['fdtype'] = fdtype | |
44823c31 AB |
54 | |
55 | if fd not in self.chrono_fds: | |
56 | self.chrono_fds[fd] = OrderedDict() | |
57 | self.chrono_fds[fd][timestamp] = chrono_metadata | |
58 | else: | |
59 | chrono_fd = self.chrono_fds[fd] | |
60 | last_ts = next(reversed(chrono_fd)) | |
3f52a605 | 61 | if filename != chrono_fd[last_ts]['filename']: |
44823c31 AB |
62 | chrono_fd[timestamp] = chrono_metadata |
63 | ||
bd3cd7c5 JD |
64 | |
65 | class CPU(): | |
3c9bf3fa AB |
66 | def __init__(self, cpu_id): |
67 | self.cpu_id = cpu_id | |
2b4a3c12 | 68 | self.current_tid = None |
f4522f09 | 69 | self.current_hard_irq = None |
26facb3a AB |
70 | # softirqs use a dict because multiple ones can be raised before |
71 | # handling. They are indexed by vec, and each entry is a list, | |
72 | # ordered chronologically | |
73 | self.current_softirqs = {} | |
bd3cd7c5 JD |
74 | |
75 | ||
994541c3 AB |
76 | class MemoryManagement(): |
77 | def __init__(self): | |
78 | self.page_count = 0 | |
79 | ||
21167679 | 80 | class SyscallEvent(): |
3b1dda96 AB |
81 | def __init__(self, name, begin_ts): |
82 | self.name = name | |
83 | self.begin_ts = begin_ts | |
84 | self.end_ts = None | |
21167679 JD |
85 | self.ret = None |
86 | self.duration = None | |
bd3cd7c5 | 87 | |
3b1dda96 AB |
88 | def process_exit(self, event): |
89 | self.end_ts = event.timestamp | |
90 | self.ret = event['ret'] | |
91 | self.duration = self.end_ts - self.begin_ts | |
92 | ||
93 | @classmethod | |
94 | def new_from_entry(cls, event): | |
95 | return cls(event.name, event.timestamp) | |
96 | ||
bd3cd7c5 JD |
97 | |
98 | class Disk(): | |
99 | def __init__(self): | |
3b1dda96 | 100 | # pending block IO Requests, indexed by sector |
bd3cd7c5 | 101 | self.pending_requests = {} |
bd3cd7c5 JD |
102 | |
103 | ||
bd3cd7c5 JD |
104 | class FDType(): |
105 | unknown = 0 | |
106 | disk = 1 | |
107 | net = 2 | |
108 | # not 100% sure they are network FDs (assumed when net_dev_xmit is | |
109 | # called during a write syscall and the type in unknown). | |
110 | maybe_net = 3 | |
111 | ||
112 | ||
113 | class FD(): | |
114 | def __init__(self): | |
3f52a605 | 115 | self.filename = '' |
2b4a3c12 | 116 | self.fd = None |
bd3cd7c5 JD |
117 | # address family |
118 | self.family = socket.AF_UNSPEC | |
119 | self.fdtype = FDType.unknown | |
120 | # if FD was inherited, parent PID | |
2b4a3c12 | 121 | self.parent = None |
6f2c9e92 | 122 | self.cloexec = False |
bd3cd7c5 JD |
123 | self.init_counts() |
124 | ||
6f2c9e92 AB |
125 | @classmethod |
126 | def new_from_fd(cls, fd): | |
127 | new_fd = cls() | |
128 | new_fd.filename = fd.filename | |
129 | new_fd.fd = fd.fd | |
130 | new_fd.family = fd.family | |
131 | new_fd.fdtype = fd.fdtype | |
132 | new_fd.parent = fd.parent | |
133 | new_fd.cloexec = fd.cloexec | |
134 | return new_fd | |
135 | ||
bd3cd7c5 JD |
136 | def init_counts(self): |
137 | # network read/write | |
138 | self.net_read = 0 | |
139 | self.net_write = 0 | |
140 | # disk read/write (might be cached) | |
141 | self.disk_read = 0 | |
142 | self.disk_write = 0 | |
143 | # unclassified read/write (FD passing and statedump) | |
144 | self.unk_read = 0 | |
145 | self.unk_write = 0 | |
146 | # total read/write | |
147 | self.read = 0 | |
148 | self.write = 0 | |
bd3cd7c5 JD |
149 | # array of syscall IORequest objects for freq analysis later |
150 | self.iorequests = [] | |
151 | ||
152 | ||
153 | class IRQ(): | |
0297cb98 | 154 | def __init__(self, id, cpu_id, begin_ts=None): |
f4522f09 | 155 | self.id = id |
9b94fe47 | 156 | self.cpu_id = cpu_id |
0297cb98 AB |
157 | self.begin_ts = begin_ts |
158 | self.end_ts = None | |
bd3cd7c5 | 159 | |
f4522f09 AB |
160 | |
161 | class HardIRQ(IRQ): | |
0297cb98 AB |
162 | def __init__(self, id, cpu_id, begin_ts): |
163 | super().__init__(id, cpu_id, begin_ts) | |
f4522f09 AB |
164 | self.ret = None |
165 | ||
166 | @classmethod | |
167 | def new_from_irq_handler_entry(cls, event): | |
3f52a605 | 168 | id = event['irq'] |
9b94fe47 | 169 | cpu_id = event['cpu_id'] |
0297cb98 AB |
170 | begin_ts = event.timestamp |
171 | return cls(id, cpu_id, begin_ts) | |
f4522f09 AB |
172 | |
173 | ||
174 | class SoftIRQ(IRQ): | |
0297cb98 AB |
175 | def __init__(self, id, cpu_id, raise_ts=None, begin_ts=None): |
176 | super().__init__(id, cpu_id, begin_ts) | |
f4522f09 AB |
177 | self.raise_ts = raise_ts |
178 | ||
179 | @classmethod | |
180 | def new_from_softirq_raise(cls, event): | |
3f52a605 | 181 | id = event['vec'] |
9b94fe47 | 182 | cpu_id = event['cpu_id'] |
f4522f09 | 183 | raise_ts = event.timestamp |
9b94fe47 | 184 | return cls(id, cpu_id, raise_ts) |
f4522f09 AB |
185 | |
186 | @classmethod | |
187 | def new_from_softirq_entry(cls, event): | |
3f52a605 | 188 | id = event['vec'] |
9b94fe47 | 189 | cpu_id = event['cpu_id'] |
0297cb98 AB |
190 | begin_ts = event.timestamp |
191 | return cls(id, cpu_id, begin_ts=begin_ts) | |
ca95b1c3 | 192 | |
bd3cd7c5 JD |
193 | |
194 | class IORequest(): | |
bd3cd7c5 JD |
195 | # I/O operations |
196 | OP_OPEN = 1 | |
197 | OP_READ = 2 | |
198 | OP_WRITE = 3 | |
199 | OP_CLOSE = 4 | |
200 | OP_SYNC = 5 | |
201 | ||
3b1dda96 AB |
202 | def __init__(self, begin_ts, size, tid, operation): |
203 | self.begin_ts = begin_ts | |
204 | self.end_ts = None | |
bd3cd7c5 | 205 | self.duration = None |
3b1dda96 AB |
206 | # request size in bytes |
207 | self.size = size | |
208 | self.operation = operation | |
209 | # tid of process that triggered the rq | |
210 | self.tid = tid | |
211 | ||
212 | ||
213 | class SyscallIORequest(IORequest): | |
214 | def __init__(self, begin_ts, size, tid, operation): | |
215 | super().__init__(begin_ts, size, tid) | |
bd3cd7c5 | 216 | self.fd = None |
3b1dda96 AB |
217 | self.syscall_name = None |
218 | # Number of pages alloc'd/freed/written to disk during the rq | |
219 | self.pages_allocated = 0 | |
220 | self.pages_freed = 0 | |
221 | self.pages_written = 0 | |
222 | # Whether kswapd was forced to wakeup during the rq | |
bd3cd7c5 | 223 | self.woke_kswapd = False |
3b1dda96 AB |
224 | |
225 | ||
226 | class BlockIORequest(IORequest): | |
227 | # Logical sector size in bytes, according to the kernel | |
228 | SECTOR_SIZE = 512 | |
229 | ||
230 | def __init__(self, begin_ts, tid, operation, dev, sector, nr_sector): | |
231 | size = nr_sector * BlockIORequest.SECTOR_SIZE | |
232 | super().__init__(begin_ts, size, tid, operation) | |
233 | self.dev = dev | |
234 | self.sector = sector | |
235 | self.nr_sector = nr_sector | |
236 | ||
237 | def update_from_rq_complete(self, event): | |
238 | self.end_ts = event.timestamp | |
239 | self.duration = self.end_ts - self.begin_ts | |
240 | ||
241 | @classmethod | |
242 | def new_from_rq_issue(cls, event): | |
243 | begin_ts = event.timestamp | |
244 | dev = event['dev'] | |
245 | sector = event['sector'] | |
246 | nr_sector = event['nr_sector'] | |
247 | tid = event['tid'] | |
248 | # An even rwbs indicates read operation, odd indicates write | |
249 | if event['rwbs'] % 2 == 0: | |
250 | operation = IORequest.OP_READ | |
251 | else: | |
252 | operation = IORequest.OP_WRITE | |
253 | ||
254 | return cls(begin_ts, tid, operation, dev, sector, nr_sector) | |
255 | ||
256 | ||
257 | class BlockRemapRequest(): | |
258 | def __init__(self, dev, sector, old_dev, old_sector): | |
259 | self.dev = dev | |
260 | self.sector = sector | |
261 | self.old_dev = old_dev | |
262 | self.old_sector = old_sector | |
bd3cd7c5 JD |
263 | |
264 | ||
265 | class Syscalls_stats(): | |
266 | def __init__(self): | |
267 | self.read_max = 0 | |
268 | self.read_min = None | |
269 | self.read_total = 0 | |
270 | self.read_count = 0 | |
271 | self.read_rq = [] | |
272 | self.all_read = [] | |
273 | ||
274 | self.write_max = 0 | |
275 | self.write_min = None | |
276 | self.write_total = 0 | |
277 | self.write_count = 0 | |
278 | self.write_rq = [] | |
279 | self.all_write = [] | |
280 | ||
281 | self.open_max = 0 | |
282 | self.open_min = None | |
283 | self.open_total = 0 | |
284 | self.open_count = 0 | |
285 | self.open_rq = [] | |
286 | self.all_open = [] | |
287 | ||
288 | self.sync_max = 0 | |
289 | self.sync_min = None | |
290 | self.sync_total = 0 | |
291 | self.sync_count = 0 | |
292 | self.sync_rq = [] | |
293 | self.all_sync = [] | |
294 | ||
295 | ||
296 | class SyscallConsts(): | |
297 | # TODO: decouple socket/family logic from this class | |
298 | INET_FAMILIES = [socket.AF_INET, socket.AF_INET6] | |
299 | DISK_FAMILIES = [socket.AF_UNIX] | |
300 | # list nof syscalls that open a FD on disk (in the exit_syscall event) | |
baa4cfe9 | 301 | DISK_OPEN_SYSCALLS = ['open', 'openat'] |
bd3cd7c5 JD |
302 | # list of syscalls that open a FD on the network |
303 | # (in the exit_syscall event) | |
baa4cfe9 | 304 | NET_OPEN_SYSCALLS = ['accept', 'accept4', 'socket'] |
bd3cd7c5 | 305 | # list of syscalls that can duplicate a FD |
baa4cfe9 AB |
306 | DUP_OPEN_SYSCALLS = ['fcntl', 'dup2'] |
307 | SYNC_SYSCALLS = ['sync', 'sync_file_range', 'fsync', 'fdatasync'] | |
bd3cd7c5 JD |
308 | # merge the 3 open lists |
309 | OPEN_SYSCALLS = DISK_OPEN_SYSCALLS + NET_OPEN_SYSCALLS + DUP_OPEN_SYSCALLS | |
3f52a605 | 310 | # list of syscalls that close a FD (in the 'fd =' field) |
baa4cfe9 | 311 | CLOSE_SYSCALLS = ['close'] |
bd3cd7c5 | 312 | # list of syscall that read on a FD, value in the exit_syscall following |
baa4cfe9 AB |
313 | READ_SYSCALLS = ['read', 'recvmsg', 'recvfrom', 'splice', 'readv', |
314 | 'sendfile64'] | |
bd3cd7c5 | 315 | # list of syscall that write on a FD, value in the exit_syscall following |
baa4cfe9 | 316 | WRITE_SYSCALLS = ['write', 'sendmsg', 'sendto', 'writev'] |
21167679 JD |
317 | # All I/O related syscalls |
318 | IO_SYSCALLS = OPEN_SYSCALLS + CLOSE_SYSCALLS + READ_SYSCALLS + \ | |
c437b3bd | 319 | WRITE_SYSCALLS + SYNC_SYSCALLS |
bd3cd7c5 JD |
320 | # generic names assigned to special FDs, don't try to match these in the |
321 | # closed_fds dict | |
3f52a605 | 322 | GENERIC_NAMES = ['unknown', 'socket'] |