Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | # The MIT License (MIT) |
2 | # | |
a3fa57c0 | 3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
4ed24f86 JD |
4 | # |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | # of this software and associated documentation files (the "Software"), to deal | |
7 | # in the Software without restriction, including without limitation the rights | |
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | # copies of the Software, and to permit persons to whom the Software is | |
10 | # furnished to do so, subject to the following conditions: | |
11 | # | |
12 | # The above copyright notice and this permission notice shall be included in | |
13 | # all copies or substantial portions of the Software. | |
14 | # | |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
21 | # SOFTWARE. | |
22 | ||
b6d9132b AB |
23 | |
24 | class AnalysisConfig: | |
25 | def __init__(self): | |
26 | self.refresh_period = None | |
43a3c04c AB |
27 | self.period_begin_ev_name = None |
28 | self.period_end_ev_name = None | |
05684c5e AB |
29 | self.period_begin_key_fields = None |
30 | self.period_end_key_fields = None | |
31 | self.period_key_value = None | |
b6d9132b AB |
32 | self.begin_ts = None |
33 | self.end_ts = None | |
34 | self.min_duration = None | |
35 | self.max_duration = None | |
43b66dd6 AB |
36 | self.proc_list = None |
37 | self.tid_list = None | |
a621ba35 | 38 | self.cpu_list = None |
b6d9132b | 39 | |
4ed24f86 | 40 | |
323b3fd6 | 41 | class Analysis: |
a0acc08c PP |
42 | TICK_CB = 'tick' |
43 | ||
b6d9132b AB |
44 | def __init__(self, state, conf): |
45 | self._state = state | |
46 | self._conf = conf | |
43a3c04c | 47 | self._period_key = None |
b6d9132b AB |
48 | self._period_start_ts = None |
49 | self._last_event_ts = None | |
50 | self._notification_cbs = {} | |
51 | self._cbs = {} | |
52 | ||
b6d9132b AB |
53 | self.started = False |
54 | self.ended = False | |
55 | ||
323b3fd6 | 56 | def process_event(self, ev): |
e350f2be AB |
57 | self._check_analysis_end(ev) |
58 | if self.ended: | |
59 | return | |
60 | ||
b6d9132b AB |
61 | self._last_event_ts = ev.timestamp |
62 | ||
63 | if not self.started: | |
64 | if self._conf.begin_ts: | |
65 | self._check_analysis_begin(ev) | |
66 | if not self.started: | |
67 | return | |
68 | else: | |
69 | self._period_start_ts = ev.timestamp | |
70 | self.started = True | |
71 | ||
43a3c04c AB |
72 | # Prioritise period events over refresh period |
73 | if self._conf.period_begin_ev_name is not None: | |
74 | self._handle_period_event(ev) | |
75 | elif self._conf.refresh_period is not None: | |
b6d9132b | 76 | self._check_refresh(ev) |
b88c1bbc | 77 | |
b5db5706 AB |
78 | def reset(self): |
79 | raise NotImplementedError() | |
80 | ||
b6d9132b | 81 | def end(self): |
5422fc1d AB |
82 | if self._period_start_ts: |
83 | self._end_period() | |
b6d9132b AB |
84 | |
85 | def register_notification_cbs(self, cbs): | |
86 | for name in cbs: | |
87 | if name not in self._notification_cbs: | |
88 | self._notification_cbs[name] = [] | |
89 | ||
90 | self._notification_cbs[name].append(cbs[name]) | |
91 | ||
92 | def _send_notification_cb(self, name, **kwargs): | |
93 | if name in self._notification_cbs: | |
94 | for cb in self._notification_cbs[name]: | |
95 | cb(**kwargs) | |
96 | ||
b88c1bbc AB |
97 | def _register_cbs(self, cbs): |
98 | self._cbs = cbs | |
99 | ||
100 | def _process_event_cb(self, ev): | |
101 | name = ev.name | |
102 | ||
103 | if name in self._cbs: | |
104 | self._cbs[name](ev) | |
c2e5e625 | 105 | elif 'syscall_entry' in self._cbs and \ |
771c7e66 | 106 | (name.startswith('sys_') or name.startswith('syscall_entry_')): |
c2e5e625 AB |
107 | self._cbs['syscall_entry'](ev) |
108 | elif 'syscall_exit' in self._cbs and \ | |
109 | (name.startswith('exit_syscall') or | |
110 | name.startswith('syscall_exit_')): | |
111 | self._cbs['syscall_exit'](ev) | |
b6d9132b AB |
112 | |
113 | def _check_analysis_begin(self, ev): | |
85bf812e | 114 | if self._conf.begin_ts and ev.timestamp >= self._conf.begin_ts: |
b6d9132b | 115 | self.started = True |
85bf812e | 116 | self._period_start_ts = ev.timestamp |
b6d9132b AB |
117 | self.reset() |
118 | ||
119 | def _check_analysis_end(self, ev): | |
120 | if self._conf.end_ts and ev.timestamp > self._conf.end_ts: | |
121 | self.ended = True | |
122 | ||
123 | def _check_refresh(self, ev): | |
124 | if not self._period_start_ts: | |
125 | self._period_start_ts = ev.timestamp | |
126 | elif ev.timestamp >= (self._period_start_ts + | |
127 | self._conf.refresh_period): | |
128 | self._end_period() | |
129 | self._period_start_ts = ev.timestamp | |
130 | ||
43a3c04c AB |
131 | def _handle_period_event(self, ev): |
132 | if ev.name != self._conf.period_begin_ev_name and \ | |
133 | ev.name != self._conf.period_end_ev_name: | |
134 | return | |
135 | ||
43a3c04c | 136 | if self._period_key: |
05684c5e AB |
137 | period_key = Analysis._get_period_event_key( |
138 | ev, self._conf.period_end_key_fields) | |
139 | ||
140 | if not period_key: | |
141 | # There was an error caused by a missing field, ignore | |
142 | # this period event | |
143 | return | |
144 | ||
43a3c04c AB |
145 | if period_key == self._period_key: |
146 | if self._conf.period_end_ev_name: | |
147 | if ev.name == self._conf.period_end_ev_name: | |
148 | self._end_period() | |
149 | self._period_key = None | |
150 | self._period_start_ts = None | |
151 | elif ev.name == self._conf.period_begin_ev_name: | |
152 | self._end_period() | |
72e07bd5 | 153 | self._begin_period(period_key, ev.timestamp) |
5422fc1d | 154 | elif ev.name == self._conf.period_begin_ev_name: |
05684c5e AB |
155 | period_key = Analysis._get_period_event_key( |
156 | ev, self._conf.period_begin_key_fields) | |
157 | ||
158 | if not period_key: | |
159 | return | |
160 | ||
161 | if self._conf.period_key_value: | |
162 | # Must convert the period key to string for comparison | |
163 | str_period_key = tuple(map(str, period_key)) | |
164 | if self._conf.period_key_value != str_period_key: | |
165 | return | |
166 | ||
72e07bd5 AB |
167 | self._begin_period(period_key, ev.timestamp) |
168 | ||
169 | def _begin_period(self, period_key, timestamp): | |
170 | self._period_key = period_key | |
171 | self._period_start_ts = timestamp | |
172 | self.reset() | |
43a3c04c | 173 | |
b6d9132b AB |
174 | def _end_period(self): |
175 | self._end_period_cb() | |
a0acc08c | 176 | self._send_notification_cb(Analysis.TICK_CB, |
b6d9132b AB |
177 | begin_ns=self._period_start_ts, |
178 | end_ns=self._last_event_ts) | |
b6d9132b AB |
179 | |
180 | def _end_period_cb(self): | |
181 | pass | |
43a3c04c | 182 | |
05684c5e AB |
183 | @staticmethod |
184 | def _get_period_event_key(ev, key_fields): | |
185 | if not key_fields: | |
5422fc1d AB |
186 | return None |
187 | ||
188 | key_values = [] | |
189 | ||
05684c5e | 190 | for field in key_fields: |
5422fc1d AB |
191 | try: |
192 | key_values.append(ev[field]) | |
193 | except KeyError: | |
194 | # Error: missing field | |
195 | return None | |
196 | ||
197 | return tuple(key_values) | |
43b66dd6 AB |
198 | |
199 | def _filter_process(self, proc): | |
200 | if not proc: | |
201 | return True | |
202 | if self._conf.proc_list and proc.comm not in self._conf.proc_list: | |
203 | return False | |
204 | if self._conf.tid_list and proc.tid not in self._conf.tid_list: | |
205 | return False | |
206 | return True | |
a621ba35 AB |
207 | |
208 | def _filter_cpu(self, cpu): | |
209 | return not (self._conf.cpu_list and cpu not in self._conf.cpu_list) |