enb,epc,ms: refactor KPI API

we previously mixed component specific and component agnostic APIs
(stdout vs. log file for example) for setting and retrieving KPI.

This patch propose to use a single abstract get_kpis() method for
all components that can be enriched with component-specific
stuff as desired.

In the case of srsLTE blocks, the main implementation will
remain in srslte_common() and is shared among srsENB/srsUE/srsEPC.

The KPI analyzer in srslte_common() extract and also manages
all three KPI sources (log, csv and stdout) independently.

In addition to the get_kpis() method that always returns a flat
dictionary, it also exposes get_kpi_tree() that return
a dict of KPI dicts that will be used for the Junit.xml generation.

Change-Id: I4bacc6b8a0cb92a581edfb947100b57022265265
This commit is contained in:
Andre Puschmann 2021-01-29 17:45:18 +01:00 committed by Pau Espin Pedrol
parent 6231c3c657
commit 3ce672594f
8 changed files with 56 additions and 45 deletions

View File

@ -356,4 +356,8 @@ class eNodeB(log.Origin, metaclass=ABCMeta):
def get_counter(self, counter_name):
pass
# vim: expandtab tabstop=4 shiftwidth=4
@abstractmethod
def get_kpis(self):
pass
# vim: expandtab tabstop=4 shiftwidth=4

View File

@ -260,6 +260,9 @@ class AmarisoftENB(enb.eNodeB):
return self.process.get_counter_stdout('PRACH:')
raise log.Error('counter %s not implemented!' % counter_name)
def get_kpis(self):
return {}
def get_rfemu(self, cell=0, dl=True):
cell_list = self.gen_conf['enb'].get('cell_list', None)
if cell_list is None or len(cell_list) < cell + 1:

View File

@ -101,7 +101,7 @@ class srsENB(enb.eNodeB, srslte_common):
self.log(repr(e))
# Collect KPIs for each TC
self.testenv.test().set_kpis(self.get_kpis())
self.testenv.test().set_kpis(self.get_kpi_tree())
# Clean up for parent class:
super().cleanup()
@ -267,6 +267,9 @@ class srsENB(enb.eNodeB, srslte_common):
return self.process.get_counter_stdout('RACH:')
raise log.Error('counter %s not implemented!' % counter_name)
def get_kpis(self):
return srslte_common.get_kpis(self)
def get_rfemu(self, cell=0, dl=True):
cell_list = self.gen_conf['enb'].get('cell_list', None)
if cell_list is None or len(cell_list) < cell + 1:

View File

@ -116,4 +116,8 @@ class EPC(log.Origin, metaclass=ABCMeta):
def run_node(self):
return self._run_node
@abstractmethod
def get_kpis(self):
pass
# vim: expandtab tabstop=4 shiftwidth=4

View File

@ -199,4 +199,7 @@ class AmarisoftEPC(epc.EPC):
# TODO: set proper addr
return '192.168.4.1'
def get_kpis(self):
return {}
# vim: expandtab tabstop=4 shiftwidth=4

View File

@ -219,4 +219,7 @@ class srsEPC(epc.EPC, srslte_common):
def tun_addr(self):
return '172.16.0.1'
def get_kpis(self):
return srslte_common.get_kpis(self)
# vim: expandtab tabstop=4 shiftwidth=4

View File

@ -126,7 +126,7 @@ class srsUE(MS, srslte_common):
self.log(repr(e))
# Collect KPIs for each TC
self.testenv.test().set_kpis(self.get_kpis())
self.testenv.test().set_kpis(self.get_kpi_tree())
def features(self):
return self._conf.get('features', [])

View File

@ -27,7 +27,9 @@ class srslte_common(): # don't inherit from log.Origin here but instead use .nam
self.process = None
self.metrics_file = None
self.stop_sleep_time = 6 # We require at most 5s to stop
self.kpis = None
self.log_kpi = None
self.stdout_kpi = None
self.csv_kpi = None
def sleep_after_stop(self):
# Only sleep once
@ -42,61 +44,50 @@ class srslte_common(): # don't inherit from log.Origin here but instead use .nam
self.sleep_after_stop()
def get_kpis(self):
''' Return all KPI '''
if self.kpis is None:
self.extract_kpis()
return self.kpis
''' Merge all KPI and return as flat dict '''
self.extract_kpis()
kpi_flat = {}
kpi_flat.update(self.log_kpi)
kpi_flat.update(self.stdout_kpi)
kpi_flat.update(self.csv_kpi)
return kpi_flat
def get_log_kpis(self):
''' Return KPIs extracted from log '''
if self.kpis is None:
self.extract_kpis()
# Use log KPIs if they exist for this node
if "log_" + self.name() in self.kpis:
log_kpi = self.kpis["log_" + self.name()]
else:
log_kpi = {}
# Make sure we have the errors and warnings counter in the dict
if 'total_errors' not in log_kpi:
log_kpi['total_errors'] = 0
if 'total_warnings' not in log_kpi:
log_kpi['total_warnings'] = 0
return log_kpi
def get_kpi_tree(self):
''' Return all KPI as dict of dict in which the source (e.g. stdout_srsue1) is the key of the first dict '''
self.extract_kpis()
kpi_tree = {}
kpi_tree["log_" + self.name()] = self.log_kpi
kpi_tree["csv_" + self.name()] = self.csv_kpi
kpi_tree["stdout_" + self.name()] = self.stdout_kpi
return kpi_tree
def extract_kpis(self):
''' Use the srsLTE KPI analyzer module (part of srsLTE.git) if available to collect KPIs '''
# Make sure this only runs once
if self.csv_kpi is not None or self.log_kpi is not None or self.stdout_kpi is not None:
return
# Start with empty KPIs
self.log_kpi = {}
self.stdout_kpi = {}
self.csv_kpi = {}
# Stop application, copy back logs and process them
if self.running():
self.stop()
self.cleanup()
self.kpis = {}
try:
# Please make sure the srsLTE scripts folder is included in your PYTHONPATH env variable
from kpi_analyzer import kpi_analyzer
analyzer = kpi_analyzer(self.name())
if self.log_file is not None:
self.kpis["log_" + self.name()] = analyzer.get_kpi_from_logfile(self.log_file)
self.log_kpi = analyzer.get_kpi_from_logfile(self.log_file)
if self.process.get_output_file('stdout') is not None:
self.kpis["stdout_" + self.name()] = analyzer.get_kpi_from_stdout(self.process.get_output_file('stdout'))
self.stdout_kpi = analyzer.get_kpi_from_stdout(self.process.get_output_file('stdout'))
if self.metrics_file is not None:
self.kpis["csv_" + self.name()] = analyzer.get_kpi_from_csv(self.metrics_file)
self.csv_kpi = analyzer.get_kpi_from_csv(self.metrics_file)
# PHY errors for either UE or eNB components from parsed KPI vector as extra entry in dict
self.log_kpi["num_phy_errors"] = analyzer.get_num_phy_errors(self.log_kpi)
except ImportError:
self.log("Can't load KPI analyzer module.")
self.kpis = {}
return self.kpis
def get_num_phy_errors(self, kpi):
""" Use KPI analyzer to calculate the number PHY errors for either UE or eNB components from parsed KPI vector """
try:
# Same as above, make sure the srsLTE scripts folder is included in your PYTHONPATH env variable
from kpi_analyzer import kpi_analyzer
analyzer = kpi_analyzer(self.name())
return analyzer.get_num_phy_errors(kpi)
except ImportError:
self.log("Can't load KPI analyzer module.")
return 0
self.log("Can't load KPI analyzer module.")