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 | |
5422fc1d | 29 | self.period_key_fields = None |
b6d9132b AB |
30 | self.begin_ts = None |
31 | self.end_ts = None | |
32 | self.min_duration = None | |
33 | self.max_duration = None | |
43b66dd6 AB |
34 | self.proc_list = None |
35 | self.tid_list = None | |
a621ba35 | 36 | self.cpu_list = None |
b6d9132b | 37 | |
4ed24f86 | 38 | |
323b3fd6 | 39 | class Analysis: |
a0acc08c PP |
40 | TICK_CB = 'tick' |
41 | ||
b6d9132b AB |
42 | def __init__(self, state, conf): |
43 | self._state = state | |
44 | self._conf = conf | |
43a3c04c | 45 | self._period_key = None |
b6d9132b AB |
46 | self._period_start_ts = None |
47 | self._last_event_ts = None | |
48 | self._notification_cbs = {} | |
49 | self._cbs = {} | |
50 | ||
b6d9132b AB |
51 | self.started = False |
52 | self.ended = False | |
53 | ||
323b3fd6 | 54 | def process_event(self, ev): |
b6d9132b AB |
55 | self._last_event_ts = ev.timestamp |
56 | ||
57 | if not self.started: | |
58 | if self._conf.begin_ts: | |
59 | self._check_analysis_begin(ev) | |
60 | if not self.started: | |
61 | return | |
62 | else: | |
63 | self._period_start_ts = ev.timestamp | |
64 | self.started = True | |
65 | ||
66 | self._check_analysis_end(ev) | |
67 | if self.ended: | |
68 | return | |
69 | ||
43a3c04c AB |
70 | # Prioritise period events over refresh period |
71 | if self._conf.period_begin_ev_name is not None: | |
72 | self._handle_period_event(ev) | |
73 | elif self._conf.refresh_period is not None: | |
b6d9132b | 74 | self._check_refresh(ev) |
b88c1bbc | 75 | |
b5db5706 AB |
76 | def reset(self): |
77 | raise NotImplementedError() | |
78 | ||
b6d9132b | 79 | def end(self): |
5422fc1d AB |
80 | if self._period_start_ts: |
81 | self._end_period() | |
b6d9132b AB |
82 | |
83 | def register_notification_cbs(self, cbs): | |
84 | for name in cbs: | |
85 | if name not in self._notification_cbs: | |
86 | self._notification_cbs[name] = [] | |
87 | ||
88 | self._notification_cbs[name].append(cbs[name]) | |
89 | ||
90 | def _send_notification_cb(self, name, **kwargs): | |
91 | if name in self._notification_cbs: | |
92 | for cb in self._notification_cbs[name]: | |
93 | cb(**kwargs) | |
94 | ||
b88c1bbc AB |
95 | def _register_cbs(self, cbs): |
96 | self._cbs = cbs | |
97 | ||
98 | def _process_event_cb(self, ev): | |
99 | name = ev.name | |
100 | ||
101 | if name in self._cbs: | |
102 | self._cbs[name](ev) | |
c2e5e625 | 103 | elif 'syscall_entry' in self._cbs and \ |
771c7e66 | 104 | (name.startswith('sys_') or name.startswith('syscall_entry_')): |
c2e5e625 AB |
105 | self._cbs['syscall_entry'](ev) |
106 | elif 'syscall_exit' in self._cbs and \ | |
107 | (name.startswith('exit_syscall') or | |
108 | name.startswith('syscall_exit_')): | |
109 | self._cbs['syscall_exit'](ev) | |
b6d9132b AB |
110 | |
111 | def _check_analysis_begin(self, ev): | |
85bf812e | 112 | if self._conf.begin_ts and ev.timestamp >= self._conf.begin_ts: |
b6d9132b | 113 | self.started = True |
85bf812e | 114 | self._period_start_ts = ev.timestamp |
b6d9132b AB |
115 | self.reset() |
116 | ||
117 | def _check_analysis_end(self, ev): | |
118 | if self._conf.end_ts and ev.timestamp > self._conf.end_ts: | |
119 | self.ended = True | |
120 | ||
121 | def _check_refresh(self, ev): | |
122 | if not self._period_start_ts: | |
123 | self._period_start_ts = ev.timestamp | |
124 | elif ev.timestamp >= (self._period_start_ts + | |
125 | self._conf.refresh_period): | |
126 | self._end_period() | |
127 | self._period_start_ts = ev.timestamp | |
128 | ||
43a3c04c AB |
129 | def _handle_period_event(self, ev): |
130 | if ev.name != self._conf.period_begin_ev_name and \ | |
131 | ev.name != self._conf.period_end_ev_name: | |
132 | return | |
133 | ||
134 | period_key = self._get_period_event_key(ev) | |
135 | if not period_key: | |
5422fc1d | 136 | # There was an error caused by a missing field, ignore |
43a3c04c AB |
137 | # this period event |
138 | return | |
139 | ||
140 | if self._period_key: | |
141 | if period_key == self._period_key: | |
142 | if self._conf.period_end_ev_name: | |
143 | if ev.name == self._conf.period_end_ev_name: | |
144 | self._end_period() | |
145 | self._period_key = None | |
146 | self._period_start_ts = None | |
147 | elif ev.name == self._conf.period_begin_ev_name: | |
148 | self._end_period() | |
149 | self._period_key = period_key | |
150 | self._period_start_ts = ev.timestamp | |
5422fc1d | 151 | elif ev.name == self._conf.period_begin_ev_name: |
43a3c04c AB |
152 | self._period_key = period_key |
153 | self._period_start_ts = ev.timestamp | |
154 | ||
b6d9132b AB |
155 | def _end_period(self): |
156 | self._end_period_cb() | |
a0acc08c | 157 | self._send_notification_cb(Analysis.TICK_CB, |
b6d9132b AB |
158 | begin_ns=self._period_start_ts, |
159 | end_ns=self._last_event_ts) | |
160 | self.reset() | |
161 | ||
162 | def _end_period_cb(self): | |
163 | pass | |
43a3c04c AB |
164 | |
165 | def _get_period_event_key(self, ev): | |
5422fc1d AB |
166 | if not self._conf.period_key_fields: |
167 | return None | |
168 | ||
169 | key_values = [] | |
170 | ||
171 | for field in self._conf.period_key_fields: | |
172 | try: | |
173 | key_values.append(ev[field]) | |
174 | except KeyError: | |
175 | # Error: missing field | |
176 | return None | |
177 | ||
178 | return tuple(key_values) | |
43b66dd6 AB |
179 | |
180 | def _filter_process(self, proc): | |
181 | if not proc: | |
182 | return True | |
183 | if self._conf.proc_list and proc.comm not in self._conf.proc_list: | |
184 | return False | |
185 | if self._conf.tid_list and proc.tid not in self._conf.tid_list: | |
186 | return False | |
187 | return True | |
a621ba35 AB |
188 | |
189 | def _filter_cpu(self, cpu): | |
190 | return not (self._conf.cpu_list and cpu not in self._conf.cpu_list) |