Fix: check whether analysis has ended before setting last timestamp
[deliverable/lttng-analyses.git] / lttnganalyses / core / analysis.py
1 # The MIT License (MIT)
2 #
3 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
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
23
24 class AnalysisConfig:
25 def __init__(self):
26 self.refresh_period = None
27 self.period_begin_ev_name = None
28 self.period_end_ev_name = None
29 self.period_begin_key_fields = None
30 self.period_end_key_fields = None
31 self.period_key_value = None
32 self.begin_ts = None
33 self.end_ts = None
34 self.min_duration = None
35 self.max_duration = None
36 self.proc_list = None
37 self.tid_list = None
38 self.cpu_list = None
39
40
41 class Analysis:
42 TICK_CB = 'tick'
43
44 def __init__(self, state, conf):
45 self._state = state
46 self._conf = conf
47 self._period_key = None
48 self._period_start_ts = None
49 self._last_event_ts = None
50 self._notification_cbs = {}
51 self._cbs = {}
52
53 self.started = False
54 self.ended = False
55
56 def process_event(self, ev):
57 self._check_analysis_end(ev)
58 if self.ended:
59 return
60
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
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:
76 self._check_refresh(ev)
77
78 def reset(self):
79 raise NotImplementedError()
80
81 def end(self):
82 if self._period_start_ts:
83 self._end_period()
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
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)
105 elif 'syscall_entry' in self._cbs and \
106 (name.startswith('sys_') or name.startswith('syscall_entry_')):
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)
112
113 def _check_analysis_begin(self, ev):
114 if self._conf.begin_ts and ev.timestamp >= self._conf.begin_ts:
115 self.started = True
116 self._period_start_ts = ev.timestamp
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
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
136 if self._period_key:
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
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()
153 self._begin_period(period_key, ev.timestamp)
154 elif ev.name == self._conf.period_begin_ev_name:
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
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()
173
174 def _end_period(self):
175 self._end_period_cb()
176 self._send_notification_cb(Analysis.TICK_CB,
177 begin_ns=self._period_start_ts,
178 end_ns=self._last_event_ts)
179
180 def _end_period_cb(self):
181 pass
182
183 @staticmethod
184 def _get_period_event_key(ev, key_fields):
185 if not key_fields:
186 return None
187
188 key_values = []
189
190 for field in key_fields:
191 try:
192 key_values.append(ev[field])
193 except KeyError:
194 # Error: missing field
195 return None
196
197 return tuple(key_values)
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
207
208 def _filter_cpu(self, cpu):
209 return not (self._conf.cpu_list and cpu not in self._conf.cpu_list)
This page took 0.034723 seconds and 5 git commands to generate.