Commit b1adc068 authored by Kirill Smelkov's avatar Kirill Smelkov

amari.drb: Teach Sampler to be multicell-aware

Since 2a016d48 (Draft support for E-UTRAN IP Throughput KPI) there was a
hardcoded limitation that x.drb_stats generation works with 1-cell
configurations only. However we do use multicell eNB configurations and
on such configurations `xamari xlog x.drb_stats` was failing on eNB
side with

    raise RuntimeError(("ue #%s belongs to %d cells;  "+
        "but only single-cell configurations are supported") % (ue_id, len(ju(['cells']))))

because an UE might be associated with multiple cells of one eNB
due to e.g. Carrier Aggregation.

Now, after we did preparatory amari.drb refactoring and taught BitSync
to be multicell-aware, we can finally remove the limitation and correct
the Sampler to handle UEs associated with multiple cells at the same
time.

The handling is mostly straightforward: for every frame the Sampler needs
to estimate time of active transmission and amount of transmitted bytes.
The transmitted amount is Σcell(tx_bytes) and we estimate transmission
time on different cells C₁ and C₂ as

    tx_time ∈ [max(t₁,t₂), min(t₁+t₂, δt/tti)]

In other words when transmission to/from UE on C₁ and C₂ is going in
parallel in one frame, the time it takes to transmit current part of UEs
data

    1) cannot be less than time spent on each cell
       (equals to the maximum if transmission on Ci was fully performed
        in the same subframes where transmission on Cj was active)

    2) cannot be more than the sum of time spent transmitting on each cell
       (equals to the sum if transmission on C₁ and C₂ was performed in
        distinct subframes of the frame), and

    3) cannot be more than the whole frame
       (t₁+t₂ could become more than that if there is some overlap in
        subframes where C₁ and C₂ transmissions were done)

The patch implements this logic when updating data flows of an UE.

Amari.drb now works for multicell configurations.

There are one-cell unit tests for Sampler already added in 2a016d48 and
now I manually verified that `xamari xlog x.drb_stats` works ok with
multicell eNB configurations.

