fix: stats with 0 requests
[deliverable/lttng-analyses.git] / lttnganalysescli / lttnganalysescli / 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
323b3fd6 25import linuxautomaton.automaton
d97f5cb2 26from lttnganalysescli import progressbar, __version__
bd3cd7c5
JD
27from linuxautomaton import common
28from babeltrace import TraceCollection
323b3fd6
PP
29import argparse
30import sys
d3014022 31import subprocess
323b3fd6
PP
32
33
34class Command:
f89605f0
JD
35 def __init__(self, add_arguments_cb,
36 enable_proc_filter_args=False,
37 enable_max_min_args=False,
38 enable_max_min_size_arg=False,
39 enable_freq_arg=False,
40 enable_log_arg=False,
41 enable_stats_arg=False):
323b3fd6 42 self._add_arguments_cb = add_arguments_cb
47ba125c
JD
43 self._enable_proc_filter_args = enable_proc_filter_args
44 self._enable_max_min_arg = enable_max_min_args
f89605f0
JD
45 self._enable_max_min_size_arg = enable_max_min_size_arg
46 self._enable_freq_arg = enable_freq_arg
47 self._enable_log_arg = enable_log_arg
48 self._enable_stats_arg = enable_stats_arg
323b3fd6
PP
49 self._create_automaton()
50
51 def _error(self, msg, exit_code=1):
52 print(msg, file=sys.stderr)
53 sys.exit(exit_code)
54
55 def _gen_error(self, msg, exit_code=1):
56 self._error('Error: {}'.format(msg), exit_code)
57
58 def _cmdline_error(self, msg, exit_code=1):
59 self._error('Command line error: {}'.format(msg), exit_code)
60
bd3cd7c5
JD
61 def _open_trace(self):
62 traces = TraceCollection()
ced36aab
AB
63 handles = traces.add_traces_recursive(self._arg_path, 'ctf')
64 if handles == {}:
73b71522 65 self._gen_error('Failed to open ' + self._arg_path, -1)
ced36aab 66 self._handles = handles
bd3cd7c5 67 self._traces = traces
652bc6b7 68 self._process_date_args()
d3014022
JD
69 if not self._arg_skip_validation:
70 self._check_lost_events()
bd3cd7c5
JD
71
72 def _close_trace(self):
ced36aab
AB
73 for handle in self._handles.values():
74 self._traces.remove_trace(handle)
bd3cd7c5 75
d3014022 76 def _check_lost_events(self):
73b71522 77 print('Checking the trace for lost events...')
d3014022 78 try:
73b71522 79 subprocess.check_output('babeltrace %s' % self._arg_path,
d3014022
JD
80 shell=True)
81 except subprocess.CalledProcessError:
73b71522
AB
82 print('Error running babeltrace on the trace, cannot verify if '
83 'events were lost during the trace recording')
d3014022 84
119f6cef 85 def _run_analysis(self, reset_cb, refresh_cb, break_cb=None):
bd3cd7c5
JD
86 self.trace_start_ts = 0
87 self.trace_end_ts = 0
88 self.current_sec = 0
89 self.start_ns = 0
90 self.end_ns = 0
64c77c5c 91 started = False
bd3cd7c5
JD
92 progressbar.progressbar_setup(self)
93 if not self._arg_begin:
64c77c5c 94 started = True
bd3cd7c5
JD
95 for event in self._traces.events:
96 progressbar.progressbar_update(self)
64c77c5c 97 if self._arg_begin and not started and \
bd3cd7c5 98 event.timestamp >= self._arg_begin:
64c77c5c 99 started = True
bd3cd7c5
JD
100 self.trace_start_ts = event.timestamp
101 self.start_ns = event.timestamp
102 reset_cb(event.timestamp)
103 if self._arg_end and event.timestamp > self._arg_end:
119f6cef
JD
104 if break_cb is not None:
105 # check if we really can break here
106 if break_cb():
107 break
108 else:
109 break
bd3cd7c5
JD
110 if self.start_ns == 0:
111 self.start_ns = event.timestamp
112 if self.trace_start_ts == 0:
113 self.trace_start_ts = event.timestamp
114 self.end_ns = event.timestamp
115 self._check_refresh(event, refresh_cb)
116 self.trace_end_ts = event.timestamp
bd3cd7c5
JD
117 # feed analysis
118 self._analysis.process_event(event)
47ba125c
JD
119 # feed automaton
120 self._automaton.process_event(event)
bd3cd7c5
JD
121 progressbar.progressbar_finish(self)
122
123 def _check_refresh(self, event, refresh_cb):
124 """Check if we need to output something"""
28ad5ec8 125 if self._arg_refresh is None:
bd3cd7c5
JD
126 return
127 event_sec = event.timestamp / common.NSEC_PER_SEC
128 if self.current_sec == 0:
129 self.current_sec = event_sec
130 elif self.current_sec != event_sec and \
131 (self.current_sec + self._arg_refresh) <= event_sec:
132 refresh_cb(self.start_ns, event.timestamp)
133 self.current_sec = event_sec
134 self.start_ns = event.timestamp
135
3664e4b0
AB
136 def _print_date(self, begin_ns, end_ns):
137 date = 'Timerange: [%s, %s]' % (
138 common.ns_to_hour_nsec(begin_ns, gmt=self._arg_gmt,
139 multi_day=True),
140 common.ns_to_hour_nsec(end_ns, gmt=self._arg_gmt,
141 multi_day=True))
142 print(date)
143
bd3cd7c5
JD
144 def _validate_transform_common_args(self, args):
145 self._arg_path = args.path
28ad5ec8 146
bd3cd7c5
JD
147 if args.limit:
148 self._arg_limit = args.limit
28ad5ec8 149
bd3cd7c5
JD
150 self._arg_begin = None
151 if args.begin:
152 self._arg_begin = args.begin
28ad5ec8 153
bd3cd7c5
JD
154 self._arg_end = None
155 if args.end:
156 self._arg_end = args.end
28ad5ec8 157
bd3cd7c5
JD
158 self._arg_timerange = None
159 if args.timerange:
160 self._arg_timerange = args.timerange
28ad5ec8 161
bd3cd7c5
JD
162 self._arg_gmt = None
163 if args.gmt:
164 self._arg_gmt = args.gmt
28ad5ec8 165
bd3cd7c5
JD
166 self._arg_refresh = args.refresh
167 self._arg_no_progress = args.no_progress
d3014022 168 self._arg_skip_validation = args.skip_validation
bd3cd7c5 169
47ba125c
JD
170 if self._enable_proc_filter_args:
171 self._arg_proc_list = None
172 if args.procname:
73b71522 173 self._arg_proc_list = args.procname.split(',')
28ad5ec8 174
47ba125c
JD
175 self._arg_pid_list = None
176 if args.pid:
73b71522 177 self._arg_pid_list = args.pid.split(',')
9447c5f1 178 self._arg_pid_list = [int(pid) for pid in self._arg_pid_list]
47ba125c
JD
179
180 if self._enable_max_min_arg:
2b4a3c12
AB
181 self._arg_max = args.max
182 self._arg_min = args.min
47ba125c 183
f89605f0 184 if self._enable_max_min_size_arg:
2b4a3c12
AB
185 self._arg_maxsize = args.maxsize
186 self._arg_minsize = args.minsize
f89605f0
JD
187
188 if self._enable_freq_arg:
189 self._arg_freq = args.freq
190 self._arg_freq_resolution = args.freq_resolution
191
192 if self._enable_log_arg:
193 self._arg_log = args.log
194
195 if self._enable_stats_arg:
196 self._arg_stats = args.stats
197
323b3fd6
PP
198 def _parse_args(self):
199 ap = argparse.ArgumentParser(description=self._DESC)
200
201 # common arguments
73b71522 202 ap.add_argument('path', metavar='<path/to/trace>', help='trace path')
bd3cd7c5 203 ap.add_argument('-r', '--refresh', type=int,
28ad5ec8 204 help='Refresh period in seconds')
bd3cd7c5
JD
205 ap.add_argument('--limit', type=int, default=10,
206 help='Limit to top X (default = 10)')
73b71522 207 ap.add_argument('--no-progress', action='store_true',
bd3cd7c5 208 help='Don\'t display the progress bar')
73b71522 209 ap.add_argument('--skip-validation', action='store_true',
d3014022 210 help='Skip the trace validation')
73b71522 211 ap.add_argument('--gmt', action='store_true',
bd3cd7c5
JD
212 help='Manipulate timestamps based on GMT instead '
213 'of local time')
214 ap.add_argument('--begin', type=str, help='start time: '
215 'hh:mm:ss[.nnnnnnnnn]')
216 ap.add_argument('--end', type=str, help='end time: '
217 'hh:mm:ss[.nnnnnnnnn]')
218 ap.add_argument('--timerange', type=str, help='time range: '
219 '[begin,end]')
220
221 if self._enable_proc_filter_args:
28ad5ec8 222 ap.add_argument('--procname', type=str,
bd3cd7c5
JD
223 help='Filter the results only for this list of '
224 'process names')
28ad5ec8 225 ap.add_argument('--pid', type=str,
bd3cd7c5
JD
226 help='Filter the results only for this list '
227 'of PIDs')
323b3fd6 228
47ba125c 229 if self._enable_max_min_arg:
28ad5ec8 230 ap.add_argument('--max', type=float,
47ba125c 231 help='Filter out, duration longer than max usec')
28ad5ec8 232 ap.add_argument('--min', type=float,
47ba125c
JD
233 help='Filter out, duration shorter than min usec')
234
f89605f0 235 if self._enable_max_min_size_arg:
28ad5ec8 236 ap.add_argument('--maxsize', type=float,
f89605f0
JD
237 help='Filter out, I/O operations working with '
238 'more that maxsize bytes')
28ad5ec8 239 ap.add_argument('--minsize', type=float,
f89605f0
JD
240 help='Filter out, I/O operations working with '
241 'less that minsize bytes')
242
243 if self._enable_freq_arg:
73b71522 244 ap.add_argument('--freq', action='store_true',
f89605f0
JD
245 help='Show the frequency distribution of '
246 'handler duration')
247 ap.add_argument('--freq-resolution', type=int, default=20,
248 help='Frequency distribution resolution '
249 '(default 20)')
250
251 if self._enable_log_arg:
73b71522 252 ap.add_argument('--log', action='store_true',
f89605f0
JD
253 help='Display the events in the order they '
254 'appeared')
255
256 if self._enable_stats_arg:
73b71522 257 ap.add_argument('--stats', action='store_true',
f89605f0
JD
258 help='Display the statistics')
259
323b3fd6
PP
260 # specific arguments
261 self._add_arguments_cb(ap)
262
263 # version of the specific command
323b3fd6 264 ap.add_argument('-V', '--version', action='version',
d97f5cb2 265 version='LTTng Analyses v' + __version__)
323b3fd6
PP
266
267 # parse arguments
268 args = ap.parse_args()
269
bd3cd7c5 270 self._validate_transform_common_args(args)
323b3fd6 271
323b3fd6
PP
272 # save all arguments
273 self._args = args
274
652bc6b7
AB
275 def _process_date_args(self):
276 self._arg_multi_day = common.is_multi_day_trace_collection(
277 self._handles)
278 if self._arg_timerange:
279 (self._arg_begin, self._arg_end) = \
280 common.extract_timerange(self._handles, self._arg_timerange,
281 self._arg_gmt)
282 if self._arg_begin is None or self._arg_end is None:
283 print('Invalid timeformat')
284 sys.exit(1)
285 else:
286 if self._arg_begin:
287 self._arg_begin = common.date_to_epoch_nsec(self._handles,
288 self._arg_begin,
289 self._arg_gmt)
290 if self._arg_begin is None:
291 print('Invalid timeformat')
292 sys.exit(1)
293 if self._arg_end:
294 self._arg_end = common.date_to_epoch_nsec(self._handles,
295 self._arg_end,
296 self._arg_gmt)
297 if self._arg_end is None:
298 print('Invalid timeformat')
299 sys.exit(1)
300
93c7af7d
AB
301 # We have to check if timestamp_begin is None, which
302 # it always is in older versions of babeltrace. In
303 # that case, the test is simply skipped and an invalid
304 # --end value will cause an empty analysis
305 if self._traces.timestamp_begin is not None and \
306 self._arg_end < self._traces.timestamp_begin:
307 print('--end timestamp before beginning of trace')
308 sys.exit(1)
309
323b3fd6
PP
310 def _create_automaton(self):
311 self._automaton = linuxautomaton.automaton.Automaton()
6e01ed18 312 self.state = self._automaton.state
bfb81992
AB
313
314 def _filter_process(self, proc):
315 if self._arg_proc_list and proc.comm not in self._arg_proc_list:
316 return False
317 if self._arg_pid_list and proc.pid not in self._arg_pid_list:
318 return False
319 return True
This page took 0.040421 seconds and 5 git commands to generate.