Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
X
xlte
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Smelkov
xlte
Commits
b56b6ba0
Commit
b56b6ba0
authored
Mar 27, 2023
by
Kirill Smelkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
py2: *: Greek -> Latin
Python2 does not support unicode characters in identifiers.
parent
612a3d0f
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
502 additions
and
477 deletions
+502
-477
amari/__init__.py
amari/__init__.py
+3
-3
amari/drb.py
amari/drb.py
+131
-131
amari/drb_test.py
amari/drb_test.py
+24
-24
amari/kpi.py
amari/kpi.py
+41
-41
amari/kpi_test.py
amari/kpi_test.py
+71
-71
amari/xlog.py
amari/xlog.py
+9
-9
amari/xlog_test.py
amari/xlog_test.py
+2
-2
demo/kpidemo.ipynb
demo/kpidemo.ipynb
+13
-13
demo/kpidemo.py
demo/kpidemo.py
+33
-33
greek2lat.sh
greek2lat.sh
+25
-0
kpi.py
kpi.py
+102
-102
kpi_test.py
kpi_test.py
+48
-48
No files found.
amari/__init__.py
View file @
b56b6ba0
...
...
@@ -185,10 +185,10 @@ class Conn:
# handle rx timeout ourselves. We cannot rely on global rx timeout
# since e.g. other replies might be coming in again and again.
δ
t
=
conn
.
_ws
.
gettimeout
()
d
t
=
conn
.
_ws
.
gettimeout
()
rxt
=
nilchan
if
δ
t
is
not
None
:
_
=
time
.
Timer
(
δ
t
)
if
d
t
is
not
None
:
_
=
time
.
Timer
(
d
t
)
defer
(
_
.
stop
)
rxt
=
_
.
c
...
...
amari/drb.py
View file @
b56b6ba0
...
...
@@ -99,7 +99,7 @@ class _UE:
__slots__
=
(
'erab_flows'
,
# {} erab_id -> _ERAB_Flow current state of all erabs related to UE
'qci_flows'
,
# {} qci -> _QCI_Flow in-progress collection of UE-related samples
'bitsync'
,
# None | _BitSync to synchronize
δ
tx_bytes with #tx on updates
'bitsync'
,
# None | _BitSync to synchronize
d
tx_bytes with #tx on updates
)
# _ERAB_Flow tracks data transmission on particular ERAB of particular UE.
...
...
@@ -111,7 +111,7 @@ class _ERAB_Flow:
# _QCI_Flow represents in-progress collection to make up a Sample.
#
# .update(
δ
t, tx_bytes, #tx, ...) updates flow with information about next
# .update(
d
t, tx_bytes, #tx, ...) updates flow with information about next
# transmission period and potentially yields some finalized Samples.
# .finish() completes Sample collection.
class
_QCI_Flow
:
...
...
@@ -121,7 +121,7 @@ class _QCI_Flow:
'tx_time_err'
,
# accuracy of ^^^
)
# _BitSync helps _Sampler to match
δ
tx_bytes and #tx in transmission updates.
# _BitSync helps _Sampler to match
d
tx_bytes and #tx in transmission updates.
#
# For example for DL a block is transmitted via PDCCH+PDSCH during one TTI, and
# then the base station awaits HARQ ACK/NACK. That ACK/NACK comes later via
...
...
@@ -141,13 +141,13 @@ class _QCI_Flow:
# adjusted stream with #tx corresponding to tx_bytes coming together
# synchronized in time.
#
# .next(
δt, tx_bytes, #tx, X) -> [](δ
t', tx_bytes', #tx', X')
# .finish() -> [](
δ
t', tx_bytes', #tx', X')
# .next(
dt, tx_bytes, #tx, X) -> [](d
t', tx_bytes', #tx', X')
# .finish() -> [](
d
t', tx_bytes', #tx', X')
#
# (*) see e.g. Figure 8.1 in "An introduction to LTE, 2nd ed."
class
_BitSync
:
__slots__
=
(
'txq'
,
# [](
δ
t,tx_bytes,#tx,X) not-yet fully processed tail of whole txv
'txq'
,
# [](
d
t,tx_bytes,#tx,X) not-yet fully processed tail of whole txv
'i_txq'
,
# txq represents txv[i_txq:]
'i_lshift'
,
# next left shift will be done on txv[i_lshift] <- txv[i_lshift+1]
)
...
...
@@ -231,9 +231,9 @@ class _Utx: # transmission state passed through bitsync
@
func
(
_Sampler
)
def
add
(
s
,
ue_stats
,
stats
,
init
=
False
):
t
=
ue_stats
[
'utc'
]
δ
t
=
t
-
s
.
t
d
t
=
t
-
s
.
t
s
.
t
=
t
assert
δ
t
>
0
assert
d
t
>
0
qci_samples
=
{}
# qci -> []Sample samples finalized during this add
ue_live
=
set
()
# of ue ue that are present in ue_stats
...
...
@@ -256,7 +256,7 @@ def add(s, ue_stats, stats, init=False):
scell
=
stats
[
'cells'
][
str
(
cell_id
)]
u
=
_Utx
()
u
.
qtx_bytes
=
{}
# qci ->
Σ
δerab_qci=qci
u
.
qtx_bytes
=
{}
# qci ->
S
δerab_qci=qci
u
.
rank
=
cell
[
'ri'
]
if
s
.
use_ri
else
1
u
.
xl_use_avg
=
scell
[
'%s_use_avg'
%
s
.
dir
]
...
...
@@ -265,7 +265,7 @@ def add(s, ue_stats, stats, init=False):
ue
=
s
.
ues
[
ue_id
]
=
_UE
(
s
.
use_bitsync
)
# erabs: δ(tx_total_bytes) -> tx_bytes ; prepare per-qci tx_bytes
tx_bytes
=
0
#
Σ
δerab
tx_bytes
=
0
#
S
δerab
eflows_live
=
set
()
# of erab erabs that are present in ue_stats for this ue
for
erab
in
ju
[
'erab_list'
]:
erab_id
=
erab
[
'erab_id'
]
...
...
@@ -302,12 +302,12 @@ def add(s, ue_stats, stats, init=False):
if
erab_id
not
in
eflows_live
:
del
ue
.
erab_flows
[
erab_id
]
# bitsync <- (
δ
t, tx_bytes, #tx, u)
# bitsync <- (
d
t, tx_bytes, #tx, u)
tx
+=
retx
# both transmission and retransmission take time
if
ue
.
bitsync
is
not
None
:
bitnext
=
ue
.
bitsync
.
next
(
δ
t
,
tx_bytes
,
tx
,
u
)
bitnext
=
ue
.
bitsync
.
next
(
d
t
,
tx_bytes
,
tx
,
u
)
else
:
bitnext
=
[(
δ
t
,
tx_bytes
,
tx
,
u
)]
bitnext
=
[(
d
t
,
tx_bytes
,
tx
,
u
)]
# update qci flows
if
init
:
...
...
@@ -326,12 +326,12 @@ def add(s, ue_stats, stats, init=False):
return
qci_samples
# _update_qci_flows updates .qci_flows for ue with (
δ
t, tx_bytes, #tx, _Utx) yielded from bitsync.
# _update_qci_flows updates .qci_flows for ue with (
d
t, tx_bytes, #tx, _Utx) yielded from bitsync.
#
# yielded samples are appended to qci_samples ({} qci -> []Sample).
@
func
(
_UE
)
def
_update_qci_flows
(
ue
,
bitnext
,
qci_samples
):
for
(
δ
t
,
tx_bytes
,
tx
,
u
)
in
bitnext
:
for
(
d
t
,
tx_bytes
,
tx
,
u
)
in
bitnext
:
qflows_live
=
set
()
# of qci qci flows that get updated from current utx entry
# it might happen that even with correct bitsync we could end up with receiving tx=0 here.
...
...
@@ -341,10 +341,10 @@ def _update_qci_flows(ue, bitnext, qci_samples):
# <-- finish
# 0 10
#
# if we see #tx = 0 we say that it might be anything in between 1 and
δ
t.
# if we see #tx = 0 we say that it might be anything in between 1 and
d
t.
tx_lo
=
tx_hi
=
tx
if
tx
==
0
:
tx_hi
=
δ
t
/
tti
tx_hi
=
d
t
/
tti
tx_lo
=
min
(
1
,
tx_hi
)
for
qci
,
tx_bytes_qci
in
u
.
qtx_bytes
.
items
():
...
...
@@ -382,12 +382,12 @@ def _update_qci_flows(ue, bitnext, qci_samples):
#
# tx_bytes(x)
# ───────────·#tx ≤ #tx(x) ≤ #tx
#
Σ
tx_bytes
#
S
tx_bytes
qtx_lo
=
tx_bytes_qci
*
tx_lo
/
tx_bytes
if
qtx_lo
>
tx_hi
:
# e.g. 6.6 * 11308 / 11308 = 6.6 + ~1e-15
qtx_lo
-=
1e-4
assert
0
<
qtx_lo
<=
tx_hi
,
(
qtx_lo
,
tx_hi
,
tx_bytes_qci
,
tx_bytes
)
_
=
qf
.
update
(
δ
t
,
tx_bytes_qci
,
qtx_lo
,
tx_hi
,
u
.
rank
,
u
.
xl_use_avg
)
_
=
qf
.
update
(
d
t
,
tx_bytes_qci
,
qtx_lo
,
tx_hi
,
u
.
rank
,
u
.
xl_use_avg
)
for
sample
in
_
:
qci_samples
.
setdefault
(
qci
,
[]).
append
(
sample
)
...
...
@@ -407,31 +407,31 @@ def __init__(qf):
qf
.
tx_time_err
=
0
# update updates flow with information that so many bytes were transmitted during
#
δ
t with using #tx transport blocks somewhere in [tx_lo,tx_hi] and with
#
d
t with using #tx transport blocks somewhere in [tx_lo,tx_hi] and with
# specified rank. It is also known that overall average usage of resource
# blocks corresponding to tx direction in the resource map is xl_use_avg.
@
func
(
_QCI_Flow
)
def
update
(
qf
,
δ
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
rank
,
xl_use_avg
):
# -> []Sample
#_debug('QF.update %.2ftti %5db %.1f-%.1ftx %drank %.2fuse' % (
δ
t/tti, tx_bytes, tx_lo, tx_hi, rank, xl_use_avg))
def
update
(
qf
,
d
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
rank
,
xl_use_avg
):
# -> []Sample
#_debug('QF.update %.2ftti %5db %.1f-%.1ftx %drank %.2fuse' % (
d
t/tti, tx_bytes, tx_lo, tx_hi, rank, xl_use_avg))
tx_lo
/=
rank
# normalize TB to TTI (if it is e.g. 2x2 mimo, we have 2x more transport blocks)
tx_hi
/=
rank
vout
=
[]
s
=
qf
.
_update
(
δ
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
xl_use_avg
)
s
=
qf
.
_update
(
d
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
xl_use_avg
)
if
s
is
not
None
:
vout
.
append
(
s
)
return
vout
@
func
(
_QCI_Flow
)
def
_update
(
qf
,
δ
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
xl_use_avg
):
# -> ?Sample
def
_update
(
qf
,
d
t
,
tx_bytes
,
tx_lo
,
tx_hi
,
xl_use_avg
):
# -> ?Sample
assert
tx_bytes
>
0
δ
t_tti
=
δ
t
/
tti
dt_tti
=
d
t
/
tti
tx_lo
=
min
(
tx_lo
,
δ
t_tti
)
# protection (should not happen)
tx_hi
=
min
(
tx_hi
,
δ
t_tti
)
# protection (should not happen)
tx_lo
=
min
(
tx_lo
,
d
t_tti
)
# protection (should not happen)
tx_hi
=
min
(
tx_hi
,
d
t_tti
)
# protection (should not happen)
# tx time is somewhere in [tx,
δ
t_tti]
# tx time is somewhere in [tx,
d
t_tti]
if
xl_use_avg
<
0.9
:
# not congested: it likely took the time to transmit ≈ #tx
pass
...
...
@@ -439,7 +439,7 @@ def _update(qf, δt, tx_bytes, tx_lo, tx_hi, xl_use_avg): # -> ?Sample
# potentially congested: we don't know how much congested it is and
# which QCIs are affected more and which less
# -> all we can say tx_time is only somewhere in between limits
tx_hi
=
δ
t_tti
tx_hi
=
d
t_tti
tx_time
=
(
tx_lo
+
tx_hi
)
/
2
*
tti
tx_time_err
=
(
tx_hi
-
tx_lo
)
/
2
*
tti
...
...
@@ -454,7 +454,7 @@ def _update(qf, δt, tx_bytes, tx_lo, tx_hi, xl_use_avg): # -> ?Sample
# - if it is not big - it coalesces and ends the sample.
# NOTE: without throwing away last tti the overall throughput statistics
# stays the same irregardless of whether we do coalesce small txes or not.
if
cont
and
tx_hi
<
0.9
*
δ
t_tti
:
if
cont
and
tx_hi
<
0.9
*
d
t_tti
:
s
=
qf
.
_sample
()
qf
.
tx_bytes
=
0
qf
.
tx_time
=
0
...
...
@@ -498,18 +498,18 @@ def __init__(s):
s
.
i_txq
=
0
s
.
i_lshift
=
0
# next feeds next (
δ
t, tx_bytes, tx) into bitsync.
# next feeds next (
d
t, tx_bytes, tx) into bitsync.
#
# and returns ready parts of adjusted stream.
@
func
(
_BitSync
)
def
next
(
s
,
δ
t
,
tx_bytes
,
tx
,
X
):
# -> [](δ
t', tx_bytes', tx', X')
s
.
txq
.
append
((
δ
t
,
tx_bytes
,
tx
,
X
))
def
next
(
s
,
dt
,
tx_bytes
,
tx
,
X
):
# -> [](d
t', tx_bytes', tx', X')
s
.
txq
.
append
((
d
t
,
tx_bytes
,
tx
,
X
))
# XXX for simplicity we currently handle sync in between only current and
# next frames. That is enough to support FDD. TODO handle next-next case to support TDD
#
# XXX for simplicity we also assume all
δ
t are ~ 10·tti and do not generally handle them
# TODO handle arbitrary
δ
t
# XXX for simplicity we also assume all
d
t are ~ 10·tti and do not generally handle them
# TODO handle arbitrary
d
t
# shift #tx to the left:
#
...
...
@@ -537,8 +537,8 @@ def next(s, δt, tx_bytes, tx, X): # -> [](δt', tx_bytes', tx', X')
assert
s
.
i_txq
<=
i
<
s
.
i_txq
+
len
(
s
.
txq
)
i
-=
s
.
i_txq
δ
t1
,
b1
,
t1
,
X1
=
s
.
txq
[
i
]
δ
t2
,
b2
,
t2
,
X2
=
s
.
txq
[
i
+
1
]
d
t1
,
b1
,
t1
,
X1
=
s
.
txq
[
i
]
d
t2
,
b2
,
t2
,
X2
=
s
.
txq
[
i
+
1
]
if
b1
!=
0
:
t22
=
b2
*
t1
/
b1
else
:
...
...
@@ -551,8 +551,8 @@ def next(s, δt, tx_bytes, tx, X): # -> [](δt', tx_bytes', tx', X')
assert
t1
>=
0
,
t1
assert
t2
>=
0
,
t2
s
.
txq
[
i
]
=
(
δ
t1
,
b1
,
t1
,
X1
)
s
.
txq
[
i
+
1
]
=
(
δ
t2
,
b2
,
t2
,
X2
)
s
.
txq
[
i
]
=
(
d
t1
,
b1
,
t1
,
X1
)
s
.
txq
[
i
+
1
]
=
(
d
t2
,
b2
,
t2
,
X2
)
#print(' < lshift ', s.txq)
while
s
.
i_lshift
+
1
<
s
.
i_txq
+
len
(
s
.
txq
):
...
...
@@ -578,7 +578,7 @@ def next(s, δt, tx_bytes, tx, X): # -> [](δt', tx_bytes', tx', X')
#
# the bitsync becomes reset.
@
func
(
_BitSync
)
def
finish
(
s
):
# -> [](
δ
t', tx_bytes', tx', X')
def
finish
(
s
):
# -> [](
d
t', tx_bytes', tx', X')
assert
len
(
s
.
txq
)
<
3
s
.
_rebalance
(
len
(
s
.
txq
))
vout
=
s
.
txq
...
...
@@ -592,14 +592,14 @@ def finish(s): # -> [](δt', tx_bytes', tx', X')
# t'_i correlates with b_i and that whole transmission time stays the same:
#
# b₁ t₁ t'₁
# b₂ t₂ -> t'₂ t'_i = α·b_i
Σt' = Σ
t
# b₂ t₂ -> t'₂ t'_i = α·b_i
St' = S
t
# b₃ t₃ t'₃
#
# that gives
#
#
Σ
t
#
S
t
# α = ──
#
Σ
b
#
S
b
#
# and has the effect of moving #tx from periods with tx_bytes=0, to periods
# where transmission actually happened (tx_bytes > 0).
...
...
@@ -609,14 +609,14 @@ def _rebalance(s, l):
assert
l
<=
len
(
s
.
txq
)
assert
l
<=
3
Σ
b
=
sum
(
_
[
1
]
for
_
in
s
.
txq
[:
l
])
Σ
t
=
sum
(
_
[
2
]
for
_
in
s
.
txq
[:
l
])
if
Σ
b
!=
0
:
S
b
=
sum
(
_
[
1
]
for
_
in
s
.
txq
[:
l
])
S
t
=
sum
(
_
[
2
]
for
_
in
s
.
txq
[:
l
])
if
S
b
!=
0
:
for
i
in
range
(
l
):
δ
t_i
,
b_i
,
t_i
,
X_i
=
s
.
txq
[
i
]
t_i
=
b_i
*
Σ
t
/
Σ
b
d
t_i
,
b_i
,
t_i
,
X_i
=
s
.
txq
[
i
]
t_i
=
b_i
*
St
/
S
b
assert
t_i
>=
0
,
t_i
s
.
txq
[
i
]
=
(
δ
t_i
,
b_i
,
t_i
,
X_i
)
s
.
txq
[
i
]
=
(
d
t_i
,
b_i
,
t_i
,
X_i
)
#print(' < rebalance', s.txq[:l])
...
...
@@ -660,11 +660,11 @@ def __repr__(s):
# rate-limits websocket requests to execute not faster than 10ms each.
@
func
def
_x_stats_srv
(
ctx
,
reqch
:
chan
,
conn
:
amari
.
Conn
):
δ
t_rate
=
10
*
tti
d
t_rate
=
10
*
tti
# rx_ue_get_stats sends `ue_get[stats]` request and returns server response.
rtt_ue_stats
=
_IncStats
()
# time it takes to send ue_get and to receive response
δ
t_ue_stats
=
_IncStats
()
# δ(ue_stats.timestamp)
d
t_ue_stats
=
_IncStats
()
# δ(ue_stats.timestamp)
t_ue_stats
=
None
# last ue_stats.timestamp
def
rx_ue_get_stats
(
ctx
):
# -> ue_stats
nonlocal
t_ue_stats
...
...
@@ -674,7 +674,7 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
rtt_ue_stats
.
add
(
t_rx
-
t_tx
)
t
=
ue_stats
[
'utc'
]
if
t_ue_stats
is
not
None
:
δ
t_ue_stats
.
add
(
t
-
t_ue_stats
)
d
t_ue_stats
.
add
(
t
-
t_ue_stats
)
t_ue_stats
=
t
return
ue_stats
...
...
@@ -687,7 +687,7 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
conn_stats
=
amari
.
connect
(
ctx
,
conn
.
wsuri
)
defer
(
conn_stats
.
close
)
rtt_stats
=
_IncStats
()
# like rtt_ue_stats but for stat instead of ue_get
δ
t_stats
=
_IncStats
()
# δ(stats.timestamp)
d
t_stats
=
_IncStats
()
# δ(stats.timestamp)
t_stats
=
None
# last stats.timestamp
def
rx_stats
(
ctx
):
# -> stats
nonlocal
t_stats
...
...
@@ -697,7 +697,7 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
rtt_stats
.
add
(
t_rx
-
t_tx
)
t
=
stats
[
'utc'
]
if
t_stats
is
not
None
:
δ
t_stats
.
add
(
t
-
t_stats
)
d
t_stats
.
add
(
t
-
t_stats
)
t_stats
=
t
return
stats
# issue first dummy stats. It won't report most of statistics due to
...
...
@@ -706,7 +706,7 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
# rx_all simultaneously issues `ue_get[stats]` and `stats` requests and returns server responses.
# the requests are issued synchronized in time.
δ
_ue_stats
=
_IncStats
()
# ue_stats.timestamp - stats.timestamp
d
_ue_stats
=
_IncStats
()
# ue_stats.timestamp - stats.timestamp
def
rx_all
(
ctx
):
# -> ue_stats, stats
uq
=
chan
(
1
)
sq
=
chan
(
1
)
...
...
@@ -741,7 +741,7 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
stats
=
_rx
sq
=
nilchan
δ
_ue_stats
.
add
(
ue_stats
[
'utc'
]
-
stats
[
'utc'
])
d
_ue_stats
.
add
(
ue_stats
[
'utc'
]
-
stats
[
'utc'
])
return
ue_stats
,
stats
ueget_reqch
=
chan
()
...
...
@@ -774,17 +774,17 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
# Tmain is the main thread that drives the process overall
def
Tmain
(
ctx
):
nonlocal
rtt_ue_stats
,
δ
t_ue_stats
nonlocal
rtt_stats
,
δ
t_stats
nonlocal
δ
_ue_stats
nonlocal
rtt_ue_stats
,
d
t_ue_stats
nonlocal
rtt_stats
,
d
t_stats
nonlocal
d
_ue_stats
t_req
=
time
.
now
()
ue_stats
,
stats
=
rx_all
(
ctx
)
S
=
Sampler
(
ue_stats
,
stats
)
qci_
Σ
dl
=
{}
# qci -> _Σ
for dl
qci_
Σ
ul
=
{}
# ----//---- for ul
class
_
Σ
:
qci_
Sdl
=
{}
# qci -> _S
for dl
qci_
S
ul
=
{}
# ----//---- for ul
class
_
S
:
__slots__
=
(
'tx_bytes'
,
'tx_time'
,
...
...
@@ -793,15 +793,15 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
'tx_time_notailtti_err'
,
'tx_nsamples'
,
)
def
__init__
(
Σ
):
for
x
in
Σ
.
__slots__
:
setattr
(
Σ
,
x
,
0
)
# account accounts samples into
Σtx_time/Σtx_bytes in qci_Σ
.
def
account
(
qci_
Σ
,
qci_samples
):
def
__init__
(
S
):
for
x
in
S
.
__slots__
:
setattr
(
S
,
x
,
0
)
# account accounts samples into
Stx_time/Stx_bytes in qci_S
.
def
account
(
qci_
S
,
qci_samples
):
for
qci
,
samplev
in
qci_samples
.
items
():
Σ
=
qci_
Σ
.
get
(
qci
)
if
Σ
is
None
:
Σ
=
qci_
Σ
[
qci
]
=
_
Σ
()
S
=
qci_S
.
get
(
qci
)
if
S
is
None
:
S
=
qci_S
[
qci
]
=
_S
()
for
s
in
samplev
:
# do not account short transmissions
# ( tx with 1 tti should be ignored per standard, but it is
...
...
@@ -810,10 +810,10 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
t_hi
=
s
.
tx_time
+
s
.
tx_time_err
if
t_hi
<=
1
*
tti
or
(
t_hi
<=
2
and
s
.
tx_bytes
<
1000
):
continue
Σ
.
tx_nsamples
+=
1
Σ
.
tx_bytes
+=
s
.
tx_bytes
Σ
.
tx_time
+=
s
.
tx_time
Σ
.
tx_time_err
+=
s
.
tx_time_err
S
.
tx_nsamples
+=
1
S
.
tx_bytes
+=
s
.
tx_bytes
S
.
tx_time
+=
s
.
tx_time
S
.
tx_time_err
+=
s
.
tx_time_err
# also aggregate .tx_time without tail tti (IP Throughput KPI needs this)
tt_hi
=
math
.
ceil
(
t_hi
/
tti
-
1
)
# in tti
...
...
@@ -822,8 +822,8 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
tt_lo
=
math
.
ceil
(
tt_lo
-
1
)
tt
=
(
tt_lo
+
tt_hi
)
/
2
tt_err
=
(
tt_hi
-
tt_lo
)
/
2
Σ
.
tx_time_notailtti
+=
tt
*
tti
Σ
.
tx_time_notailtti_err
+=
tt_err
*
tti
S
.
tx_time_notailtti
+=
tt
*
tti
S
.
tx_time_notailtti_err
+=
tt_err
*
tti
while
1
:
...
...
@@ -842,71 +842,71 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
# wrap-up flows and account finalized samples
qci_dl
,
qci_ul
=
S
.
finish
()
account
(
qci_
Σ
dl
,
qci_dl
)
account
(
qci_
Σ
ul
,
qci_ul
)
account
(
qci_
S
dl
,
qci_dl
)
account
(
qci_
S
ul
,
qci_ul
)
_debug
()
_debug
(
'rtt_ue: %s ms'
%
rtt_ue_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'
δt_ue: %s ms'
%
δ
t_ue_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'
dt_ue: %s ms'
%
d
t_ue_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'rtt_stats: %s ms'
%
rtt_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'
δt_stats: %s ms'
%
δ
t_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'δ(ue,stat): %s ms'
%
δ
_ue_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'
dt_stats: %s ms'
%
d
t_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
_debug
(
'δ(ue,stat): %s ms'
%
d
_ue_stats
.
str
(
'%.2f'
,
time
.
millisecond
))
qci_dict
=
{}
Σ
0
=
_
Σ
()
for
qci
in
set
(
qci_
Σ
dl
.
keys
())
.
union
(
qci_
Σ
ul
.
keys
()):
Σ
dl
=
qci_
Σ
dl
.
get
(
qci
,
Σ
0
)
Σ
ul
=
qci_
Σ
ul
.
get
(
qci
,
Σ
0
)
S0
=
_S
()
for
qci
in
set
(
qci_
Sdl
.
keys
())
.
union
(
qci_S
ul
.
keys
()):
Sdl
=
qci_Sdl
.
get
(
qci
,
S
0
)
Sul
=
qci_Sul
.
get
(
qci
,
S
0
)
qci_dict
[
qci
]
=
{
'dl_tx_bytes'
:
Σ
dl
.
tx_bytes
,
'dl_tx_time'
:
Σ
dl
.
tx_time
,
'dl_tx_time_err'
:
Σ
dl
.
tx_time_err
,
'dl_tx_time_notailtti'
:
Σ
dl
.
tx_time_notailtti
,
'dl_tx_time_notailtti_err'
:
Σ
dl
.
tx_time_notailtti_err
,
'dl_tx_nsamples'
:
Σ
dl
.
tx_nsamples
,
'ul_tx_bytes'
:
Σ
ul
.
tx_bytes
,
'ul_tx_time'
:
Σ
ul
.
tx_time
,
'ul_tx_time_err'
:
Σ
ul
.
tx_time_err
,
'ul_tx_time_notailtti'
:
Σ
ul
.
tx_time_notailtti
,
'ul_tx_time_notailtti_err'
:
Σ
ul
.
tx_time_notailtti_err
,
'u;_tx_nsamples'
:
Σ
ul
.
tx_nsamples
,
'dl_tx_bytes'
:
S
dl
.
tx_bytes
,
'dl_tx_time'
:
S
dl
.
tx_time
,
'dl_tx_time_err'
:
S
dl
.
tx_time_err
,
'dl_tx_time_notailtti'
:
S
dl
.
tx_time_notailtti
,
'dl_tx_time_notailtti_err'
:
S
dl
.
tx_time_notailtti_err
,
'dl_tx_nsamples'
:
S
dl
.
tx_nsamples
,
'ul_tx_bytes'
:
S
ul
.
tx_bytes
,
'ul_tx_time'
:
S
ul
.
tx_time
,
'ul_tx_time_err'
:
S
ul
.
tx_time_err
,
'ul_tx_time_notailtti'
:
S
ul
.
tx_time_notailtti
,
'ul_tx_time_notailtti_err'
:
S
ul
.
tx_time_notailtti_err
,
'u;_tx_nsamples'
:
S
ul
.
tx_nsamples
,
}
r
=
{
'time'
:
ue_stats
[
'time'
],
'utc'
:
ue_stats
[
'utc'
],
'qci_dict'
:
qci_dict
,
'
δ
t_ueget'
:
{
'min'
:
δ
t_ue_stats
.
min
,
'avg'
:
δ
t_ue_stats
.
avg
(),
'max'
:
δ
t_ue_stats
.
max
,
'std'
:
δ
t_ue_stats
.
std
(),
'
d
t_ueget'
:
{
'min'
:
d
t_ue_stats
.
min
,
'avg'
:
d
t_ue_stats
.
avg
(),
'max'
:
d
t_ue_stats
.
max
,
'std'
:
d
t_ue_stats
.
std
(),
},
'δ_ueget_vs_stats'
:
{
'min'
:
δ
_ue_stats
.
min
,
'avg'
:
δ
_ue_stats
.
avg
(),
'max'
:
δ
_ue_stats
.
max
,
'std'
:
δ
_ue_stats
.
std
(),
'min'
:
d
_ue_stats
.
min
,
'avg'
:
d
_ue_stats
.
avg
(),
'max'
:
d
_ue_stats
.
max
,
'std'
:
d
_ue_stats
.
std
(),
},
}
respch
.
send
(
r
)
# reset
qci_
Σ
dl
=
{}
qci_
Σ
ul
=
{}
qci_
S
dl
=
{}
qci_
S
ul
=
{}
rtt_ue_stats
=
_IncStats
()
δ
t_ue_stats
=
_IncStats
()
d
t_ue_stats
=
_IncStats
()
rtt_stats
=
_IncStats
()
δ
t_stats
=
_IncStats
()
δ
_ue_stats
=
_IncStats
()
d
t_stats
=
_IncStats
()
d
_ue_stats
=
_IncStats
()
# sync time to keep t_req' - t_req ≈
δ
t_rate
# this should automatically translate to
δt(ue_stats) ≈ δ
t_rate
# sync time to keep t_req' - t_req ≈
d
t_rate
# this should automatically translate to
dt(ue_stats) ≈ d
t_rate
t
=
time
.
now
()
δ
tsleep
=
δ
t_rate
-
(
t
-
t_req
)
if
δ
tsleep
>
0
:
time
.
sleep
(
δ
tsleep
)
dtsleep
=
d
t_rate
-
(
t
-
t_req
)
if
d
tsleep
>
0
:
time
.
sleep
(
d
tsleep
)
# retrieve ue_get[stats] and stats data for next frame from enb
t_req
=
time
.
now
()
...
...
@@ -914,8 +914,8 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
# pass data to sampler and account already detected samples
qci_dl
,
qci_ul
=
S
.
add
(
ue_stats
,
stats
)
account
(
qci_
Σ
dl
,
qci_dl
)
account
(
qci_
Σ
ul
,
qci_ul
)
account
(
qci_
S
dl
,
qci_dl
)
account
(
qci_
S
ul
,
qci_ul
)
# run everything
wg
=
sync
.
WorkGroup
(
ctx
)
...
...
@@ -932,25 +932,25 @@ def _x_stats_srv(ctx, reqch: chan, conn: amari.Conn):
class
_IncStats
:
__slots__
=
(
'n'
,
# number of samples seen so far
'
μ
'
,
# current mean
'
σ
2'
,
# ~ current variance
'
mu
'
,
# current mean
'
s
2'
,
# ~ current variance
'min'
,
# current min / max
'max'
,
)
def
__init__
(
s
):
s
.
n
=
0
s
.
μ
=
0.
s
.
σ
2
=
0.
s
.
mu
=
0.
s
.
s
2
=
0.
s
.
min
=
+
float
(
'inf'
)
s
.
max
=
-
float
(
'inf'
)
def
add
(
s
,
x
):
# https://www.johndcook.com/blog/standard_deviation/
s
.
n
+=
1
μ
_
=
s
.
μ
# μ
_{n-1}
s
.
μ
+=
(
x
-
μ
_
)
/
s
.
n
s
.
σ
2
+=
(
x
-
μ
_
)
*
(
x
-
s
.
μ
)
mu_
=
s
.
mu
# mu
_{n-1}
s
.
mu
+=
(
x
-
mu
_
)
/
s
.
n
s
.
s2
+=
(
x
-
mu_
)
*
(
x
-
s
.
mu
)
s
.
min
=
min
(
s
.
min
,
x
)
s
.
max
=
max
(
s
.
max
,
x
)
...
...
@@ -958,12 +958,12 @@ class _IncStats:
def
avg
(
s
):
if
s
.
n
==
0
:
return
float
(
'nan'
)
return
s
.
μ
return
s
.
mu
def
var
(
s
):
if
s
.
n
==
0
:
return
float
(
'nan'
)
return
s
.
σ
2
/
s
.
n
# note johndcook uses / (s.n-1) to unbias
return
s
.
s
2
/
s
.
n
# note johndcook uses / (s.n-1) to unbias
def
std
(
s
):
return
math
.
sqrt
(
s
.
var
())
...
...
@@ -973,17 +973,17 @@ class _IncStats:
return
s
.
str
(
'%s'
,
1
)
def
str
(
s
,
fmt
,
scale
):
t
=
"min/avg/max/
σ
"
t
=
"min/avg/max/
std
"
if
s
.
n
==
0
:
t
+=
"?/?/? ±?"
else
:
μ
=
s
.
avg
()
/
scale
σ
=
s
.
std
()
/
scale
mu
=
s
.
avg
()
/
scale
std
=
s
.
std
()
/
scale
min
=
s
.
min
/
scale
max
=
s
.
max
/
scale
f
=
"%s/%s/%s ±%s"
%
((
fmt
,)
*
4
)
t
+=
f
%
(
min
,
μ
,
max
,
σ
)
t
+=
f
%
(
min
,
mu
,
max
,
std
)
return
t
...
...
amari/drb_test.py
View file @
b56b6ba0
...
...
@@ -57,8 +57,8 @@ class tSampler:
t
.
sampler
=
_Sampler
(
'zz'
,
ue_stats0
,
stats0
,
use_bitsync
=
use_bitsync
,
use_ri
=
use_ri
)
t
.
qci_samples
=
{}
# in-progress collection until final get
def
add
(
t
,
δ
t_tti
,
*
uev
):
ue_stats
,
stats
=
t
.
tstats
.
next
(
δ
t_tti
,
*
uev
)
def
add
(
t
,
d
t_tti
,
*
uev
):
ue_stats
,
stats
=
t
.
tstats
.
next
(
d
t_tti
,
*
uev
)
qci_samples
=
t
.
sampler
.
add
(
ue_stats
,
stats
)
t
.
_update_qci_samples
(
qci_samples
)
...
...
@@ -77,21 +77,21 @@ class tSampler:
# _tUEstats provides environment to generate test ue_get[stats].
class
_tUEstats
:
def
__init__
(
t
):
t
.
τ
=
0
t
.
tau
=
0
t
.
tx_total
=
{}
# (ue,erab) -> tx_total_bytes
# next returns next (ue_stats, stats) with specified ue transmissions
def
next
(
t
,
δτ
_tti
,
*
uev
):
def
next
(
t
,
dtau
_tti
,
*
uev
):
for
_
in
uev
:
assert
isinstance
(
_
,
UE
)
t
.
τ
+=
δτ
_tti
*
tti
t
.
tau
+=
dtau
_tti
*
tti
tx_total
=
t
.
tx_total
t
.
tx_total
=
{}
# if ue/erab is missing in ue_stats, its tx_total is reset
ue_list
=
[]
ue_stats
=
{
'time'
:
t
.
τ
,
'utc'
:
100
+
t
.
τ
,
'time'
:
t
.
tau
,
'utc'
:
100
+
t
.
tau
,
'ue_list'
:
ue_list
}
for
ue
in
uev
:
...
...
@@ -137,14 +137,14 @@ class _tUEstats:
# S is shortcut to create Sample.
def
S
(
tx_bytes
,
tx_time_tti
):
if
isinstance
(
tx_time_tti
,
tuple
):
τ
_lo
,
τ
_hi
=
tx_time_tti
tau_lo
,
tau
_hi
=
tx_time_tti
else
:
τ
_lo
=
τ
_hi
=
tx_time_tti
tau_lo
=
tau
_hi
=
tx_time_tti
s
=
Sample
()
s
.
tx_bytes
=
tx_bytes
s
.
tx_time
=
(
τ
_lo
+
τ
_hi
)
/
2
*
tti
s
.
tx_time_err
=
(
τ
_hi
-
τ
_lo
)
/
2
*
tti
s
.
tx_time
=
(
tau_lo
+
tau
_hi
)
/
2
*
tti
s
.
tx_time_err
=
(
tau_hi
-
tau
_lo
)
/
2
*
tti
return
s
...
...
@@ -154,7 +154,7 @@ def S(tx_bytes, tx_time_tti):
def
test_Sampler1
():
# _ constructs tSampler, feeds tx stats into it and returns yielded Samples.
#
# tx_statsv = [](
δ
t_tti, tx_bytes, #tx, #retx)
# tx_statsv = [](
d
t_tti, tx_bytes, #tx, #retx)
#
# only 1 ue, 1 qci and 1 erab are used in this test to verify the tricky
# parts of the Sampler in how single flow is divided into samples. The other
...
...
@@ -163,8 +163,8 @@ def test_Sampler1():
def
_
(
*
tx_statsv
,
bitsync
=
None
):
# -> []Sample
def
b
(
bitsync
):
t
=
tSampler
(
use_bitsync
=
bitsync
)
for
(
δ
t_tti
,
tx_bytes
,
tx
,
retx
)
in
tx_statsv
:
t
.
add
(
δ
t_tti
,
UE
(
17
,
tx
,
retx
,
Etx
(
23
,
4
,
tx_bytes
)))
for
(
d
t_tti
,
tx_bytes
,
tx
,
retx
)
in
tx_statsv
:
t
.
add
(
d
t_tti
,
UE
(
17
,
tx
,
retx
,
Etx
(
23
,
4
,
tx_bytes
)))
qci_samplev
=
t
.
get
()
if
len
(
qci_samplev
)
==
0
:
return
[]
...
...
@@ -181,7 +181,7 @@ def test_Sampler1():
return
bon
if
bitsync
else
boff
#
δ
t_tti tx_bytes #tx #retx
#
d
t_tti tx_bytes #tx #retx
assert
_
()
==
[]
assert
_
((
10
,
1000
,
1
,
0
))
==
[
S
(
1000
,
1
)]
assert
_
((
10
,
1000
,
2
,
0
))
==
[
S
(
1000
,
2
)]
...
...
@@ -195,7 +195,7 @@ def test_Sampler1():
for
retx
in
range
(
1
,
10
-
tx
+
1
):
assert
_
((
10
,
1000
,
tx
,
retx
))
==
[
S
(
1000
,
tx
+
retx
)]
assert
_
((
10
,
1000
,
77
,
88
))
==
[
S
(
1000
,
10
)]
# tx_time ≤
δ
t (bug in #tx / #retx)
assert
_
((
10
,
1000
,
77
,
88
))
==
[
S
(
1000
,
10
)]
# tx_time ≤
d
t (bug in #tx / #retx)
# coalesce/wrap-up 2 frames
def
_2tx
(
tx1
,
tx2
):
return
_
((
10
,
100
*
tx1
,
tx1
,
0
),
...
...
@@ -255,7 +255,7 @@ def test_Sampler1():
# bitsync lightly (BitSync itself is verified in details in test_BitSync)
def
b
(
*
btx_statsv
):
tx_statsv
=
[]
for
(
tx_bytes
,
tx
)
in
btx_statsv
:
# note: no
δ
t_tti, #retx
for
(
tx_bytes
,
tx
)
in
btx_statsv
:
# note: no
d
t_tti, #retx
tx_statsv
.
append
((
10
,
tx_bytes
,
tx
,
0
))
return
_
(
*
tx_statsv
,
bitsync
=
True
)
...
...
@@ -272,7 +272,7 @@ def test_Sampler1():
(
0
,
0
))
==
[
S
(
1000
+
500
,
10
+
5
),
S
(
1000
,
10
)]
# sampler starts from non-scratch - correctly detects
δ
for erabs.
# sampler starts from non-scratch - correctly detects
delta
for erabs.
def
test_Sampler_start_from_nonscratch
():
t
=
tSampler
(
UE
(
17
,
0
,
0
,
Etx
(
23
,
4
,
10000
,
tx_total
=
True
)))
t
.
add
(
10
,
UE
(
17
,
10
,
0
,
Etx
(
23
,
4
,
123
)))
...
...
@@ -313,7 +313,7 @@ def test_Sampler_tx_total_down():
# N tx transport blocks is shared/distributed between multiple QCIs
#
# tx_lo ∼ tx_bytes /
Σ
tx_bytes
# tx_lo ∼ tx_bytes /
S
tx_bytes
# tx_hi = whole #tx even if tx_bytes are different
def
test_Sampler_txtb_shared_between_qci
():
def
ue
(
tx
,
*
etxv
):
return
UE
(
17
,
tx
,
0
,
*
etxv
)
...
...
@@ -356,7 +356,7 @@ def test_Sampler_rank():
def
test_BitSync
():
# _ passes txv_in into _BitSync and returns output stream.
#
# txv_in = [](tx_bytes, #tx) ;
δ
t=10·tti
# txv_in = [](tx_bytes, #tx) ;
d
t=10·tti
def
_
(
*
txv_in
):
def
do_bitsync
(
*
txv_in
):
txv_out
=
[]
...
...
@@ -365,14 +365,14 @@ def test_BitSync():
for
x
,
(
tx_bytes
,
tx
)
in
enumerate
(
txv_in
):
_
=
bitsync
.
next
(
10
*
tti
,
tx_bytes
,
tx
,
chr
(
ord
(
'a'
)
+
x
))
for
(
δ
t
,
tx_bytes
,
tx
,
x_
)
in
_
:
assert
δ
t
==
10
*
tti
for
(
d
t
,
tx_bytes
,
tx
,
x_
)
in
_
:
assert
d
t
==
10
*
tti
txv_out
.
append
((
tx_bytes
,
tx
))
xv_out
+=
x_
_
=
bitsync
.
finish
()
for
(
δ
t
,
tx_bytes
,
tx
,
x_
)
in
_
:
assert
δ
t
==
10
*
tti
for
(
d
t
,
tx_bytes
,
tx
,
x_
)
in
_
:
assert
d
t
==
10
*
tti
txv_out
.
append
((
tx_bytes
,
tx
))
xv_out
+=
x_
...
...
amari/kpi.py
View file @
b56b6ba0
...
...
@@ -259,8 +259,8 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# do init/fini correction if there was also third preceding stats message.
m
=
logm
.
_m
.
copy
()
# [stats_prev, stats)
#
δ
cc(counter) tells how specified cumulative counter changed since last stats result.
def
δ
cc
(
counter
):
#
d
cc(counter) tells how specified cumulative counter changed since last stats result.
def
d
cc
(
counter
):
old
=
_stats_cc
(
stats_prev
,
counter
)
new
=
_stats_cc
(
stats
,
counter
)
if
new
<
old
:
...
...
@@ -285,38 +285,38 @@ def _handle_stats(logm, stats: xlog.Message, m_prev: kpi.Measurement):
# overall statistics if it is computed taking both periods into account.
if
p
is
not
None
:
if
p
[
fini
]
<
p
[
init
]:
δ
=
min
(
p
[
init
]
-
p
[
fini
],
m
[
fini
])
p
[
fini
]
+=
δ
m
[
fini
]
-=
δ
delta
=
min
(
p
[
init
]
-
p
[
fini
],
m
[
fini
])
p
[
fini
]
+=
delta
m
[
fini
]
-=
delta
# if we still have too much fini - throw it away pretending that it
# came from even older uncovered period
if
m
[
fini
]
>
m
[
init
]:
m
[
fini
]
=
m
[
init
]
# compute
δ
for counters.
# compute
delta
for counters.
# any logic error in data will be reported via LogError.
try
:
# RRC: connection establishment
m_initfini
(
'RRC.ConnEstabAtt.sum'
,
δ
cc
(
'rrc_connection_request'
),
'RRC.ConnEstabSucc.sum'
,
δ
cc
(
'rrc_connection_setup_complete'
))
'RRC.ConnEstabAtt.sum'
,
d
cc
(
'rrc_connection_request'
),
'RRC.ConnEstabSucc.sum'
,
d
cc
(
'rrc_connection_setup_complete'
))
# S1: connection establishment
m_initfini
(
'S1SIG.ConnEstabAtt'
,
δ
cc
(
's1_initial_context_setup_request'
),
'S1SIG.ConnEstabSucc'
,
δ
cc
(
's1_initial_context_setup_response'
))
'S1SIG.ConnEstabAtt'
,
d
cc
(
's1_initial_context_setup_request'
),
'S1SIG.ConnEstabSucc'
,
d
cc
(
's1_initial_context_setup_response'
))
# ERAB: Initial establishment
# FIXME not correct if multiple ERABs are present in one message
m_initfini
(
'ERAB.EstabInitAttNbr.sum'
,
δ
cc
(
's1_initial_context_setup_request'
),
'ERAB.EstabInitSuccNbr.sum'
,
δ
cc
(
's1_initial_context_setup_response'
))
'ERAB.EstabInitAttNbr.sum'
,
d
cc
(
's1_initial_context_setup_request'
),
'ERAB.EstabInitSuccNbr.sum'
,
d
cc
(
's1_initial_context_setup_response'
))
# ERAB: Additional establishment
# FIXME not correct if multiple ERABs are present in one message
m_initfini
(
'ERAB.EstabAddAttNbr.sum'
,
δ
cc
(
's1_erab_setup_request'
),
'ERAB.EstabAddSuccNbr.sum'
,
δ
cc
(
's1_erab_setup_response'
))
'ERAB.EstabAddAttNbr.sum'
,
d
cc
(
's1_erab_setup_request'
),
'ERAB.EstabAddSuccNbr.sum'
,
d
cc
(
's1_erab_setup_response'
))
except
Exception
as
e
:
if
not
isinstance
(
e
,
LogError
):
...
...
@@ -383,22 +383,22 @@ def _handle_drb_stats(logm, drb_stats: xlog.Message):
assert
drb_stats_prev
.
message
==
"x.drb_stats"
# time coverage for current drb_stats
τ
_lo
=
drb_stats_prev
.
timestamp
τ
_hi
=
drb_stats
.
timestamp
δτ
=
τ
_hi
-
τ
_lo
tau
_lo
=
drb_stats_prev
.
timestamp
tau
_hi
=
drb_stats
.
timestamp
dtau
=
tau_hi
-
tau
_lo
# see with which ._m or ._m_next, if any, drb_stats overlaps with ≥ 50% of
# time first, and update that measurement correspondingly.
if
not
(
δτ
>
0
):
if
not
(
dtau
>
0
):
return
if
logm
.
_m
is
not
None
:
m_lo
=
logm
.
_m
[
'X.Tstart'
]
m_hi
=
m_lo
+
logm
.
_m
[
'X.δT'
]
d
=
max
(
0
,
min
(
τ
_hi
,
m_hi
)
-
max
(
τ
_lo
,
m_lo
))
if
d
>=
δτ
/
2
:
# NOTE ≥ 50%, not > 50% not to skip drb_stats if fill is exactly 50%
d
=
max
(
0
,
min
(
tau
_hi
,
m_hi
)
-
max
(
tau
_lo
,
m_lo
))
if
d
>=
dtau
/
2
:
# NOTE ≥ 50%, not > 50% not to skip drb_stats if fill is exactly 50%
_drb_update
(
logm
.
_m
,
drb_stats
)
return
...
...
@@ -406,9 +406,9 @@ def _handle_drb_stats(logm, drb_stats: xlog.Message):
n_lo
=
logm
.
_m_next
[
'X.Tstart'
]
# n_hi - don't know as _m_next['X.δT'] is ø yet
d
=
max
(
0
,
τ
_hi
-
max
(
τ
_lo
,
n_lo
))
if
d
>=
δτ
/
2
:
d
=
max
(
0
,
tau
_hi
-
max
(
tau
_lo
,
n_lo
))
if
d
>=
dtau
/
2
:
_drb_update
(
logm
.
_m_next
,
drb_stats
)
return
...
...
@@ -434,16 +434,16 @@ def _drb_update(m: kpi.Measurement, drb_stats: xlog.Message):
# DRB.IPVol and DRB.IPTime are collected to compute throughput.
#
# thp =
ΣB*/Σ
T* where B* is tx'ed bytes in the sample without taking last tti into account
# thp =
SB*/S
T* where B* is tx'ed bytes in the sample without taking last tti into account
# and T* is time of tx also without taking that sample's tail tti.
#
# we only know
ΣB (whole amount of tx), ΣT and Σ
T* with some error.
# we only know
SB (whole amount of tx), ST and S
T* with some error.
#
# -> thp can be estimated to be inside the following interval:
#
#
ΣB Σ
B
#
SB S
B
# ───── ≤ thp ≤ ───── (1)
#
ΣT_hi Σ
T*_lo
#
ST_hi S
T*_lo
#
# the upper layer in xlte.kpi will use the following formula for
# final throughput calculation:
...
...
@@ -452,28 +452,28 @@ def _drb_update(m: kpi.Measurement, drb_stats: xlog.Message):
# thp = ────────── (2)
# DRB.IPTime
#
# -> set DRB.IPTime and its error to mean and
δ of ΣT_hi and Σ
T*_lo
# -> set DRB.IPTime and its error to mean and
delta of ST_hi and S
T*_lo
# so that (2) becomes (1).
# FIXME we account whole PDCP instead of only IP traffic
Σ
B
=
trx
[
'%s_tx_bytes'
%
dir
]
Σ
T
=
trx
[
'%s_tx_time'
%
dir
]
Σ
T_err
=
trx
[
'%s_tx_time_err'
%
dir
]
Σ
TT
=
trx
[
'%s_tx_time_notailtti'
%
dir
]
Σ
TT_err
=
trx
[
'%s_tx_time_notailtti_err'
%
dir
]
S
B
=
trx
[
'%s_tx_bytes'
%
dir
]
S
T
=
trx
[
'%s_tx_time'
%
dir
]
S
T_err
=
trx
[
'%s_tx_time_err'
%
dir
]
S
TT
=
trx
[
'%s_tx_time_notailtti'
%
dir
]
S
TT_err
=
trx
[
'%s_tx_time_notailtti_err'
%
dir
]
Σ
T_hi
=
Σ
T
+
Σ
T_err
Σ
TT_lo
=
Σ
TT
-
Σ
TT_err
ST_hi
=
ST
+
S
T_err
STT_lo
=
STT
-
S
TT_err
qvol
[
qci
]
=
8
*
Σ
B
# in bits
qtime
[
qci
]
=
(
Σ
T_hi
+
Σ
TT_lo
)
/
2
qtime_err
[
qci
]
=
(
Σ
T_hi
-
Σ
TT_lo
)
/
2
qvol
[
qci
]
=
8
*
S
B
# in bits
qtime
[
qci
]
=
(
ST_hi
+
S
TT_lo
)
/
2
qtime_err
[
qci
]
=
(
ST_hi
-
S
TT_lo
)
/
2
# LogError(timestamp|None, *argv).
@
func
(
LogError
)
def
__init__
(
e
,
τ
,
*
argv
):
e
.
timestamp
=
τ
def
__init__
(
e
,
tau
,
*
argv
):
e
.
timestamp
=
tau
super
(
LogError
,
e
).
__init__
(
*
argv
)
# __str__ returns human-readable form.
...
...
amari/kpi_test.py
View file @
b56b6ba0
...
...
@@ -118,10 +118,10 @@ class tLogMeasure:
if
t
.
_mok
is
None
:
t
.
_mok
=
Measurement
()
tstart
=
t
.
_mok
[
'X.Tstart'
]
δ
t
=
t
.
_mok
[
'X.δT'
]
d
t
=
t
.
_mok
[
'X.δT'
]
t
.
_mok
=
Measurement
()
# reinit with all NA
t
.
_mok
[
'X.Tstart'
]
=
tstart
t
.
_mok
[
'X.δT'
]
=
δ
t
t
.
_mok
[
'X.δT'
]
=
d
t
# read retrieves next measurement from LogMeasure and verifies it to be as expected.
def
read
(
t
):
# -> Measurement
...
...
@@ -169,51 +169,51 @@ def test_LogMeasure():
# _(...) # verify effect on Measurements returned with period
# _(...) # ending by timestamp of the above stats call.
# _(...) # i.e. Measurement₁ if tstats call corresponds to xlog₂.
τ
_xlog
=
1
# timestamp of last emitted xlog entry
τ
_logm
=
τ
_xlog
-
2
+
1
# timestamp of next measurement to be read from logm
tau
_xlog
=
1
# timestamp of last emitted xlog entry
tau_logm
=
tau
_xlog
-
2
+
1
# timestamp of next measurement to be read from logm
counters_prev
=
{}
def
tstats
(
counters
):
nonlocal
τ
_xlog
,
τ
_logm
,
counters_prev
trace
(
'
\
n
>>> tstats
τ_xlog: %s τ_logm: %s'
%
(
τ
_xlog
,
τ
_logm
))
t
.
xlog
(
jstats
(
τ
_xlog
+
1
,
counters
)
)
# xlog τ
+1
t
.
read
()
# read+assert M for
τ
-1
_
(
'X.Tstart'
,
τ
_logm
+
1
)
# start preparing next expected M at τ
nonlocal
tau_xlog
,
tau
_logm
,
counters_prev
trace
(
'
\
n
>>> tstats
tau_xlog: %s tau_logm: %s'
%
(
tau_xlog
,
tau
_logm
))
t
.
xlog
(
jstats
(
tau_xlog
+
1
,
counters
)
)
# xlog tau
+1
t
.
read
()
# read+assert M for
tau
-1
_
(
'X.Tstart'
,
tau_logm
+
1
)
# start preparing next expected M at tau
_
(
'X.δT'
,
1
)
τ
_xlog
+=
1
τ
_logm
+=
1
tau
_xlog
+=
1
tau
_logm
+=
1
counters_prev
=
counters
# t
δstats is like tstats but takes δ
for counters.
def
t
δ
stats
(
δ
counters
):
# t
dstats is like tstats but takes delta
for counters.
def
t
dstats
(
d
counters
):
counters
=
counters_prev
.
copy
()
for
k
,
δ
v
in
δ
counters
.
items
():
counters
[
k
]
=
counters
.
get
(
k
,
0
)
+
δ
v
for
k
,
dv
in
d
counters
.
items
():
counters
[
k
]
=
counters
.
get
(
k
,
0
)
+
d
v
tstats
(
counters
)
# tevent is the verb to verify handling of events.
# its logic is similar to tstats.
def
tevent
(
event
):
nonlocal
τ
_xlog
,
τ
_logm
,
counters_prev
trace
(
'
\
n
>>> tstats
τ_xlog: %s τ_logm: %s'
%
(
τ
_xlog
,
τ
_logm
))
t
.
xlog
(
json
.
dumps
({
"meta"
:
{
"event"
:
event
,
"time"
:
τ
_xlog
+
1
}}))
nonlocal
tau_xlog
,
tau
_logm
,
counters_prev
trace
(
'
\
n
>>> tstats
tau_xlog: %s tau_logm: %s'
%
(
tau_xlog
,
tau
_logm
))
t
.
xlog
(
json
.
dumps
({
"meta"
:
{
"event"
:
event
,
"time"
:
tau
_xlog
+
1
}}))
t
.
read
()
_
(
'X.Tstart'
,
τ
_logm
+
1
)
_
(
'X.Tstart'
,
tau
_logm
+
1
)
_
(
'X.δT'
,
1
)
τ
_xlog
+=
1
τ
_logm
+=
1
tau
_xlog
+=
1
tau
_logm
+=
1
counters_prev
=
{}
# reset
# tdrb_stats is the verb to verify handling of x.drb_stats message.
#
# it xlogs drb stats with given
δτ relative to either previous (δτ
> 0) or
# next (
δτ
< 0) stats or event.
def
tdrb_stats
(
δτ
,
qci_trx
):
if
δτ
>=
0
:
τ
=
τ
_xlog
+
δτ
# after previous stats or event
# it xlogs drb stats with given
dtau relative to either previous (dtau
> 0) or
# next (
dtau
< 0) stats or event.
def
tdrb_stats
(
dtau
,
qci_trx
):
if
dtau
>=
0
:
tau
=
tau_xlog
+
dtau
# after previous stats or event
else
:
τ
=
τ
_xlog
+
1
+
δτ
# before next stats or event
trace
(
'
\
n
>>> tdrb_stats
τ: %s τ_xlog: %s τ_logm: %s'
%
(
τ
,
τ
_xlog
,
τ
_logm
))
t
.
xlog
(
jdrb_stats
(
τ
,
qci_trx
)
)
tau
=
tau_xlog
+
1
+
dtau
# before next stats or event
trace
(
'
\
n
>>> tdrb_stats
tau: %s tau_xlog: %s tau_logm: %s'
%
(
tau
,
tau_xlog
,
tau
_logm
))
t
.
xlog
(
jdrb_stats
(
tau
,
qci_trx
)
)
...
...
@@ -271,14 +271,14 @@ def test_LogMeasure():
# S1SIG.ConnEstab, ERAB.InitEstab
t
δ
stats
({
's1_initial_context_setup_request'
:
+
3
,
t
d
stats
({
's1_initial_context_setup_request'
:
+
3
,
's1_initial_context_setup_response'
:
+
2
})
_
(
'S1SIG.ConnEstabAtt'
,
3
)
_
(
'S1SIG.ConnEstabSucc'
,
3
)
# 2 + 1(from_next)
_
(
'ERAB.EstabInitAttNbr.sum'
,
3
)
# currently same as S1SIG.ConnEstab
_
(
'ERAB.EstabInitSuccNbr.sum'
,
3
)
# ----//----
t
δ
stats
({
's1_initial_context_setup_request'
:
+
4
,
t
d
stats
({
's1_initial_context_setup_request'
:
+
4
,
's1_initial_context_setup_response'
:
+
3
})
_
(
'S1SIG.ConnEstabAtt'
,
4
)
_
(
'S1SIG.ConnEstabSucc'
,
2
)
# 3 - 1(to_prev)
...
...
@@ -287,24 +287,24 @@ def test_LogMeasure():
# ERAB.EstabAdd
t
δ
stats
({
's1_erab_setup_request'
:
+
1
,
t
d
stats
({
's1_erab_setup_request'
:
+
1
,
's1_erab_setup_response'
:
+
1
})
_
(
'ERAB.EstabAddAttNbr.sum'
,
1
)
_
(
'ERAB.EstabAddSuccNbr.sum'
,
1
)
t
δ
stats
({
's1_erab_setup_request'
:
+
3
,
t
d
stats
({
's1_erab_setup_request'
:
+
3
,
's1_erab_setup_response'
:
+
2
})
_
(
'ERAB.EstabAddAttNbr.sum'
,
3
)
_
(
'ERAB.EstabAddSuccNbr.sum'
,
2
)
# DRB.IPVol / DRB.IPTime (testing all variants of stats/x.drb_stats interaction)
t
δ
stats
({})
t
δ
stats
({})
# ──S₁·d₁─────S₂·d₂─────S₃·d₃──
t
d
stats
({})
t
d
stats
({})
# ──S₁·d₁─────S₂·d₂─────S₃·d₃──
tdrb_stats
(
+
0.1
,
{
1
:
drb_trx
(
1.1
,
10
,
1.2
,
20
),
11
:
drb_trx
(
1.3
,
30
,
1.4
,
40
)})
# nothing here - d₁ comes as the first drb_stats
t
δ
stats
({})
# S₂
t
d
stats
({})
# S₂
tdrb_stats
(
+
0.1
,
{
2
:
drb_trx
(
2.1
,
100
,
2.2
,
200
),
# d₂ is included into S₁-S₂
22
:
drb_trx
(
2.3
,
300
,
2.4
,
400
)})
_
(
'DRB.IPTimeDl.2'
,
2.1
);
_
(
'DRB.IPVolDl.2'
,
8
*
100
)
...
...
@@ -312,7 +312,7 @@ def test_LogMeasure():
_
(
'DRB.IPTimeDl.22'
,
2.3
);
_
(
'DRB.IPVolDl.22'
,
8
*
300
)
_
(
'DRB.IPTimeUl.22'
,
2.4
);
_
(
'DRB.IPVolUl.22'
,
8
*
400
)
t
δ
stats
({})
# S₃
t
d
stats
({})
# S₃
tdrb_stats
(
+
0.1
,
{
3
:
drb_trx
(
3.1
,
1000
,
3.2
,
2000
),
# d₃ is included int S₂-S₃
33
:
drb_trx
(
3.3
,
3000
,
3.4
,
4000
)})
_
(
'DRB.IPTimeDl.3'
,
3.1
);
_
(
'DRB.IPVolDl.3'
,
8
*
1000
)
...
...
@@ -322,20 +322,20 @@ def test_LogMeasure():
tdrb_stats
(
-
0.1
,
{
1
:
drb_trx
(
1.1
,
11
,
1.2
,
12
)})
# ──S·d─────d·S─────d·S──
t
δ
stats
({})
# cont↑
t
d
stats
({})
# cont↑
_
(
'DRB.IPTimeDl.1'
,
1.1
);
_
(
'DRB.IPVolDl.1'
,
8
*
11
)
_
(
'DRB.IPTimeUl.1'
,
1.2
);
_
(
'DRB.IPVolUl.1'
,
8
*
12
)
tdrb_stats
(
-
0.1
,
{
2
:
drb_trx
(
2.1
,
21
,
2.2
,
22
)})
t
δ
stats
({})
t
d
stats
({})
_
(
'DRB.IPTimeDl.2'
,
2.1
);
_
(
'DRB.IPVolDl.2'
,
8
*
21
)
_
(
'DRB.IPTimeUl.2'
,
2.2
);
_
(
'DRB.IPVolUl.2'
,
8
*
22
)
tdrb_stats
(
-
0.1
,
{
3
:
drb_trx
(
3.1
,
31
,
3.2
,
32
)})
# ──d·S─────d·S─────d·S·d──
t
δ
stats
({})
# cont↑
t
d
stats
({})
# cont↑
_
(
'DRB.IPTimeDl.3'
,
3.1
);
_
(
'DRB.IPVolDl.3'
,
8
*
31
)
_
(
'DRB.IPTimeUl.3'
,
3.2
);
_
(
'DRB.IPVolUl.3'
,
8
*
32
)
tdrb_stats
(
-
0.1
,
{
4
:
drb_trx
(
4.1
,
41
,
4.2
,
42
)})
t
δ
stats
({})
t
d
stats
({})
tdrb_stats
(
+
0.1
,
{
5
:
drb_trx
(
5.1
,
51
,
5.2
,
52
)})
_
(
'DRB.IPTimeDl.4'
,
4.1
);
_
(
'DRB.IPVolDl.4'
,
8
*
41
)
_
(
'DRB.IPTimeUl.4'
,
4.2
);
_
(
'DRB.IPVolUl.4'
,
8
*
42
)
...
...
@@ -343,16 +343,16 @@ def test_LogMeasure():
_
(
'DRB.IPTimeUl.5'
,
5.2
);
_
(
'DRB.IPVolUl.5'
,
8
*
52
)
tdrb_stats
(
+
0.5
,
{
6
:
drb_trx
(
6.1
,
61
,
6.2
,
62
)})
# ──d·S·d──d──S───d──S──
t
δ
stats
({})
# cont↑
t
d
stats
({})
# cont↑
_
(
'DRB.IPTimeDl.6'
,
6.1
);
_
(
'DRB.IPVolDl.6'
,
8
*
61
)
_
(
'DRB.IPTimeUl.6'
,
6.2
);
_
(
'DRB.IPVolUl.6'
,
8
*
62
)
tdrb_stats
(
+
0.51
,{
7
:
drb_trx
(
7.1
,
71
,
7.2
,
72
)})
t
δ
stats
({})
t
d
stats
({})
_
(
'DRB.IPTimeDl.7'
,
7.1
);
_
(
'DRB.IPVolDl.7'
,
8
*
71
)
_
(
'DRB.IPTimeUl.7'
,
7.2
);
_
(
'DRB.IPVolUl.7'
,
8
*
72
)
tdrb_stats
(
-
0.1
,
{
8
:
drb_trx
(
8.1
,
81
,
8.2
,
82
)})
# combined d + S with nonzero counters
t
δ
stats
({
's1_initial_context_setup_request'
:
+
3
,
# d──S────d·S──
t
d
stats
({
's1_initial_context_setup_request'
:
+
3
,
# d──S────d·S──
's1_initial_context_setup_response'
:
+
2
})
# cont↑
_
(
'DRB.IPTimeDl.8'
,
8.1
);
_
(
'DRB.IPVolDl.8'
,
8
*
81
)
_
(
'DRB.IPTimeUl.8'
,
8.2
);
_
(
'DRB.IPVolUl.8'
,
8
*
82
)
...
...
@@ -363,15 +363,15 @@ def test_LogMeasure():
# service detach/attach, connect failure, xlog failure
t
δ
stats
({})
# untie from previous history
t
d
stats
({})
# untie from previous history
i
,
f
=
'rrc_connection_request'
,
'rrc_connection_setup_complete'
I
,
F
=
'RRC.ConnEstabAtt.sum'
,
'RRC.ConnEstabSucc.sum'
t
δ
stats
({
i
:
2
,
f
:
1
})
t
d
stats
({
i
:
2
,
f
:
1
})
_
(
I
,
2
)
_
(
F
,
2
)
# +1(from_next)
t
δ
stats
({
i
:
2
,
f
:
2
})
t
d
stats
({
i
:
2
,
f
:
2
})
_
(
I
,
2
)
_
(
F
,
1
)
# -1(to_prev)
...
...
@@ -379,10 +379,10 @@ def test_LogMeasure():
t
.
expect_nodata
()
t
.
read
()
# LogMeasure flushes its queue on "service detach".
_
(
'X.Tstart'
,
τ
_logm
+
1
)
# After the flush t.read will need to go only 1 step behind
_
(
'X.Tstart'
,
tau
_logm
+
1
)
# After the flush t.read will need to go only 1 step behind
_
(
'X.δT'
,
1
)
# corresponding t.xlog call instead of previously going 2 steps beyond.
t
.
expect_nodata
()
# Do one t.read step manually to catch up.
τ
_logm
+=
1
tau
_logm
+=
1
tevent
(
"service connect failure"
)
t
.
expect_nodata
()
...
...
@@ -397,8 +397,8 @@ def test_LogMeasure():
tevent
(
"service attach"
)
t
.
expect_nodata
()
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
t
.
xlog
(
jstats
(
tau
_xlog
+
1
,
{
i
:
1000
,
f
:
1000
})
)
# LogMeasure restarts the queue after data starts to
tau
_xlog
+=
1
# come in again. Do one t.xlog step manually to
# increase t.read - t.xlog distance back to 2.
tstats
({
i
:
1000
+
2
,
f
:
1000
+
2
})
_
(
I
,
2
)
# no "extra" events even if counters start with jumped values after reattach
...
...
@@ -451,15 +451,15 @@ def test_LogMeasure_badinput():
t
.
xlog
(
jstats
(
51
,
{
cc
:
50
+
8
})
)
t
.
xlog
(
jstats
(
52
,
{
cc
:
50
+
8
+
9
})
)
def
readok
(
τ
,
CC_value
):
_
(
'X.Tstart'
,
τ
)
def
readok
(
tau
,
CC_value
):
_
(
'X.Tstart'
,
tau
)
_
(
'X.δT'
,
1
)
_
(
CC
,
CC_value
)
t
.
read
()
def
read_nodata
(
τ
,
δτ
=
1
):
_
(
'X.Tstart'
,
τ
)
_
(
'X.δT'
,
δτ
)
def
read_nodata
(
tau
,
dtau
=
1
):
_
(
'X.Tstart'
,
tau
)
_
(
'X.δT'
,
dtau
)
t
.
expect_nodata
()
t
.
read
()
...
...
@@ -468,8 +468,8 @@ def test_LogMeasure_badinput():
readok
(
2
,
3
)
# 2-3
read_nodata
(
3
,
8
)
# 3-11
def
tbadcell
(
τ
,
ncell
):
with
raises
(
LogError
,
match
=
"t%s: stats describes %d cells;"
%
(
τ
,
ncell
)
+
def
tbadcell
(
tau
,
ncell
):
with
raises
(
LogError
,
match
=
"t%s: stats describes %d cells;"
%
(
tau
,
ncell
)
+
" but only single-cell configurations are supported"
):
t
.
read
()
tbadcell
(
11
,
0
)
...
...
@@ -480,8 +480,8 @@ def test_LogMeasure_badinput():
read_nodata
(
13
,
1
)
tbadcell
(
14
,
3
)
def
tbadstats
(
τ
,
error
):
with
raises
(
LogError
,
match
=
"t%s: stats: %s"
%
(
τ
,
error
)):
def
tbadstats
(
tau
,
error
):
with
raises
(
LogError
,
match
=
"t%s: stats: %s"
%
(
tau
,
error
)):
t
.
read
()
read_nodata
(
14
,
7
)
tbadstats
(
21
,
":10/cells/1 no `counters`"
)
...
...
@@ -520,9 +520,9 @@ def test_LogMeasure_cc_wraparound():
t
.
xlog
(
jstats
(
4
,
{
cc
:
140
})
)
# cc↑↑ - should start afresh
t
.
xlog
(
jstats
(
5
,
{
cc
:
150
})
)
def
readok
(
τ
,
CC_value
):
_
(
'X.Tstart'
,
τ
)
_
(
'X.δT'
,
int
(
τ
+
1
)
-
τ
)
def
readok
(
tau
,
CC_value
):
_
(
'X.Tstart'
,
tau
)
_
(
'X.δT'
,
int
(
tau
+
1
)
-
tau
)
if
CC_value
is
not
None
:
_
(
CC
,
CC_value
)
else
:
...
...
@@ -553,9 +553,9 @@ def test_LogMeasure_sync():
t.xlog( '{"
meta
": {"
event
": "
sync
", "
time
": 2.5, "
state
": "
attached
", "
reason
": "
periodic
", "
generator
": "
xlog
ws
:
//
localhost
:
9001
stats
[]
/
30.0
s
"}}' )
t.xlog( jstats(3, {cc: 7}) )
def readok(
τ
, CC_value):
_('X.Tstart',
τ
)
_('X.δT', int(
τ+1)-τ
)
def readok(
tau
, CC_value):
_('X.Tstart',
tau
)
_('X.δT', int(
tau+1)-tau
)
if CC_value is not None:
_(CC, CC_value)
else:
...
...
@@ -568,8 +568,8 @@ def test_LogMeasure_sync():
# jstats returns json-encoded stats message corresponding to counters dict.
#
τ
goes directly to stats['utc'] as is.
def jstats(
τ
, counters): # -> str
#
tau
goes directly to stats['utc'] as is.
def jstats(
tau
, counters): # -> str
g_cc = {} # global
cell_cc = {} # per-cell
...
...
@@ -581,7 +581,7 @@ def jstats(τ, counters): # -> str
s = {
"
message
": "
stats
",
"
utc
":
τ
,
"
utc
":
tau
,
"
cells
": {"
1
": {"
counters
": {"
messages
": cell_cc}}},
"
counters
": {"
messages
": g_cc},
}
...
...
@@ -596,7 +596,7 @@ def test_jstats():
# jdrb_stats, similarly to jstats, returns json-encoded x.drb_stats message
# corresponding to per-QCI dl/ul tx_time/tx_bytes.
def jdrb_stats(
τ
, qci_dlul): # -> str
def jdrb_stats(
tau
, qci_dlul): # -> str
qci_dlul = qci_dlul.copy()
for qci, dlul in qci_dlul.items():
assert isinstance(dlul, dict)
...
...
@@ -609,7 +609,7 @@ def jdrb_stats(τ, qci_dlul): # -> str
s = {
"
message
": "
x
.
drb_stats
",
"
utc
":
τ
,
"
utc
":
tau
,
"
qci_dict
": qci_dlul,
}
...
...
amari/xlog.py
View file @
b56b6ba0
...
...
@@ -190,20 +190,20 @@ def xlog(ctx, wsuri, logspecv):
# e.g. disk full in xl.jemit itself
log
.
exception
(
'xlog failure (second level):'
)
δ
t_reconnect
=
min
(
3
,
lsync
.
period
)
d
t_reconnect
=
min
(
3
,
lsync
.
period
)
_
,
_rx
=
select
(
ctx
.
done
().
recv
,
# 0
time
.
after
(
δ
t_reconnect
).
recv
,
# 1
time
.
after
(
d
t_reconnect
).
recv
,
# 1
)
if
_
==
0
:
raise
ctx
.
err
()
# _XLogger serves xlog implementation.
class
_XLogger
:
def
__init__
(
xl
,
wsuri
,
logspecv
,
δ
t_sync
):
def
__init__
(
xl
,
wsuri
,
logspecv
,
d
t_sync
):
xl
.
wsuri
=
wsuri
xl
.
logspecv
=
logspecv
xl
.
δ
t_sync
=
δ
t_sync
# = logspecv.get("meta.sync").period
xl
.
dt_sync
=
d
t_sync
# = logspecv.get("meta.sync").period
xl
.
tsync
=
float
(
'-inf'
)
# never yet
# emit saves line to the log.
...
...
@@ -235,7 +235,7 @@ class _XLogger:
def
xlog1
(
xl
,
ctx
):
# emit sync periodically even in detached state
# this is useful to still know e.g. intended logspec if the service is stopped for a long time
if
time
.
now
()
-
xl
.
tsync
>=
xl
.
δ
t_sync
:
if
time
.
now
()
-
xl
.
tsync
>=
xl
.
d
t_sync
:
xl
.
jemit_sync
(
"detached"
,
"periodic"
,
{})
# connect to the service
...
...
@@ -336,11 +336,11 @@ class _XLogger:
# TODO detect time overruns and correct schedule correspondingly
tnow
=
time
.
now
()
tarm
=
t0
+
tmin
δ
tsleep
=
tarm
-
tnow
if
δ
tsleep
>
0
:
d
tsleep
=
tarm
-
tnow
if
d
tsleep
>
0
:
_
,
_rx
=
select
(
ctx
.
done
().
recv
,
# 0
time
.
after
(
δ
tsleep
).
recv
,
# 1
time
.
after
(
d
tsleep
).
recv
,
# 1
)
if
_
==
0
:
raise
ctx
.
err
()
...
...
@@ -420,7 +420,7 @@ class _XMsgServer:
resp_raw
=
json
.
dumps
(
resp
,
separators
=
(
','
,
':'
),
# most compact, like Amari does
ensure_ascii
=
False
)
# so that e.g.
δ
t comes as is
ensure_ascii
=
False
)
# so that e.g.
d
t comes as is
return
resp
,
resp_raw
...
...
amari/xlog_test.py
View file @
b56b6ba0
...
...
@@ -145,10 +145,10 @@ def test_Reader_readahead_vs_eof():
fxlog
.
seek
(
pos
,
io
.
SEEK_SET
)
xr
=
xlog
.
Reader
(
fxlog
)
def
expect_msg
(
τ
,
msg
):
def
expect_msg
(
tau
,
msg
):
_
=
xr
.
read
()
assert
type
(
_
)
is
xlog
.
Message
assert
_
.
timestamp
==
τ
assert
_
.
timestamp
==
tau
assert
_
.
message
==
msg
logit
(
'{"message": "aaa", "utc": 1}'
)
...
...
demo/kpidemo.ipynb
View file @
b56b6ba0
...
...
@@ -116,26 +116,26 @@
"\n",
"# calc_each_period partitions mlog data into periods and yields kpi.Calc for each period.\n",
"def calc_each_period(mlog: kpi.MeasurementLog, tperiod: float): # -> yield kpi.Calc\n",
"
τ
= mlog.data()[0]['X.Tstart']\n",
"
tau
= mlog.data()[0]['X.Tstart']\n",
" for m in mlog.data()[1:]:\n",
"
τ
_ = m['X.Tstart']\n",
" if (
τ_ - τ
) >= tperiod:\n",
" calc = kpi.Calc(mlog,
τ, τ
+tperiod)\n",
"
τ = calc.τ
_hi\n",
"
tau
_ = m['X.Tstart']\n",
" if (
tau_ - tau
) >= tperiod:\n",
" calc = kpi.Calc(mlog,
tau, tau
+tperiod)\n",
"
tau = calc.tau
_hi\n",
" yield calc\n",
"\n",
"tperiod = 1*60 # 1 minute\n",
"v
τ
= []\n",
"v
tau
= []\n",
"vInititialEPSBEstabSR = []\n",
"vAddedEPSBEstabSR = []\n",
"\n",
"for calc in calc_each_period(mlog, tperiod):\n",
" v
τ.append(calc.τ
_lo)\n",
" v
tau.append(calc.tau
_lo)\n",
" _ = calc.erab_accessibility() # E-RAB Accessibility\n",
" vInititialEPSBEstabSR.append(_[0])\n",
" vAddedEPSBEstabSR .append(_[1])\n",
"\n",
"v
τ = np.asarray([datetime.fromtimestamp(_) for _ in vτ
])\n",
"v
tau = np.asarray([datetime.fromtimestamp(_) for _ in vtau
])\n",
"vInititialEPSBEstabSR = np.asarray(vInititialEPSBEstabSR)\n",
"vAddedEPSBEstabSR = np.asarray(vAddedEPSBEstabSR)"
]
...
...
@@ -188,7 +188,7 @@
"from xlte.demo import kpidemo\n",
"import matplotlib.pyplot as plt\n",
"\n",
"kpidemo.figplot_erab_accessibility(plt.gcf(), v
τ
, vInititialEPSBEstabSR, vAddedEPSBEstabSR, tperiod)"
"kpidemo.figplot_erab_accessibility(plt.gcf(), v
tau
, vInititialEPSBEstabSR, vAddedEPSBEstabSR, tperiod)"
]
},
{
...
...
@@ -264,15 +264,15 @@
"outputs": [],
"source": [
"tperiod = 3 # 3 seconds\n",
"v
τ
= []\n",
"v
tau
= []\n",
"vIPThp_qci = []\n",
"\n",
"for calc in calc_each_period(mlog, tperiod):\n",
" v
τ.append(calc.τ
_lo)\n",
" v
tau.append(calc.tau
_lo)\n",
" _ = calc.eutran_ip_throughput() # E-UTRAN IP Throughput\n",
" vIPThp_qci.append(_)\n",
"\n",
"v
τ = np.asarray([datetime.fromtimestamp(_) for _ in vτ
])\n",
"v
tau = np.asarray([datetime.fromtimestamp(_) for _ in vtau
])\n",
"vIPThp_qci = np.asarray(vIPThp_qci)"
]
},
...
...
@@ -304,7 +304,7 @@
"source": [
"fig = plt.gcf()\n",
"fig.set_size_inches(10, 8)\n",
"kpidemo.figplot_eutran_ip_throughput(fig, v
τ
, vIPThp_qci, tperiod)"
"kpidemo.figplot_eutran_ip_throughput(fig, v
tau
, vIPThp_qci, tperiod)"
]
},
{
...
...
demo/kpidemo.py
View file @
b56b6ba0
...
...
@@ -67,22 +67,22 @@ def main():
# calc_each_period partitions mlog data into periods and yields kpi.Calc for each period.
def
calc_each_period
(
mlog
:
kpi
.
MeasurementLog
,
tperiod
:
float
):
# -> yield kpi.Calc
τ
=
mlog
.
data
()[
0
][
'X.Tstart'
]
tau
=
mlog
.
data
()[
0
][
'X.Tstart'
]
for
m
in
mlog
.
data
()[
1
:]:
τ
_
=
m
[
'X.Tstart'
]
if
(
τ
_
-
τ
)
>=
tperiod
:
calc
=
kpi
.
Calc
(
mlog
,
τ
,
τ
+
tperiod
)
τ
=
calc
.
τ
_hi
tau
_
=
m
[
'X.Tstart'
]
if
(
tau_
-
tau
)
>=
tperiod
:
calc
=
kpi
.
Calc
(
mlog
,
tau
,
tau
+
tperiod
)
tau
=
calc
.
tau
_hi
yield
calc
tperiod
=
float
(
sys
.
argv
[
1
])
v
τ
=
[]
v
tau
=
[]
vInititialEPSBEstabSR
=
[]
vAddedEPSBEstabSR
=
[]
vIPThp_qci
=
[]
for
calc
in
calc_each_period
(
mlog
,
tperiod
):
v
τ
.
append
(
calc
.
τ
_lo
)
v
tau
.
append
(
calc
.
tau
_lo
)
_
=
calc
.
erab_accessibility
()
# E-RAB Accessibility
vInititialEPSBEstabSR
.
append
(
_
[
0
])
...
...
@@ -91,7 +91,7 @@ def main():
_
=
calc
.
eutran_ip_throughput
()
# E-UTRAN IP Throughput
vIPThp_qci
.
append
(
_
)
v
τ
=
np
.
asarray
([
datetime
.
fromtimestamp
(
_
)
for
_
in
v
τ
])
v
tau
=
np
.
asarray
([
datetime
.
fromtimestamp
(
_
)
for
_
in
vtau
])
vInititialEPSBEstabSR
=
np
.
asarray
(
vInititialEPSBEstabSR
)
vAddedEPSBEstabSR
=
np
.
asarray
(
vAddedEPSBEstabSR
)
vIPThp_qci
=
np
.
asarray
(
vIPThp_qci
)
...
...
@@ -125,30 +125,30 @@ def main():
fig
=
plt
.
figure
(
constrained_layout
=
True
,
figsize
=
(
12
,
8
))
facc
,
fthp
=
fig
.
subfigures
(
1
,
2
)
figplot_erab_accessibility
(
facc
,
v
τ
,
vInititialEPSBEstabSR
,
vAddedEPSBEstabSR
,
tperiod
)
figplot_eutran_ip_throughput
(
fthp
,
v
τ
,
vIPThp_qci
,
tperiod
)
figplot_erab_accessibility
(
facc
,
v
tau
,
vInititialEPSBEstabSR
,
vAddedEPSBEstabSR
,
tperiod
)
figplot_eutran_ip_throughput
(
fthp
,
v
tau
,
vIPThp_qci
,
tperiod
)
plt
.
show
()
# ---- plotting routines ----
# figplot_erab_accessibility plots E-RAB Accessibility KPI data on the figure.
def
figplot_erab_accessibility
(
fig
:
plt
.
Figure
,
v
τ
,
vInititialEPSBEstabSR
,
vAddedEPSBEstabSR
,
tperiod
=
None
):
def
figplot_erab_accessibility
(
fig
:
plt
.
Figure
,
v
tau
,
vInititialEPSBEstabSR
,
vAddedEPSBEstabSR
,
tperiod
=
None
):
ax1
,
ax2
=
fig
.
subplots
(
2
,
1
,
sharex
=
True
)
fig
.
suptitle
(
"E-RAB Accessibility / %s"
%
(
tpretty
(
tperiod
)
if
tperiod
is
not
None
else
v
τ
_period_pretty
(
v
τ
)))
v
tau_period_pretty
(
vtau
)))
ax1
.
set_title
(
"Initial E-RAB establishment success rate"
)
ax2
.
set_title
(
"Added E-RAB establishment success rate"
)
plot_success_rate
(
ax1
,
v
τ
,
vInititialEPSBEstabSR
,
"InititialEPSBEstabSR"
)
plot_success_rate
(
ax2
,
v
τ
,
vAddedEPSBEstabSR
,
"AddedEPSBEstabSR"
)
plot_success_rate
(
ax1
,
v
tau
,
vInititialEPSBEstabSR
,
"InititialEPSBEstabSR"
)
plot_success_rate
(
ax2
,
v
tau
,
vAddedEPSBEstabSR
,
"AddedEPSBEstabSR"
)
# figplot_eutran_ip_throughput plots E-UTRAN IP Throughput KPI data on the figure.
def
figplot_eutran_ip_throughput
(
fig
:
plt
.
Figure
,
v
τ
,
vIPThp_qci
,
tperiod
=
None
):
def
figplot_eutran_ip_throughput
(
fig
:
plt
.
Figure
,
v
tau
,
vIPThp_qci
,
tperiod
=
None
):
ax1
,
ax2
=
fig
.
subplots
(
2
,
1
,
sharex
=
True
)
fig
.
suptitle
(
"E-UTRAN IP Throughput / %s"
%
(
tpretty
(
tperiod
)
if
tperiod
is
not
None
else
v
τ
_period_pretty
(
v
τ
)))
v
tau_period_pretty
(
vtau
)))
ax1
.
set_title
(
"Downlink"
)
ax2
.
set_title
(
"Uplink"
)
ax1
.
set_ylabel
(
"Mbit/s"
)
...
...
@@ -156,8 +156,8 @@ def figplot_eutran_ip_throughput(fig: plt.Figure, vτ, vIPThp_qci, tperiod=None)
v_qci
=
(
vIPThp_qci
.
view
(
np
.
float64
)
/
1e6
)
\
.
view
(
vIPThp_qci
.
dtype
)
plot_per_qci
(
ax1
,
v
τ
,
v_qci
[:,:][
'dl'
],
'IPThp'
)
plot_per_qci
(
ax2
,
v
τ
,
v_qci
[:,:][
'ul'
],
'IPThp'
)
plot_per_qci
(
ax1
,
v
tau
,
v_qci
[:,:][
'dl'
],
'IPThp'
)
plot_per_qci
(
ax2
,
v
tau
,
v_qci
[:,:][
'ul'
],
'IPThp'
)
_
,
dmax
=
ax1
.
get_ylim
()
_
,
umax
=
ax2
.
get_ylim
()
...
...
@@ -167,9 +167,9 @@ def figplot_eutran_ip_throughput(fig: plt.Figure, vτ, vIPThp_qci, tperiod=None)
# plot_success_rate plots success-rate data from vector v on ax.
# v is array with Intervals.
def
plot_success_rate
(
ax
,
v
τ
,
v
,
label
):
ax
.
plot
(
v
τ
,
v
[
'lo'
],
drawstyle
=
'steps-post'
,
label
=
label
)
ax
.
fill_between
(
v
τ
,
v
[
'lo'
],
v
[
'hi'
],
def
plot_success_rate
(
ax
,
v
tau
,
v
,
label
):
ax
.
plot
(
v
tau
,
v
[
'lo'
],
drawstyle
=
'steps-post'
,
label
=
label
)
ax
.
fill_between
(
v
tau
,
v
[
'lo'
],
v
[
'hi'
],
step
=
'post'
,
alpha
=
0.1
,
label
=
'%s
\
n
uncertainty'
%
label
)
ax
.
set_ylabel
(
"%"
)
...
...
@@ -185,8 +185,8 @@ def plot_success_rate(ax, vτ, v, label):
#
# v_qci should be array[t, QCI].
# QCIs, for which v[:,qci] is all zeros, are said to be silent and are not plotted.
def
plot_per_qci
(
ax
,
v
τ
,
v_qci
,
label
):
ax
.
set_xlim
((
v
τ
[
0
],
v
τ
[
-
1
]))
# to have correct x range even if we have no data
def
plot_per_qci
(
ax
,
v
tau
,
v_qci
,
label
):
ax
.
set_xlim
((
v
tau
[
0
],
vtau
[
-
1
]))
# to have correct x range even if we have no data
assert
len
(
v_qci
.
shape
)
==
2
silent
=
True
propv
=
list
(
plt
.
rcParams
[
'axes.prop_cycle'
])
...
...
@@ -196,8 +196,8 @@ def plot_per_qci(ax, vτ, v_qci, label):
continue
silent
=
False
prop
=
propv
[
qci
%
len
(
propv
)]
# to have same colors for same qci in different graphs
ax
.
plot
(
v
τ
,
v
[
'lo'
],
label
=
"%s.%d"
%
(
label
,
qci
),
**
prop
)
ax
.
fill_between
(
v
τ
,
v
[
'lo'
],
v
[
'hi'
],
alpha
=
0.3
,
**
prop
)
ax
.
plot
(
v
tau
,
v
[
'lo'
],
label
=
"%s.%d"
%
(
label
,
qci
),
**
prop
)
ax
.
fill_between
(
v
tau
,
v
[
'lo'
],
v
[
'hi'
],
alpha
=
0.3
,
**
prop
)
if
silent
:
ax
.
plot
([],[],
' '
,
label
=
"all QCI silent"
)
...
...
@@ -222,17 +222,17 @@ def tpretty(t):
return
"%s%s"
%
(
"%d'"
%
tmin
if
tmin
else
''
,
'%d"'
%
tsec
if
tsec
else
''
)
# v
τ_period_pretty returns pretty form for time period in vector vτ
.
# v
tau_period_pretty returns pretty form for time period in vector vtau
.
# for example [2,5,8,11] gives 3'.
def
v
τ
_period_pretty
(
v
τ
):
if
len
(
v
τ
)
<
2
:
def
v
tau_period_pretty
(
vtau
):
if
len
(
v
tau
)
<
2
:
return
"?"
s
=
timedelta
(
seconds
=
1
)
δ
v
τ
=
(
v
τ
[
1
:]
-
v
τ
[:
-
1
])
/
s
# in seconds
min
=
δ
v
τ
.
min
()
avg
=
δ
v
τ
.
mean
()
max
=
δ
v
τ
.
max
()
std
=
δ
v
τ
.
std
()
dvtau
=
(
vtau
[
1
:]
-
vtau
[:
-
1
])
/
s
# in seconds
min
=
dvtau
.
min
()
avg
=
dvtau
.
mean
()
max
=
dvtau
.
max
()
std
=
dvtau
.
std
()
if
min
==
max
:
return
tpretty
(
min
)
return
"%s ±%s [%s, %s]"
%
(
tpretty
(
avg
),
tpretty
(
std
),
tpretty
(
min
),
tpretty
(
max
))
...
...
greek2lat.sh
0 → 100755
View file @
b56b6ba0
#!/bin/bash -e
for
f
in
`
git ls-files |grep
-v
greek2lat
`
;
do
sed
-e
"
s/Σqci/Sqci/g
s/Σcause/Scause/g
s/τ/tau/g
s/Σ/S/g
s/δtau/dtau/g
s/δt/dt/g
s/δ_ue_stats/d_ue_stats/g
s/
\b
μ
\b
/mu/g
s/
\b
μ_
\b
/mu_/g
s/
\b
σ
\b
/std/g
s/
\b
σ2
\b
/s2/g
s/tδstats/tdstats/g
s/δcounters/dcounters/g
s/
\b
δv
\b
/dv/g
s/δcc/dcc/g
s/ δ / delta /g
s/ δ
$/
delta/g
s/δvtau/dvtau/g
"
-i
$f
done
kpi.py
View file @
b56b6ba0
...
...
@@ -56,7 +56,7 @@ from golang import func
# Calc provides way to compute KPIs over given measurement data and time interval.
#
# It is constructed from MeasurementLog and [
τ_lo, τ
_hi) and further provides
# It is constructed from MeasurementLog and [
tau_lo, tau
_hi) and further provides
# following methods for computing 3GPP KPIs:
#
# .erab_accessibility() - TS 32.450 6.1.1 "E-RAB Accessibility"
...
...
@@ -66,15 +66,15 @@ from golang import func
# Upon construction specified time interval is potentially widened to cover
# corresponding data in full granularity periods:
#
#
τ'lo τ
'hi
#
tau'lo tau
'hi
# ──────|─────|────[────|────)──────|──────|────────>
# ←─
τ_lo τ
_hi ──→ time
# ←─
tau_lo tau
_hi ──→ time
#
#
# See also: MeasurementLog, Measurement.
class
Calc
:
# ._data []Measurement - fully inside [.
τ_lo, .τ
_hi)
# [.
τ_lo, .τ
_hi) time interval to compute over. Potentially wider than originally requested.
# ._data []Measurement - fully inside [.
tau_lo, .tau
_hi)
# [.
tau_lo, .tau
_hi) time interval to compute over. Potentially wider than originally requested.
pass
...
...
@@ -265,8 +265,8 @@ def _():
expv
.
append
((
name
,
typ
,
nqci
))
# X.QCI[nqci]
elif
name
.
endswith
(
'.CAUSE'
):
Σ
,
causev
=
_all_cause
(
name
)
for
_
in
(
Σ
,)
+
causev
:
S
,
causev
=
_all_cause
(
name
)
for
_
in
(
S
,)
+
causev
:
expv
.
append
((
_
,
typ
))
else
:
...
...
@@ -414,14 +414,14 @@ def append(mlog, m: Measurement):
# verify .Tstart↑
if
len
(
mlog
.
_data
)
>
0
:
m_
=
mlog
.
_data
[
-
1
]
τ
=
m
[
'X.Tstart'
]
τ
_
=
m_
[
'X.Tstart'
]
δτ
_
=
m_
[
'X.δT'
]
if
not
(
τ
_
<
τ
):
raise
AssertionError
(
".Tstart not ↑ (%s -> %s)"
%
(
τ
_
,
τ
))
if
not
(
τ
_
+
δτ
_
<=
τ
):
tau
=
m
[
'X.Tstart'
]
tau
_
=
m_
[
'X.Tstart'
]
dtau
_
=
m_
[
'X.δT'
]
if
not
(
tau_
<
tau
):
raise
AssertionError
(
".Tstart not ↑ (%s -> %s)"
%
(
tau_
,
tau
))
if
not
(
tau_
+
dtau_
<=
tau
):
raise
AssertionError
(
".Tstart overlaps with previous measurement: %s ∈ [%s, %s)"
%
(
τ
,
τ
_
,
τ
_
+
δτ
_
))
(
tau
,
tau_
,
tau_
+
dtau
_
))
_
=
np
.
append
(
mlog
.
_data
.
view
(
Measurement
.
_dtype0
),
# dtype0 because np.append does not handle aliased
m
.
view
(
Measurement
.
_dtype0
))
# fields as such and increases out itemsize
...
...
@@ -443,32 +443,32 @@ def forget_past(mlog, Tcut):
# Calc() is initialized from slice of data in the measurement log that is
# covered/overlapped with [
τ_lo, τ
_hi) time interval.
# covered/overlapped with [
tau_lo, tau
_hi) time interval.
#
# The time interval, that will actually be used for computations, is potentially wider.
# See Calc class documentation for details.
@
func
(
Calc
)
def
__init__
(
calc
,
mlog
:
MeasurementLog
,
τ
_lo
,
τ
_hi
):
assert
τ
_lo
<=
τ
_hi
def
__init__
(
calc
,
mlog
:
MeasurementLog
,
tau_lo
,
tau
_hi
):
assert
tau_lo
<=
tau
_hi
data
=
mlog
.
data
()
l
=
len
(
data
)
# find min i:
τ
_lo < [i].(Tstart+δT) ; i=l if not found
# find min i:
tau
_lo < [i].(Tstart+δT) ; i=l if not found
# TODO binary search
i
=
0
while
i
<
l
:
m
=
data
[
i
]
m_
τ
hi
=
m
[
'X.Tstart'
]
+
m
[
'X.δT'
]
if
τ
_lo
<
m_
τ
hi
:
m_
tau
hi
=
m
[
'X.Tstart'
]
+
m
[
'X.δT'
]
if
tau_lo
<
m_tau
hi
:
break
i
+=
1
# find min j:
τ
_hi ≤ [j].Tstart ; j=l if not found
# find min j:
tau
_hi ≤ [j].Tstart ; j=l if not found
j
=
i
while
j
<
l
:
m
=
data
[
j
]
m_
τ
lo
=
m
[
'X.Tstart'
]
if
τ
_hi
<=
m_
τ
lo
:
m_
tau
lo
=
m
[
'X.Tstart'
]
if
tau_hi
<=
m_tau
lo
:
break
j
+=
1
...
...
@@ -476,12 +476,12 @@ def __init__(calc, mlog: MeasurementLog, τ_lo, τ_hi):
if
len
(
data
)
>
0
:
m_lo
=
data
[
0
]
m_hi
=
data
[
-
1
]
τ
_lo
=
min
(
τ
_lo
,
m_lo
[
'X.Tstart'
])
τ
_hi
=
max
(
τ
_hi
,
m_hi
[
'X.Tstart'
]
+
m_hi
[
'X.δT'
])
tau_lo
=
min
(
tau
_lo
,
m_lo
[
'X.Tstart'
])
tau_hi
=
max
(
tau
_hi
,
m_hi
[
'X.Tstart'
]
+
m_hi
[
'X.δT'
])
calc
.
_data
=
data
calc
.
τ
_lo
=
τ
_lo
calc
.
τ
_hi
=
τ
_hi
calc
.
tau_lo
=
tau
_lo
calc
.
tau_hi
=
tau
_hi
# erab_accessibility computes "E-RAB Accessibility" KPI.
...
...
@@ -499,20 +499,20 @@ def __init__(calc, mlog: MeasurementLog, τ_lo, τ_hi):
def
erab_accessibility
(
calc
):
# -> InitialEPSBEstabSR, AddedEPSBEstabSR
SR
=
calc
.
_success_rate
x
=
SR
(
"
Σ
cause RRC.ConnEstabSucc.CAUSE"
,
"
Σ
cause RRC.ConnEstabAtt.CAUSE"
)
x
=
SR
(
"
S
cause RRC.ConnEstabSucc.CAUSE"
,
"
S
cause RRC.ConnEstabAtt.CAUSE"
)
y
=
SR
(
"S1SIG.ConnEstabSucc"
,
"S1SIG.ConnEstabAtt"
)
z
=
SR
(
"
Σ
qci ERAB.EstabInitSuccNbr.QCI"
,
"
Σ
qci ERAB.EstabInitAttNbr.QCI"
)
z
=
SR
(
"
S
qci ERAB.EstabInitSuccNbr.QCI"
,
"
S
qci ERAB.EstabInitAttNbr.QCI"
)
InititialEPSBEstabSR
=
Interval
(
x
[
'lo'
]
*
y
[
'lo'
]
*
z
[
'lo'
],
# x·y·z
x
[
'hi'
]
*
y
[
'hi'
]
*
z
[
'hi'
])
AddedEPSBEstabSR
=
SR
(
"
Σ
qci ERAB.EstabAddSuccNbr.QCI"
,
"
Σ
qci ERAB.EstabAddAttNbr.QCI"
)
AddedEPSBEstabSR
=
SR
(
"
S
qci ERAB.EstabAddSuccNbr.QCI"
,
"
S
qci ERAB.EstabAddAttNbr.QCI"
)
return
_i2pc
(
InititialEPSBEstabSR
),
\
_i2pc
(
AddedEPSBEstabSR
)
# as %
...
...
@@ -535,60 +535,60 @@ def erab_accessibility(calc): # -> InitialEPSBEstabSR, AddedEPSBEstabSR
#
# This gives the following for resulting success rate confidence interval:
#
# time covered by periods with data:
Σ
t
# time covered by periods with data:
S
t
# time covered by periods with no data: t⁺ t⁺
# extrapolation for incoming initiation events: init⁺ = ──·
Σ
(init)
#
Σ
t
# extrapolation for incoming initiation events: init⁺ = ──·
S
(init)
#
S
t
# fini events for "no data" time is full uncertainty: fini⁺ ∈ [0,init⁺]
#
# => success rate over whole time is uncertain in between
#
#
Σ(fini) Σ
(fini) + init⁺
#
S(fini) S
(fini) + init⁺
# ────────────── ≤ SR ≤ ──────────────
#
Σ(init) + init⁺ Σ
(init) + init⁺
#
S(init) + init⁺ S
(init) + init⁺
#
# that confidence interval is returned as the result.
#
# fini/init events can be prefixed with "
Σqci " or "Σ
cause ". If such prefix is
# present, then fini/init value is obtained via call to
Σqci or Σ
cause correspondingly.
# fini/init events can be prefixed with "
Sqci " or "S
cause ". If such prefix is
# present, then fini/init value is obtained via call to
Sqci or S
cause correspondingly.
@
func
(
Calc
)
def
_success_rate
(
calc
,
fini
,
init
):
# -> Interval in [0,1]
def
vget
(
m
,
name
):
if
name
.
startswith
(
"
Σ
qci "
):
return
Σ
qci
(
m
,
name
[
len
(
"Σ
qci "
):])
if
name
.
startswith
(
"
Σ
cause "
):
return
Σ
cause
(
m
,
name
[
len
(
"Σ
cause "
):])
if
name
.
startswith
(
"
S
qci "
):
return
Sqci
(
m
,
name
[
len
(
"S
qci "
):])
if
name
.
startswith
(
"
S
cause "
):
return
Scause
(
m
,
name
[
len
(
"S
cause "
):])
return
m
[
name
]
t_
=
0.
Σ
t
=
0.
Σ
init
=
0
Σ
fini
=
0
Σ
ufini
=
0
# Σ
init where fini=ø but init is not ø
S
t
=
0.
S
init
=
0
S
fini
=
0
Sufini
=
0
# S
init where fini=ø but init is not ø
for
m
in
calc
.
_miter
():
τ
=
m
[
'X.δT'
]
tau
=
m
[
'X.δT'
]
vinit
=
vget
(
m
,
init
)
vfini
=
vget
(
m
,
fini
)
if
isNA
(
vinit
):
t_
+=
τ
t_
+=
tau
# ignore fini, even if it is not ø.
# TODO more correct approach: init⁺ for this period ∈ [fini,∞] and
# once we extrapolate init⁺ we should check if it lies in that
# interval and adjust if not. Then fini could be used as is.
else
:
Σ
t
+=
τ
Σ
init
+=
vinit
St
+=
tau
S
init
+=
vinit
if
isNA
(
vfini
):
Σ
ufini
+=
vinit
S
ufini
+=
vinit
else
:
Σ
fini
+=
vfini
S
fini
+=
vfini
if
Σ
init
==
0
or
Σ
t
==
0
:
if
Sinit
==
0
or
S
t
==
0
:
return
Interval
(
0
,
1
)
# full uncertainty
init_
=
t_
*
Σ
init
/
Σ
t
a
=
Σ
fini
/
(
Σ
init
+
init_
)
b
=
(
Σ
fini
+
init_
+
Σ
ufini
)
/
(
Σ
init
+
init_
)
init_
=
t_
*
Sinit
/
S
t
a
=
Sfini
/
(
S
init
+
init_
)
b
=
(
Sfini
+
init_
+
Sufini
)
/
(
S
init
+
init_
)
return
Interval
(
a
,
b
)
...
...
@@ -606,15 +606,15 @@ def _success_rate(calc, fini, init): # -> Interval in [0,1]
# 3GPP reference: TS 32.450 6.3.1 "E-UTRAN IP Throughput".
@
func
(
Calc
)
def
eutran_ip_throughput
(
calc
):
# -> IPThp[QCI][dl,ul]
qdl
Σ
v
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qdl
Σ
t
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qdl
Σ
te
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
Σ
v
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
Σ
t
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
Σ
te
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qdl
S
v
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qdl
S
t
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qdl
S
te
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
S
v
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
S
t
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
qul
S
te
=
np
.
zeros
(
nqci
,
dtype
=
np
.
float64
)
for
m
in
calc
.
_miter
():
τ
=
m
[
'X.δT'
]
tau
=
m
[
'X.δT'
]
for
qci
in
range
(
nqci
):
dl_vol
=
m
[
"DRB.IPVolDl.QCI"
]
[
qci
]
...
...
@@ -630,68 +630,68 @@ def eutran_ip_throughput(calc): # -> IPThp[QCI][dl,ul]
# plain 3GPP spec for now.
pass
else
:
qdl
Σ
v
[
qci
]
+=
dl_vol
qdl
Σ
t
[
qci
]
+=
dl_time
qdl
Σ
te
[
qci
]
+=
dl_time_err
qdl
S
v
[
qci
]
+=
dl_vol
qdl
S
t
[
qci
]
+=
dl_time
qdl
S
te
[
qci
]
+=
dl_time_err
if
isNA
(
ul_vol
)
or
isNA
(
ul_time
)
or
isNA
(
ul_time_err
):
# no uncertainty accounting - see ^^^
pass
else
:
qul
Σ
v
[
qci
]
+=
ul_vol
qul
Σ
t
[
qci
]
+=
ul_time
qul
Σ
te
[
qci
]
+=
ul_time_err
qul
S
v
[
qci
]
+=
ul_vol
qul
S
t
[
qci
]
+=
ul_time
qul
S
te
[
qci
]
+=
ul_time_err
thp
=
np
.
zeros
(
nqci
,
dtype
=
np
.
dtype
([
(
'dl'
,
Interval
.
_dtype
),
(
'ul'
,
Interval
.
_dtype
),
]))
for
qci
in
range
(
nqci
):
if
qdl
Σ
t
[
qci
]
>
0
:
thp
[
qci
][
'dl'
][
'lo'
]
=
qdl
Σ
v
[
qci
]
/
(
qdl
Σ
t
[
qci
]
+
qdl
Σ
te
[
qci
])
thp
[
qci
][
'dl'
][
'hi'
]
=
qdl
Σ
v
[
qci
]
/
(
qdl
Σ
t
[
qci
]
-
qdl
Σ
te
[
qci
])
if
qul
Σ
t
[
qci
]
>
0
:
thp
[
qci
][
'ul'
][
'lo'
]
=
qul
Σ
v
[
qci
]
/
(
qul
Σ
t
[
qci
]
+
qul
Σ
te
[
qci
])
thp
[
qci
][
'ul'
][
'hi'
]
=
qul
Σ
v
[
qci
]
/
(
qul
Σ
t
[
qci
]
-
qul
Σ
te
[
qci
])
if
qdl
S
t
[
qci
]
>
0
:
thp
[
qci
][
'dl'
][
'lo'
]
=
qdl
Sv
[
qci
]
/
(
qdlSt
[
qci
]
+
qdlS
te
[
qci
])
thp
[
qci
][
'dl'
][
'hi'
]
=
qdl
Sv
[
qci
]
/
(
qdlSt
[
qci
]
-
qdlS
te
[
qci
])
if
qul
S
t
[
qci
]
>
0
:
thp
[
qci
][
'ul'
][
'lo'
]
=
qul
Sv
[
qci
]
/
(
qulSt
[
qci
]
+
qulS
te
[
qci
])
thp
[
qci
][
'ul'
][
'hi'
]
=
qul
Sv
[
qci
]
/
(
qulSt
[
qci
]
-
qulS
te
[
qci
])
return
thp
# _miter iterates through [.
τ_lo, .τ
_hi) yielding Measurements.
# _miter iterates through [.
tau_lo, .tau
_hi) yielding Measurements.
#
# The measurements are yielded with consecutive timestamps. There is no gaps
# as NA Measurements are yielded for time holes in original MeasurementLog data.
@
func
(
Calc
)
def
_miter
(
calc
):
# -> iter(Measurement)
τ
=
calc
.
τ
_lo
tau
=
calc
.
tau
_lo
l
=
len
(
calc
.
_data
)
i
=
0
# current Measurement from data
while
i
<
l
:
m
=
calc
.
_data
[
i
]
m_
τ
lo
=
m
[
'X.Tstart'
]
m_
τ
hi
=
m_
τ
lo
+
m
[
'X.δT'
]
assert
m_
τ
lo
<
m_
τ
hi
m_
tau
lo
=
m
[
'X.Tstart'
]
m_
tauhi
=
m_tau
lo
+
m
[
'X.δT'
]
assert
m_
taulo
<
m_tau
hi
if
τ
<
m_
τ
lo
:
# <- M(ø)[
τ, m_τ
lo)
if
tau
<
m_tau
lo
:
# <- M(ø)[
tau, m_tau
lo)
h
=
Measurement
()
h
[
'X.Tstart'
]
=
τ
h
[
'X.δT'
]
=
m_
τ
lo
-
τ
h
[
'X.Tstart'
]
=
tau
h
[
'X.δT'
]
=
m_
taulo
-
tau
yield
h
# <- M from mlog
yield
m
τ
=
m_
τ
hi
tau
=
m_tau
hi
i
+=
1
assert
τ
<=
calc
.
τ
_hi
if
τ
<
calc
.
τ
_hi
:
# <- trailing M(ø)[
τ, τ
_hi)
assert
tau
<=
calc
.
tau
_hi
if
tau
<
calc
.
tau
_hi
:
# <- trailing M(ø)[
tau, tau
_hi)
h
=
Measurement
()
h
[
'X.Tstart'
]
=
τ
h
[
'X.δT'
]
=
calc
.
τ
_hi
-
τ
h
[
'X.Tstart'
]
=
tau
h
[
'X.δT'
]
=
calc
.
tau_hi
-
tau
yield
h
...
...
@@ -704,28 +704,28 @@ def __new__(cls, lo, hi):
return
i
#
Σ
qci performs summation over all qci for m[name_qci].
#
S
qci performs summation over all qci for m[name_qci].
#
# usage example:
#
#
Σ
qci(m, 'ERAB.EstabInitSuccNbr.QCI')
#
S
qci(m, 'ERAB.EstabInitSuccNbr.QCI')
#
# name_qci must have '.QCI' suffix.
def
Σ
qci
(
m
:
Measurement
,
name_qci
:
str
):
return
_
Σ
x
(
m
,
name_qci
,
_all_qci
)
def
S
qci
(
m
:
Measurement
,
name_qci
:
str
):
return
_
S
x
(
m
,
name_qci
,
_all_qci
)
#
Σ
cause, performs summation over all causes for m[name_cause].
#
S
cause, performs summation over all causes for m[name_cause].
#
# usage example:
#
#
Σ
cause(m, 'RRC.ConnEstabSucc.CAUSE')
#
S
cause(m, 'RRC.ConnEstabSucc.CAUSE')
#
# name_cause must have '.CAUSE' suffix.
def
Σ
cause
(
m
:
Measurement
,
name_cause
:
str
):
return
_
Σ
x
(
m
,
name_cause
,
_all_cause
)
def
S
cause
(
m
:
Measurement
,
name_cause
:
str
):
return
_
S
x
(
m
,
name_cause
,
_all_cause
)
# _
Σx serves Σqci and Σ
cause.
def
_
Σ
x
(
m
:
Measurement
,
name_x
:
str
,
_all_x
:
func
):
# _
Sx serves Sqci and S
cause.
def
_
S
x
(
m
:
Measurement
,
name_x
:
str
,
_all_x
:
func
):
name_sum
,
name_xv
=
_all_x
(
name_x
)
s
=
m
[
name_sum
]
if
not
isNA
(
s
):
...
...
kpi_test.py
View file @
b56b6ba0
...
...
@@ -20,7 +20,7 @@
from
__future__
import
print_function
,
division
,
absolute_import
from
xlte.kpi
import
Calc
,
MeasurementLog
,
Measurement
,
Interval
,
NA
,
isNA
,
Σ
qci
,
Σ
cause
,
nqci
from
xlte.kpi
import
Calc
,
MeasurementLog
,
Measurement
,
Interval
,
NA
,
isNA
,
Sqci
,
S
cause
,
nqci
import
numpy
as
np
from
pytest
import
raises
...
...
@@ -81,10 +81,10 @@ def test_Measurement():
# verify that time fields has enough precision
t2022
=
1670691601.8999548
# in 2022.Dec
t2118
=
4670691601.1234567
# in 2118.Jan
def
_
(
τ
):
m
[
'X.Tstart'
]
=
τ
τ
_
=
m
[
'X.Tstart'
]
assert
τ
_
==
τ
def
_
(
tau
):
m
[
'X.Tstart'
]
=
tau
tau
_
=
m
[
'X.Tstart'
]
assert
tau_
==
tau
_
(
t2022
)
_
(
t2118
)
...
...
@@ -166,15 +166,15 @@ def test_MeasurementLog():
assert
_
.
shape
==
(
0
,)
# verify (
τ_lo, τ
_hi) widening and overlapping with Measurements on Calc initialization.
# verify (
tau_lo, tau
_hi) widening and overlapping with Measurements on Calc initialization.
def
test_Calc_init
():
mlog
=
MeasurementLog
()
# _ asserts that Calc(mlog,
τ_lo,τ_hi) has .τ_lo/.τ
_hi as specified by
#
τ_wlo/τ
_whi, and ._data as specified by mokv.
def
_
(
τ
_lo
,
τ
_hi
,
τ
_wlo
,
τ
_whi
,
*
mokv
):
c
=
Calc
(
mlog
,
τ
_lo
,
τ
_hi
)
assert
(
c
.
τ
_lo
,
c
.
τ
_hi
)
==
(
τ
_wlo
,
τ
_whi
)
# _ asserts that Calc(mlog,
tau_lo,tau_hi) has .tau_lo/.tau
_hi as specified by
#
tau_wlo/tau
_whi, and ._data as specified by mokv.
def
_
(
tau_lo
,
tau_hi
,
tau_wlo
,
tau
_whi
,
*
mokv
):
c
=
Calc
(
mlog
,
tau_lo
,
tau
_hi
)
assert
(
c
.
tau_lo
,
c
.
tau_hi
)
==
(
tau_wlo
,
tau
_whi
)
mv
=
list
(
c
.
_data
[
i
]
for
i
in
range
(
len
(
c
.
_data
)))
assert
mv
==
list
(
mokv
)
...
...
@@ -223,18 +223,18 @@ def test_Calc_init():
def
test_Calc_miter
():
mlog
=
MeasurementLog
()
# _ asserts that Calc(mlog,
τ_lo,τ
_hi)._miter yields Measurement as specified by mokv.
def
_
(
τ
_lo
,
τ
_hi
,
*
mokv
):
c
=
Calc
(
mlog
,
τ
_lo
,
τ
_hi
)
# _ asserts that Calc(mlog,
tau_lo,tau
_hi)._miter yields Measurement as specified by mokv.
def
_
(
tau_lo
,
tau
_hi
,
*
mokv
):
c
=
Calc
(
mlog
,
tau_lo
,
tau
_hi
)
mv
=
list
(
c
.
_miter
())
assert
mv
==
list
(
mokv
)
# na returns Measurement with specified
τ_lo/τ
_hi and NA for all other data.
def
na
(
τ
_lo
,
τ
_hi
):
assert
τ
_lo
<=
τ
_hi
# na returns Measurement with specified
tau_lo/tau
_hi and NA for all other data.
def
na
(
tau_lo
,
tau
_hi
):
assert
tau_lo
<=
tau
_hi
m
=
Measurement
()
m
[
'X.Tstart'
]
=
τ
_lo
m
[
'X.δT'
]
=
τ
_hi
-
τ
_lo
m
[
'X.Tstart'
]
=
tau
_lo
m
[
'X.δT'
]
=
tau_hi
-
tau
_lo
return
m
# mlog(ø)
...
...
@@ -275,10 +275,10 @@ def test_Calc_success_rate():
fini
=
"S1SIG.ConnEstabSucc"
# M returns Measurement with specified time coverage and init/fini values.
def
M
(
τ
_lo
,
τ
_hi
,
vinit
=
None
,
vfini
=
None
):
def
M
(
tau_lo
,
tau
_hi
,
vinit
=
None
,
vfini
=
None
):
m
=
Measurement
()
m
[
'X.Tstart'
]
=
τ
_lo
m
[
'X.δT'
]
=
τ
_hi
-
τ
_lo
m
[
'X.Tstart'
]
=
tau
_lo
m
[
'X.δT'
]
=
tau_hi
-
tau
_lo
if
vinit
is
not
None
:
m
[
init
]
=
vinit
if
vfini
is
not
None
:
...
...
@@ -292,10 +292,10 @@ def test_Calc_success_rate():
for
m
in
mv
:
mlog
.
append
(
m
)
# _ asserts that Calc(mlog,
τ_lo,τ
_hi)._success_rate(fini, init) returns Interval(sok_lo, sok_hi).
def
_
(
τ
_lo
,
τ
_hi
,
sok_lo
,
sok_hi
):
# _ asserts that Calc(mlog,
tau_lo,tau
_hi)._success_rate(fini, init) returns Interval(sok_lo, sok_hi).
def
_
(
tau_lo
,
tau
_hi
,
sok_lo
,
sok_hi
):
sok
=
Interval
(
sok_lo
,
sok_hi
)
c
=
Calc
(
mlog
,
τ
_lo
,
τ
_hi
)
c
=
Calc
(
mlog
,
tau_lo
,
tau
_hi
)
s
=
c
.
_success_rate
(
fini
,
init
)
assert
type
(
s
)
is
Interval
eps
=
np
.
finfo
(
s
[
'lo'
].
dtype
).
eps
...
...
@@ -323,7 +323,7 @@ def test_Calc_success_rate():
# i₁=8
# f₁=4
# ────|──────|─────────────|──────────
# 10 t₁ 20 ←── t₂ ──→
τ
_hi
# 10 t₁ 20 ←── t₂ ──→
tau
_hi
#
# t with data: t₁
# t with no data: t₂
...
...
@@ -355,7 +355,7 @@ def test_Calc_success_rate():
# i₁=8 i₂=50
# f₁=4 f₂=50
# ────|──────|──────|───────|──────────────────|──────────
# 10 t₁ 20 ↑ 30 t₂ 40 ↑
τ
_hi
# 10 t₁ 20 ↑ 30 t₂ 40 ↑
tau
_hi
# │ │
# │ │
# `────────────────── t₃
...
...
@@ -387,18 +387,18 @@ def test_Calc_success_rate():
_
(
0
,
99
,
0.18808777429467083
,
0.9860675722744688
)
# t₃=79
#
Σ
qci
init
=
"
Σ
qci ERAB.EstabInitAttNbr.QCI"
fini
=
"
Σ
qci ERAB.EstabInitSuccNbr.QCI"
#
S
qci
init
=
"
S
qci ERAB.EstabInitAttNbr.QCI"
fini
=
"
S
qci ERAB.EstabInitSuccNbr.QCI"
m
=
M
(
10
,
20
)
m
[
'ERAB.EstabInitAttNbr.sum'
]
=
10
m
[
'ERAB.EstabInitSuccNbr.sum'
]
=
2
Mlog
(
m
)
_
(
10
,
20
,
1
/
5
,
1
/
5
)
#
Σ
cause
init
=
"
Σ
cause RRC.ConnEstabAtt.CAUSE"
fini
=
"
Σ
cause RRC.ConnEstabSucc.CAUSE"
#
S
cause
init
=
"
S
cause RRC.ConnEstabAtt.CAUSE"
fini
=
"
S
cause RRC.ConnEstabSucc.CAUSE"
m
=
M
(
10
,
20
)
m
[
'RRC.ConnEstabSucc.sum'
]
=
5
m
[
'RRC.ConnEstabAtt.sum'
]
=
10
...
...
@@ -496,42 +496,42 @@ def test_Calc_eutran_ip_throughput():
assert
thp
[
qci
][
'ul'
]
==
I
(
0
)
# verify
Σ
qci.
def
test_
Σ
qci
():
# verify
S
qci.
def
test_
S
qci
():
m
=
Measurement
()
x
=
'ERAB.EstabInitAttNbr'
def
Σ
():
return
Σ
qci
(
m
,
x
+
'.QCI'
)
def
S
():
return
S
qci
(
m
,
x
+
'.QCI'
)
assert
isNA
(
Σ
())
assert
isNA
(
S
())
m
[
x
+
'.sum'
]
=
123
assert
Σ
()
==
123
assert
S
()
==
123
m
[
x
+
'.17'
]
=
17
m
[
x
+
'.23'
]
=
23
m
[
x
+
'.255'
]
=
255
assert
Σ
()
==
123
# from .sum
assert
S
()
==
123
# from .sum
m
[
x
+
'.sum'
]
=
NA
(
m
[
x
+
'.sum'
].
dtype
)
assert
isNA
(
Σ
())
# from array, but NA values lead to sum being NA
assert
isNA
(
S
())
# from array, but NA values lead to sum being NA
v
=
m
[
x
+
'.QCI'
]
l
=
len
(
v
)
for
i
in
range
(
l
):
v
[
i
]
=
1
+
i
assert
Σ
()
==
1
*
l
+
(
l
-
1
)
*
l
/
2
assert
S
()
==
1
*
l
+
(
l
-
1
)
*
l
/
2
# verify
Σ
cause.
def
test_
Σ
cause
():
# verify
S
cause.
def
test_
S
cause
():
m
=
Measurement
()
x
=
'RRC.ConnEstabAtt'
def
Σ
():
return
Σ
cause
(
m
,
x
+
'.CAUSE'
)
def
S
():
return
S
cause
(
m
,
x
+
'.CAUSE'
)
assert
isNA
(
Σ
())
assert
isNA
(
S
())
m
[
x
+
'.sum'
]
=
123
assert
Σ
()
==
123
assert
S
()
==
123
# TODO sum over individual causes (when implemented)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment