date_to_epoch_nsec(): fix and improve regexes
[deliverable/lttng-analyses.git] / lttnganalyses / cli / command.py
CommitLineData
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 25from ..core import analysis
56936af2
MJ
26from ..linuxautomaton import automaton
27from .. import __version__
28from . import progressbar
29from ..linuxautomaton import common
bd3cd7c5 30from babeltrace import TraceCollection
323b3fd6
PP
31import argparse
32import sys
d3014022 33import subprocess
323b3fd6
PP
34
35
36class 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
This page took 0.05478 seconds and 5 git commands to generate.