Unittests for Sampler in multicell environment are left as TODO.
parent 80d82d4e
...@@ -277,30 +277,27 @@ def add(s, ue_stats, stats, init=False): ...@@ -277,30 +277,27 @@ def add(s, ue_stats, stats, init=False):
ue_id = ju['enb_ue_id'] # TODO 5G: -> ran_ue_id + qos_flow_list + sst? ue_id = ju['enb_ue_id'] # TODO 5G: -> ran_ue_id + qos_flow_list + sst?
ue_live.add(ue_id) ue_live.add(ue_id)
if len(ju['cells']) != 1:
raise RuntimeError(("ue #%s belongs to %d cells; "+
"but only single-cell configurations are supported") % (ue_id, len(ju(['cells']))))
cell = ju['cells'][0]
cell_id = cell['cell_id'] # int
scell = stats['cells'][str(cell_id)]
u = _Utx() u = _Utx()
u.qtx_bytes = {} # qci -> Σδerab_qci=qci u.qtx_bytes = {} # qci -> Σδerab_qci=qci
u.cutx = {} # cell -> _UCtx u.cutx = {} # cell -> _UCtx
for ucell in ju['cells']:
cell_id = ucell['cell_id'] # int
stats_cell = stats['cells'][str(cell_id)]
uc = _UCtx() uc = _UCtx()
assert cell_id not in u.cutx, u.cutx
u.cutx[cell_id] = uc u.cutx[cell_id] = uc
uc.tx = cell['%s_tx' % s.dir] # in transport blocks uc.tx = ucell['%s_tx' % s.dir] # in transport blocks
uc.retx = cell['%s_retx' % s.dir] # ----//---- uc.retx = ucell['%s_retx' % s.dir] # ----//----
uc.bitrate = cell['%s_bitrate' % s.dir] # bits/s uc.bitrate = ucell['%s_bitrate' % s.dir] # bits/s
assert uc.tx >= 0, uc.tx assert uc.tx >= 0, uc.tx
assert uc.retx >= 0, uc.retx assert uc.retx >= 0, uc.retx
assert uc.bitrate >= 0, uc.bitrate assert uc.bitrate >= 0, uc.bitrate
uc.rank = cell['ri'] if s.use_ri else 1 uc.rank = ucell['ri'] if s.use_ri else 1
uc.xl_use_avg = scell['%s_use_avg' % s.dir] uc.xl_use_avg = stats_cell['%s_use_avg' % s.dir]
uc.tx_bytes = None uc.tx_bytes = None
...@@ -339,11 +336,16 @@ def add(s, ue_stats, stats, init=False): ...@@ -339,11 +336,16 @@ def add(s, ue_stats, stats, init=False):
if 0 and \ if 0 and \
s.dir == 'dl' and ( \ s.dir == 'dl' and ( \
etx_bytes != 0 or \ etx_bytes != 0 or \
uc.tx != 0 or uc.retx != 0 or uc.bitrate != 0 \ any([(uc.tx != 0 or uc.retx != 0 or uc.bitrate != 0) for uc in u.cutx.values()]) \
) and qci==9: ) and qci==9:
sfnx = ((t // tti) / 10) % 1024 # = SFN.subframe sfnx = ((t // tti) / 10) % 1024 # = SFN.subframe
_debug('% 4.1f ue%s %s .%d: etx_total_bytes: %d +%5d tx: %2d retx: %d ri: %d bitrate: %d' % \ dtx = '% 4.1f ue%s %s .%d: etx_total_bytes: %d +%5d' % \
(sfnx, ue_id, s.dir, qci, etx_total_bytes, etx_bytes, uc.tx, uc.retx, uc.rank, uc.bitrate)) (sfnx, ue_id, s.dir, qci, etx_total_bytes, etx_bytes)
for cell_id in sorted(u.cutx):
uc = u.cutx[cell_id]
dtx += '| C%d: tx %2d retx %d ri %d bitrate %d' % \
(cell_id, uc.tx, uc.retx, uc.rank, uc.bitrate)
_debug(dtx)
# gc non-live erabs # gc non-live erabs
for erab_id in set(ue.erab_flows.keys()): for erab_id in set(ue.erab_flows.keys()):
...@@ -379,40 +381,47 @@ def add(s, ue_stats, stats, init=False): ...@@ -379,40 +381,47 @@ def add(s, ue_stats, stats, init=False):
@func(_UE) @func(_UE)
def _update_qci_flows(ue, bitnext, qci_samples): def _update_qci_flows(ue, bitnext, qci_samples):
for (δt, tx_bytes, u) in bitnext: for (δt, tx_bytes, u) in bitnext:
assert len(u.cutx) == 1
uc = _peek(u.cutx.values())
qflows_live = set() # of qci qci flows that get updated from current utx entry qflows_live = set() # of qci qci flows that get updated from current utx entry
# estimate time for current transmission # estimate time for current transmission
# normalize transport blocks to time in TTI units (if it is e.g. # first normalize transport blocks to time in TTI units (if it is e.g.
# 2x2 mimo, we have 2x more transport blocks). # 2x2 mimo, we have 2x more transport blocks) and then estimate tx time
# from transmission time on different cells C₁ and C₂ as
#
# tx_time ∈ [max(t₁,t₂), min(t₁+t₂, δt/tti)]
δt_tti = δt / tti δt_tti = δt / tti
tx = (uc.tx + uc.retx) / uc.rank # both transmission and retransmission take time tx_lo = 0
tx = min(tx, δt_tti) # protection (should not happen) tx_hi = 0
for uc in u.cutx.values():
# it might happen that even with correct bitsync we could end up with receiving tx=0 here. ctx = (uc.tx + uc.retx) / uc.rank # both transmission and retransmission take time
ctx = min(ctx, δt_tti) # protection (should not happen)
ctx_lo = ctx_hi = ctx
# it might happen that even with correct bitsync we could end up with receiving ctx=0 here.
# for example it happens if finish interrupts proper bitsync workflow e.g. as follows: # for example it happens if finish interrupts proper bitsync workflow e.g. as follows:
# #
# 1000 0 # 1000 0
# <-- finish # <-- finish
# 0 10 # 0 10
# #
# if we see #tx = 0 we say that it might be anything in between 1 and δt. # if we see ctx = 0 we say that it might be anything in between 1 and δt.
tx_lo = tx_hi = tx if ctx_lo == 0:
if tx == 0: ctx_hi = δt_tti
tx_hi = δt_tti ctx_lo = min(1, ctx_hi)
tx_lo = min(1, tx_hi)
# tx time on the cell is somewhere in [tx, δt_tti] # tx time on the cell is somewhere in [ctx, δt_tti]
if uc.xl_use_avg < 0.9: if uc.xl_use_avg < 0.9:
# not congested: it likely took the time to transmit ≈ tx # not congested: it likely took the time to transmit ≈ ctx
pass pass
else: else:
# potentially congested: we don't know how much congested it is and # potentially congested: we don't know how much congested it is and
# which QCIs are affected more and which less # which QCIs are affected more and which less
# -> all we can say tx_time is only somewhere in between limits # -> all we can say tx_time is only somewhere in between limits
tx_hi = δt_tti ctx_hi = δt_tti
tx_lo = max(tx_lo, ctx_lo)
tx_hi += ctx_hi
tx_hi = min(tx_hi, δt_tti)
# share/distribute tx time over all QCIs. # share/distribute tx time over all QCIs.
for qci, tx_bytes_qci in u.qtx_bytes.items(): for qci, tx_bytes_qci in u.qtx_bytes.items():
......
...@@ -164,6 +164,8 @@ def UCtx(tx, bitrate, rank, xl_use_avg): ...@@ -164,6 +164,8 @@ def UCtx(tx, bitrate, rank, xl_use_avg):
# -------- tests -------- # -------- tests --------
# TODO verify Sampler/multicell.
# test_Sampler1 verifies Sampler on single erab/qci flows. # test_Sampler1 verifies Sampler on single erab/qci flows.
def test_Sampler1(): def test_Sampler1():
# _ constructs tSampler, feeds tx stats into it and returns yielded Samples. # _ constructs tSampler, feeds tx stats into it and returns yielded Samples.
......
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