kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40058?usp=email )
Change subject: trx_toolkit/data_if: No need to explicitly cast bytes to bytearray when doing RxMsg.parse_msg
......................................................................
trx_toolkit/data_if: No need to explicitly cast bytes to bytearray when doing RxMsg.parse_msg
I already did similar change for TxMsg.parse_msg in 06456f11
(trx_toolkit/*: Try to avoid copying burst data where possible) but
missed to do the symmetrical case for RxMsg.
-> Do that.
Change-Id: I54b8623a99b24da4ae1c4d379e9f45871f931554
---
M src/target/trx_toolkit/data_if.pyx
1 file changed, 1 insertion(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/58/40058/1
diff --git a/src/target/trx_toolkit/data_if.pyx b/src/target/trx_toolkit/data_if.pyx
index ca4d82d..14b8808 100644
--- a/src/target/trx_toolkit/data_if.pyx
+++ b/src/target/trx_toolkit/data_if.pyx
@@ -87,7 +87,7 @@
# Attempt to parse a TRXD Rx message
try:
msg = RxMsg()
- msg.parse_msg(bytearray(data))
+ msg.parse_msg(data)
except:
log.error("Failed to parse a TRXD Rx message "
"from R:%s" % self.desc_remote())
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40058?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I54b8623a99b24da4ae1c4d379e9f45871f931554
Gerrit-Change-Number: 40058
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40059?usp=email )
Change subject: trx_toolkit/udp_link,data_if: Optimize socket receive
......................................................................
trx_toolkit/udp_link,data_if: Optimize socket receive
As can be seen from http://navytux.spb.ru/~kirr/osmo/fake_trx/pyx-base.html (recv_raw_data)
the system is spending almost 2x time in wrapping py-overhead compared
to actually doing recv syscall! As fake_trx invokes recv a lot of times
it makes sense to cut that overhead.
-> Do that:
- add C-level UDPLink.recv which invokes recv syscall directly without
doing any gil release/acquire and which constructs destination
bytearray directly via py CAPI.
- invoke that UDPLink via C and also invoke DATAInterface.recv_raw_data
also via C because the only users of those functions are in
DATAInterface.
- we can be sure that returning bytearray instead of bytes is ok because
those bytearrays are passed to Msg.parse_msg and it accepts bytearrays
just fine. Using bytearrays instead of bytes will be convenient in the
follow-up patches.
Change-Id: I7e6c10071c31be5947f7c47d0ccbeee90dcb365b
---
M src/target/trx_toolkit/data_if.pxd
M src/target/trx_toolkit/data_if.pyx
M src/target/trx_toolkit/udp_link.pxd
M src/target/trx_toolkit/udp_link.pyx
A src/target/trx_toolkit/xpy.pxd
5 files changed, 24 insertions(+), 3 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/59/40059/1
diff --git a/src/target/trx_toolkit/data_if.pxd b/src/target/trx_toolkit/data_if.pxd
index 03a11c5..564a6cd 100644
--- a/src/target/trx_toolkit/data_if.pxd
+++ b/src/target/trx_toolkit/data_if.pxd
@@ -4,3 +4,5 @@
cdef class DATAInterface(UDPLink):
cdef readonly int _hdr_ver
+
+ cdef recv_raw_data(self)
diff --git a/src/target/trx_toolkit/data_if.pyx b/src/target/trx_toolkit/data_if.pyx
index 14b8808..d5e4910 100644
--- a/src/target/trx_toolkit/data_if.pyx
+++ b/src/target/trx_toolkit/data_if.pyx
@@ -56,9 +56,8 @@
msg.ver, self._hdr_ver))
return False
- def recv_raw_data(self):
- data, _ = self.sock.recvfrom(512)
- return data
+ cdef recv_raw_data(self):
+ return self.recv(512)
def recv_tx_msg(self):
# Read raw data from socket
diff --git a/src/target/trx_toolkit/udp_link.pxd b/src/target/trx_toolkit/udp_link.pxd
index 8698cab..ba145ec 100644
--- a/src/target/trx_toolkit/udp_link.pxd
+++ b/src/target/trx_toolkit/udp_link.pxd
@@ -18,6 +18,7 @@
cdef sockaddr_in remote_addr
cdef _send(self, object data) # bytes|bytearray|str
+ cdef bytearray recv(self, Py_ssize_t bufsize)
cdef _raise_oserr()
diff --git a/src/target/trx_toolkit/udp_link.pyx b/src/target/trx_toolkit/udp_link.pyx
index c54845b..696f966 100644
--- a/src/target/trx_toolkit/udp_link.pyx
+++ b/src/target/trx_toolkit/udp_link.pyx
@@ -22,6 +22,7 @@
from cpython cimport PyBytes_AS_STRING, PyBytes_GET_SIZE, PyUnicode_AsUTF8AndSize
from cpython.bytearray cimport PyByteArray_FromStringAndSize, PyByteArray_AS_STRING, PyByteArray_GET_SIZE
+from xpy cimport PyByteArray_Resize
from libc.errno cimport errno
from libc.string cimport strerror
@@ -33,6 +34,7 @@
cdef extern from "<sys/socket.h>":
ssize_t sendto(int fd, const void *buf, size_t len, int flags, const sockaddr *dst_addr, int addrlen)
+ ssize_t recv(int fd, void *buf, size_t len, int flags)
cdef class UDPLink:
@@ -95,5 +97,18 @@
self.sock.sendto(data, remote)
+ cdef bytearray recv(self, Py_ssize_t bufsize):
+ buf = PyByteArray_FromStringAndSize(NULL, bufsize)
+
+ # NOTE we do not release/reacquire gil to save us from gil ping-pong performance penalty
+ # we can do that because the socket is non-blocking
+ n = recv(self.sock_fd, PyByteArray_AS_STRING(buf), bufsize, 0)
+ if n == -1:
+ _raise_oserr()
+ if n != bufsize:
+ PyByteArray_Resize(buf, n)
+ return buf
+
+
cdef _raise_oserr():
raise OSError(errno, strerror(errno))
diff --git a/src/target/trx_toolkit/xpy.pxd b/src/target/trx_toolkit/xpy.pxd
new file mode 100644
index 0000000..3993dc7
--- /dev/null
+++ b/src/target/trx_toolkit/xpy.pxd
@@ -0,0 +1,4 @@
+cdef extern from "Python.h":
+ # PyByteArray_Resize is wrongly declared in cpython.bytearray
+ # https://github.com/cython/cython/pull/6787
+ int PyByteArray_Resize(bytearray buf, Py_ssize_t len) except -1
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40059?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I7e6c10071c31be5947f7c47d0ccbeee90dcb365b
Gerrit-Change-Number: 40059
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40060?usp=email )
Change subject: trx_toolkit/_fake_trx: Switch Runner to cdef class
......................................................................
trx_toolkit/_fake_trx: Switch Runner to cdef class
- Put fields into the object struct; fields are now accessed directly
via that C-level struct instead of via __dict__ lookup. Cannot
properly type them though yet. This will be fixed in follow-up patches.
- there are no pyx-level users of Runner so no need to change import ->
cimport anywhere.
Change-Id: I5c65c77f95de596b1aca62beaeb23d3cf5678996
---
M src/target/trx_toolkit/_fake_trx.pyx
1 file changed, 5 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/60/40060/1
diff --git a/src/target/trx_toolkit/_fake_trx.pyx b/src/target/trx_toolkit/_fake_trx.pyx
index 92573f8..c17c16d 100644
--- a/src/target/trx_toolkit/_fake_trx.pyx
+++ b/src/target/trx_toolkit/_fake_trx.pyx
@@ -373,7 +373,11 @@
# Runner organizes execution of several FakeTRX instances with common clock.
-class Runner:
+cdef class Runner:
+ cdef object clck_gen # CLCKGen
+ cdef object burst_fwd # BurstForwarder
+ cdef list[Transceiver] trx_list
+
def __init__(self, clck_gen, trx_list):
self.clck_gen = clck_gen
self.trx_list = trx_list.trx_list
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40060?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I5c65c77f95de596b1aca62beaeb23d3cf5678996
Gerrit-Change-Number: 40060
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40050?usp=email )
Change subject: trx_toolkit/fake_trx: Factor functionality to run IO loop out of Application
......................................................................
trx_toolkit/fake_trx: Factor functionality to run IO loop out of Application
-> into Runner.
We will need to move this functionality to pyx (Cython) domain soon,
while Application will remain at py (Python) level.
Preparatory refactoring step for that.
Change-Id: I28613087f48c327763537b53b97cad1e67d91f1a
---
M src/target/trx_toolkit/fake_trx.py
1 file changed, 47 insertions(+), 35 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/50/40050/1
diff --git a/src/target/trx_toolkit/fake_trx.py b/src/target/trx_toolkit/fake_trx.py
index 12d5b77..9bf9efd 100755
--- a/src/target/trx_toolkit/fake_trx.py
+++ b/src/target/trx_toolkit/fake_trx.py
@@ -382,6 +382,51 @@
# Unhandled command
return None
+
+# Runner organizes execution of several FakeTRX instances with common clock.
+class Runner:
+ def __init__(self, clck_gen, trx_list):
+ self.clck_gen = clck_gen
+ self.trx_list = trx_list.trx_list
+
+ # This method will be called on each TDMA frame
+ self.clck_gen.clck_handler = self.clck_handler
+
+ self.burst_fwd = BurstForwarder(trx_list.trx_list)
+
+ # This method will be called by the clock generator
+ def clck_handler(self, fn):
+ # We assume that this list is immutable at run-time
+ for trx in self.trx_list:
+ trx.clck_tick(self.burst_fwd, fn)
+
+ # loops runs IO loop on specified CLCKGen and TRXes forever.
+ def loop(self):
+ sock_list = [self.clck_gen._timerfd]
+ for trx in self.trx_list:
+ sock_list.append(trx.ctrl_if.sock)
+ sock_list.append(trx.data_if.sock)
+
+ # Enter main loop
+ while True:
+ # Wait until we get any data on any socket
+ r_event, _, _ = select.select(sock_list, [], [])
+
+ # clock is priority
+ if self.clck_gen._timerfd in r_event:
+ self.clck_gen.tick()
+
+ # Iterate over all transceivers
+ for trx in self.trx_list:
+ # DATA interface
+ if trx.data_if.sock in r_event:
+ trx.recv_data_msg()
+
+ # CTRL interface
+ if trx.ctrl_if.sock in r_event:
+ trx.ctrl_if.handle_rx()
+
+
class Application(ApplicationBase):
def __init__(self):
self.app_print_copyright(APP_CR_HOLDERS)
@@ -398,8 +443,6 @@
# Init shared clock generator
self.clck_gen = CLCKGen([])
- # This method will be called on each TDMA frame
- self.clck_gen.clck_handler = self.clck_handler
# Power measurement emulation
# Noise: -120 .. -105
@@ -419,9 +462,6 @@
(name, addr, port, idx) = trx_def
self.append_child_trx(addr, port, name = name, child_idx = idx)
- # Burst forwarding between transceivers
- self.burst_fwd = BurstForwarder(self.trx_list.trx_list)
-
log.info("Init complete")
def append_trx(self, remote_addr, base_port, **kwargs):
@@ -458,36 +498,8 @@
except OSError:
log.error("Failed to set real time process scheduler to SCHED_RR, priority %u" % (self.argv.sched_rr_prio))
- # Compose list of to be monitored sockets
- sock_list = [self.clck_gen._timerfd]
- for trx in self.trx_list.trx_list:
- sock_list.append(trx.ctrl_if.sock)
- sock_list.append(trx.data_if.sock)
-
- # Enter main loop
- while True:
- # Wait until we get any data on any socket
- r_event, _, _ = select.select(sock_list, [], [])
-
- # clock is priority
- if self.clck_gen._timerfd in r_event:
- self.clck_gen.tick()
-
- # Iterate over all transceivers
- for trx in self.trx_list.trx_list:
- # DATA interface
- if trx.data_if.sock in r_event:
- trx.recv_data_msg()
-
- # CTRL interface
- if trx.ctrl_if.sock in r_event:
- trx.ctrl_if.handle_rx()
-
- # This method will be called by the clock generator
- def clck_handler(self, fn):
- # We assume that this list is immutable at run-time
- for trx in self.trx_list.trx_list:
- trx.clck_tick(self.burst_fwd, fn)
+ runner = Runner(self.clck_gen, self.trx_list)
+ runner.loop()
def shutdown(self):
log.info("Shutting down...")
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40050?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I28613087f48c327763537b53b97cad1e67d91f1a
Gerrit-Change-Number: 40050
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40051?usp=email )
Change subject: trx_toolkit/fake_trx: Split it into fake_trx and _fake_trx modules
......................................................................
trx_toolkit/fake_trx: Split it into fake_trx and _fake_trx modules
fake_trx will remain at Python while _fake_trx will be later converted
to Cython for speed to avoid py-related overhead. This patch does only
plain code movement as a preparatory step for that.
Change-Id: Iadffd49de8197564e57bfd9cb660b1d11136ffd4
---
A src/target/trx_toolkit/_fake_trx.py
M src/target/trx_toolkit/fake_trx.py
2 files changed, 415 insertions(+), 395 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/51/40051/1
diff --git a/src/target/trx_toolkit/_fake_trx.py b/src/target/trx_toolkit/_fake_trx.py
new file mode 100644
index 0000000..87a352b
--- /dev/null
+++ b/src/target/trx_toolkit/_fake_trx.py
@@ -0,0 +1,414 @@
+# TRX Toolkit
+# Virtual Um-interface (fake transceiver)
+#
+# (C) 2017-2019 by Vadim Yanitskiy <axilirator(a)gmail.com>
+#
+# All Rights Reserved
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import logging as log
+import random
+import select
+
+from burst_fwd import BurstForwarder
+from transceiver import Transceiver
+from data_msg import Modulation
+from gsm_shared import *
+
+
+class FakeTRX(Transceiver):
+ """ Fake transceiver with RF path (burst loss, RSSI, TA, ToA) simulation.
+
+ == ToA / RSSI measurement simulation
+
+ Since this is a virtual environment, we can simulate different
+ parameters of the physical RF interface:
+
+ - ToA (Timing of Arrival) - measured difference between expected
+ and actual time of burst arrival in units of 1/256 of GSM symbol
+ periods. A pair of both base and threshold values defines a range
+ of ToA value randomization:
+
+ from (toa256_base - toa256_rand_threshold)
+ to (toa256_base + toa256_rand_threshold).
+
+ - RSSI (Received Signal Strength Indication) - measured "power" of
+ the signal (per burst) in dBm. A pair of both base and threshold
+ values defines a range of RSSI value randomization:
+
+ from (rssi_base - rssi_rand_threshold)
+ to (rssi_base + rssi_rand_threshold).
+
+ - C/I (Carrier-to-Interference ratio) - value in cB (centiBels),
+ computed from the training sequence of each received burst, by
+ comparing the "ideal" training sequence with the actual one.
+ A pair of both base and threshold values defines a range of
+ C/I randomization:
+
+ from (ci_base - ci_rand_threshold)
+ to (ci_base + ci_rand_threshold).
+
+ Please note that the randomization is optional and disabled by default.
+
+ == Timing Advance handling
+
+ The BTS is using ToA measurements for UL bursts in order to calculate
+ Timing Advance value, that is then indicated to a MS, which in its turn
+ shall apply this value to the transmitted signal in order to compensate
+ the delay. Basically, every burst is transmitted in advance defined by
+ the indicated Timing Advance value. The valid range is 0..63, where
+ each unit means one GSM symbol advance. The actual Timing Advance value
+ is set using SETTA control command from MS. By default, it's set to 0.
+
+ == Path loss simulation
+
+ === Burst dropping
+
+ In some cases, e.g. due to a weak signal or high interference, a burst
+ can be lost, i.e. not detected by the receiver. This can also be
+ simulated using FAKE_DROP command on the control interface:
+
+ - burst_drop_amount - the amount of DL/UL bursts
+ to be dropped (i.e. not forwarded towards the MS/BTS),
+
+ - burst_drop_period - drop a DL/UL burst if its (fn % period) == 0.
+
+ == Configuration
+
+ All simulation parameters mentioned above can be changed at runtime
+ using the commands with prefix 'FAKE_' on the control interface.
+ All of them are handled by our custom CTRL command handler.
+
+ """
+
+ NOMINAL_TX_POWER_DEFAULT = 50 # dBm
+ TX_ATT_DEFAULT = 0 # dB
+ PATH_LOSS_DEFAULT = 110 # dB
+
+ TOA256_BASE_DEFAULT = 0
+ CI_BASE_DEFAULT = 90
+
+ # Default values for NOPE / IDLE indications
+ TOA256_NOISE_DEFAULT = 0
+ RSSI_NOISE_DEFAULT = -110
+ CI_NOISE_DEFAULT = -30
+
+ def __init__(self, *trx_args, **trx_kwargs):
+ Transceiver.__init__(self, *trx_args, **trx_kwargs)
+
+ # fake RSSI is disabled by default, only enabled through TRXC FAKE_RSSI.
+ # When disabled, RSSI is calculated based on Tx power and Rx path loss
+ self.fake_rssi_enabled = False
+
+ self.rf_muted = False
+
+ # Actual ToA, RSSI, C/I, TA values
+ self.tx_power_base = self.NOMINAL_TX_POWER_DEFAULT
+ self.tx_att_base = self.TX_ATT_DEFAULT
+ self.toa256_base = self.TOA256_BASE_DEFAULT
+ self.rssi_base = self.NOMINAL_TX_POWER_DEFAULT - self.TX_ATT_DEFAULT - self.PATH_LOSS_DEFAULT
+ self.ci_base = self.CI_BASE_DEFAULT
+ self.ta = 0
+
+ # ToA, RSSI, C/I randomization thresholds
+ self.toa256_rand_threshold = 0
+ self.rssi_rand_threshold = 0
+ self.ci_rand_threshold = 0
+
+ # Path loss simulation (burst dropping)
+ self.burst_drop_amount = 0
+ self.burst_drop_period = 1
+
+ @property
+ def toa256(self):
+ # Check if randomization is required
+ if self.toa256_rand_threshold == 0:
+ return self.toa256_base
+
+ # Generate a random ToA value in required range
+ toa256_min = self.toa256_base - self.toa256_rand_threshold
+ toa256_max = self.toa256_base + self.toa256_rand_threshold
+ return random.randint(toa256_min, toa256_max)
+
+ @property
+ def rssi(self):
+ # Check if randomization is required
+ if self.rssi_rand_threshold == 0:
+ return self.rssi_base
+
+ # Generate a random RSSI value in required range
+ rssi_min = self.rssi_base - self.rssi_rand_threshold
+ rssi_max = self.rssi_base + self.rssi_rand_threshold
+ return random.randint(rssi_min, rssi_max)
+
+ @property
+ def tx_power(self):
+ return self.tx_power_base - self.tx_att_base
+
+ @property
+ def ci(self):
+ # Check if randomization is required
+ if self.ci_rand_threshold == 0:
+ return self.ci_base
+
+ # Generate a random C/I value in required range
+ ci_min = self.ci_base - self.ci_rand_threshold
+ ci_max = self.ci_base + self.ci_rand_threshold
+ return random.randint(ci_min, ci_max)
+
+ # Path loss simulation: burst dropping
+ # Returns: True - drop, False - keep
+ def sim_burst_drop(self, msg):
+ # Check if dropping is required
+ if self.burst_drop_amount == 0:
+ return False
+
+ if msg.fn % self.burst_drop_period == 0:
+ log.info("(%s) Simulation: dropping burst (fn=%u %% %u == 0)"
+ % (self, msg.fn, self.burst_drop_period))
+ self.burst_drop_amount -= 1
+ return True
+
+ return False
+
+ def _handle_data_msg_v1(self, src_msg, msg):
+ # C/I (Carrier-to-Interference ratio)
+ msg.ci = self.ci
+
+ # Pick modulation type by burst length
+ bl = len(src_msg.burst)
+ msg.mod_type = Modulation.pick_by_bl(bl)
+
+ # Pick TSC (Training Sequence Code) and TSC set
+ if msg.mod_type is Modulation.ModGMSK:
+ ss = TrainingSeqGMSK.pick(src_msg.burst)
+ msg.tsc = ss.tsc if ss is not None else 0
+ msg.tsc_set = ss.tsc_set if ss is not None else 0
+ else: # TODO: other modulation types (at least 8-PSK)
+ msg.tsc_set = 0
+ msg.tsc = 0
+
+ # Takes (partially initialized) TRXD Rx message,
+ # simulates RF path parameters (such as RSSI),
+ # and sends towards the L1
+ def handle_data_msg(self, src_trx, src_msg, msg):
+ if self.rf_muted:
+ msg.nope_ind = True
+ elif not msg.nope_ind:
+ # Path loss simulation
+ msg.nope_ind = self.sim_burst_drop(msg)
+ if msg.nope_ind:
+ # Before TRXDv1, we simply drop the message
+ if msg.ver < 0x01:
+ del msg
+ return
+
+ # Since TRXDv1, we should send a NOPE.ind
+ msg.burst = None # burst bits are omited
+
+ # TODO: shoud we make these values configurable?
+ msg.toa256 = self.TOA256_NOISE_DEFAULT
+ msg.rssi = self.RSSI_NOISE_DEFAULT
+ msg.ci = self.CI_NOISE_DEFAULT
+
+ self.data_if.send_msg(msg)
+ return
+
+ # Complete message header
+ msg.toa256 = self.toa256
+
+ # Apply RSSI based on transmitter:
+ if not self.fake_rssi_enabled:
+ msg.rssi = src_trx.tx_power - src_msg.pwr - self.PATH_LOSS_DEFAULT
+ else: # Apply fake RSSI
+ msg.rssi = self.rssi
+
+ # Version specific fields
+ if msg.ver >= 0x01:
+ self._handle_data_msg_v1(src_msg, msg)
+
+ # Apply optional Timing Advance
+ if src_trx.ta != 0:
+ msg.toa256 -= src_trx.ta * 256
+
+ Transceiver.handle_data_msg(self, msg)
+
+ # Simulation specific CTRL command handler
+ def ctrl_cmd_handler(self, request):
+ # Timing Advance
+ # Syntax: CMD SETTA <TA>
+ if self.ctrl_if.verify_cmd(request, "SETTA", 1):
+ log.debug("(%s) Recv SETTA cmd" % self)
+
+ # Store indicated value
+ self.ta = int(request[1])
+ return 0
+
+ # Timing of Arrival simulation
+ # Absolute form: CMD FAKE_TOA <BASE> <THRESH>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_TOA", 2):
+ log.debug("(%s) Recv FAKE_TOA cmd" % self)
+
+ # Parse and apply both base and threshold
+ self.toa256_base = int(request[1])
+ self.toa256_rand_threshold = int(request[2])
+ return 0
+
+ # Timing of Arrival simulation
+ # Relative form: CMD FAKE_TOA <+-BASE_DELTA>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_TOA", 1):
+ log.debug("(%s) Recv FAKE_TOA cmd" % self)
+
+ # Parse and apply delta
+ self.toa256_base += int(request[1])
+ return 0
+
+ # RSSI simulation
+ # Absolute form: CMD FAKE_RSSI <BASE> <THRESH>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_RSSI", 2):
+ log.debug("(%s) Recv FAKE_RSSI cmd" % self)
+
+ # Use negative threshold to disable fake_rssi if previously enabled:
+ if int(request[2]) < 0:
+ self.fake_rssi_enabled = False
+ return 0
+
+ # Parse and apply both base and threshold
+ self.rssi_base = int(request[1])
+ self.rssi_rand_threshold = int(request[2])
+ self.fake_rssi_enabled = True
+ return 0
+
+ # RSSI simulation
+ # Relative form: CMD FAKE_RSSI <+-BASE_DELTA>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_RSSI", 1):
+ log.debug("(%s) Recv FAKE_RSSI cmd" % self)
+
+ # Parse and apply delta
+ self.rssi_base += int(request[1])
+ return 0
+
+ # C/I simulation
+ # Absolute form: CMD FAKE_CI <BASE> <THRESH>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_CI", 2):
+ log.debug("(%s) Recv FAKE_CI cmd" % self)
+
+ # Parse and apply both base and threshold
+ self.ci_base = int(request[1])
+ self.ci_rand_threshold = int(request[2])
+ return 0
+
+ # C/I simulation
+ # Relative form: CMD FAKE_CI <+-BASE_DELTA>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_CI", 1):
+ log.debug("(%s) Recv FAKE_CI cmd" % self)
+
+ # Parse and apply delta
+ self.ci_base += int(request[1])
+ return 0
+
+ # Path loss simulation: burst dropping
+ # Syntax: CMD FAKE_DROP <AMOUNT>
+ # Dropping pattern: fn % 1 == 0
+ elif self.ctrl_if.verify_cmd(request, "FAKE_DROP", 1):
+ log.debug("(%s) Recv FAKE_DROP cmd" % self)
+
+ # Parse / validate amount of bursts
+ num = int(request[1])
+ if num < 0:
+ log.error("(%s) FAKE_DROP amount shall not "
+ "be negative" % self)
+ return -1
+
+ self.burst_drop_amount = num
+ self.burst_drop_period = 1
+ return 0
+
+ # Path loss simulation: burst dropping
+ # Syntax: CMD FAKE_DROP <AMOUNT> <FN_PERIOD>
+ # Dropping pattern: fn % period == 0
+ elif self.ctrl_if.verify_cmd(request, "FAKE_DROP", 2):
+ log.debug("(%s) Recv FAKE_DROP cmd" % self)
+
+ # Parse / validate amount of bursts
+ num = int(request[1])
+ if num < 0:
+ log.error("(%s) FAKE_DROP amount shall not "
+ "be negative" % self)
+ return -1
+
+ # Parse / validate period
+ period = int(request[2])
+ if period <= 0:
+ log.error("(%s) FAKE_DROP period shall "
+ "be greater than zero" % self)
+ return -1
+
+ self.burst_drop_amount = num
+ self.burst_drop_period = period
+ return 0
+
+ # Artificial delay for the TRXC interface
+ # Syntax: CMD FAKE_TRXC_DELAY <DELAY_MS>
+ elif self.ctrl_if.verify_cmd(request, "FAKE_TRXC_DELAY", 1):
+ log.debug("(%s) Recv FAKE_TRXC_DELAY cmd", self)
+
+ self.ctrl_if.rsp_delay_ms = int(request[1])
+ log.info("(%s) Artificial TRXC delay set to %d",
+ self, self.ctrl_if.rsp_delay_ms)
+
+ # Unhandled command
+ return None
+
+
+# Runner organizes execution of several FakeTRX instances with common clock.
+class Runner:
+ def __init__(self, clck_gen, trx_list):
+ self.clck_gen = clck_gen
+ self.trx_list = trx_list.trx_list
+
+ # This method will be called on each TDMA frame
+ self.clck_gen.clck_handler = self.clck_handler
+
+ self.burst_fwd = BurstForwarder(trx_list.trx_list)
+
+ # This method will be called by the clock generator
+ def clck_handler(self, fn):
+ # We assume that this list is immutable at run-time
+ for trx in self.trx_list:
+ trx.clck_tick(self.burst_fwd, fn)
+
+ # loops runs IO loop on specified CLCKGen and TRXes forever.
+ def loop(self):
+ sock_list = [self.clck_gen._timerfd]
+ for trx in self.trx_list:
+ sock_list.append(trx.ctrl_if.sock)
+ sock_list.append(trx.data_if.sock)
+
+ # Enter main loop
+ while True:
+ # Wait until we get any data on any socket
+ r_event, _, _ = select.select(sock_list, [], [])
+
+ # clock is priority
+ if self.clck_gen._timerfd in r_event:
+ self.clck_gen.tick()
+
+ # Iterate over all transceivers
+ for trx in self.trx_list:
+ # DATA interface
+ if trx.data_if.sock in r_event:
+ trx.recv_data_msg()
+
+ # CTRL interface
+ if trx.ctrl_if.sock in r_event:
+ trx.ctrl_if.handle_rx()
diff --git a/src/target/trx_toolkit/fake_trx.py b/src/target/trx_toolkit/fake_trx.py
index 9bf9efd..e72a52b 100755
--- a/src/target/trx_toolkit/fake_trx.py
+++ b/src/target/trx_toolkit/fake_trx.py
@@ -23,409 +23,15 @@
import logging as log
import signal
import argparse
-import random
-import select
import sys
import re
import os
from app_common import ApplicationBase
-from burst_fwd import BurstForwarder
-from transceiver import Transceiver
-from data_msg import Modulation
from clck_gen import CLCKGen
from trx_list import TRXList
from fake_pm import FakePM
-from gsm_shared import *
-
-class FakeTRX(Transceiver):
- """ Fake transceiver with RF path (burst loss, RSSI, TA, ToA) simulation.
-
- == ToA / RSSI measurement simulation
-
- Since this is a virtual environment, we can simulate different
- parameters of the physical RF interface:
-
- - ToA (Timing of Arrival) - measured difference between expected
- and actual time of burst arrival in units of 1/256 of GSM symbol
- periods. A pair of both base and threshold values defines a range
- of ToA value randomization:
-
- from (toa256_base - toa256_rand_threshold)
- to (toa256_base + toa256_rand_threshold).
-
- - RSSI (Received Signal Strength Indication) - measured "power" of
- the signal (per burst) in dBm. A pair of both base and threshold
- values defines a range of RSSI value randomization:
-
- from (rssi_base - rssi_rand_threshold)
- to (rssi_base + rssi_rand_threshold).
-
- - C/I (Carrier-to-Interference ratio) - value in cB (centiBels),
- computed from the training sequence of each received burst, by
- comparing the "ideal" training sequence with the actual one.
- A pair of both base and threshold values defines a range of
- C/I randomization:
-
- from (ci_base - ci_rand_threshold)
- to (ci_base + ci_rand_threshold).
-
- Please note that the randomization is optional and disabled by default.
-
- == Timing Advance handling
-
- The BTS is using ToA measurements for UL bursts in order to calculate
- Timing Advance value, that is then indicated to a MS, which in its turn
- shall apply this value to the transmitted signal in order to compensate
- the delay. Basically, every burst is transmitted in advance defined by
- the indicated Timing Advance value. The valid range is 0..63, where
- each unit means one GSM symbol advance. The actual Timing Advance value
- is set using SETTA control command from MS. By default, it's set to 0.
-
- == Path loss simulation
-
- === Burst dropping
-
- In some cases, e.g. due to a weak signal or high interference, a burst
- can be lost, i.e. not detected by the receiver. This can also be
- simulated using FAKE_DROP command on the control interface:
-
- - burst_drop_amount - the amount of DL/UL bursts
- to be dropped (i.e. not forwarded towards the MS/BTS),
-
- - burst_drop_period - drop a DL/UL burst if its (fn % period) == 0.
-
- == Configuration
-
- All simulation parameters mentioned above can be changed at runtime
- using the commands with prefix 'FAKE_' on the control interface.
- All of them are handled by our custom CTRL command handler.
-
- """
-
- NOMINAL_TX_POWER_DEFAULT = 50 # dBm
- TX_ATT_DEFAULT = 0 # dB
- PATH_LOSS_DEFAULT = 110 # dB
-
- TOA256_BASE_DEFAULT = 0
- CI_BASE_DEFAULT = 90
-
- # Default values for NOPE / IDLE indications
- TOA256_NOISE_DEFAULT = 0
- RSSI_NOISE_DEFAULT = -110
- CI_NOISE_DEFAULT = -30
-
- def __init__(self, *trx_args, **trx_kwargs):
- Transceiver.__init__(self, *trx_args, **trx_kwargs)
-
- # fake RSSI is disabled by default, only enabled through TRXC FAKE_RSSI.
- # When disabled, RSSI is calculated based on Tx power and Rx path loss
- self.fake_rssi_enabled = False
-
- self.rf_muted = False
-
- # Actual ToA, RSSI, C/I, TA values
- self.tx_power_base = self.NOMINAL_TX_POWER_DEFAULT
- self.tx_att_base = self.TX_ATT_DEFAULT
- self.toa256_base = self.TOA256_BASE_DEFAULT
- self.rssi_base = self.NOMINAL_TX_POWER_DEFAULT - self.TX_ATT_DEFAULT - self.PATH_LOSS_DEFAULT
- self.ci_base = self.CI_BASE_DEFAULT
- self.ta = 0
-
- # ToA, RSSI, C/I randomization thresholds
- self.toa256_rand_threshold = 0
- self.rssi_rand_threshold = 0
- self.ci_rand_threshold = 0
-
- # Path loss simulation (burst dropping)
- self.burst_drop_amount = 0
- self.burst_drop_period = 1
-
- @property
- def toa256(self):
- # Check if randomization is required
- if self.toa256_rand_threshold == 0:
- return self.toa256_base
-
- # Generate a random ToA value in required range
- toa256_min = self.toa256_base - self.toa256_rand_threshold
- toa256_max = self.toa256_base + self.toa256_rand_threshold
- return random.randint(toa256_min, toa256_max)
-
- @property
- def rssi(self):
- # Check if randomization is required
- if self.rssi_rand_threshold == 0:
- return self.rssi_base
-
- # Generate a random RSSI value in required range
- rssi_min = self.rssi_base - self.rssi_rand_threshold
- rssi_max = self.rssi_base + self.rssi_rand_threshold
- return random.randint(rssi_min, rssi_max)
-
- @property
- def tx_power(self):
- return self.tx_power_base - self.tx_att_base
-
- @property
- def ci(self):
- # Check if randomization is required
- if self.ci_rand_threshold == 0:
- return self.ci_base
-
- # Generate a random C/I value in required range
- ci_min = self.ci_base - self.ci_rand_threshold
- ci_max = self.ci_base + self.ci_rand_threshold
- return random.randint(ci_min, ci_max)
-
- # Path loss simulation: burst dropping
- # Returns: True - drop, False - keep
- def sim_burst_drop(self, msg):
- # Check if dropping is required
- if self.burst_drop_amount == 0:
- return False
-
- if msg.fn % self.burst_drop_period == 0:
- log.info("(%s) Simulation: dropping burst (fn=%u %% %u == 0)"
- % (self, msg.fn, self.burst_drop_period))
- self.burst_drop_amount -= 1
- return True
-
- return False
-
- def _handle_data_msg_v1(self, src_msg, msg):
- # C/I (Carrier-to-Interference ratio)
- msg.ci = self.ci
-
- # Pick modulation type by burst length
- bl = len(src_msg.burst)
- msg.mod_type = Modulation.pick_by_bl(bl)
-
- # Pick TSC (Training Sequence Code) and TSC set
- if msg.mod_type is Modulation.ModGMSK:
- ss = TrainingSeqGMSK.pick(src_msg.burst)
- msg.tsc = ss.tsc if ss is not None else 0
- msg.tsc_set = ss.tsc_set if ss is not None else 0
- else: # TODO: other modulation types (at least 8-PSK)
- msg.tsc_set = 0
- msg.tsc = 0
-
- # Takes (partially initialized) TRXD Rx message,
- # simulates RF path parameters (such as RSSI),
- # and sends towards the L1
- def handle_data_msg(self, src_trx, src_msg, msg):
- if self.rf_muted:
- msg.nope_ind = True
- elif not msg.nope_ind:
- # Path loss simulation
- msg.nope_ind = self.sim_burst_drop(msg)
- if msg.nope_ind:
- # Before TRXDv1, we simply drop the message
- if msg.ver < 0x01:
- del msg
- return
-
- # Since TRXDv1, we should send a NOPE.ind
- msg.burst = None # burst bits are omited
-
- # TODO: shoud we make these values configurable?
- msg.toa256 = self.TOA256_NOISE_DEFAULT
- msg.rssi = self.RSSI_NOISE_DEFAULT
- msg.ci = self.CI_NOISE_DEFAULT
-
- self.data_if.send_msg(msg)
- return
-
- # Complete message header
- msg.toa256 = self.toa256
-
- # Apply RSSI based on transmitter:
- if not self.fake_rssi_enabled:
- msg.rssi = src_trx.tx_power - src_msg.pwr - self.PATH_LOSS_DEFAULT
- else: # Apply fake RSSI
- msg.rssi = self.rssi
-
- # Version specific fields
- if msg.ver >= 0x01:
- self._handle_data_msg_v1(src_msg, msg)
-
- # Apply optional Timing Advance
- if src_trx.ta != 0:
- msg.toa256 -= src_trx.ta * 256
-
- Transceiver.handle_data_msg(self, msg)
-
- # Simulation specific CTRL command handler
- def ctrl_cmd_handler(self, request):
- # Timing Advance
- # Syntax: CMD SETTA <TA>
- if self.ctrl_if.verify_cmd(request, "SETTA", 1):
- log.debug("(%s) Recv SETTA cmd" % self)
-
- # Store indicated value
- self.ta = int(request[1])
- return 0
-
- # Timing of Arrival simulation
- # Absolute form: CMD FAKE_TOA <BASE> <THRESH>
- elif self.ctrl_if.verify_cmd(request, "FAKE_TOA", 2):
- log.debug("(%s) Recv FAKE_TOA cmd" % self)
-
- # Parse and apply both base and threshold
- self.toa256_base = int(request[1])
- self.toa256_rand_threshold = int(request[2])
- return 0
-
- # Timing of Arrival simulation
- # Relative form: CMD FAKE_TOA <+-BASE_DELTA>
- elif self.ctrl_if.verify_cmd(request, "FAKE_TOA", 1):
- log.debug("(%s) Recv FAKE_TOA cmd" % self)
-
- # Parse and apply delta
- self.toa256_base += int(request[1])
- return 0
-
- # RSSI simulation
- # Absolute form: CMD FAKE_RSSI <BASE> <THRESH>
- elif self.ctrl_if.verify_cmd(request, "FAKE_RSSI", 2):
- log.debug("(%s) Recv FAKE_RSSI cmd" % self)
-
- # Use negative threshold to disable fake_rssi if previously enabled:
- if int(request[2]) < 0:
- self.fake_rssi_enabled = False
- return 0
-
- # Parse and apply both base and threshold
- self.rssi_base = int(request[1])
- self.rssi_rand_threshold = int(request[2])
- self.fake_rssi_enabled = True
- return 0
-
- # RSSI simulation
- # Relative form: CMD FAKE_RSSI <+-BASE_DELTA>
- elif self.ctrl_if.verify_cmd(request, "FAKE_RSSI", 1):
- log.debug("(%s) Recv FAKE_RSSI cmd" % self)
-
- # Parse and apply delta
- self.rssi_base += int(request[1])
- return 0
-
- # C/I simulation
- # Absolute form: CMD FAKE_CI <BASE> <THRESH>
- elif self.ctrl_if.verify_cmd(request, "FAKE_CI", 2):
- log.debug("(%s) Recv FAKE_CI cmd" % self)
-
- # Parse and apply both base and threshold
- self.ci_base = int(request[1])
- self.ci_rand_threshold = int(request[2])
- return 0
-
- # C/I simulation
- # Relative form: CMD FAKE_CI <+-BASE_DELTA>
- elif self.ctrl_if.verify_cmd(request, "FAKE_CI", 1):
- log.debug("(%s) Recv FAKE_CI cmd" % self)
-
- # Parse and apply delta
- self.ci_base += int(request[1])
- return 0
-
- # Path loss simulation: burst dropping
- # Syntax: CMD FAKE_DROP <AMOUNT>
- # Dropping pattern: fn % 1 == 0
- elif self.ctrl_if.verify_cmd(request, "FAKE_DROP", 1):
- log.debug("(%s) Recv FAKE_DROP cmd" % self)
-
- # Parse / validate amount of bursts
- num = int(request[1])
- if num < 0:
- log.error("(%s) FAKE_DROP amount shall not "
- "be negative" % self)
- return -1
-
- self.burst_drop_amount = num
- self.burst_drop_period = 1
- return 0
-
- # Path loss simulation: burst dropping
- # Syntax: CMD FAKE_DROP <AMOUNT> <FN_PERIOD>
- # Dropping pattern: fn % period == 0
- elif self.ctrl_if.verify_cmd(request, "FAKE_DROP", 2):
- log.debug("(%s) Recv FAKE_DROP cmd" % self)
-
- # Parse / validate amount of bursts
- num = int(request[1])
- if num < 0:
- log.error("(%s) FAKE_DROP amount shall not "
- "be negative" % self)
- return -1
-
- # Parse / validate period
- period = int(request[2])
- if period <= 0:
- log.error("(%s) FAKE_DROP period shall "
- "be greater than zero" % self)
- return -1
-
- self.burst_drop_amount = num
- self.burst_drop_period = period
- return 0
-
- # Artificial delay for the TRXC interface
- # Syntax: CMD FAKE_TRXC_DELAY <DELAY_MS>
- elif self.ctrl_if.verify_cmd(request, "FAKE_TRXC_DELAY", 1):
- log.debug("(%s) Recv FAKE_TRXC_DELAY cmd", self)
-
- self.ctrl_if.rsp_delay_ms = int(request[1])
- log.info("(%s) Artificial TRXC delay set to %d",
- self, self.ctrl_if.rsp_delay_ms)
-
- # Unhandled command
- return None
-
-
-# Runner organizes execution of several FakeTRX instances with common clock.
-class Runner:
- def __init__(self, clck_gen, trx_list):
- self.clck_gen = clck_gen
- self.trx_list = trx_list.trx_list
-
- # This method will be called on each TDMA frame
- self.clck_gen.clck_handler = self.clck_handler
-
- self.burst_fwd = BurstForwarder(trx_list.trx_list)
-
- # This method will be called by the clock generator
- def clck_handler(self, fn):
- # We assume that this list is immutable at run-time
- for trx in self.trx_list:
- trx.clck_tick(self.burst_fwd, fn)
-
- # loops runs IO loop on specified CLCKGen and TRXes forever.
- def loop(self):
- sock_list = [self.clck_gen._timerfd]
- for trx in self.trx_list:
- sock_list.append(trx.ctrl_if.sock)
- sock_list.append(trx.data_if.sock)
-
- # Enter main loop
- while True:
- # Wait until we get any data on any socket
- r_event, _, _ = select.select(sock_list, [], [])
-
- # clock is priority
- if self.clck_gen._timerfd in r_event:
- self.clck_gen.tick()
-
- # Iterate over all transceivers
- for trx in self.trx_list:
- # DATA interface
- if trx.data_if.sock in r_event:
- trx.recv_data_msg()
-
- # CTRL interface
- if trx.ctrl_if.sock in r_event:
- trx.ctrl_if.handle_rx()
-
+from _fake_trx import FakeTRX, Runner
class Application(ApplicationBase):
def __init__(self):
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40051?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: Iadffd49de8197564e57bfd9cb660b1d11136ffd4
Gerrit-Change-Number: 40051
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40052?usp=email )
Change subject: trx_toolkit: Move FakeTRX-related performance-sensitive modules to Cython
......................................................................
trx_toolkit: Move FakeTRX-related performance-sensitive modules to Cython
Cython (https://cython.org/ , https://cython.readthedocs.io/en/latest/)
is Python-compatible compiled language, that extends Python and allows
to add type annotations which help to optimize generated code. It is
based on Pyrex (https://www.csse.canterbury.ac.nz/greg.ewing/python/Pyrex/)
which is no longer actively maintained this days.
Using Cython allows one to take Python codebase and improve its speed
incrementally step by step instead of doing full rewrite from scratch.
Here we are only hooking Cython build system into the project and move
*.py to *.pyx files for the following modules:
- burst_fwd.c
- _clck_gen.c
- data_if.c
- data_msg.c
- _fake_trx.c
- udp_link.c
- transceiver.c
Those modules turned up to require being optimized because their
functionality is used by fake_trx loop non-stop at the very least ~2000
times a second if there is only one BTS attached to fake_trx.
We will optimize those modules step by step in the follow-up patches.
For now it is only plain code movement with adding
# cython: language_level=3
comment to the top of each file because else Cython uses 3str language
mode and emits a warning. We use language_level=3 because it has the
same semantic as of regular Python3.
The project now needs to be build before running: either `pip install -e .`
or `python setup.py build_ext -i` when doing development, or simple
`pip install .` for non-develop build.
There is no performance increase just from the move to Cython. We will
add optimizations step by step in the follow-up patches.
Change-Id: I2159a07bece13bda4f6ccd957063d4644d8b5e4f
---
M src/target/trx_toolkit/.gitignore
R src/target/trx_toolkit/_clck_gen.pyx
R src/target/trx_toolkit/_fake_trx.pyx
R src/target/trx_toolkit/burst_fwd.pyx
R src/target/trx_toolkit/data_if.pyx
R src/target/trx_toolkit/data_msg.pyx
A src/target/trx_toolkit/pyproject.toml
A src/target/trx_toolkit/setup.py
R src/target/trx_toolkit/transceiver.pyx
R src/target/trx_toolkit/udp_link.pyx
10 files changed, 35 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/52/40052/1
diff --git a/src/target/trx_toolkit/.gitignore b/src/target/trx_toolkit/.gitignore
index dc634e2..3ea0ef1 100644
--- a/src/target/trx_toolkit/.gitignore
+++ b/src/target/trx_toolkit/.gitignore
@@ -2,5 +2,14 @@
__pycache__/
*.py[cod]
*$py.class
+/build/
+/*.so
/perf.data*
/flamegraph.html
+/burst_fwd.c
+/_clck_gen.c
+/data_if.c
+/data_msg.c
+/_fake_trx.c
+/udp_link.c
+/transceiver.c
diff --git a/src/target/trx_toolkit/_clck_gen.py b/src/target/trx_toolkit/_clck_gen.pyx
similarity index 98%
rename from src/target/trx_toolkit/_clck_gen.py
rename to src/target/trx_toolkit/_clck_gen.pyx
index 9d9fd15..914b821 100644
--- a/src/target/trx_toolkit/_clck_gen.py
+++ b/src/target/trx_toolkit/_clck_gen.pyx
@@ -1,3 +1,5 @@
+# cython: language_level=3
+
# TRX Toolkit
# Simple TDMA frame clock generator
#
diff --git a/src/target/trx_toolkit/_fake_trx.py b/src/target/trx_toolkit/_fake_trx.pyx
similarity index 99%
rename from src/target/trx_toolkit/_fake_trx.py
rename to src/target/trx_toolkit/_fake_trx.pyx
index 87a352b..92573f8 100644
--- a/src/target/trx_toolkit/_fake_trx.py
+++ b/src/target/trx_toolkit/_fake_trx.pyx
@@ -1,3 +1,5 @@
+# cython: language_level=3
+
# TRX Toolkit
# Virtual Um-interface (fake transceiver)
#
diff --git a/src/target/trx_toolkit/burst_fwd.py b/src/target/trx_toolkit/burst_fwd.pyx
similarity index 98%
rename from src/target/trx_toolkit/burst_fwd.py
rename to src/target/trx_toolkit/burst_fwd.pyx
index 2824e0a..2737232 100644
--- a/src/target/trx_toolkit/burst_fwd.py
+++ b/src/target/trx_toolkit/burst_fwd.pyx
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# cython: language_level=3
# TRX Toolkit
# Burst forwarding between transceivers
diff --git a/src/target/trx_toolkit/data_if.py b/src/target/trx_toolkit/data_if.pyx
similarity index 98%
rename from src/target/trx_toolkit/data_if.py
rename to src/target/trx_toolkit/data_if.pyx
index f59ca17..396987e 100644
--- a/src/target/trx_toolkit/data_if.py
+++ b/src/target/trx_toolkit/data_if.pyx
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# cython: language_level=3
# TRX Toolkit
# DATA interface implementation
diff --git a/src/target/trx_toolkit/data_msg.py b/src/target/trx_toolkit/data_msg.pyx
similarity index 99%
rename from src/target/trx_toolkit/data_msg.py
rename to src/target/trx_toolkit/data_msg.pyx
index b1673f5..af0aac6 100644
--- a/src/target/trx_toolkit/data_msg.py
+++ b/src/target/trx_toolkit/data_msg.pyx
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# cython: language_level=3
# TRX Toolkit
# DATA interface message definitions and helpers
diff --git a/src/target/trx_toolkit/pyproject.toml b/src/target/trx_toolkit/pyproject.toml
new file mode 100644
index 0000000..e9f294d
--- /dev/null
+++ b/src/target/trx_toolkit/pyproject.toml
@@ -0,0 +1,2 @@
+[build-system]
+requires = ["setuptools", "wheel", "cython"]
diff --git a/src/target/trx_toolkit/setup.py b/src/target/trx_toolkit/setup.py
new file mode 100644
index 0000000..78de7ff
--- /dev/null
+++ b/src/target/trx_toolkit/setup.py
@@ -0,0 +1,15 @@
+from setuptools import setup
+from Cython.Build import cythonize
+
+setup(
+ name='trx_toolkit',
+ ext_modules = cythonize([
+ "burst_fwd.pyx",
+ "_clck_gen.pyx",
+ "data_if.pyx",
+ "data_msg.pyx",
+ "_fake_trx.pyx",
+ "udp_link.pyx",
+ "transceiver.pyx"
+ ])
+)
diff --git a/src/target/trx_toolkit/transceiver.py b/src/target/trx_toolkit/transceiver.pyx
similarity index 99%
rename from src/target/trx_toolkit/transceiver.py
rename to src/target/trx_toolkit/transceiver.pyx
index 2ebb29a..aaf3a90 100644
--- a/src/target/trx_toolkit/transceiver.py
+++ b/src/target/trx_toolkit/transceiver.pyx
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# cython: language_level=3
# TRX Toolkit
# Transceiver implementation
diff --git a/src/target/trx_toolkit/udp_link.py b/src/target/trx_toolkit/udp_link.pyx
similarity index 97%
rename from src/target/trx_toolkit/udp_link.py
rename to src/target/trx_toolkit/udp_link.pyx
index 53a0bfb..779c0fc 100644
--- a/src/target/trx_toolkit/udp_link.py
+++ b/src/target/trx_toolkit/udp_link.pyx
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# cython: language_level=3
# TRX Toolkit
# UDP link implementation
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40052?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I2159a07bece13bda4f6ccd957063d4644d8b5e4f
Gerrit-Change-Number: 40052
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40053?usp=email )
Change subject: trx_toolkit/burst_fwd: Do not inherit BurstForwarder from TRXList
......................................................................
trx_toolkit/burst_fwd: Do not inherit BurstForwarder from TRXList
That is simply unneeded as TRXList is used primarly by
fake_trx.Application when doing initial setup of TRX list, while
for BurstForwarder it is enough to have just a regular list of TRX
instances.
We need to remove TRXList from BurstForwarder base to be able to switch
BurstForwarder to cdef class in a follow-up patch as that is possible
only with cdef class basesm, while TRXList is plain py class.
Change-Id: I9ce8ca1531eea5d59358fb41cc99d8c349757abb
---
M src/target/trx_toolkit/burst_fwd.pyx
1 file changed, 4 insertions(+), 3 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/53/40053/1
diff --git a/src/target/trx_toolkit/burst_fwd.pyx b/src/target/trx_toolkit/burst_fwd.pyx
index 2737232..36e5095 100644
--- a/src/target/trx_toolkit/burst_fwd.pyx
+++ b/src/target/trx_toolkit/burst_fwd.pyx
@@ -21,9 +21,7 @@
import logging as log
-from trx_list import TRXList
-
-class BurstForwarder(TRXList):
+class BurstForwarder:
""" Performs burst forwarding between transceivers.
BurstForwarder distributes bursts between the list of given
@@ -42,6 +40,9 @@
"""
+ def __init__(self, trx_list):
+ self.trx_list = trx_list
+
def forward_msg(self, src_trx, rx_msg):
# Originating Transceiver may use frequency hopping,
# so let's precalculate its Tx frequency in advance
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40053?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I9ce8ca1531eea5d59358fb41cc99d8c349757abb
Gerrit-Change-Number: 40053
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40055?usp=email )
Change subject: trx_toolkit/udp_link: Optimize UDPLink.send
......................................................................
trx_toolkit/udp_link: Optimize UDPLink.send
As can be seen from http://navytux.spb.ru/~kirr/osmo/fake_trx/pyx-base.html (UDPLink_11send)
UDPLink.send spends ~ 30% of its time in python overhead, with doing
only ~ 70% of the time in the sendto syscall. As fake_trx invokes send
a lot it makes sense to cut that overhead.
-> Do that:
- prepare destination address in parsed form only once instead of parsing it in py runtime at every call
- avoid doing PyGetBuffer... dance - just support bytes|bytearray|str
for data and use fast CAPI macros to retrieve underlying data pointer
- invoke sendto syscall directly ourselves
- do not release/reacquire GIL to avoid corresponding performance
penalty. As Iaa675c95059ec8ccfad667f69984d5a7f608c249
(trx_toolkit/clck_gen: Don't use threads because Python GIL is latency killer)
shown GIL-related functions can be little in the profile, but
harm latency a lot. We can skip doing release/reacquire GIL because we
know UDPLink's socket is non-blocking.
Change-Id: I83204545066a925dadbcd0b72cbbc2e3407129fe
---
M src/target/trx_toolkit/udp_link.pxd
M src/target/trx_toolkit/udp_link.pyx
2 files changed, 64 insertions(+), 9 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/55/40055/1
diff --git a/src/target/trx_toolkit/udp_link.pxd b/src/target/trx_toolkit/udp_link.pxd
index 2d02497..d50fe5f 100644
--- a/src/target/trx_toolkit/udp_link.pxd
+++ b/src/target/trx_toolkit/udp_link.pxd
@@ -1,6 +1,21 @@
# cython: language_level=3
+from libc.stdint cimport uint16_t, uint32_t
+
+cdef extern from "<arpa/inet.h>":
+ struct sockaddr:
+ pass
+ struct in_addr:
+ uint32_t s_addr
+ struct sockaddr_in:
+ int sin_family
+ uint16_t sin_port
+ in_addr sin_addr
+
cdef class UDPLink:
cdef readonly object sock
- cdef str remote_addr
- cdef int remote_port
+ cdef int sock_fd
+ cdef sockaddr_in remote_addr
+
+
+cdef _raise_oserr()
diff --git a/src/target/trx_toolkit/udp_link.pyx b/src/target/trx_toolkit/udp_link.pyx
index ae90021..d75e058 100644
--- a/src/target/trx_toolkit/udp_link.pyx
+++ b/src/target/trx_toolkit/udp_link.pyx
@@ -20,38 +20,78 @@
import socket
+from cpython cimport PyBytes_AS_STRING, PyBytes_GET_SIZE, PyUnicode_AsUTF8AndSize
+from cpython.bytearray cimport PyByteArray_FromStringAndSize, PyByteArray_AS_STRING, PyByteArray_GET_SIZE
+
+from libc.errno cimport errno
+from libc.string cimport strerror
+
+cdef extern from "<arpa/inet.h>":
+ int inet_pton(int af, const char *src, void *dst)
+ char *inet_ntoa(in_addr)
+ uint16_t htons(uint16_t)
+
+cdef extern from "<sys/socket.h>":
+ ssize_t sendto(int fd, const void *buf, size_t len, int flags, const sockaddr *dst_addr, int addrlen)
+
+
cdef class UDPLink:
def __init__(self, str remote_addr, int remote_port, bind_addr = '0.0.0.0', bind_port = 0):
+ # Save remote address in parsed form
+ self.remote_addr.sin_family = socket.AF_INET
+ self.remote_addr.sin_port = htons(remote_port)
+ err = inet_pton(socket.AF_INET, remote_addr.encode(), &self.remote_addr.sin_addr)
+ if err <= 0:
+ _raise_oserr()
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((bind_addr, bind_port))
self.sock.setblocking(False)
- # Save remote info
- self.remote_addr = remote_addr
- self.remote_port = remote_port
+ self.sock_fd = self.sock.fileno()
def __del__(self):
self.sock.close()
+ self.sock_fd = -1
def desc_local(self):
(bind_addr, bind_port) = self.sock.getsockname()
return "%s:%u" % (bind_addr, bind_port)
def desc_remote(self):
- return "%s:%u" % (self.remote_addr, self.remote_port)
+ return "%s:%u" % (inet_ntoa(self.remote_addr.sin_addr), self.remote_addr.sin_port)
def desc_link(self):
return "L:%s <-> R:%s" % (self.desc_local(), self.desc_remote())
def send(self, data):
- if type(data) not in [bytearray, bytes]:
- data = data.encode()
+ cdef const char *buf
+ cdef Py_ssize_t buflen
+ if type(data) is bytearray:
+ buf = PyByteArray_AS_STRING(data)
+ buflen = PyByteArray_GET_SIZE(data)
+ elif type(data) is bytes:
+ buf = PyBytes_AS_STRING(data)
+ buflen = PyBytes_GET_SIZE(data)
+ elif type(data) is str:
+ buf = PyUnicode_AsUTF8AndSize(data, &buflen)
+ else:
+ raise TypeError("send: accept only bytes|bytearray|str ; got %r" % type(data))
- self.sock.sendto(data, (self.remote_addr, self.remote_port))
+ # NOTE we do not release/reacquire gil to save us from gil ping-pong performance penalty
+ # we can do that because the socket is non-blocking
+ n = sendto(self.sock_fd, buf, buflen,
+ 0, <sockaddr*>&self.remote_addr, sizeof(self.remote_addr))
+ if n == -1:
+ _raise_oserr()
def sendto(self, data, remote):
if type(data) not in [bytearray, bytes]:
data = data.encode()
self.sock.sendto(data, remote)
+
+
+cdef _raise_oserr():
+ raise OSError(errno, strerror(errno))
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40055?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I83204545066a925dadbcd0b72cbbc2e3407129fe
Gerrit-Change-Number: 40055
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>