Commit eb9f1fa9 authored by Paul Graydon's avatar Paul Graydon

amari.kpi: Add DRB.UEActive measurement

parent 7164e99c
...@@ -29,6 +29,8 @@ from xlte import kpi ...@@ -29,6 +29,8 @@ from xlte import kpi
from xlte.amari import xlog from xlte.amari import xlog
from golang import func from golang import func
from math import floor
# LogMeasure takes enb.xlog (TODO and enb.log) as input, and produces kpi.Measurements on output. # LogMeasure takes enb.xlog (TODO and enb.log) as input, and produces kpi.Measurements on output.
# #
...@@ -205,10 +207,14 @@ def _read(logm): ...@@ -205,10 +207,14 @@ def _read(logm):
# _handle_stats handles next stats xlog entry upon _read request. # _handle_stats handles next stats xlog entry upon _read request.
@func(LogMeasure) @func(LogMeasure)
def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# build Measurement from stats' counters. # build Measurement from stats' values, by mapping Amarisoft non-counters
# (non-cumulative) and counters to their respective 3GPP value specified by
# kpi.Measurement.
#
# for non-counters, simply map the value to the corresponding 3GPP value.
# #
# we take δ(stats_prev, stat) and process it mapping Amarisoft counters to # for cumulative counters, we take δ(stats_prev, stat) and process it
# 3GPP ones specified by kpi.Measurement. This approach has following limitations: # before performing the mapping. This approach has following limitations:
# #
# - for most of the counters there is no direct mapping in between # - for most of the counters there is no direct mapping in between
# Amarisoft and 3GPP. For example we currently use s1_erab_setup_request for # Amarisoft and 3GPP. For example we currently use s1_erab_setup_request for
...@@ -302,6 +308,17 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): ...@@ -302,6 +308,17 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
if m[fini] > m[init]: if m[fini] > m[init]:
m[fini] = m[init] m[fini] = m[init]
cells = set(stats['cells'].keys()) # NOTE cells are taken only from stats, not from stat_prev
# map non-counter values, which are independent between periods
# and do not need any special processing.
Σue_active_count_avg = 0
for cell in cells:
Σue_active_count_avg += _stats_cell_nc(stats, cell, 'ue_active_count_avg')
# flooring the sum of averages is more accurate to the 3GPP spec than summing the floors.
Σue_active_count_avg = int(floor(Σue_active_count_avg))
m['DRB.UEActive'] = Σue_active_count_avg
# compute δ for counters. # compute δ for counters.
# any logic error in data will be reported via LogError. # any logic error in data will be reported via LogError.
try: try:
...@@ -315,7 +332,6 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): ...@@ -315,7 +332,6 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# same whether we do aggregation here or in kpi.Calc.erab_accessibility(). # same whether we do aggregation here or in kpi.Calc.erab_accessibility().
# #
# TODO rework to emit per-cell measurements when/if we need per-cell KPIs # TODO rework to emit per-cell measurements when/if we need per-cell KPIs
cells = set(stats['cells'].keys()) # NOTE cells are taken only from stats, not from stat_prev
δΣcell_rrc_connection_request = 0 # (if a cell disappears its counters stop to be accounted) δΣcell_rrc_connection_request = 0 # (if a cell disappears its counters stop to be accounted)
δΣcell_rrc_connection_setup_complete = 0 δΣcell_rrc_connection_setup_complete = 0
for cell in cells: for cell in cells:
...@@ -368,12 +384,25 @@ def _stats_check(stats: xlog.Message): ...@@ -368,12 +384,25 @@ def _stats_check(stats: xlog.Message):
raise LogError(stats.timestamp, "stats: %s" % e) from None raise LogError(stats.timestamp, "stats: %s" % e) from None
return return
# _stats_nc returns specified global non-counter from stats result.
#
# stats is assumed to be already verified by _stats_check.
def _stats_nc(stats: xlog.Message, non_counter: str):
return stats.get(non_counter, 0.0)
# _stats_cc returns specified global cumulative counter from stats result. # _stats_cc returns specified global cumulative counter from stats result.
# #
# stats is assumed to be already verified by _stats_check. # stats is assumed to be already verified by _stats_check.
def _stats_cc(stats: xlog.Message, counter: str): def _stats_cc(stats: xlog.Message, counter: str):
return stats['counters']['messages'].get(counter, 0) return stats['counters']['messages'].get(counter, 0)
# _stats_cell_nc is like _stats_nc but returns specified per-cell non-counter from stats result.
def _stats_cell_nc(stats: xlog.Message, cell: str, non_counter: str):
_ = stats['cells'].get(cell)
if _ is None:
return 0.0 # cell is absent in this stats
return _.get(non_counter, 0.0)
# _stats_cell_cc is like _stats_cc but returns specified per-cell cumulative counter from stats result. # _stats_cell_cc is like _stats_cc but returns specified per-cell cumulative counter from stats result.
def _stats_cell_cc(stats: xlog.Message, cell: str, counter: str): def _stats_cell_cc(stats: xlog.Message, cell: str, counter: str):
_ = stats['cells'].get(cell) _ = stats['cells'].get(cell)
......
This diff is collapsed.
...@@ -179,6 +179,9 @@ class Measurement(np.void): ...@@ -179,6 +179,9 @@ class Measurement(np.void):
('DRB.PdcpSduBitrateUl.QCI', np.float64),# bit/s 4.4.1.1 NOTE not kbit/s ('DRB.PdcpSduBitrateUl.QCI', np.float64),# bit/s 4.4.1.1 NOTE not kbit/s
('DRB.PdcpSduBitrateDl.QCI', np.float64),# bit/s 4.4.1.2 NOTE not kbit/s ('DRB.PdcpSduBitrateDl.QCI', np.float64),# bit/s 4.4.1.2 NOTE not kbit/s
('DRB.UEActive', np.int32), # 1 4.4.2.4 36.314:4.1.3.3
# XXX mean is not good for our model # XXX mean is not good for our model
# TODO mean -> total + npkt? # TODO mean -> total + npkt?
#('DRB.IPLatDl.QCI', Ttime), # s 4.4.5.1 32.450:6.3.2 NOTE not ms #('DRB.IPLatDl.QCI', Ttime), # s 4.4.5.1 32.450:6.3.2 NOTE not ms
......
...@@ -53,6 +53,8 @@ def test_Measurement(): ...@@ -53,6 +53,8 @@ def test_Measurement():
assert m['S1SIG.ConnEstabAtt'] == 123 assert m['S1SIG.ConnEstabAtt'] == 123
m['RRC.ConnEstabAtt.sum'] = 17 m['RRC.ConnEstabAtt.sum'] = 17
assert m['RRC.ConnEstabAtt.sum'] == 17 assert m['RRC.ConnEstabAtt.sum'] == 17
m['DRB.UEActive'] = 6
assert m['DRB.UEActive'] == 6
m['DRB.IPVolDl.QCI'][:] = 0 m['DRB.IPVolDl.QCI'][:] = 0
m['DRB.IPVolDl.5'] = 55 m['DRB.IPVolDl.5'] = 55
m['DRB.IPVolDl.7'] = NA(m['DRB.IPVolDl.7'].dtype) m['DRB.IPVolDl.7'] = NA(m['DRB.IPVolDl.7'].dtype)
...@@ -67,13 +69,14 @@ def test_Measurement(): ...@@ -67,13 +69,14 @@ def test_Measurement():
assert m['DRB.IPVolDl.QCI'][k] == 0 assert m['DRB.IPVolDl.QCI'][k] == 0
# str/repr # str/repr
assert repr(m) == "Measurement(RRC.ConnEstabAtt.sum=17, DRB.IPVolDl.QCI={5:55 7:ø 9:99}, S1SIG.ConnEstabAtt=123)" assert repr(m) == "Measurement(RRC.ConnEstabAtt.sum=17, DRB.UEActive=6, DRB.IPVolDl.QCI={5:55 7:ø 9:99}, S1SIG.ConnEstabAtt=123)"
s = str(m) s = str(m)
assert s[0] == '(' assert s[0] == '('
assert s[-1] == ')' assert s[-1] == ')'
v = s[1:-1].split(', ') v = s[1:-1].split(', ')
vok = ['ø'] * len(m._dtype0.names) vok = ['ø'] * len(m._dtype0.names)
vok[m.dtype.names.index("RRC.ConnEstabAtt.sum")] = "17" vok[m.dtype.names.index("RRC.ConnEstabAtt.sum")] = "17"
vok[m.dtype.names.index("DRB.UEActive")] = "6"
vok[m.dtype.names.index("S1SIG.ConnEstabAtt")] = "123" vok[m.dtype.names.index("S1SIG.ConnEstabAtt")] = "123"
vok[m.dtype.names.index("DRB.IPVolDl.QCI")] = "{5:55 7:ø 9:99}" vok[m.dtype.names.index("DRB.IPVolDl.QCI")] = "{5:55 7:ø 9:99}"
assert v == vok assert v == vok
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment