Output last process priority in cputop
[deliverable/lttng-analyses.git] / lttnganalyses / cli / command.py
CommitLineData
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>
4ed24f86
JD
5#
6# Permission is hereby granted, free of charge, to any person obtaining a copy
7# of this software and associated documentation files (the "Software"), to deal
8# in the Software without restriction, including without limitation the rights
9# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10# copies of the Software, and to permit persons to whom the Software is
11# furnished to do so, subject to the following conditions:
12#
13# The above copyright notice and this permission notice shall be included in
14# all copies or substantial portions of the Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22# SOFTWARE.
23
b6d9132b 24from ..core import analysis
56936af2
MJ
25from ..linuxautomaton import automaton
26from .. import __version__
27from . import progressbar
28from ..linuxautomaton import common
a0acc08c 29from .. import _version
bd3cd7c5 30from babeltrace import TraceCollection
323b3fd6
PP
31import argparse
32import sys
d3014022 33import subprocess
a0acc08c
PP
34import json
35import re
36from . import mi
323b3fd6
PP
37
38
39class Command:
a0acc08c
PP
40 _MI_BASE_TAGS = ['linux-kernel', 'lttng-analyses']
41 _MI_AUTHORS = [
42 'Julien Desfossez',
43 'Antoine Busque',
44 'Philippe Proulx',
45 ]
46 _MI_URL = 'https://github.com/lttng/lttng-analyses'
47
48 def __init__(self, mi_mode=False):
b6d9132b
AB
49 self._analysis = None
50 self._analysis_conf = None
51 self._args = None
52 self._handles = None
53 self._traces = None
a0acc08c
PP
54 self._ticks = 0
55 self._mi_mode = mi_mode
323b3fd6 56 self._create_automaton()
a0acc08c
PP
57 self._mi_setup()
58
59 @property
60 def mi_mode(self):
61 return self._mi_mode
323b3fd6 62
b6d9132b
AB
63 def run(self):
64 self._parse_args()
65 self._open_trace()
66 self._create_analysis()
67 self._run_analysis()
68 self._close_trace()
69
323b3fd6 70 def _error(self, msg, exit_code=1):
d6c76c60
PP
71 try:
72 import termcolor
73
74 msg = termcolor.colored(msg, 'red', attrs=['bold'])
75 except:
76 pass
77
323b3fd6
PP
78 print(msg, file=sys.stderr)
79 sys.exit(exit_code)
80
81 def _gen_error(self, msg, exit_code=1):
82 self._error('Error: {}'.format(msg), exit_code)
83
84 def _cmdline_error(self, msg, exit_code=1):
85 self._error('Command line error: {}'.format(msg), exit_code)
86
a0acc08c
PP
87 def _print(self, msg):
88 if not self._mi_mode:
89 print(msg)
90
91 def _mi_create_result_table(self, table_class_name, begin, end,
92 subtitle=None):
93 return mi.ResultTable(self._mi_table_classes[table_class_name],
94 begin, end, subtitle)
95
96 def _mi_setup(self):
97 self._mi_table_classes = {}
98
99 for tc_tuple in self._MI_TABLE_CLASSES:
100 table_class = mi.TableClass(tc_tuple[0], tc_tuple[1], tc_tuple[2])
101 self._mi_table_classes[table_class.name] = table_class
102
103 self._mi_clear_result_tables()
104
105 def _mi_print_metadata(self):
106 tags = self._MI_BASE_TAGS + self._MI_TAGS
107 infos = mi.get_metadata(version=self._MI_VERSION, title=self._MI_TITLE,
108 description=self._MI_DESCRIPTION,
109 authors=self._MI_AUTHORS, url=self._MI_URL,
110 tags=tags,
111 table_classes=self._mi_table_classes.values())
112 print(json.dumps(infos))
113
114 def _mi_append_result_table(self, result_table):
115 if not result_table or not result_table.rows:
116 return
117
118 tc_name = result_table.table_class.name
119 self._mi_get_result_tables(tc_name).append(result_table)
120
121 def _mi_append_result_tables(self, result_tables):
122 if not result_tables:
123 return
124
125 for result_table in result_tables:
126 self._mi_append_result_table(result_table)
127
128 def _mi_clear_result_tables(self):
129 self._result_tables = {}
130
131 def _mi_get_result_tables(self, table_class_name):
132 if table_class_name not in self._result_tables:
133 self._result_tables[table_class_name] = []
134
135 return self._result_tables[table_class_name]
136
137 def _mi_print(self):
138 results = []
139
140 for result_tables in self._result_tables.values():
141 for result_table in result_tables:
142 results.append(result_table.to_native_object())
143
144 obj = {
145 'results': results,
146 }
147
148 print(json.dumps(obj))
149
150 def _create_summary_result_tables(self):
151 pass
152
bd3cd7c5
JD
153 def _open_trace(self):
154 traces = TraceCollection()
b6d9132b 155 handles = traces.add_traces_recursive(self._args.path, 'ctf')
ced36aab 156 if handles == {}:
b6d9132b 157 self._gen_error('Failed to open ' + self._args.path, -1)
ced36aab 158 self._handles = handles
bd3cd7c5 159 self._traces = traces
652bc6b7 160 self._process_date_args()
b6d9132b 161 if not self._args.skip_validation:
d3014022 162 self._check_lost_events()
bd3cd7c5
JD
163
164 def _close_trace(self):
ced36aab
AB
165 for handle in self._handles.values():
166 self._traces.remove_trace(handle)
bd3cd7c5 167
d3014022 168 def _check_lost_events(self):
a0acc08c 169 self._print('Checking the trace for lost events...')
d3014022 170 try:
e0bc16fe 171 subprocess.check_output('babeltrace "%s"' % self._args.path,
d3014022
JD
172 shell=True)
173 except subprocess.CalledProcessError:
b9f05f8d
AB
174 self._gen_error('Cannot run babeltrace on the trace, cannot verify'
175 ' if events were lost during the trace recording')
a0acc08c
PP
176
177 def _pre_analysis(self):
178 pass
179
180 def _post_analysis(self):
181 if not self._mi_mode:
182 return
183
184 if self._ticks > 1:
185 self._create_summary_result_tables()
186
187 self._mi_print()
d3014022 188
b6d9132b 189 def _run_analysis(self):
a0acc08c 190 self._pre_analysis()
bd3cd7c5 191 progressbar.progressbar_setup(self)
b6d9132b 192
bd3cd7c5
JD
193 for event in self._traces.events:
194 progressbar.progressbar_update(self)
bd3cd7c5 195 self._analysis.process_event(event)
b6d9132b
AB
196 if self._analysis.ended:
197 break
47ba125c 198 self._automaton.process_event(event)
bd3cd7c5 199
b6d9132b
AB
200 progressbar.progressbar_finish(self)
201 self._analysis.end()
a0acc08c 202 self._post_analysis()
bd3cd7c5 203
3664e4b0
AB
204 def _print_date(self, begin_ns, end_ns):
205 date = 'Timerange: [%s, %s]' % (
b6d9132b 206 common.ns_to_hour_nsec(begin_ns, gmt=self._args.gmt,
3664e4b0 207 multi_day=True),
b6d9132b 208 common.ns_to_hour_nsec(end_ns, gmt=self._args.gmt,
3664e4b0 209 multi_day=True))
a0acc08c 210 self._print(date)
3664e4b0 211
bd3cd7c5 212 def _validate_transform_common_args(self, args):
83ad157b
AB
213 refresh_period_ns = None
214 if args.refresh is not None:
215 try:
216 refresh_period_ns = common.duration_str_to_ns(args.refresh)
217 except ValueError as e:
218 self._cmdline_error(str(e))
219
b6d9132b 220 self._analysis_conf = analysis.AnalysisConfig()
83ad157b 221 self._analysis_conf.refresh_period = refresh_period_ns
43a3c04c
AB
222 self._analysis_conf.period_begin_ev_name = args.period_begin
223 self._analysis_conf.period_end_ev_name = args.period_end
5422fc1d 224 self._analysis_conf.period_key_fields = args.period_key.split(',')
a621ba35
AB
225 if args.cpu:
226 self._analysis_conf.cpu_list = args.cpu.split(',')
227 self._analysis_conf.cpu_list = [int(cpu) for cpu in
228 self._analysis_conf.cpu_list]
b6d9132b
AB
229
230 # convert min/max args from µs to ns, if needed
231 if hasattr(args, 'min') and args.min is not None:
232 args.min *= 1000
233 self._analysis_conf.min_duration = args.min
234 if hasattr(args, 'max') and args.max is not None:
235 args.max *= 1000
236 self._analysis_conf.max_duration = args.max
237
238 if hasattr(args, 'procname'):
47ba125c 239 if args.procname:
43b66dd6 240 self._analysis_conf.proc_list = args.procname.split(',')
28ad5ec8 241
43b66dd6
AB
242 if hasattr(args, 'tid'):
243 if args.tid:
244 self._analysis_conf.tid_list = args.tid.split(',')
245 self._analysis_conf.tid_list = [int(tid) for tid in
246 self._analysis_conf.tid_list]
f89605f0 247
1a68e04c
AB
248 if hasattr(args, 'freq'):
249 args.uniform_min = None
250 args.uniform_max = None
251 args.uniform_step = None
252
a0acc08c
PP
253 if self._mi_mode:
254 # force no progress in MI mode
255 args.no_progress = True
256
257 # print MI metadata if required
258 if args.metadata:
259 self._mi_print_metadata()
260 sys.exit(0)
261
262 # validate path argument (required at this point)
263 if not args.path:
264 self._cmdline_error('Please specify a trace path')
265
266 if type(args.path) is list:
267 args.path = args.path[0]
268
b6d9132b
AB
269 def _validate_transform_args(self, args):
270 pass
f89605f0 271
323b3fd6
PP
272 def _parse_args(self):
273 ap = argparse.ArgumentParser(description=self._DESC)
274
275 # common arguments
83ad157b
AB
276 ap.add_argument('-r', '--refresh', type=str,
277 help='Refresh period, with optional units suffix '
278 '(default units: s)')
a0acc08c
PP
279 ap.add_argument('--gmt', action='store_true',
280 help='Manipulate timestamps based on GMT instead '
281 'of local time')
73b71522 282 ap.add_argument('--skip-validation', action='store_true',
d3014022 283 help='Skip the trace validation')
bd3cd7c5
JD
284 ap.add_argument('--begin', type=str, help='start time: '
285 'hh:mm:ss[.nnnnnnnnn]')
286 ap.add_argument('--end', type=str, help='end time: '
287 'hh:mm:ss[.nnnnnnnnn]')
43a3c04c
AB
288 ap.add_argument('--period-begin', type=str,
289 help='Analysis period start marker event name')
290 ap.add_argument('--period-end', type=str,
291 help='Analysis period end marker event name '
292 '(requires --period-begin)')
5422fc1d 293 ap.add_argument('--period-key', type=str, default='cpu_id',
b9f05f8d
AB
294 help='Optional, list of event field names used to '
295 'match period markers (default: cpu_id)')
a621ba35
AB
296 ap.add_argument('--cpu', type=str,
297 help='Filter the results only for this list of '
298 'CPU IDs')
a0acc08c
PP
299 ap.add_argument('--timerange', type=str, help='time range: '
300 '[begin,end]')
323b3fd6 301 ap.add_argument('-V', '--version', action='version',
d97f5cb2 302 version='LTTng Analyses v' + __version__)
323b3fd6 303
a0acc08c
PP
304 # MI mode-dependent arguments
305 if self._mi_mode:
306 ap.add_argument('--metadata', action='store_true',
b9f05f8d
AB
307 help='Show analysis\'s metadata')
308 ap.add_argument('path', metavar='<path/to/trace>',
309 help='trace path', nargs='*')
a0acc08c
PP
310 else:
311 ap.add_argument('--no-progress', action='store_true',
312 help='Don\'t display the progress bar')
b9f05f8d
AB
313 ap.add_argument('path', metavar='<path/to/trace>',
314 help='trace path')
a0acc08c 315
b6d9132b
AB
316 # Used to add command-specific args
317 self._add_arguments(ap)
323b3fd6 318
b6d9132b 319 args = ap.parse_args()
bd3cd7c5 320 self._validate_transform_common_args(args)
b6d9132b 321 self._validate_transform_args(args)
323b3fd6
PP
322 self._args = args
323
b6d9132b
AB
324 @staticmethod
325 def _add_proc_filter_args(ap):
326 ap.add_argument('--procname', type=str,
327 help='Filter the results only for this list of '
328 'process names')
43b66dd6
AB
329 ap.add_argument('--tid', type=str,
330 help='Filter the results only for this list of TIDs')
b6d9132b
AB
331
332 @staticmethod
333 def _add_min_max_args(ap):
334 ap.add_argument('--min', type=float,
335 help='Filter out durations shorter than min usec')
336 ap.add_argument('--max', type=float,
337 help='Filter out durations longer than max usec')
338
339 @staticmethod
340 def _add_freq_args(ap, help=None):
341 if not help:
342 help = 'Output the frequency distribution'
343
344 ap.add_argument('--freq', action='store_true', help=help)
345 ap.add_argument('--freq-resolution', type=int, default=20,
346 help='Frequency distribution resolution '
347 '(default 20)')
1a68e04c
AB
348 ap.add_argument('--freq-uniform', action='store_true',
349 help='Use a uniform resolution across distributions')
b6d9132b
AB
350
351 @staticmethod
352 def _add_log_args(ap, help=None):
353 if not help:
354 help = 'Output the events in chronological order'
355
356 ap.add_argument('--log', action='store_true', help=help)
357
b9f05f8d
AB
358 @staticmethod
359 def _add_top_args(ap, help=None):
360 if not help:
361 help = 'Output the top results'
362
363 ap.add_argument('--limit', type=int, default=10,
364 help='Limit to top X (default = 10)')
365 ap.add_argument('--top', action='store_true', help=help)
366
b6d9132b
AB
367 @staticmethod
368 def _add_stats_args(ap, help=None):
369 if not help:
370 help = 'Output statistics'
371
372 ap.add_argument('--stats', action='store_true', help=help)
373
374 def _add_arguments(self, ap):
375 pass
376
652bc6b7 377 def _process_date_args(self):
b6d9132b
AB
378 def date_to_epoch_nsec(date):
379 ts = common.date_to_epoch_nsec(self._handles, date, self._args.gmt)
380 if ts is None:
381 self._cmdline_error('Invalid date format: "{}"'.format(date))
382
383 return ts
384
385 self._args.multi_day = common.is_multi_day_trace_collection(
652bc6b7 386 self._handles)
602ac199
PP
387 begin_ts = None
388 end_ts = None
389
390 if self._args.timerange:
391 begin_ts, end_ts = common.extract_timerange(self._handles,
392 self._args.timerange,
393 self._args.gmt)
394 if None in [begin_ts, end_ts]:
b9f05f8d
AB
395 self._cmdline_error(
396 'Invalid time format: "{}"'.format(self._args.timerange))
652bc6b7 397 else:
b6d9132b 398 if self._args.begin:
602ac199 399 begin_ts = date_to_epoch_nsec(self._args.begin)
b6d9132b 400 if self._args.end:
602ac199 401 end_ts = date_to_epoch_nsec(self._args.end)
652bc6b7 402
93c7af7d
AB
403 # We have to check if timestamp_begin is None, which
404 # it always is in older versions of babeltrace. In
405 # that case, the test is simply skipped and an invalid
406 # --end value will cause an empty analysis
407 if self._traces.timestamp_begin is not None and \
602ac199 408 end_ts < self._traces.timestamp_begin:
b6d9132b
AB
409 self._cmdline_error(
410 '--end timestamp before beginning of trace')
411
602ac199
PP
412 self._analysis_conf.begin_ts = begin_ts
413 self._analysis_conf.end_ts = end_ts
b6d9132b
AB
414
415 def _create_analysis(self):
416 notification_cbs = {
a0acc08c 417 analysis.Analysis.TICK_CB: self._analysis_tick_cb
b6d9132b
AB
418 }
419
420 self._analysis = self._ANALYSIS_CLASS(self.state, self._analysis_conf)
421 self._analysis.register_notification_cbs(notification_cbs)
93c7af7d 422
323b3fd6 423 def _create_automaton(self):
56936af2 424 self._automaton = automaton.Automaton()
6e01ed18 425 self.state = self._automaton.state
bfb81992 426
a0acc08c 427 def _analysis_tick_cb(self, **kwargs):
b6d9132b
AB
428 begin_ns = kwargs['begin_ns']
429 end_ns = kwargs['end_ns']
430
a0acc08c
PP
431 self._analysis_tick(begin_ns, end_ns)
432 self._ticks += 1
b6d9132b 433
a0acc08c 434 def _analysis_tick(self, begin_ns, end_ns):
b6d9132b
AB
435 raise NotImplementedError()
436
a0acc08c
PP
437
438# create MI version
439_cmd_version = _version.get_versions()['version']
440_version_match = re.match(r'(\d+)\.(\d+)\.(\d+)(.*)', _cmd_version)
441Command._MI_VERSION = [
442 int(_version_match.group(1)),
443 int(_version_match.group(2)),
444 int(_version_match.group(3)),
445 _version_match.group(4),
446]
This page took 0.057473 seconds and 5 git commands to generate.