Commit 26a82c6e authored by Kirill Smelkov's avatar Kirill Smelkov

amari.drb: Split _Utx into global and per-cell parts

Continue preparatory steps to support multicell configurations and for
that split the class that tracks UE transmission state into global part
(_Utx) and per-cell parts (_UCtx).

Everywhere else in the code we still assert that the number of cells an UE
attached to is 1, so no support for multicell yet - only preparatory
non-functional changes currently.
parent bd57acbb
...@@ -224,6 +224,11 @@ def add(s, ue_stats, stats): # -> dl/ul samples ; dl/ul = {} qci -> []Sample ...@@ -224,6 +224,11 @@ def add(s, ue_stats, stats): # -> dl/ul samples ; dl/ul = {} qci -> []Sample
class _Utx: # UE transmission state class _Utx: # UE transmission state
__slots__ = ( __slots__ = (
'qtx_bytes', 'qtx_bytes',
'cutx', # {} cell -> _UCtx
)
class _UCtx: # UE transmission state on particular cell
__slots__ = (
'tx', 'tx',
'retx', 'retx',
'rank', 'rank',
...@@ -254,15 +259,19 @@ def add(s, ue_stats, stats, init=False): ...@@ -254,15 +259,19 @@ def add(s, ue_stats, stats, init=False):
scell = stats['cells'][str(cell_id)] scell = stats['cells'][str(cell_id)]
u = _Utx() u = _Utx()
u.qtx_bytes = {} # qci -> Σδerab_qci=qci
u.cutx = {} # cell -> _UCtx
u.tx = cell['%s_tx' % s.dir] # in transport blocks uc = _UCtx()
u.retx = cell['%s_retx' % s.dir] # ----//---- u.cutx[cell_id] = uc
assert u.tx >= 0, u.tx
assert u.retx >= 0, u.retx
u.qtx_bytes = {} # qci -> Σδerab_qci=qci uc.tx = cell['%s_tx' % s.dir] # in transport blocks
u.rank = cell['ri'] if s.use_ri else 1 uc.retx = cell['%s_retx' % s.dir] # ----//----
u.xl_use_avg = scell['%s_use_avg' % s.dir] assert uc.tx >= 0, uc.tx
assert uc.retx >= 0, uc.retx
uc.rank = cell['ri'] if s.use_ri else 1
uc.xl_use_avg = scell['%s_use_avg' % s.dir]
ue = s.ues.get(ue_id) ue = s.ues.get(ue_id)
if ue is None: if ue is None:
...@@ -296,10 +305,10 @@ def add(s, ue_stats, stats, init=False): ...@@ -296,10 +305,10 @@ def add(s, ue_stats, stats, init=False):
u.qtx_bytes[qci] = u.qtx_bytes.get(qci,0) + etx_bytes u.qtx_bytes[qci] = u.qtx_bytes.get(qci,0) + etx_bytes
# debug # debug
if 0 and s.dir == 'dl' and (etx_bytes != 0 or u.tx != 0 or u.retx != 0) and qci==9: if 0 and s.dir == 'dl' and (etx_bytes != 0 or uc.tx != 0 or uc.retx != 0) 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' % \ _debug('% 4.1f ue%s %s .%d: etx_total_bytes: %d +%5d tx: %2d retx: %d ri: %d bitrate: %d' % \
(sfnx, ue_id, s.dir, qci, etx_total_bytes, etx_bytes, u.tx, u.retx, u.rank, cell['%s_bitrate' % s.dir])) (sfnx, ue_id, s.dir, qci, etx_total_bytes, etx_bytes, uc.tx, uc.retx, uc.rank, cell['%s_bitrate' % s.dir]))
# 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()):
...@@ -335,13 +344,15 @@ def add(s, ue_stats, stats, init=False): ...@@ -335,13 +344,15 @@ 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. # 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).
δt_tti = δt / tti δt_tti = δt / tti
tx = (u.tx + u.retx) / u.rank # both transmission and retransmission take time tx = (uc.tx + uc.retx) / uc.rank # both transmission and retransmission take time
tx = min(tx, δt_tti) # protection (should not happen) tx = min(tx, δt_tti) # protection (should not happen)
# it might happen that even with correct bitsync we could end up with receiving tx=0 here. # it might happen that even with correct bitsync we could end up with receiving tx=0 here.
...@@ -358,7 +369,7 @@ def _update_qci_flows(ue, bitnext, qci_samples): ...@@ -358,7 +369,7 @@ def _update_qci_flows(ue, bitnext, qci_samples):
tx_lo = min(1, tx_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 [tx, δt_tti]
if u.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 ≈ tx
pass pass
else: else:
...@@ -509,8 +520,10 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u') ...@@ -509,8 +520,10 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u')
s.txq.append((δt, tx_bytes, u)) s.txq.append((δt, tx_bytes, u))
# move all time to .tx # move all time to .tx
u.tx += u.retx assert len(u.cutx) == 1
u.retx = 0 uc = _peek(u.cutx.values())
uc.tx += uc.retx
uc.retx = 0
# XXX for simplicity we currently handle sync in between only current and # 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 # next frames. That is enough to support FDD. TODO handle next-next case to support TDD
...@@ -544,8 +557,8 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u') ...@@ -544,8 +557,8 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u')
assert s.i_txq <= i < s.i_txq + len(s.txq) assert s.i_txq <= i < s.i_txq + len(s.txq)
i -= s.i_txq i -= s.i_txq
δt1, b1, u1 = s.txq[i]; t1 = u1.tx δt1, b1, u1 = s.txq[i]; uc1 = _peek(u1.cutx.values()); t1 = uc1.tx
δt2, b2, u2 = s.txq[i+1]; t2 = u2.tx δt2, b2, u2 = s.txq[i+1]; uc2 = _peek(u2.cutx.values()); t2 = uc2.tx
if b1 != 0: if b1 != 0:
t22 = b2*t1/b1 t22 = b2*t1/b1
else: else:
...@@ -558,8 +571,8 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u') ...@@ -558,8 +571,8 @@ def next(s, δt, tx_bytes, u: _Utx): # -> [](δt', tx_bytes', u')
assert t1 >= 0, t1 assert t1 >= 0, t1
assert t2 >= 0, t2 assert t2 >= 0, t2
u1.tx = t1 uc1.tx = t1
u2.tx = t2 uc2.tx = t2
s.txq[i] = (δt1, b1, u1) s.txq[i] = (δt1, b1, u1)
s.txq[i+1] = (δt2, b2, u2) s.txq[i+1] = (δt2, b2, u2)
#print(' < lshift ', s.txq) #print(' < lshift ', s.txq)
...@@ -619,13 +632,13 @@ def _rebalance(s, l): ...@@ -619,13 +632,13 @@ def _rebalance(s, l):
assert l <= 3 assert l <= 3
Σb = sum(_[1] for _ in s.txq[:l]) Σb = sum(_[1] for _ in s.txq[:l])
Σt = sum(_[2].tx for _ in s.txq[:l]) Σt = sum(_peek(_[2].cutx.values()).tx for _ in s.txq[:l])
if Σb != 0: if Σb != 0:
for i in range(l): for i in range(l):
δt_i, b_i, u_i = s.txq[i]; t_i = u_i.tx δt_i, b_i, u_i = s.txq[i]; uc_i = _peek(u_i.cutx.values()); t_i = uc_i.tx
t_i = b_i * Σt / Σb t_i = b_i * Σt / Σb
assert t_i >= 0, t_i assert t_i >= 0, t_i
u_i.tx = t_i uc_i.tx = t_i
s.txq[i] = (δt_i, b_i, u_i) s.txq[i] = (δt_i, b_i, u_i)
#print(' < rebalance', s.txq[:l]) #print(' < rebalance', s.txq[:l])
...@@ -1003,3 +1016,9 @@ __debug = False ...@@ -1003,3 +1016,9 @@ __debug = False
def _debug(*argv): def _debug(*argv):
if __debug: if __debug:
print(*argv, file=sys.stderr) print(*argv, file=sys.stderr)
# _peek peeks first item from a sequence.
# it is handy to use e.g. as _peek(dict.values()).
def _peek(seq):
return next(iter(seq))
...@@ -20,12 +20,12 @@ ...@@ -20,12 +20,12 @@
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from xlte.amari.drb import _Sampler, Sample, _BitSync, _Utx, tti, _IncStats from xlte.amari.drb import _Sampler, Sample, _BitSync, _Utx, _UCtx, tti, _IncStats
import numpy as np import numpy as np
from golang import func from golang import func
# tSampler, UE, Etx and S provide infrastructure for testing _Sampler: # tSampler, UE, Etx, S and UCtx provide infrastructure for testing _Sampler:
# Etx represents transmission on erab with qci of tx_bytes. # Etx represents transmission on erab with qci of tx_bytes.
class Etx: class Etx:
...@@ -148,6 +148,16 @@ def S(tx_bytes, tx_time_tti): ...@@ -148,6 +148,16 @@ def S(tx_bytes, tx_time_tti):
return s return s
# UCtx is shortcut to create _UCtx.
def UCtx(tx, rank, xl_use_avg):
uc = _UCtx()
uc.tx = tx
uc.retx = 0
uc.rank = rank
uc.xl_use_avg = xl_use_avg
return uc
# -------- tests -------- # -------- tests --------
# test_Sampler1 verifies Sampler on single erab/qci flows. # test_Sampler1 verifies Sampler on single erab/qci flows.
...@@ -365,23 +375,26 @@ def test_BitSync(): ...@@ -365,23 +375,26 @@ def test_BitSync():
for x, (tx_bytes, tx) in enumerate(txv_in): for x, (tx_bytes, tx) in enumerate(txv_in):
u = _Utx() u = _Utx()
u.qtx_bytes = None # bitsync itself does not use .qtx_bytes u.qtx_bytes = None # bitsync itself does not use .qtx_bytes
u.tx = tx u.cutx = {1: UCtx(tx, 1, 0.1)}
u.retx = 0
u.rank = 1
u.xl_use_avg = 0.1
u.qtx_bytes = x # XXX hack - see ^^^ u.qtx_bytes = x # XXX hack - see ^^^
_ = bitsync.next(10*tti, tx_bytes, u) _ = bitsync.next(10*tti, tx_bytes, u)
for (δt, tx_bytes, u_) in _: for (δt, tx_bytes, u_) in _:
assert δt == 10*tti assert δt == 10*tti
assert u_.retx == 0 assert len(u_.cutx) == 1
txv_out.append((tx_bytes, u_.tx)) assert list(u_.cutx.keys()) == [1]
uc_ = u_.cutx[1]
assert uc_.retx == 0
txv_out.append((tx_bytes, uc_.tx))
xv_out .append(u_.qtx_bytes) xv_out .append(u_.qtx_bytes)
_ = bitsync.finish() _ = bitsync.finish()
for (δt, tx_bytes, u_) in _: for (δt, tx_bytes, u_) in _:
assert δt == 10*tti assert δt == 10*tti
assert u_.retx == 0 assert len(u_.cutx) == 1
txv_out.append((tx_bytes, u_.tx)) assert list(u_.cutx.keys()) == [1]
uc_ = u_.cutx[1]
assert uc_.retx == 0
txv_out.append((tx_bytes, uc_.tx))
xv_out .append(u_.qtx_bytes) xv_out .append(u_.qtx_bytes)
xv_out = ''.join(chr(ord('a')+_) for _ in xv_out) xv_out = ''.join(chr(ord('a')+_) for _ in xv_out)
......
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