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 | ||
b6d9132b | 25 | from ..core import analysis |
56936af2 MJ |
26 | from ..linuxautomaton import automaton |
27 | from .. import __version__ | |
28 | from . import progressbar | |
29 | from ..linuxautomaton import common | |
bd3cd7c5 | 30 | from babeltrace import TraceCollection |
323b3fd6 PP |
31 | import argparse |
32 | import sys | |
d3014022 | 33 | import subprocess |
323b3fd6 PP |
34 | |
35 | ||
36 | class Command: | |
b6d9132b AB |
37 | def __init__(self): |
38 | self._analysis = None | |
39 | self._analysis_conf = None | |
40 | self._args = None | |
41 | self._handles = None | |
42 | self._traces = None | |
43 | ||
323b3fd6 PP |
44 | self._create_automaton() |
45 | ||
b6d9132b AB |
46 | def run(self): |
47 | self._parse_args() | |
48 | self._open_trace() | |
49 | self._create_analysis() | |
50 | self._run_analysis() | |
51 | self._close_trace() | |
52 | ||
323b3fd6 PP |
53 | def _error(self, msg, exit_code=1): |
54 | print(msg, file=sys.stderr) | |
55 | sys.exit(exit_code) | |
56 | ||
57 | def _gen_error(self, msg, exit_code=1): | |
58 | self._error('Error: {}'.format(msg), exit_code) | |
59 | ||
60 | def _cmdline_error(self, msg, exit_code=1): | |
61 | self._error('Command line error: {}'.format(msg), exit_code) | |
62 | ||
bd3cd7c5 JD |
63 | def _open_trace(self): |
64 | traces = TraceCollection() | |
b6d9132b | 65 | handles = traces.add_traces_recursive(self._args.path, 'ctf') |
ced36aab | 66 | if handles == {}: |
b6d9132b | 67 | self._gen_error('Failed to open ' + self._args.path, -1) |
ced36aab | 68 | self._handles = handles |
bd3cd7c5 | 69 | self._traces = traces |
652bc6b7 | 70 | self._process_date_args() |
b6d9132b | 71 | if not self._args.skip_validation: |
d3014022 | 72 | self._check_lost_events() |
bd3cd7c5 JD |
73 | |
74 | def _close_trace(self): | |
ced36aab AB |
75 | for handle in self._handles.values(): |
76 | self._traces.remove_trace(handle) | |
bd3cd7c5 | 77 | |
d3014022 | 78 | def _check_lost_events(self): |
73b71522 | 79 | print('Checking the trace for lost events...') |
d3014022 | 80 | try: |
b6d9132b | 81 | subprocess.check_output('babeltrace %s' % self._args.path, |
d3014022 JD |
82 | shell=True) |
83 | except subprocess.CalledProcessError: | |
73b71522 AB |
84 | print('Error running babeltrace on the trace, cannot verify if ' |
85 | 'events were lost during the trace recording') | |
d3014022 | 86 | |
b6d9132b | 87 | def _run_analysis(self): |
bd3cd7c5 | 88 | progressbar.progressbar_setup(self) |
b6d9132b | 89 | |
bd3cd7c5 JD |
90 | for event in self._traces.events: |
91 | progressbar.progressbar_update(self) | |
bd3cd7c5 | 92 | self._analysis.process_event(event) |
b6d9132b AB |
93 | if self._analysis.ended: |
94 | break | |
47ba125c | 95 | self._automaton.process_event(event) |
bd3cd7c5 | 96 | |
b6d9132b AB |
97 | progressbar.progressbar_finish(self) |
98 | self._analysis.end() | |
bd3cd7c5 | 99 | |
3664e4b0 AB |
100 | def _print_date(self, begin_ns, end_ns): |
101 | date = 'Timerange: [%s, %s]' % ( | |
b6d9132b | 102 | common.ns_to_hour_nsec(begin_ns, gmt=self._args.gmt, |
3664e4b0 | 103 | multi_day=True), |
b6d9132b | 104 | common.ns_to_hour_nsec(end_ns, gmt=self._args.gmt, |
3664e4b0 AB |
105 | multi_day=True)) |
106 | print(date) | |
107 | ||
bd3cd7c5 | 108 | def _validate_transform_common_args(self, args): |
83ad157b AB |
109 | refresh_period_ns = None |
110 | if args.refresh is not None: | |
111 | try: | |
112 | refresh_period_ns = common.duration_str_to_ns(args.refresh) | |
113 | except ValueError as e: | |
114 | self._cmdline_error(str(e)) | |
115 | ||
b6d9132b | 116 | self._analysis_conf = analysis.AnalysisConfig() |
83ad157b | 117 | self._analysis_conf.refresh_period = refresh_period_ns |
43a3c04c AB |
118 | self._analysis_conf.period_begin_ev_name = args.period_begin |
119 | self._analysis_conf.period_end_ev_name = args.period_end | |
5422fc1d | 120 | self._analysis_conf.period_key_fields = args.period_key.split(',') |
b6d9132b AB |
121 | |
122 | # convert min/max args from µs to ns, if needed | |
123 | if hasattr(args, 'min') and args.min is not None: | |
124 | args.min *= 1000 | |
125 | self._analysis_conf.min_duration = args.min | |
126 | if hasattr(args, 'max') and args.max is not None: | |
127 | args.max *= 1000 | |
128 | self._analysis_conf.max_duration = args.max | |
129 | ||
130 | if hasattr(args, 'procname'): | |
131 | args.proc_list = None | |
47ba125c | 132 | if args.procname: |
b6d9132b | 133 | args.proc_list = args.procname.split(',') |
28ad5ec8 | 134 | |
b6d9132b AB |
135 | if hasattr(args, 'pid'): |
136 | args.pid_list = None | |
47ba125c | 137 | if args.pid: |
b6d9132b AB |
138 | args.pid_list = args.pid.split(',') |
139 | args.pid_list = [int(pid) for pid in args.pid_list] | |
f89605f0 | 140 | |
b6d9132b AB |
141 | def _validate_transform_args(self, args): |
142 | pass | |
f89605f0 | 143 | |
323b3fd6 PP |
144 | def _parse_args(self): |
145 | ap = argparse.ArgumentParser(description=self._DESC) | |
146 | ||
147 | # common arguments | |
73b71522 | 148 | ap.add_argument('path', metavar='<path/to/trace>', help='trace path') |
83ad157b AB |
149 | ap.add_argument('-r', '--refresh', type=str, |
150 | help='Refresh period, with optional units suffix ' | |
151 | '(default units: s)') | |
bd3cd7c5 JD |
152 | ap.add_argument('--limit', type=int, default=10, |
153 | help='Limit to top X (default = 10)') | |
73b71522 | 154 | ap.add_argument('--no-progress', action='store_true', |
bd3cd7c5 | 155 | help='Don\'t display the progress bar') |
73b71522 | 156 | ap.add_argument('--skip-validation', action='store_true', |
d3014022 | 157 | help='Skip the trace validation') |
73b71522 | 158 | ap.add_argument('--gmt', action='store_true', |
bd3cd7c5 JD |
159 | help='Manipulate timestamps based on GMT instead ' |
160 | 'of local time') | |
161 | ap.add_argument('--begin', type=str, help='start time: ' | |
162 | 'hh:mm:ss[.nnnnnnnnn]') | |
163 | ap.add_argument('--end', type=str, help='end time: ' | |
164 | 'hh:mm:ss[.nnnnnnnnn]') | |
165 | ap.add_argument('--timerange', type=str, help='time range: ' | |
166 | '[begin,end]') | |
43a3c04c AB |
167 | ap.add_argument('--period-begin', type=str, |
168 | help='Analysis period start marker event name') | |
169 | ap.add_argument('--period-end', type=str, | |
170 | help='Analysis period end marker event name ' | |
171 | '(requires --period-begin)') | |
5422fc1d AB |
172 | ap.add_argument('--period-key', type=str, default='cpu_id', |
173 | help='Optional, list of event field names used to match ' | |
174 | 'period markers (default: cpu_id)') | |
323b3fd6 | 175 | ap.add_argument('-V', '--version', action='version', |
d97f5cb2 | 176 | version='LTTng Analyses v' + __version__) |
323b3fd6 | 177 | |
b6d9132b AB |
178 | # Used to add command-specific args |
179 | self._add_arguments(ap) | |
323b3fd6 | 180 | |
b6d9132b | 181 | args = ap.parse_args() |
bd3cd7c5 | 182 | self._validate_transform_common_args(args) |
b6d9132b | 183 | self._validate_transform_args(args) |
323b3fd6 PP |
184 | self._args = args |
185 | ||
b6d9132b AB |
186 | @staticmethod |
187 | def _add_proc_filter_args(ap): | |
188 | ap.add_argument('--procname', type=str, | |
189 | help='Filter the results only for this list of ' | |
190 | 'process names') | |
191 | ap.add_argument('--pid', type=str, | |
192 | help='Filter the results only for this list of PIDs') | |
193 | ||
194 | @staticmethod | |
195 | def _add_min_max_args(ap): | |
196 | ap.add_argument('--min', type=float, | |
197 | help='Filter out durations shorter than min usec') | |
198 | ap.add_argument('--max', type=float, | |
199 | help='Filter out durations longer than max usec') | |
200 | ||
201 | @staticmethod | |
202 | def _add_freq_args(ap, help=None): | |
203 | if not help: | |
204 | help = 'Output the frequency distribution' | |
205 | ||
206 | ap.add_argument('--freq', action='store_true', help=help) | |
207 | ap.add_argument('--freq-resolution', type=int, default=20, | |
208 | help='Frequency distribution resolution ' | |
209 | '(default 20)') | |
210 | ||
211 | @staticmethod | |
212 | def _add_log_args(ap, help=None): | |
213 | if not help: | |
214 | help = 'Output the events in chronological order' | |
215 | ||
216 | ap.add_argument('--log', action='store_true', help=help) | |
217 | ||
218 | @staticmethod | |
219 | def _add_stats_args(ap, help=None): | |
220 | if not help: | |
221 | help = 'Output statistics' | |
222 | ||
223 | ap.add_argument('--stats', action='store_true', help=help) | |
224 | ||
225 | def _add_arguments(self, ap): | |
226 | pass | |
227 | ||
652bc6b7 | 228 | def _process_date_args(self): |
b6d9132b AB |
229 | |
230 | def date_to_epoch_nsec(date): | |
231 | ts = common.date_to_epoch_nsec(self._handles, date, self._args.gmt) | |
232 | if ts is None: | |
233 | self._cmdline_error('Invalid date format: "{}"'.format(date)) | |
234 | ||
235 | return ts | |
236 | ||
237 | self._args.multi_day = common.is_multi_day_trace_collection( | |
652bc6b7 | 238 | self._handles) |
b6d9132b AB |
239 | if self._args.timerange: |
240 | (self._analysis_conf.begin_ts, self._analysis_conf.end_ts) = \ | |
241 | common.extract_timerange(self._handles, self._args.timerange, | |
242 | self._args.gmt) | |
243 | if self._args.begin is None or self._args.end is None: | |
652bc6b7 AB |
244 | print('Invalid timeformat') |
245 | sys.exit(1) | |
246 | else: | |
b6d9132b AB |
247 | if self._args.begin: |
248 | self._args.begin = date_to_epoch_nsec( | |
249 | self._args.begin) | |
250 | if self._args.end: | |
251 | self._analysis_conf.end_ts = date_to_epoch_nsec( | |
252 | self._args.end) | |
652bc6b7 | 253 | |
93c7af7d AB |
254 | # We have to check if timestamp_begin is None, which |
255 | # it always is in older versions of babeltrace. In | |
256 | # that case, the test is simply skipped and an invalid | |
257 | # --end value will cause an empty analysis | |
258 | if self._traces.timestamp_begin is not None and \ | |
b6d9132b AB |
259 | self._analysis_conf.end_ts < self._traces.timestamp_begin: |
260 | self._cmdline_error( | |
261 | '--end timestamp before beginning of trace') | |
262 | ||
263 | self._analysis_conf.begin_ts = self._args.begin | |
264 | self._analysis_conf.end_ts = self._args.end | |
265 | ||
266 | def _create_analysis(self): | |
267 | notification_cbs = { | |
268 | 'output_results': self._output_results | |
269 | } | |
270 | ||
271 | self._analysis = self._ANALYSIS_CLASS(self.state, self._analysis_conf) | |
272 | self._analysis.register_notification_cbs(notification_cbs) | |
93c7af7d | 273 | |
323b3fd6 | 274 | def _create_automaton(self): |
56936af2 | 275 | self._automaton = automaton.Automaton() |
6e01ed18 | 276 | self.state = self._automaton.state |
bfb81992 | 277 | |
b6d9132b AB |
278 | def _output_results(self, **kwargs): |
279 | begin_ns = kwargs['begin_ns'] | |
280 | end_ns = kwargs['end_ns'] | |
281 | ||
282 | # TODO allow output of results to some other place/in other | |
283 | # format than plain text-cli | |
284 | self._print_results(begin_ns, end_ns) | |
285 | ||
286 | def _print_results(self, begin_ns, end_ns): | |
287 | raise NotImplementedError() | |
288 | ||
bfb81992 | 289 | def _filter_process(self, proc): |
f7a2ca1b JD |
290 | if not proc: |
291 | return True | |
b6d9132b | 292 | if self._args.proc_list and proc.comm not in self._args.proc_list: |
bfb81992 | 293 | return False |
b6d9132b | 294 | if self._args.pid_list and proc.pid not in self._args.pid_list: |
bfb81992 AB |
295 | return False |
296 | return True |