Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | # The MIT License (MIT) |
2 | # | |
a3fa57c0 | 3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
cee855a2 | 4 | # 2015 - Philippe Proulx <pproulx@efficios.com> |
0b250a71 | 5 | # 2015 - Antoine Busque <abusque@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 | ||
323b3fd6 | 25 | import argparse |
a0acc08c | 26 | import json |
4ac5e240 | 27 | import os |
a0acc08c | 28 | import re |
0b250a71 AB |
29 | import sys |
30 | import subprocess | |
810e6be8 | 31 | import traceback |
0b250a71 | 32 | from babeltrace import TraceCollection |
9079847d | 33 | from . import mi, progressbar |
9463174b | 34 | from .. import __version__ |
0b250a71 | 35 | from ..core import analysis |
9079847d | 36 | from ..common import ( |
e4a48ea8 | 37 | format_utils, parse_utils, trace_utils, version_utils |
9079847d | 38 | ) |
0b250a71 | 39 | from ..linuxautomaton import automaton |
323b3fd6 PP |
40 | |
41 | ||
42 | class Command: | |
a0acc08c PP |
43 | _MI_BASE_TAGS = ['linux-kernel', 'lttng-analyses'] |
44 | _MI_AUTHORS = [ | |
45 | 'Julien Desfossez', | |
46 | 'Antoine Busque', | |
47 | 'Philippe Proulx', | |
48 | ] | |
49 | _MI_URL = 'https://github.com/lttng/lttng-analyses' | |
9463174b | 50 | _VERSION = version_utils.Version.new_from_string(__version__) |
a0ea9266 | 51 | _BT_INTERSECT_VERSION = version_utils.Version(1, 4, 0) |
810e6be8 | 52 | _DEBUG_ENV_VAR = 'LTTNG_ANALYSES_DEBUG' |
a0acc08c PP |
53 | |
54 | def __init__(self, mi_mode=False): | |
b6d9132b AB |
55 | self._analysis = None |
56 | self._analysis_conf = None | |
57 | self._args = None | |
a0ea9266 | 58 | self._babeltrace_version = None |
b6d9132b AB |
59 | self._handles = None |
60 | self._traces = None | |
a0acc08c PP |
61 | self._ticks = 0 |
62 | self._mi_mode = mi_mode | |
810e6be8 | 63 | self._debug_mode = os.environ.get(self._DEBUG_ENV_VAR) |
5bc26da4 PP |
64 | self._run_step('create automaton', self._create_automaton) |
65 | self._run_step('setup MI', self._mi_setup) | |
a0acc08c PP |
66 | |
67 | @property | |
68 | def mi_mode(self): | |
69 | return self._mi_mode | |
323b3fd6 | 70 | |
5bc26da4 | 71 | def _run_step(self, action_title, fn): |
74d112b5 | 72 | try: |
5bc26da4 | 73 | fn() |
74d112b5 | 74 | except KeyboardInterrupt: |
5bc26da4 | 75 | self._print('Cancelled by user') |
74d112b5 | 76 | sys.exit(0) |
5bc26da4 | 77 | except Exception as e: |
810e6be8 AB |
78 | if self._debug_mode: |
79 | traceback.print_exc() | |
80 | ||
5bc26da4 PP |
81 | self._gen_error('Cannot {}: {}'.format(action_title, e)) |
82 | ||
83 | def run(self): | |
84 | self._run_step('parse arguments', self._parse_args) | |
85 | self._run_step('open trace', self._open_trace) | |
86 | self._run_step('create analysis', self._create_analysis) | |
87 | ||
78f6647f | 88 | if not self._mi_mode or not self._args.test_compatibility: |
5bc26da4 PP |
89 | self._run_step('run analysis', self._run_analysis) |
90 | ||
91 | self._run_step('close trace', self._close_trace) | |
b6d9132b | 92 | |
8311b968 PP |
93 | def _mi_error(self, msg, code=None): |
94 | print(json.dumps(mi.get_error(msg, code))) | |
95 | ||
96 | def _non_mi_error(self, msg): | |
dc3ccf03 AB |
97 | if self._args.color: |
98 | try: | |
99 | import termcolor | |
d6c76c60 | 100 | |
dc3ccf03 AB |
101 | msg = termcolor.colored(msg, 'red', attrs=['bold']) |
102 | except ImportError: | |
103 | pass | |
d6c76c60 | 104 | |
323b3fd6 | 105 | print(msg, file=sys.stderr) |
8311b968 | 106 | |
45d44f25 | 107 | def _error(self, msg, exit_code=1): |
8311b968 PP |
108 | if self._mi_mode: |
109 | self._mi_error(msg) | |
110 | else: | |
111 | self._non_mi_error(msg) | |
112 | ||
323b3fd6 PP |
113 | sys.exit(exit_code) |
114 | ||
115 | def _gen_error(self, msg, exit_code=1): | |
116 | self._error('Error: {}'.format(msg), exit_code) | |
117 | ||
118 | def _cmdline_error(self, msg, exit_code=1): | |
119 | self._error('Command line error: {}'.format(msg), exit_code) | |
120 | ||
a0acc08c PP |
121 | def _print(self, msg): |
122 | if not self._mi_mode: | |
123 | print(msg) | |
124 | ||
125 | def _mi_create_result_table(self, table_class_name, begin, end, | |
126 | subtitle=None): | |
127 | return mi.ResultTable(self._mi_table_classes[table_class_name], | |
128 | begin, end, subtitle) | |
129 | ||
130 | def _mi_setup(self): | |
131 | self._mi_table_classes = {} | |
132 | ||
133 | for tc_tuple in self._MI_TABLE_CLASSES: | |
134 | table_class = mi.TableClass(tc_tuple[0], tc_tuple[1], tc_tuple[2]) | |
135 | self._mi_table_classes[table_class.name] = table_class | |
136 | ||
137 | self._mi_clear_result_tables() | |
138 | ||
139 | def _mi_print_metadata(self): | |
140 | tags = self._MI_BASE_TAGS + self._MI_TAGS | |
9463174b | 141 | infos = mi.get_metadata(version=self._VERSION, title=self._MI_TITLE, |
a0acc08c PP |
142 | description=self._MI_DESCRIPTION, |
143 | authors=self._MI_AUTHORS, url=self._MI_URL, | |
144 | tags=tags, | |
145 | table_classes=self._mi_table_classes.values()) | |
146 | print(json.dumps(infos)) | |
147 | ||
148 | def _mi_append_result_table(self, result_table): | |
149 | if not result_table or not result_table.rows: | |
150 | return | |
151 | ||
152 | tc_name = result_table.table_class.name | |
153 | self._mi_get_result_tables(tc_name).append(result_table) | |
154 | ||
155 | def _mi_append_result_tables(self, result_tables): | |
156 | if not result_tables: | |
157 | return | |
158 | ||
159 | for result_table in result_tables: | |
160 | self._mi_append_result_table(result_table) | |
161 | ||
162 | def _mi_clear_result_tables(self): | |
163 | self._result_tables = {} | |
164 | ||
165 | def _mi_get_result_tables(self, table_class_name): | |
166 | if table_class_name not in self._result_tables: | |
167 | self._result_tables[table_class_name] = [] | |
168 | ||
169 | return self._result_tables[table_class_name] | |
170 | ||
171 | def _mi_print(self): | |
172 | results = [] | |
173 | ||
174 | for result_tables in self._result_tables.values(): | |
175 | for result_table in result_tables: | |
176 | results.append(result_table.to_native_object()) | |
177 | ||
178 | obj = { | |
179 | 'results': results, | |
180 | } | |
181 | ||
182 | print(json.dumps(obj)) | |
183 | ||
184 | def _create_summary_result_tables(self): | |
185 | pass | |
186 | ||
bd3cd7c5 | 187 | def _open_trace(self): |
a0ea9266 AB |
188 | self._read_babeltrace_version() |
189 | if self._babeltrace_version >= self._BT_INTERSECT_VERSION: | |
190 | traces = TraceCollection(intersect_mode=self._args.intersect_mode) | |
191 | else: | |
192 | if self._args.intersect_mode: | |
193 | self._print('Warning: intersect mode not available - disabling') | |
194 | self._print(' Use babeltrace {} or later to enable'.format( | |
195 | self._BT_INTERSECT_VERSION)) | |
196 | self._args.intersect_mode = False | |
197 | traces = TraceCollection() | |
b6d9132b | 198 | handles = traces.add_traces_recursive(self._args.path, 'ctf') |
ced36aab | 199 | if handles == {}: |
b6d9132b | 200 | self._gen_error('Failed to open ' + self._args.path, -1) |
ced36aab | 201 | self._handles = handles |
bd3cd7c5 | 202 | self._traces = traces |
dd2efe70 PP |
203 | self._ts_begin = traces.timestamp_begin |
204 | self._ts_end = traces.timestamp_end | |
652bc6b7 | 205 | self._process_date_args() |
ee6a5866 | 206 | self._read_tracer_version() |
b6d9132b | 207 | if not self._args.skip_validation: |
d3014022 | 208 | self._check_lost_events() |
bd3cd7c5 JD |
209 | |
210 | def _close_trace(self): | |
ced36aab AB |
211 | for handle in self._handles.values(): |
212 | self._traces.remove_trace(handle) | |
bd3cd7c5 | 213 | |
ee6a5866 | 214 | def _read_tracer_version(self): |
4ac5e240 | 215 | kernel_path = None |
2dca9c55 JD |
216 | # remove the trailing / |
217 | while self._args.path.endswith('/'): | |
218 | self._args.path = self._args.path[:-1] | |
4ac5e240 AB |
219 | for root, _, _ in os.walk(self._args.path): |
220 | if root.endswith('kernel'): | |
221 | kernel_path = root | |
222 | break | |
223 | ||
224 | if kernel_path is None: | |
225 | self._gen_error('Could not find kernel trace directory') | |
226 | ||
ee6a5866 | 227 | try: |
0349f942 | 228 | ret, metadata = subprocess.getstatusoutput( |
4ac5e240 | 229 | 'babeltrace -o ctf-metadata "%s"' % kernel_path) |
ee6a5866 AB |
230 | except subprocess.CalledProcessError: |
231 | self._gen_error('Cannot run babeltrace on the trace, cannot read' | |
232 | ' tracer version') | |
233 | ||
0349f942 JD |
234 | # fallback to reading the text metadata if babeltrace failed to |
235 | # output the CTF metadata | |
236 | if ret != 0: | |
237 | try: | |
238 | metadata = subprocess.getoutput( | |
239 | 'cat "%s"' % os.path.join(kernel_path, 'metadata')) | |
240 | except subprocess.CalledProcessError: | |
241 | self._gen_error('Cannot read the metadata of the trace, cannot' | |
242 | 'extract tracer version') | |
243 | ||
244 | major_match = re.search(r'tracer_major = "*(\d+)"*', metadata) | |
245 | minor_match = re.search(r'tracer_minor = "*(\d+)"*', metadata) | |
246 | patch_match = re.search(r'tracer_patchlevel = "*(\d+)"*', metadata) | |
ee6a5866 AB |
247 | |
248 | if not major_match or not minor_match or not patch_match: | |
249 | self._gen_error('Malformed metadata, cannot read tracer version') | |
250 | ||
251 | self.state.tracer_version = version_utils.Version( | |
252 | int(major_match.group(1)), | |
253 | int(minor_match.group(1)), | |
254 | int(patch_match.group(1)), | |
255 | ) | |
256 | ||
a0ea9266 AB |
257 | def _read_babeltrace_version(self): |
258 | try: | |
259 | output = subprocess.check_output('babeltrace') | |
260 | except subprocess.CalledProcessError: | |
261 | self._gen_error('Could not run babeltrace to verify version') | |
262 | ||
263 | output = output.decode(sys.stdout.encoding) | |
264 | first_line = output.splitlines()[0] | |
265 | version_string = first_line.split()[-1] | |
266 | ||
267 | self._babeltrace_version = \ | |
268 | version_utils.Version.new_from_string(version_string) | |
269 | ||
d3014022 | 270 | def _check_lost_events(self): |
73f9d005 PP |
271 | msg = 'Checking the trace for lost events...' |
272 | self._print(msg) | |
273 | ||
274 | if self._mi_mode and self._args.output_progress: | |
275 | mi.print_progress(0, msg) | |
276 | ||
d3014022 | 277 | try: |
e0bc16fe | 278 | subprocess.check_output('babeltrace "%s"' % self._args.path, |
d3014022 JD |
279 | shell=True) |
280 | except subprocess.CalledProcessError: | |
b9f05f8d AB |
281 | self._gen_error('Cannot run babeltrace on the trace, cannot verify' |
282 | ' if events were lost during the trace recording') | |
a0acc08c PP |
283 | |
284 | def _pre_analysis(self): | |
285 | pass | |
286 | ||
287 | def _post_analysis(self): | |
288 | if not self._mi_mode: | |
289 | return | |
290 | ||
291 | if self._ticks > 1: | |
292 | self._create_summary_result_tables() | |
293 | ||
294 | self._mi_print() | |
d3014022 | 295 | |
73f9d005 | 296 | def _pb_setup(self): |
dd2efe70 PP |
297 | if self._args.no_progress: |
298 | return | |
299 | ||
300 | ts_end = self._ts_end | |
301 | ||
302 | if self._analysis_conf.end_ts is not None: | |
303 | ts_end = self._analysis_conf.end_ts | |
73f9d005 | 304 | |
73f9d005 | 305 | if self._mi_mode: |
dd2efe70 | 306 | cls = progressbar.MiProgress |
73f9d005 | 307 | else: |
dd2efe70 PP |
308 | cls = progressbar.FancyProgressBar |
309 | ||
310 | self._progress = cls(self._ts_begin, ts_end, self._args.path, | |
311 | self._args.progress_use_size) | |
312 | ||
313 | def _pb_update(self, event): | |
314 | if self._args.no_progress: | |
315 | return | |
316 | ||
317 | self._progress.update(event) | |
73f9d005 PP |
318 | |
319 | def _pb_finish(self): | |
dd2efe70 PP |
320 | if self._args.no_progress: |
321 | return | |
322 | ||
323 | self._progress.finalize() | |
73f9d005 | 324 | |
b6d9132b | 325 | def _run_analysis(self): |
a0acc08c | 326 | self._pre_analysis() |
73f9d005 | 327 | self._pb_setup() |
b6d9132b | 328 | |
a0ea9266 AB |
329 | if self._args.intersect_mode: |
330 | if not self._traces.has_intersection: | |
331 | self._gen_error('Trace has no intersection. ' | |
332 | 'Use --no-intersection to override') | |
333 | ||
bd3cd7c5 | 334 | for event in self._traces.events: |
dd2efe70 | 335 | self._pb_update(event) |
bd3cd7c5 | 336 | self._analysis.process_event(event) |
b6d9132b AB |
337 | if self._analysis.ended: |
338 | break | |
47ba125c | 339 | self._automaton.process_event(event) |
bd3cd7c5 | 340 | |
73f9d005 | 341 | self._pb_finish() |
b6d9132b | 342 | self._analysis.end() |
a0acc08c | 343 | self._post_analysis() |
bd3cd7c5 | 344 | |
3664e4b0 | 345 | def _print_date(self, begin_ns, end_ns): |
9079847d AB |
346 | time_range_str = format_utils.format_time_range( |
347 | begin_ns, end_ns, print_date=True, gmt=self._args.gmt | |
348 | ) | |
349 | date = 'Timerange: {}'.format(time_range_str) | |
350 | ||
a0acc08c | 351 | self._print(date) |
3664e4b0 | 352 | |
9079847d AB |
353 | def _format_timestamp(self, timestamp): |
354 | return format_utils.format_timestamp( | |
355 | timestamp, print_date=self._args.multi_day, gmt=self._args.gmt | |
356 | ) | |
357 | ||
dbbdd963 PP |
358 | def _get_uniform_freq_values(self, durations): |
359 | if self._args.uniform_step is not None: | |
360 | return (self._args.uniform_min, self._args.uniform_max, | |
361 | self._args.uniform_step) | |
362 | ||
363 | if self._args.min is not None: | |
364 | self._args.uniform_min = self._args.min | |
365 | else: | |
366 | self._args.uniform_min = min(durations) | |
367 | if self._args.max is not None: | |
368 | self._args.uniform_max = self._args.max | |
369 | else: | |
370 | self._args.uniform_max = max(durations) | |
371 | ||
372 | # ns to µs | |
373 | self._args.uniform_min /= 1000 | |
374 | self._args.uniform_max /= 1000 | |
375 | self._args.uniform_step = ( | |
376 | (self._args.uniform_max - self._args.uniform_min) / | |
377 | self._args.freq_resolution | |
378 | ) | |
379 | ||
380 | return self._args.uniform_min, self._args.uniform_max, \ | |
650e7f57 | 381 | self._args.uniform_step |
dbbdd963 | 382 | |
bd3cd7c5 | 383 | def _validate_transform_common_args(self, args): |
83ad157b AB |
384 | refresh_period_ns = None |
385 | if args.refresh is not None: | |
386 | try: | |
9079847d | 387 | refresh_period_ns = parse_utils.parse_duration(args.refresh) |
83ad157b AB |
388 | except ValueError as e: |
389 | self._cmdline_error(str(e)) | |
390 | ||
b6d9132b | 391 | self._analysis_conf = analysis.AnalysisConfig() |
83ad157b | 392 | self._analysis_conf.refresh_period = refresh_period_ns |
43a3c04c AB |
393 | self._analysis_conf.period_begin_ev_name = args.period_begin |
394 | self._analysis_conf.period_end_ev_name = args.period_end | |
05684c5e | 395 | self._analysis_conf.period_begin_key_fields = \ |
007d3fe0 | 396 | args.period_begin_key.split(',') |
05684c5e AB |
397 | |
398 | if args.period_end_key: | |
399 | self._analysis_conf.period_end_key_fields = \ | |
007d3fe0 | 400 | args.period_end_key.split(',') |
05684c5e AB |
401 | else: |
402 | self._analysis_conf.period_end_key_fields = \ | |
007d3fe0 | 403 | self._analysis_conf.period_begin_key_fields |
05684c5e AB |
404 | |
405 | if args.period_key_value: | |
406 | self._analysis_conf.period_key_value = \ | |
007d3fe0 | 407 | tuple(args.period_key_value.split(',')) |
05684c5e | 408 | |
a621ba35 AB |
409 | if args.cpu: |
410 | self._analysis_conf.cpu_list = args.cpu.split(',') | |
411 | self._analysis_conf.cpu_list = [int(cpu) for cpu in | |
412 | self._analysis_conf.cpu_list] | |
b6d9132b | 413 | |
810e6be8 AB |
414 | if args.debug: |
415 | self._debug_mode = True | |
416 | ||
b6d9132b AB |
417 | # convert min/max args from µs to ns, if needed |
418 | if hasattr(args, 'min') and args.min is not None: | |
419 | args.min *= 1000 | |
420 | self._analysis_conf.min_duration = args.min | |
421 | if hasattr(args, 'max') and args.max is not None: | |
422 | args.max *= 1000 | |
423 | self._analysis_conf.max_duration = args.max | |
424 | ||
425 | if hasattr(args, 'procname'): | |
47ba125c | 426 | if args.procname: |
43b66dd6 | 427 | self._analysis_conf.proc_list = args.procname.split(',') |
28ad5ec8 | 428 | |
43b66dd6 AB |
429 | if hasattr(args, 'tid'): |
430 | if args.tid: | |
431 | self._analysis_conf.tid_list = args.tid.split(',') | |
432 | self._analysis_conf.tid_list = [int(tid) for tid in | |
433 | self._analysis_conf.tid_list] | |
f89605f0 | 434 | |
1a68e04c AB |
435 | if hasattr(args, 'freq'): |
436 | args.uniform_min = None | |
437 | args.uniform_max = None | |
438 | args.uniform_step = None | |
439 | ||
dbbdd963 PP |
440 | if args.freq_series: |
441 | # implies uniform buckets | |
442 | args.freq_uniform = True | |
443 | ||
a0acc08c | 444 | if self._mi_mode: |
1ab6b93a PP |
445 | # print MI version if required |
446 | if args.mi_version: | |
447 | print(mi.get_version_string()) | |
448 | sys.exit(0) | |
449 | ||
a0acc08c PP |
450 | # print MI metadata if required |
451 | if args.metadata: | |
452 | self._mi_print_metadata() | |
453 | sys.exit(0) | |
454 | ||
455 | # validate path argument (required at this point) | |
456 | if not args.path: | |
457 | self._cmdline_error('Please specify a trace path') | |
458 | ||
459 | if type(args.path) is list: | |
460 | args.path = args.path[0] | |
461 | ||
b6d9132b AB |
462 | def _validate_transform_args(self, args): |
463 | pass | |
f89605f0 | 464 | |
323b3fd6 PP |
465 | def _parse_args(self): |
466 | ap = argparse.ArgumentParser(description=self._DESC) | |
467 | ||
468 | # common arguments | |
83ad157b AB |
469 | ap.add_argument('-r', '--refresh', type=str, |
470 | help='Refresh period, with optional units suffix ' | |
471 | '(default units: s)') | |
a0acc08c PP |
472 | ap.add_argument('--gmt', action='store_true', |
473 | help='Manipulate timestamps based on GMT instead ' | |
474 | 'of local time') | |
73b71522 | 475 | ap.add_argument('--skip-validation', action='store_true', |
d3014022 | 476 | help='Skip the trace validation') |
bd3cd7c5 JD |
477 | ap.add_argument('--begin', type=str, help='start time: ' |
478 | 'hh:mm:ss[.nnnnnnnnn]') | |
479 | ap.add_argument('--end', type=str, help='end time: ' | |
480 | 'hh:mm:ss[.nnnnnnnnn]') | |
43a3c04c AB |
481 | ap.add_argument('--period-begin', type=str, |
482 | help='Analysis period start marker event name') | |
483 | ap.add_argument('--period-end', type=str, | |
484 | help='Analysis period end marker event name ' | |
485 | '(requires --period-begin)') | |
05684c5e | 486 | ap.add_argument('--period-begin-key', type=str, default='cpu_id', |
b9f05f8d AB |
487 | help='Optional, list of event field names used to ' |
488 | 'match period markers (default: cpu_id)') | |
05684c5e AB |
489 | ap.add_argument('--period-end-key', type=str, |
490 | help='Optional, list of event field names used to ' | |
491 | 'match period marker. If none specified, use the same ' | |
492 | ' --period-begin-key') | |
493 | ap.add_argument('--period-key-value', type=str, | |
494 | help='Optional, define a fixed key value to which a' | |
495 | ' period must correspond to be considered.') | |
a621ba35 AB |
496 | ap.add_argument('--cpu', type=str, |
497 | help='Filter the results only for this list of ' | |
498 | 'CPU IDs') | |
a0acc08c PP |
499 | ap.add_argument('--timerange', type=str, help='time range: ' |
500 | '[begin,end]') | |
dd2efe70 PP |
501 | ap.add_argument('--progress-use-size', action='store_true', |
502 | help='use trace size to approximate progress') | |
a0ea9266 AB |
503 | ap.add_argument('--no-intersection', action='store_false', |
504 | dest='intersect_mode', | |
505 | help='disable stream intersection mode') | |
323b3fd6 | 506 | ap.add_argument('-V', '--version', action='version', |
9463174b | 507 | version='LTTng Analyses v{}'.format(self._VERSION)) |
810e6be8 AB |
508 | ap.add_argument('--debug', action='store_true', |
509 | help='Enable debug mode (or set {} environment variable)'.format( | |
510 | self._DEBUG_ENV_VAR)) | |
dc3ccf03 AB |
511 | ap.add_argument('--no-color', action='store_false', dest='color', |
512 | help='Disable colored output') | |
323b3fd6 | 513 | |
a0acc08c PP |
514 | # MI mode-dependent arguments |
515 | if self._mi_mode: | |
1ab6b93a PP |
516 | ap.add_argument('--mi-version', action='store_true', |
517 | help='Print MI version') | |
a0acc08c | 518 | ap.add_argument('--metadata', action='store_true', |
1ab6b93a | 519 | help='Print analysis\' metadata') |
ee39b192 PP |
520 | ap.add_argument('--test-compatibility', action='store_true', |
521 | help='Check if the provided trace is supported and exit') | |
b9f05f8d AB |
522 | ap.add_argument('path', metavar='<path/to/trace>', |
523 | help='trace path', nargs='*') | |
73f9d005 PP |
524 | ap.add_argument('--output-progress', action='store_true', |
525 | help='Print progress indication lines') | |
a0acc08c PP |
526 | else: |
527 | ap.add_argument('--no-progress', action='store_true', | |
528 | help='Don\'t display the progress bar') | |
b9f05f8d AB |
529 | ap.add_argument('path', metavar='<path/to/trace>', |
530 | help='trace path') | |
a0acc08c | 531 | |
b6d9132b AB |
532 | # Used to add command-specific args |
533 | self._add_arguments(ap) | |
323b3fd6 | 534 | |
b6d9132b | 535 | args = ap.parse_args() |
dd2efe70 PP |
536 | |
537 | if self._mi_mode: | |
538 | args.no_progress = True | |
539 | ||
540 | if args.output_progress: | |
541 | args.no_progress = False | |
542 | ||
bd3cd7c5 | 543 | self._validate_transform_common_args(args) |
b6d9132b | 544 | self._validate_transform_args(args) |
323b3fd6 PP |
545 | self._args = args |
546 | ||
b6d9132b AB |
547 | @staticmethod |
548 | def _add_proc_filter_args(ap): | |
549 | ap.add_argument('--procname', type=str, | |
550 | help='Filter the results only for this list of ' | |
551 | 'process names') | |
43b66dd6 AB |
552 | ap.add_argument('--tid', type=str, |
553 | help='Filter the results only for this list of TIDs') | |
b6d9132b AB |
554 | |
555 | @staticmethod | |
556 | def _add_min_max_args(ap): | |
557 | ap.add_argument('--min', type=float, | |
558 | help='Filter out durations shorter than min usec') | |
559 | ap.add_argument('--max', type=float, | |
560 | help='Filter out durations longer than max usec') | |
561 | ||
562 | @staticmethod | |
563 | def _add_freq_args(ap, help=None): | |
564 | if not help: | |
565 | help = 'Output the frequency distribution' | |
566 | ||
567 | ap.add_argument('--freq', action='store_true', help=help) | |
568 | ap.add_argument('--freq-resolution', type=int, default=20, | |
569 | help='Frequency distribution resolution ' | |
570 | '(default 20)') | |
1a68e04c AB |
571 | ap.add_argument('--freq-uniform', action='store_true', |
572 | help='Use a uniform resolution across distributions') | |
86ea0394 | 573 | ap.add_argument('--freq-series', action='store_true', |
650e7f57 AB |
574 | help='Consolidate frequency distribution histogram ' |
575 | 'as a single one') | |
b6d9132b AB |
576 | |
577 | @staticmethod | |
578 | def _add_log_args(ap, help=None): | |
579 | if not help: | |
580 | help = 'Output the events in chronological order' | |
581 | ||
582 | ap.add_argument('--log', action='store_true', help=help) | |
583 | ||
b9f05f8d AB |
584 | @staticmethod |
585 | def _add_top_args(ap, help=None): | |
586 | if not help: | |
587 | help = 'Output the top results' | |
588 | ||
589 | ap.add_argument('--limit', type=int, default=10, | |
590 | help='Limit to top X (default = 10)') | |
591 | ap.add_argument('--top', action='store_true', help=help) | |
592 | ||
b6d9132b AB |
593 | @staticmethod |
594 | def _add_stats_args(ap, help=None): | |
595 | if not help: | |
596 | help = 'Output statistics' | |
597 | ||
598 | ap.add_argument('--stats', action='store_true', help=help) | |
599 | ||
600 | def _add_arguments(self, ap): | |
601 | pass | |
602 | ||
652bc6b7 | 603 | def _process_date_args(self): |
9079847d AB |
604 | def parse_date(date): |
605 | try: | |
606 | ts = parse_utils.parse_trace_collection_date( | |
607 | self._traces, date, self._args.gmt | |
608 | ) | |
609 | except ValueError as e: | |
610 | self._cmdline_error(str(e)) | |
b6d9132b AB |
611 | |
612 | return ts | |
613 | ||
9079847d AB |
614 | self._args.multi_day = trace_utils.is_multi_day_trace_collection( |
615 | self._traces | |
616 | ) | |
602ac199 PP |
617 | begin_ts = None |
618 | end_ts = None | |
619 | ||
620 | if self._args.timerange: | |
9079847d AB |
621 | try: |
622 | begin_ts, end_ts = ( | |
623 | parse_utils.parse_trace_collection_time_range( | |
624 | self._traces, self._args.timerange, self._args.gmt | |
625 | ) | |
626 | ) | |
627 | except ValueError as e: | |
628 | self._cmdline_error(str(e)) | |
652bc6b7 | 629 | else: |
b6d9132b | 630 | if self._args.begin: |
9079847d | 631 | begin_ts = parse_date(self._args.begin) |
b6d9132b | 632 | if self._args.end: |
9079847d | 633 | end_ts = parse_date(self._args.end) |
652bc6b7 | 634 | |
93c7af7d AB |
635 | # We have to check if timestamp_begin is None, which |
636 | # it always is in older versions of babeltrace. In | |
637 | # that case, the test is simply skipped and an invalid | |
638 | # --end value will cause an empty analysis | |
dd2efe70 PP |
639 | if self._ts_begin is not None and \ |
640 | end_ts < self._ts_begin: | |
b6d9132b AB |
641 | self._cmdline_error( |
642 | '--end timestamp before beginning of trace') | |
643 | ||
602ac199 PP |
644 | self._analysis_conf.begin_ts = begin_ts |
645 | self._analysis_conf.end_ts = end_ts | |
b6d9132b AB |
646 | |
647 | def _create_analysis(self): | |
648 | notification_cbs = { | |
a0acc08c | 649 | analysis.Analysis.TICK_CB: self._analysis_tick_cb |
b6d9132b AB |
650 | } |
651 | ||
652 | self._analysis = self._ANALYSIS_CLASS(self.state, self._analysis_conf) | |
653 | self._analysis.register_notification_cbs(notification_cbs) | |
93c7af7d | 654 | |
323b3fd6 | 655 | def _create_automaton(self): |
56936af2 | 656 | self._automaton = automaton.Automaton() |
6e01ed18 | 657 | self.state = self._automaton.state |
bfb81992 | 658 | |
a0acc08c | 659 | def _analysis_tick_cb(self, **kwargs): |
b6d9132b AB |
660 | begin_ns = kwargs['begin_ns'] |
661 | end_ns = kwargs['end_ns'] | |
662 | ||
a0acc08c PP |
663 | self._analysis_tick(begin_ns, end_ns) |
664 | self._ticks += 1 | |
b6d9132b | 665 | |
a0acc08c | 666 | def _analysis_tick(self, begin_ns, end_ns): |
b6d9132b | 667 | raise NotImplementedError() |