cli: add --mi-version to print produced MI version
[deliverable/lttng-analyses.git] / lttnganalyses / cli / command.py
index 93df78657c181e41d43422ad9ac6673c0d12a3e2..602d7dd722d5baa05e184b4897dadc530925d72f 100644 (file)
@@ -29,13 +29,12 @@ import re
 import sys
 import subprocess
 from babeltrace import TraceCollection
-from . import mi
-from .. import _version
-from . import progressbar
-from .. import __version__
-from ..common import version_utils
+from . import mi, progressbar
+from .. import _version, __version__
 from ..core import analysis
-from ..linuxautomaton import common
+from ..common import (
+    format_utils, parse_utils, time_utils, trace_utils, version_utils
+)
 from ..linuxautomaton import automaton
 
 
@@ -73,7 +72,10 @@ class Command:
         except KeyboardInterrupt:
             sys.exit(0)
 
-    def _error(self, msg, exit_code=1):
+    def _mi_error(self, msg, code=None):
+        print(json.dumps(mi.get_error(msg, code)))
+
+    def _non_mi_error(self, msg):
         try:
             import termcolor
 
@@ -82,6 +84,13 @@ class Command:
             pass
 
         print(msg, file=sys.stderr)
+
+    def _error(self, msg, code, exit_code=1):
+        if self._mi_mode:
+            self._mi_error(msg)
+        else:
+            self._non_mi_error(msg)
+
         sys.exit(exit_code)
 
     def _gen_error(self, msg, exit_code=1):
@@ -163,6 +172,8 @@ class Command:
             self._gen_error('Failed to open ' + self._args.path, -1)
         self._handles = handles
         self._traces = traces
+        self._ts_begin = traces.timestamp_begin
+        self._ts_end = traces.timestamp_end
         self._process_date_args()
         self._read_tracer_version()
         if not self._args.skip_validation:
@@ -174,6 +185,9 @@ class Command:
 
     def _read_tracer_version(self):
         kernel_path = None
+        # remove the trailing /
+        while self._args.path.endswith('/'):
+            self._args.path = self._args.path[:-1]
         for root, _, _ in os.walk(self._args.path):
             if root.endswith('kernel'):
                 kernel_path = root
@@ -183,15 +197,25 @@ class Command:
             self._gen_error('Could not find kernel trace directory')
 
         try:
-            metadata = subprocess.getoutput(
+            ret, metadata = subprocess.getstatusoutput(
                 'babeltrace -o ctf-metadata "%s"' % kernel_path)
         except subprocess.CalledProcessError:
             self._gen_error('Cannot run babeltrace on the trace, cannot read'
                             ' tracer version')
 
-        major_match = re.search(r'tracer_major = (\d+)', metadata)
-        minor_match = re.search(r'tracer_minor = (\d+)', metadata)
-        patch_match = re.search(r'tracer_patchlevel = (\d+)', metadata)
+        # fallback to reading the text metadata if babeltrace failed to
+        # output the CTF metadata
+        if ret != 0:
+            try:
+                metadata = subprocess.getoutput(
+                    'cat "%s"' % os.path.join(kernel_path, 'metadata'))
+            except subprocess.CalledProcessError:
+                self._gen_error('Cannot read the metadata of the trace, cannot'
+                                'extract tracer version')
+
+        major_match = re.search(r'tracer_major = "*(\d+)"*', metadata)
+        minor_match = re.search(r'tracer_minor = "*(\d+)"*', metadata)
+        patch_match = re.search(r'tracer_patchlevel = "*(\d+)"*', metadata)
 
         if not major_match or not minor_match or not patch_match:
             self._gen_error('Malformed metadata, cannot read tracer version')
@@ -203,7 +227,12 @@ class Command:
         )
 
     def _check_lost_events(self):
-        self._print('Checking the trace for lost events...')
+        msg = 'Checking the trace for lost events...'
+        self._print(msg)
+
+        if self._mi_mode and self._args.output_progress:
+            mi.print_progress(0, msg)
+
         try:
             subprocess.check_output('babeltrace "%s"' % self._args.path,
                                     shell=True)
@@ -223,29 +252,63 @@ class Command:
 
         self._mi_print()
 
+    def _pb_setup(self):
+        if self._args.no_progress:
+            return
+
+        ts_end = self._ts_end
+
+        if self._analysis_conf.end_ts is not None:
+            ts_end = self._analysis_conf.end_ts
+
+        if self._mi_mode:
+            cls = progressbar.MiProgress
+        else:
+            cls = progressbar.FancyProgressBar
+
+        self._progress = cls(self._ts_begin, ts_end, self._args.path,
+                             self._args.progress_use_size)
+
+    def _pb_update(self, event):
+        if self._args.no_progress:
+            return
+
+        self._progress.update(event)
+
+    def _pb_finish(self):
+        if self._args.no_progress:
+            return
+
+        self._progress.finalize()
+
     def _run_analysis(self):
         self._pre_analysis()
-        progressbar.progressbar_setup(self)
+        self._pb_setup()
 
         for event in self._traces.events:
-            progressbar.progressbar_update(self)
+            self._pb_update(event)
             self._analysis.process_event(event)
             if self._analysis.ended:
                 break
             self._automaton.process_event(event)
 
-        progressbar.progressbar_finish(self)
+        self._pb_finish()
         self._analysis.end()
         self._post_analysis()
 
     def _print_date(self, begin_ns, end_ns):
-        date = 'Timerange: [%s, %s]' % (
-            common.ns_to_hour_nsec(begin_ns, gmt=self._args.gmt,
-                                   multi_day=True),
-            common.ns_to_hour_nsec(end_ns, gmt=self._args.gmt,
-                                   multi_day=True))
+        time_range_str = format_utils.format_time_range(
+            begin_ns, end_ns, print_date=True, gmt=self._args.gmt
+        )
+        date = 'Timerange: {}'.format(time_range_str)
+
         self._print(date)
 
+    def _format_timestamp(self, timestamp):
+        return format_utils.format_timestamp(
+            timestamp, print_date=self._args.multi_day, gmt=self._args.gmt
+        )
+
     def _get_uniform_freq_values(self, durations):
         if self._args.uniform_step is not None:
             return (self._args.uniform_min, self._args.uniform_max,
@@ -275,7 +338,7 @@ class Command:
         refresh_period_ns = None
         if args.refresh is not None:
             try:
-                refresh_period_ns = common.duration_str_to_ns(args.refresh)
+                refresh_period_ns = parse_utils.parse_duration(args.refresh)
             except ValueError as e:
                 self._cmdline_error(str(e))
 
@@ -284,18 +347,18 @@ class Command:
         self._analysis_conf.period_begin_ev_name = args.period_begin
         self._analysis_conf.period_end_ev_name = args.period_end
         self._analysis_conf.period_begin_key_fields = \
-                                            args.period_begin_key.split(',')
+            args.period_begin_key.split(',')
 
         if args.period_end_key:
             self._analysis_conf.period_end_key_fields = \
-                                            args.period_end_key.split(',')
+                args.period_end_key.split(',')
         else:
             self._analysis_conf.period_end_key_fields = \
-                                    self._analysis_conf.period_begin_key_fields
+                self._analysis_conf.period_begin_key_fields
 
         if args.period_key_value:
             self._analysis_conf.period_key_value = \
-                                        tuple(args.period_key_value.split(','))
+                tuple(args.period_key_value.split(','))
 
         if args.cpu:
             self._analysis_conf.cpu_list = args.cpu.split(',')
@@ -330,8 +393,10 @@ class Command:
                 args.freq_uniform = True
 
         if self._mi_mode:
-            # force no progress in MI mode
-            args.no_progress = True
+            # print MI version if required
+            if args.mi_version:
+                print(mi.get_version_string())
+                sys.exit(0)
 
             # print MI metadata if required
             if args.metadata:
@@ -384,15 +449,21 @@ class Command:
                         'CPU IDs')
         ap.add_argument('--timerange', type=str, help='time range: '
                                                       '[begin,end]')
+        ap.add_argument('--progress-use-size', action='store_true',
+                        help='use trace size to approximate progress')
         ap.add_argument('-V', '--version', action='version',
                         version='LTTng Analyses v' + __version__)
 
         # MI mode-dependent arguments
         if self._mi_mode:
+            ap.add_argument('--mi-version', action='store_true',
+                            help='Print MI version')
             ap.add_argument('--metadata', action='store_true',
-                            help='Show analysis\'s metadata')
+                            help='Print analysis\' metadata')
             ap.add_argument('path', metavar='<path/to/trace>',
                             help='trace path', nargs='*')
+            ap.add_argument('--output-progress', action='store_true',
+                            help='Print progress indication lines')
         else:
             ap.add_argument('--no-progress', action='store_true',
                             help='Don\'t display the progress bar')
@@ -403,6 +474,13 @@ class Command:
         self._add_arguments(ap)
 
         args = ap.parse_args()
+
+        if self._mi_mode:
+            args.no_progress = True
+
+            if args.output_progress:
+                args.no_progress = False
+
         self._validate_transform_common_args(args)
         self._validate_transform_args(args)
         self._args = args
@@ -464,37 +542,43 @@ class Command:
         pass
 
     def _process_date_args(self):
-        def date_to_epoch_nsec(date):
-            ts = common.date_to_epoch_nsec(self._handles, date, self._args.gmt)
-            if ts is None:
-                self._cmdline_error('Invalid date format: "{}"'.format(date))
+        def parse_date(date):
+            try:
+                ts = parse_utils.parse_trace_collection_date(
+                    self._traces, date, self._args.gmt
+                )
+            except ValueError as e:
+                self._cmdline_error(str(e))
 
             return ts
 
-        self._args.multi_day = common.is_multi_day_trace_collection(
-            self._handles)
+        self._args.multi_day = trace_utils.is_multi_day_trace_collection(
+            self._traces
+        )
         begin_ts = None
         end_ts = None
 
         if self._args.timerange:
-            begin_ts, end_ts = common.extract_timerange(self._handles,
-                                                        self._args.timerange,
-                                                        self._args.gmt)
-            if None in [begin_ts, end_ts]:
-                self._cmdline_error(
-                    'Invalid time format: "{}"'.format(self._args.timerange))
+            try:
+                begin_ts, end_ts = (
+                    parse_utils.parse_trace_collection_time_range(
+                        self._traces, self._args.timerange, self._args.gmt
+                    )
+                )
+            except ValueError as e:
+                self._cmdline_error(str(e))
         else:
             if self._args.begin:
-                begin_ts = date_to_epoch_nsec(self._args.begin)
+                begin_ts = parse_date(self._args.begin)
             if self._args.end:
-                end_ts = date_to_epoch_nsec(self._args.end)
+                end_ts = parse_date(self._args.end)
 
                 # We have to check if timestamp_begin is None, which
                 # it always is in older versions of babeltrace. In
                 # that case, the test is simply skipped and an invalid
                 # --end value will cause an empty analysis
-                if self._traces.timestamp_begin is not None and \
-                   end_ts < self._traces.timestamp_begin:
+                if self._ts_begin is not None and \
+                   end_ts < self._ts_begin:
                     self._cmdline_error(
                         '--end timestamp before beginning of trace')
 
This page took 0.029113 seconds and 5 git commands to generate.