Commit 6fb3b11b authored by Paul Graydon's avatar Paul Graydon

amari.kpi: Add support for DRB.UEActive measurement

parent 3a35162b
...@@ -205,9 +205,9 @@ def _read(logm): ...@@ -205,9 +205,9 @@ 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' statistical profiles and counters.
# #
# we take δ(stats_prev, stat) and process it mapping Amarisoft counters to # we take δ(stats_prev, stat) and process it, mapping Amarisoft counters to
# 3GPP ones specified by kpi.Measurement. This approach has following limitations: # 3GPP ones specified by kpi.Measurement. 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
...@@ -260,6 +260,13 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): ...@@ -260,6 +260,13 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# do init/fini correction if there was also third preceding stats message. # do init/fini correction if there was also third preceding stats message.
m = logm._m.copy() # [stats_prev, stats) m = logm._m.copy() # [stats_prev, stats)
# cell_statt_profile returns the StatT statistical profile value triplet in stats
def cell_statt_profile(cell, statt_profile):
return tuple(
_stats_cell_sp_el(stats, cell, statt_profile + '_' + el)
for el in ['min', 'avg', 'max']
)
# δcc(counter) tells how specified global cumulative counter changed since last stats result. # δcc(counter) tells how specified global cumulative counter changed since last stats result.
def δcc(counter): def δcc(counter):
old = _stats_cc(stats_prev, counter) old = _stats_cc(stats_prev, counter)
...@@ -305,6 +312,30 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): ...@@ -305,6 +312,30 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# 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:
cells = set(stats['cells'].keys()) # NOTE cells are taken only from stats, not from stat_prev
# DRB: number of active UEs
#
# Aggregate statistical profile for all cells.
# While summing the averages is correct, it is impossible to compute
# an accurate value of the aggregate minimum and maximum across all cells.
# Summing them is the best approximation that will produce
# a wider interval containing the correct values, i.e.:
# Σ(minimums) <= min(Σ) and max(Σ) <= Σ(maximums)
# [ min, avg, max]
Σue_active_count = [None, None, None]
for cell in cells:
cell_ue_active_count = cell_statt_profile(cell, 'ue_active_count')
for i, el in enumerate(cell_ue_active_count):
if el is not None:
Σue_active_count[i] = Σue_active_count[i] or 0
Σue_active_count[i] += el
# any None here will map to kpi.NA
m['DRB.UEActive'] = kpi.StatT(*Σue_active_count)
# RRC: connection establishment # RRC: connection establishment
# #
# Aggregate statistics for all cells because in E-RAB Accessibility we need # Aggregate statistics for all cells because in E-RAB Accessibility we need
...@@ -315,7 +346,6 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement): ...@@ -315,7 +346,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,6 +398,15 @@ def _stats_check(stats: xlog.Message): ...@@ -368,6 +398,15 @@ 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_cell_sp_el returns specified per-cell element of a statistical profile.
#
# stats is assumed to be already verified by _stats_check.
def _stats_cell_sp_el(stats: xlog.Message, cell: str, stat_profile_el: str):
_ = stats['cells'].get(cell)
if _ is None:
return None # cell is absent in this stats
return _.get(stat_profile_el, None)
# _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.
...@@ -378,7 +417,7 @@ def _stats_cc(stats: xlog.Message, counter: str): ...@@ -378,7 +417,7 @@ def _stats_cc(stats: xlog.Message, counter: str):
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)
if _ is None: if _ is None:
return 0 # cell is absent in this stats return 0 # cell is absent in this stats
return _['counters']['messages'].get(counter, 0) return _['counters']['messages'].get(counter, 0)
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from xlte.amari.kpi import LogMeasure, LogError, _trace as trace from xlte.amari.kpi import LogMeasure, LogError, _trace as trace
from xlte.kpi import Measurement, isNA from xlte.kpi import Measurement, isNA, StatT
from golang import func, defer, b from golang import func, defer, b
import io, json, re import io, json, re
...@@ -75,7 +75,8 @@ class tLogMeasure: ...@@ -75,7 +75,8 @@ class tLogMeasure:
def _mok_init(t): def _mok_init(t):
t._mok = Measurement() t._mok = Measurement()
# init fields extracted by amari.kpi from stats to 0 # init fields extracted by amari.kpi from stats to 0
# this will be default values to verify against # this will be default values to verify against.
# all other fields remain NA.
for field in ( for field in (
'RRC.ConnEstabAtt.sum', 'RRC.ConnEstabAtt.sum',
'RRC.ConnEstabSucc.sum', 'RRC.ConnEstabSucc.sum',
...@@ -143,7 +144,7 @@ def test_LogMeasure(): ...@@ -143,7 +144,7 @@ def test_LogMeasure():
_ = t.expect1 _ = t.expect1
# empty stats after first attach # empty stats after first attach
t.xlog( jstats(1, {}) ) t.xlog( jstats(1, {}, {}) )
_('X.Tstart', 0.02) _('X.Tstart', 0.02)
_('X.δT', 1-0.02) _('X.δT', 1-0.02)
t.expect_nodata() t.expect_nodata()
...@@ -173,12 +174,12 @@ def test_LogMeasure(): ...@@ -173,12 +174,12 @@ def test_LogMeasure():
τ_xlog = 1 # timestamp of last emitted xlog entry τ_xlog = 1 # timestamp of last emitted xlog entry
τ_logm = τ_xlog-2+1 # timestamp of next measurement to be read from logm τ_logm = τ_xlog-2+1 # timestamp of next measurement to be read from logm
counters_prev = {} counters_prev = {}
def tstats(counters): def tstats(stat_profiles, counters):
nonlocal τ_xlog, τ_logm, counters_prev nonlocal τ_xlog, τ_logm, counters_prev
trace('\n>>> tstats τ_xlog: %s τ_logm: %s' % (τ_xlog, τ_logm)) trace('\n>>> tstats τ_xlog: %s τ_logm: %s' % (τ_xlog, τ_logm))
t.xlog( jstats(τ_xlog+1, counters) ) # xlog τ+1 t.xlog( jstats(τ_xlog+1, stat_profiles, counters) ) # xlog τ+1
t.read() # read+assert M for τ-1 t.read() # read+assert M for τ-1
_('X.Tstart', τ_logm+1) # start preparing next expected M at τ _('X.Tstart', τ_logm+1) # start preparing next expected M at τ
_('X.δT', 1) _('X.δT', 1)
τ_xlog += 1 τ_xlog += 1
τ_logm += 1 τ_logm += 1
...@@ -189,7 +190,7 @@ def test_LogMeasure(): ...@@ -189,7 +190,7 @@ def test_LogMeasure():
counters = counters_prev.copy() counters = counters_prev.copy()
for k,δv in δcounters.items(): for k,δv in δcounters.items():
counters[k] = counters.get(k,0) + δv counters[k] = counters.get(k,0) + δv
tstats(counters) tstats({}, counters)
# tevent is the verb to verify handling of events. # tevent is the verb to verify handling of events.
# its logic is similar to tstats. # its logic is similar to tstats.
...@@ -216,10 +217,8 @@ def test_LogMeasure(): ...@@ -216,10 +217,8 @@ def test_LogMeasure():
trace('\n>>> tdrb_stats τ: %s τ_xlog: %s τ_logm: %s' % (τ, τ_xlog, τ_logm)) trace('\n>>> tdrb_stats τ: %s τ_xlog: %s τ_logm: %s' % (τ, τ_xlog, τ_logm))
t.xlog( jdrb_stats(τ, qci_trx) ) t.xlog( jdrb_stats(τ, qci_trx) )
# further empty stats # further empty stats
tstats({}) tstats({}, {})
_('X.Tstart', 1) _('X.Tstart', 1)
_('X.δT', 1) _('X.δT', 1)
_('RRC.ConnEstabAtt.sum', 0) _('RRC.ConnEstabAtt.sum', 0)
...@@ -232,6 +231,32 @@ def test_LogMeasure(): ...@@ -232,6 +231,32 @@ def test_LogMeasure():
_('ERAB.EstabAddSuccNbr.sum', 0) _('ERAB.EstabAddSuccNbr.sum', 0)
# DRB.UEActive
tstats(
{'C1.ue_active_count_min': 0,
'C1.ue_active_count_avg': 2.73,
'C1.ue_active_count_max': 3},
{}
)
_('DRB.UEActive', StatT(0, 2.73, 3))
# p2
tstats(
{'C1.ue_active_count_min': 1,
'C1.ue_active_count_avg': 6.0,
'C1.ue_active_count_max': 8},
{}
)
_('DRB.UEActive', StatT(1, 6.0, 8))
# p3
tstats(
{'C1.ue_active_count_min': 3,
'C1.ue_active_count_avg': 4.49,
'C1.ue_active_count_max': 7},
{}
)
_('DRB.UEActive', StatT(3, 4.49, 7))
# RRC.ConnEstab # RRC.ConnEstab
# #
# For init/fini correction LogMeasure accounts termination events in the # For init/fini correction LogMeasure accounts termination events in the
...@@ -245,42 +270,59 @@ def test_LogMeasure(): ...@@ -245,42 +270,59 @@ def test_LogMeasure():
# init 0 3 2 5 0 # init 0 3 2 5 0
# fini ø ←─── 2 1←─── 2←─── 4←─── 1 # fini ø ←─── 2 1←─── 2←─── 4←─── 1
# fini' 0 3 ² 2 ² 3 ¹ 0 # fini' 0 3 ² 2 ² 3 ¹ 0
tstats({'C1.rrc_connection_request': 0, tstats(
'C1.rrc_connection_setup_complete': 2}) # completions for previous uncovered period {},
{'C1.rrc_connection_request': 0,
'C1.rrc_connection_setup_complete': 2} # completions for previous uncovered period
)
_('RRC.ConnEstabAtt.sum', 0) _('RRC.ConnEstabAtt.sum', 0)
_('RRC.ConnEstabSucc.sum', 0) # not 2 _('RRC.ConnEstabSucc.sum', 0) # not 2
# p2 # p2
tstats({'C1.rrc_connection_request': 0 +3, # 3 new initiations tstats(
'C1.rrc_connection_setup_complete': 2 +1}) # 1 new completion {},
{'C1.rrc_connection_request': 0 +3, # 3 new initiations
'C1.rrc_connection_setup_complete': 2 +1} # 1 new completion
)
_('RRC.ConnEstabAtt.sum', 3) _('RRC.ConnEstabAtt.sum', 3)
_('RRC.ConnEstabSucc.sum', 3) # not 1 _('RRC.ConnEstabSucc.sum', 3) # not 1
# p3 # p3
tstats({'C1.rrc_connection_request': 0+3 +2, # 2 new initiations tstats(
'C1.rrc_connection_setup_complete': 2+1 +2}) # 2 completions for p2 {},
{'C1.rrc_connection_request': 0+3 +2, # 2 new initiations
'C1.rrc_connection_setup_complete': 2+1 +2} # 2 completions for p2
)
_('RRC.ConnEstabAtt.sum', 2) _('RRC.ConnEstabAtt.sum', 2)
_('RRC.ConnEstabSucc.sum', 2) # 2, but it is 2 - 2(for_p2) + 2(from_p4) _('RRC.ConnEstabSucc.sum', 2) # 2, but it is 2 - 2(for_p2) + 2(from_p4)
# p4 # p4
tstats({'C1.rrc_connection_request': 0+3+2 +5, # 5 new initiations tstats(
'C1.rrc_connection_setup_complete': 2+1+2 +4}) # 2 completions for p3 + 2 new {},
{'C1.rrc_connection_request': 0+3+2 +5, # 5 new initiations
'C1.rrc_connection_setup_complete': 2+1+2 +4} # 2 completions for p3 + 2 new
)
_('RRC.ConnEstabAtt.sum', 5) _('RRC.ConnEstabAtt.sum', 5)
_('RRC.ConnEstabSucc.sum', 3) _('RRC.ConnEstabSucc.sum', 3)
# p5 # p5
tstats({'C1.rrc_connection_request': 0+3+2+5 +0, # no new initiations tstats(
'C1.rrc_connection_setup_complete': 2+1+2+4 +1}) # 1 completion for p4 {},
{'C1.rrc_connection_request': 0+3+2+5 +0, # no new initiations
'C1.rrc_connection_setup_complete': 2+1+2+4 +1} # 1 completion for p4
)
_('RRC.ConnEstabAtt.sum', 0) _('RRC.ConnEstabAtt.sum', 0)
_('RRC.ConnEstabSucc.sum', 0) _('RRC.ConnEstabSucc.sum', 0)
# S1SIG.ConnEstab, ERAB.InitEstab # S1SIG.ConnEstab, ERAB.InitEstab
tδstats({'s1_initial_context_setup_request': +3, tδstats({'s1_initial_context_setup_request': +3,
's1_initial_context_setup_response': +2}) 's1_initial_context_setup_response': +2}
)
_('S1SIG.ConnEstabAtt', 3) _('S1SIG.ConnEstabAtt', 3)
_('S1SIG.ConnEstabSucc', 3) # 2 + 1(from_next) _('S1SIG.ConnEstabSucc', 3) # 2 + 1(from_next)
_('ERAB.EstabInitAttNbr.sum', 3) # currently same as S1SIG.ConnEstab _('ERAB.EstabInitAttNbr.sum', 3) # currently same as S1SIG.ConnEstab
_('ERAB.EstabInitSuccNbr.sum', 3) # ----//---- _('ERAB.EstabInitSuccNbr.sum', 3) # ----//----
tδstats({'s1_initial_context_setup_request': +4, tδstats({'s1_initial_context_setup_request': +4,
's1_initial_context_setup_response': +3}) 's1_initial_context_setup_response': +3}
)
_('S1SIG.ConnEstabAtt', 4) _('S1SIG.ConnEstabAtt', 4)
_('S1SIG.ConnEstabSucc', 2) # 3 - 1(to_prev) _('S1SIG.ConnEstabSucc', 2) # 3 - 1(to_prev)
_('ERAB.EstabInitAttNbr.sum', 4) # currently same as S1SIG.ConnEstab _('ERAB.EstabInitAttNbr.sum', 4) # currently same as S1SIG.ConnEstab
...@@ -407,10 +449,10 @@ def test_LogMeasure(): ...@@ -407,10 +449,10 @@ def test_LogMeasure():
tevent("service attach") tevent("service attach")
t.expect_nodata() t.expect_nodata()
t.xlog( jstats(τ_xlog+1, {i:1000, f:1000}) ) # LogMeasure restarts the queue after data starts to t.xlog( jstats(τ_xlog+1, {}, {i:1000, f:1000}) ) # LogMeasure restarts the queue after data starts to
τ_xlog += 1 # come in again. Do one t.xlog step manually to τ_xlog += 1 # come in again. Do one t.xlog step manually to
# increase t.read - t.xlog distance back to 2. # increase t.read - t.xlog distance back to 2.
tstats({i:1000+2, f:1000+2}) tstats({}, {i:1000+2, f:1000+2})
_(I, 2) # no "extra" events even if counters start with jumped values after reattach _(I, 2) # no "extra" events even if counters start with jumped values after reattach
_(F, 2) # and no fini correction going back through detach _(F, 2) # and no fini correction going back through detach
...@@ -424,31 +466,69 @@ def test_LogMeasure(): ...@@ -424,31 +466,69 @@ def test_LogMeasure():
# multiple cells # multiple cells
# TODO emit per-cell measurements instead of accumulating all cells # TODO emit per-cell measurements instead of accumulating all cells
tstats({}) tstats({}, {})
t.expect_nodata() t.expect_nodata()
tstats({}) tstats({}, {})
_('RRC.ConnEstabAtt.sum', 0) _('RRC.ConnEstabAtt.sum', 0)
_('RRC.ConnEstabSucc.sum', 0) _('RRC.ConnEstabSucc.sum', 0)
# C1 appears # C1 appears
tstats({'C1.rrc_connection_request': 12, 'C1.rrc_connection_setup_complete': 11}) tstats(
{
'C1.ue_active_count_min': 2,
'C1.ue_active_count_avg': 3.59,
'C1.ue_active_count_max': 4
},
{'C1.rrc_connection_request': 12, 'C1.rrc_connection_setup_complete': 11}
)
_('RRC.ConnEstabAtt.sum', 12) _('RRC.ConnEstabAtt.sum', 12)
_('RRC.ConnEstabSucc.sum', 11+1) _('RRC.ConnEstabSucc.sum', 11+1)
_('DRB.UEActive', StatT(2, 3.59, 4))
# C2 appears # C2 appears
tstats({'C1.rrc_connection_request': 12+3, 'C1.rrc_connection_setup_complete': 11+3, tstats(
'C2.rrc_connection_request': 22, 'C2.rrc_connection_setup_complete': 21}) {
'C1.ue_active_count_min': 1,
'C1.ue_active_count_avg': 2.87,
'C1.ue_active_count_max': 5,
'C2.ue_active_count_min': 1,
'C2.ue_active_count_avg': 1.43,
'C2.ue_active_count_max': 3
},
{'C1.rrc_connection_request': 12+3, 'C1.rrc_connection_setup_complete': 11+3,
'C2.rrc_connection_request': 22, 'C2.rrc_connection_setup_complete': 21}
)
_('RRC.ConnEstabAtt.sum', 3+22) _('RRC.ConnEstabAtt.sum', 3+22)
_('RRC.ConnEstabSucc.sum', -1+3+21+2) _('RRC.ConnEstabSucc.sum', -1+3+21+2)
_('DRB.UEActive', StatT(1+1, 2.87+1.43, 5+3))
# C1 and C2 stays # C1 and C2 stays
tstats({'C1.rrc_connection_request': 12+3+3, 'C1.rrc_connection_setup_complete': 11+3+3, tstats(
'C2.rrc_connection_request': 22+4, 'C2.rrc_connection_setup_complete': 21+4}) {
'C1.ue_active_count_min': 3,
'C1.ue_active_count_avg': 3.10,
'C1.ue_active_count_max': 5,
'C2.ue_active_count_min': 0,
'C2.ue_active_count_avg': 0.62,
'C2.ue_active_count_max': 1
},
{'C1.rrc_connection_request': 12+3+3, 'C1.rrc_connection_setup_complete': 11+3+3,
'C2.rrc_connection_request': 22+4, 'C2.rrc_connection_setup_complete': 21+4}
)
_('RRC.ConnEstabAtt.sum', 3+4) _('RRC.ConnEstabAtt.sum', 3+4)
_('RRC.ConnEstabSucc.sum', -2+3+4+2) _('RRC.ConnEstabSucc.sum', -2+3+4+2)
_('DRB.UEActive', StatT(3+0, 3.10+0.62, 5+1))
# C1 disappears # C1 disappears
tstats({'C2.rrc_connection_request': 22+4+4, 'C2.rrc_connection_setup_complete': 21+4+4}) tstats(
{
'C2.ue_active_count_min': 0,
'C2.ue_active_count_avg': 1.19,
'C2.ue_active_count_max': 3
},
{'C2.rrc_connection_request': 22+4+4, 'C2.rrc_connection_setup_complete': 21+4+4}
)
_('RRC.ConnEstabAtt.sum', 4) _('RRC.ConnEstabAtt.sum', 4)
_('RRC.ConnEstabSucc.sum', 4-2) _('RRC.ConnEstabSucc.sum', 4-2)
_('DRB.UEActive', StatT(0, 1.19, 3))
# C2 disappears # C2 disappears
tstats({}) tstats({}, {})
_('RRC.ConnEstabAtt.sum', 0) _('RRC.ConnEstabAtt.sum', 0)
_('RRC.ConnEstabSucc.sum', 0) _('RRC.ConnEstabSucc.sum', 0)
...@@ -467,28 +547,28 @@ def test_LogMeasure_badinput(): ...@@ -467,28 +547,28 @@ def test_LogMeasure_badinput():
CC = 'RRC.ConnEstabAtt.sum' CC = 'RRC.ConnEstabAtt.sum'
# initial ok entries # initial ok entries
t.xlog( jstats(1, {}) ) t.xlog( jstats(1, {}, {}) )
t.xlog( jstats(2, {cc: 2}) ) t.xlog( jstats(2, {}, {cc: 2}) )
t.xlog( jstats(3, {cc: 2+3}) ) t.xlog( jstats(3, {}, {cc: 2+3}) )
# bad: no counters # bad: no counters
t.xlog('{"message":"stats", "utc":21, "counters": {"messages": {}}, "cells": {"1": {}}}') t.xlog('{"message":"stats", "utc":21, "counters": {"messages": {}}, "cells": {"1": {}}}')
t.xlog('{"message":"stats", "utc":22, "counters": {"messages": {}}, "cells": {"1": {"counters": {}}}}') t.xlog('{"message":"stats", "utc":22, "counters": {"messages": {}}, "cells": {"1": {"counters": {}}}}')
t.xlog('{"message":"stats", "utc":23, "cells": {"1": {"counters": {"messages": {}}}}}') t.xlog('{"message":"stats", "utc":23, "cells": {"1": {"counters": {"messages": {}}}}}')
t.xlog('{"message":"stats", "utc":24, "counters": {}, "cells": {"1": {"counters": {"messages": {}}}}}') t.xlog('{"message":"stats", "utc":24, "counters": {}, "cells": {"1": {"counters": {"messages": {}}}}}')
# follow-up ok entries # follow-up ok entries
t.xlog( jstats(31, {cc: 30+4}) ) t.xlog( jstats(31, {}, {cc: 30+4}) )
t.xlog( jstats(32, {cc: 30+4+5}) ) t.xlog( jstats(32, {}, {cc: 30+4+5}) )
# badline 1 # badline 1
t.xlog( "zzzqqqrrr" ) t.xlog( "zzzqqqrrr" )
# more ok entries # more ok entries
t.xlog( jstats(41, {cc: 40+6}) ) t.xlog( jstats(41, {}, {cc: 40+6}) )
t.xlog( jstats(42, {cc: 40+6+7}) ) t.xlog( jstats(42, {}, {cc: 40+6+7}) )
# badline 2 + followup event # badline 2 + followup event
t.xlog( "hello world" ) t.xlog( "hello world" )
t.xlog( '{"meta": {"event": "service attach", "time": 50}}' ) t.xlog( '{"meta": {"event": "service attach", "time": 50}}' )
# more ok entries # more ok entries
t.xlog( jstats(51, {cc: 50+8}) ) t.xlog( jstats(51, {}, {cc: 50+8}) )
t.xlog( jstats(52, {cc: 50+8+9}) ) t.xlog( jstats(52, {}, {cc: 50+8+9}) )
def readok(τ, CC_value): def readok(τ, CC_value):
_('X.Tstart', τ) _('X.Tstart', τ)
...@@ -540,11 +620,11 @@ def test_LogMeasure_cc_wraparound(): ...@@ -540,11 +620,11 @@ def test_LogMeasure_cc_wraparound():
cc = 'C1.rrc_connection_request' cc = 'C1.rrc_connection_request'
CC = 'RRC.ConnEstabAtt.sum' CC = 'RRC.ConnEstabAtt.sum'
t.xlog( jstats(1, {}) ) t.xlog( jstats(1, {}, {}) )
t.xlog( jstats(2, {cc: 13}) ) t.xlog( jstats(2, {}, {cc: 13}) )
t.xlog( jstats(3, {cc: 12}) ) # cc↓ - should be reported t.xlog( jstats(3, {}, {cc: 12}) ) # cc↓ - should be reported
t.xlog( jstats(4, {cc: 140}) ) # cc↑↑ - should start afresh t.xlog( jstats(4, {}, {cc: 140}) ) # cc↑↑ - should start afresh
t.xlog( jstats(5, {cc: 150}) ) t.xlog( jstats(5, {}, {cc: 150}) )
def readok(τ, CC_value): def readok(τ, CC_value):
_('X.Tstart', τ) _('X.Tstart', τ)
...@@ -574,10 +654,10 @@ def test_LogMeasure_sync(): ...@@ -574,10 +654,10 @@ def test_LogMeasure_sync():
cc = 'C1.rrc_connection_request' cc = 'C1.rrc_connection_request'
CC = 'RRC.ConnEstabAtt.sum' CC = 'RRC.ConnEstabAtt.sum'
t.xlog( jstats(1, {}) ) t.xlog( jstats(1, {}, {}) )
t.xlog( jstats(2, {cc: 4}) ) t.xlog( jstats(2, {}, {cc: 4}) )
t.xlog( '{"meta": {"event": "sync", "time": 2.5, "state": "attached", "reason": "periodic", "generator": "xlog ws://localhost:9001 stats[]/30.0s"}}' ) t.xlog( '{"meta": {"event": "sync", "time": 2.5, "state": "attached", "reason": "periodic", "generator": "xlog ws://localhost:9001 stats[]/30.0s"}}' )
t.xlog( jstats(3, {cc: 7}) ) t.xlog( jstats(3, {}, {cc: 7}) )
def readok(τ, CC_value): def readok(τ, CC_value):
_('X.Tstart', τ) _('X.Tstart', τ)
...@@ -593,14 +673,29 @@ def test_LogMeasure_sync(): ...@@ -593,14 +673,29 @@ def test_LogMeasure_sync():
readok(2, 3) # 2-3 jumping over sync readok(2, 3) # 2-3 jumping over sync
# jstats returns json-encoded stats message corresponding to counters dict. # jstats returns json-encoded stats message corresponding to
# the given statistical profile and counter dicts.
# #
# if a counter goes as "Cxxx.yyy" it is emitted as counter yyy of cell xxx in the output. # a key formatted as "Cxxx.yyy" is emitted as measurement yyy of cell xxx in the output.
# τ goes directly to stats['utc'] as is. # τ goes directly to stats['utc'] as is.
def jstats(τ, counters): # -> str def jstats(τ, stat_profiles, counters): # -> str
g_sp = {} # global statistical profiles
g_cc = {} # global cumulative counters g_cc = {} # global cumulative counters
cells = {} # .cells cells = {} # .cells
for sp, value in stat_profiles.items():
_ = re.match(r"^C([^.]+)\.(.+)$", sp)
if _ is not None:
cell = _.group(1)
sp = _.group(2)
# enforce correct cell structure
cells.setdefault(cell, {}) \
.setdefault("counters", {}) \
.setdefault("messages", {})
cells[cell][sp] = value
else:
g_sp[sp] = value
for cc, value in counters.items(): for cc, value in counters.items():
_ = re.match(r"^C([^.]+)\.(.+)$", cc) _ = re.match(r"^C([^.]+)\.(.+)$", cc)
if _ is not None: if _ is not None:
...@@ -616,6 +711,7 @@ def jstats(τ, counters): # -> str ...@@ -616,6 +711,7 @@ def jstats(τ, counters): # -> str
s = { s = {
"message": "stats", "message": "stats",
"utc": τ, "utc": τ,
**g_sp,
"cells": cells, "cells": cells,
"counters": {"messages": g_cc}, "counters": {"messages": g_cc},
} }
...@@ -623,17 +719,29 @@ def jstats(τ, counters): # -> str ...@@ -623,17 +719,29 @@ def jstats(τ, counters): # -> str
return json.dumps(s) return json.dumps(s)
def test_jstats(): def test_jstats():
assert jstats(0, {}) == '{"message": "stats", "utc": 0, "cells": {}, "counters": {"messages": {}}}' assert jstats(0, {}, {}) == '{"message": "stats", "utc": 0, "cells": {}, "counters": {"messages": {}}}'
assert jstats(123.4, {"C1.rrc_x": 1, "s1_y": 2, "C1.rrc_z": 3, "x2_zz": 4}) == \
'{"message": "stats", "utc": 123.4, "cells": {"1": {"counters": {"messages": {"rrc_x": 1, "rrc_z": 3}}}}, "counters": {"messages": {"s1_y": 2, "x2_zz": 4}}}' # only statistical profiles
assert jstats(1.2, {"C1.ue_x_min": 1, "r1_y": 2, "C1.ue_x_avg": 3, "s2_z": 4, "C1.ue_x_max": 5}, {}) == \
# multiple cells '{"message": "stats", "utc": 1.2, "r1_y": 2, "s2_z": 4, ' + \
assert jstats(432.1, {"C1.rrc_x": 11, "C2.rrc_y": 22, "C3.xyz": 33, "C1.abc": 111, "xyz": 44}) == \ '"cells": {"1": {"counters": {"messages": {}}, "ue_x_min": 1, "ue_x_avg": 3, "ue_x_max": 5}}, ' + \
'{"message": "stats", "utc": 432.1, "cells": {' + \ '"counters": {"messages": {}}}'
'"1": {"counters": {"messages": {"rrc_x": 11, "abc": 111}}}, ' + \
'"2": {"counters": {"messages": {"rrc_y": 22}}}, ' + \ # only counters
'"3": {"counters": {"messages": {"xyz": 33}}}}, ' + \ assert jstats(12.34, {}, {"C1.rrc_x": 1, "s1_y": 2, "C1.rrc_z": 3, "x2_zz": 4}) == \
'"counters": {"messages": {"xyz": 44}}}' '{"message": "stats", "utc": 12.34, "cells": {"1": {"counters": {"messages": {"rrc_x": 1, "rrc_z": 3}}}}, ' + \
'"counters": {"messages": {"s1_y": 2, "x2_zz": 4}}}'
# multiple cells with both statistical profiles and counters
assert jstats(
432.1,
{"C1.ue_w": 11, "C2.ue_ww": 22, "C3.ue_x": 33, "C1.ue_xx": 44, "rst": 55},
{"C1.rrc_x": 11, "C2.rrc_y": 22, "C3.xyz": 33, "C1.abc": 44, "xyz": 55}
) == '{"message": "stats", "utc": 432.1, "rst": 55, "cells": {' + \
'"1": {"counters": {"messages": {"rrc_x": 11, "abc": 44}}, "ue_w": 11, "ue_xx": 44}, ' + \
'"2": {"counters": {"messages": {"rrc_y": 22}}, "ue_ww": 22}, ' + \
'"3": {"counters": {"messages": {"xyz": 33}}, "ue_x": 33}}, ' + \
'"counters": {"messages": {"xyz": 55}}}'
# jdrb_stats, similarly to jstats, returns json-encoded x.drb_stats message # jdrb_stats, similarly to jstats, returns json-encoded x.drb_stats message
......
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