srsue: Introduce metrics verification procedures

Change-Id: Ib1da58615cdc4f53ac1a27080e94e5b47760c508
diff --git a/src/osmo_gsm_tester/srs_ue.py b/src/osmo_gsm_tester/srs_ue.py
index cbd8a68..f90ea32 100644
--- a/src/osmo_gsm_tester/srs_ue.py
+++ b/src/osmo_gsm_tester/srs_ue.py
@@ -22,6 +22,7 @@
 
 from . import log, util, config, template, process, remote
 from .run_node import RunNode
+from .event_loop import MainLoop
 from .ms import MS
 
 def rf_type_valid(rf_type_str):
@@ -91,10 +92,6 @@
             self.rem_host.scpfrom('scp-back-pcap', self.remote_pcap_file, self.pcap_file)
         except Exception as e:
             self.log(repr(e))
-        try:
-            self.rem_host.scpfrom('scp-back-metrics', self.remote_metrics_file, self.metrics_file)
-        except Exception as e:
-            self.log(repr(e))
 
     def setup_runs_locally(self):
         return self.remote_user is None
@@ -102,6 +99,9 @@
     def netns(self):
         return "srsue1"
 
+    def stop(self):
+        self.suite_run.stop_process(self.process)
+
     def connect(self, enb):
         self.log('Starting srsue')
         self.enb = enb
@@ -247,4 +247,80 @@
             proc = self.rem_host.RemoteNetNSProcess(name, self.netns(), popen_args, env={})
         proc.launch_sync()
 
+    def verify_metric(self, value, operation='avg', metric='dl_brate', criterion='gt'):
+        # file is not properly flushed until the process has stopped.
+        if self.running():
+            self.stop()
+            # metrics file is not flushed immediatelly by the OS during process
+            # tear down, we need to wait some extra time:
+            MainLoop.sleep(self, 2)
+            if not self.setup_runs_locally():
+                try:
+                    self.rem_host.scpfrom('scp-back-metrics', self.remote_metrics_file, self.metrics_file)
+                except Exception as e:
+                    self.err('Failed copying back metrics file from remote host')
+                    raise e
+        metrics = srsUEMetrics(self.metrics_file)
+        return metrics.verify(value, operation, metric, criterion)
+
+import numpy
+
+class srsUEMetrics(log.Origin):
+
+    VALID_OPERATIONS = ['avg', 'sum']
+    VALID_CRITERION = ['eq','gt','lt']
+    CRITERION_TO_SYM = { 'eq' : '==', 'gt' : '>', 'lt' : '<' }
+    CRYTERION_TO_SYM_OPPOSITE = { 'eq' : '!=', 'gt' : '<=', 'lt' : '>=' }
+
+
+    def __init__(self, metrics_file):
+        super().__init__(log.C_RUN, 'srsue_metrics')
+        self.raw_data = None
+        self.metrics_file = metrics_file
+        # read CSV, guessing data type with first row being the legend
+        try:
+            self.raw_data = numpy.genfromtxt(self.metrics_file, names=True, delimiter=';', dtype=None)
+        except (ValueError, IndexError, IOError) as error:
+            self.err("Error parsing metrics CSV file %s" % self.metrics_file)
+            raise error
+
+    def verify(self, value, operation='avg', metric='dl_brate', criterion='gt'):
+        if operation not in self.VALID_OPERATIONS:
+            raise log.Error('Unknown operation %s not in %r' % (operation, self.VALID_OPERATIONS))
+        if criterion not in self.VALID_CRITERION:
+            raise log.Error('Unknown operation %s not in %r' % (operation, self.VALID_CRITERION))
+        # check if given metric exists in data
+        try:
+            sel_data = self.raw_data[metric]
+        except ValueError as err:
+            print('metric %s not available' % metric)
+            raise err
+
+        if operation == 'avg':
+            result = numpy.average(sel_data)
+        elif operation == 'sum':
+            result = numpy.sum(sel_data)
+        self.dbg(result=result, value=value)
+
+        success = False
+        if criterion == 'eq' and result == value or \
+           criterion == 'gt' and result > value or \
+           criterion == 'lt' and result < value:
+            success = True
+
+        # Convert bitrate in Mbit/s:
+        if metric.find('brate') > 0:
+            result /= 1e6
+            value /= 1e6
+            mbit_str = ' Mbit/s'
+        else:
+            mbit_str = ''
+
+        if not success:
+            result_msg = "{:.2f}{} {} {:.2f}{}".format(result, mbit_str, self.CRYTERION_TO_SYM_OPPOSITE[criterion], value, mbit_str)
+            raise log.Error(result_msg)
+        result_msg = "{:.2f}{} {} {:.2f}{}".format(result, mbit_str, self.CRITERION_TO_SYM[criterion], value, mbit_str)
+        # TODO: overwrite test system-out with this text.
+        return result_msg
+
 # vim: expandtab tabstop=4 shiftwidth=4