A proof-of-concept for a developer-friendly system service to handle time-sensitive applications.
This is a prototype of a system service to handle time-sensitive applications
Developer friendliness
Flexibility for experimentation
The following snippet illustrates the add_talker interface:
interface_name = "eth0"
interval = 2 * 1000 * 1000 # ns for 2 ms
size = 1522 # Bytes
txoffset = 250 * 1000 # ns for 250 us
addr = "7a:b9:ed:d6:d2:12"
vid = 3
pcp = 6
interface = Interface(interface_name)
traffic = TrafficSpecification(interval, size)
stream = StreamConfiguration(addr, vid, pcp, txoffset)
config = Configuration(interface, stream, traffic)
manager = Manager()
manager.add_talker(config)
In a little bit more detail:
See Contributing :)
The following code will connect to the detd service and configure the specified stream.
from detd import *
def setup_stream_config():
interface_name = "eno1"
interval = 20 * 1000 * 1000 # ns
size = 1522 # Bytes
txoffset = 250 * 1000 # ns
addr = "03:C0:FF:EE:FF:4E"
vid = 3
pcp = 6
interface = Interface(interface_name)
stream = StreamConfiguration(addr, vid, pcp, txoffset)
traffic = TrafficSpecification(interval, size)
config = Configuration(interface, stream, traffic)
return config
proxy = ServiceProxy()
config = setup_stream_config()
response = proxy.add_talker(config)
print(response)
This example adds the talker and then prints the VLAN configured interface and socket priority that the calling application should use to send data through that stream:
('eno1.3', 7)
The behaviour can be inspected in the log file /var/log/detd.log:
[2022-09-15 18:18:29,608 - INFO] detd.service __init__() - * * * detd Service starting * * *
[2022-09-15 18:18:29,608 - INFO] detd.service __init__() - Initializing Service
[2022-09-15 18:18:29,609 - INFO] detd.manager __init__() - Initializing Manager
[2022-09-15 18:18:29,609 - INFO] detd.service run() - Entering Service main loop
[2022-09-15 18:18:34,173 - INFO] detd.service setup() - ============================== REQUEST DISPATCHED ==================================
[2022-09-15 18:18:34,173 - INFO] detd.service setup() - Setting up ServiceRequestHandler
[2022-09-15 18:18:34,173 - INFO] detd.service handle() - Handling request
[2022-09-15 18:18:34,178 - INFO] detd.devices __init__() - Initializing IntelMgbeEhl
[2022-09-15 18:18:34,178 - INFO] detd.manager add_talker() - Adding talker to Manager
[2022-09-15 18:18:34,178 - INFO] detd.manager __init__() - Initializing InterfaceManager
[2022-09-15 18:18:34,178 - INFO] detd.mapping __init__() - Initializing Mapping
[2022-09-15 18:18:34,179 - INFO] detd.systemconf __init__() - Initializing SystemConfigurator
[2022-09-15 18:18:34,179 - INFO] detd.scheduler __init__() - Initializing Scheduler
[2022-09-15 18:18:34,179 - INFO] detd.manager add_talker() - Adding talker to InterfaceManager
[2022-09-15 18:18:34,179 - INFO] detd.mapping assign_and_map() - Assigning and mapping resources
[2022-09-15 18:18:34,179 - INFO] detd.scheduler add() - Adding traffic to schedule
[2022-09-15 18:18:34,179 - INFO] detd.systemconf setup() - Setting up platform and devices
detd code uses high level constructs. Only in the final step it translates to specific command calls. For the example above, such commands would be:
tc qdisc replace
dev eno1
parent root
taprio
num_tc 2
map 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7
base-time <now + 2 cycles>
sched-entry S 01 250000
sched-entry S 02 12176
sched-entry S 01 19737824
flags 0x2
ip link add
link eno1
name eno1.3
type vlan
protocol 802.1Q
id 3
egress 0:0 1:1 2:2 3:3 4:4 5:5 6:7 7:7
ethtool --set-eee eth0 eee off
The following code will connect to the detd service and configure the specified stream with options.
from detd import *
def setup_stream_config():
interface_name = "eno1"
interval = 20 * 1000 * 1000 # ns
size = 1522 # Bytes
txoffset = 250 * 1000 # ns
addr = "03:C0:FF:EE:FF:4E"
vid = 3
pcp = 6
preemption = False
launch_time_control = False
tx_selection_offload = False
datapath = DataPath.AF_PACKET
tx_selection = TxSelection.EST
interface = Interface(interface_name)
stream = StreamConfiguration(addr, vid, pcp, txoffset)
traffic = TrafficSpecification(interval, size)
hints = Hints(tx_selection, tx_selection_offload, datapath, preemption, launch_time_control)
config = Configuration(interface, stream, traffic, hints)
return config
proxy = ServiceProxy()
config = setup_stream_config()
response = proxy.add_talker(config)
print(response)
This example specifies the map parameter for taprio, otherwise it behaves same as the example above.
The script setup_qos.sh allows for quick experimentation without modifying an existing application.
It performs the configuration using detd commands, as in the first example.
Then it uses the cgroup net_prio to automatically map the egress traffic from the command to a given socket prio. So all the queuing disciplines and PCP mapping in place works properly.
Example:
# 2ms period, 1522 bytes, TxOffset 250 microseconds, interface eth0
# stream DMAC AB:CD:EF:FE:DC:BA, stream VID 3, stream PCP 6
# Command: ping 8.8.8.8 for one second (Busybox's ping)
./setup_qos.sh --period 2000000 --bytes 1522 --offset 250000 --interface eth0 \
--address AB:CD:EF:FE:DC:BA --vid 3 --pcp 6 \
-- ping -w 1 8.8.8.8
Although it is not the intended use case, you could also call the detd classes from ad-hoc applications. Please note that in this case there is no connection to any service.
interface_name = "eth0"
interval = 20 * 1000 * 1000 # ns for 20 ms
size = 1522 # Bytes
txoffset = 250 * 1000 # ns for 250 us
addr = "7a:b9:ed:d6:d2:12"
vid = 3
pcp = 6
interface = Interface(interface_name)
traffic = TrafficSpecification(interval, size)
stream = StreamConfiguration(addr, vid, pcp, txoffset)
config = Configuration(interface, stream, traffic)
manager = Manager()
manager.add_talker(config)
The only relevant change in the code above is using the class Manager instead of ServiceProxy to add the talker.
By default, calls to system commands are mocked, so almost every code path can be exercised without TSN target hardware.
The test environment is controlled by the following environment variable.
DETD_TESTENV
Host testing is configured by default inside the test suite.
To override the default and setup target testing (e.g. calling actual commands):
DETD_TESTENV=TARGET
For integration testing, the service is spawned as a different process. This prevents regular use of patch. Hence, the Server class accepts a parameter test_mode, that when set to True will patch the required methods in order to maximize coverage. This is automatically performed when running the unittests.
With host test environment
python3 -m unittest tests.test_manager
With target test environment
DETD_TESTENV=TARGET python3 -m unittest tests.test_manager
Integration tests include a client-server example in python.
Run all unit and integration tests in the development host test environment:
python3 -m unittest discover
A convenience script test.sh is included in the detd directory, that basically runs the above command:
cd detd
./test.sh
Runtime dependencies (Debian / Ubuntu)
Build dependencies (Debian / Ubuntu)
Packaging dependencies (Debian / Ubuntu)
A convenience script package_debian.sh is provided to generate a deb file for easier installation on Debian-based distributions. It generates a deb file and stores it in /tmp.
cd tools
./package_debian.sh
apt install /tmp/detd_*deb
Upon installation, the service is started by systemd, with logging redirected to /var/log/detd.log:
tail /var/log/detd.log
[2022-09-14 23:02:28,780 - INFO] detd.service __init__() - * * * detd Service starting * * *
[2022-09-14 23:02:28,780 - INFO] detd.service __init__() - Initializing Service
[2022-09-14 23:02:28,780 - INFO] detd.manager __init__() - Initializing Manager
[2022-09-14 23:02:28,780 - INFO] detd.service run() - Entering Service main loop
At this point the service is ready to receive requests.
The script detd_docker_build.sh allows to build the deb packages inside a docker container in a convenient way. E.g. just run:
cd tools
./detd_docker_build.sh
Different variants are supported, like Debian Bookworm or Ubuntu 22.04. In order to build for a specific target, provide the name as argument. The value should match that used by the FROM clause inside a Dockerfile. For example:
./detd_docker_build.sh ubuntu:24.04
If you are behind a proxy and experience connectivity issues, e.g. to update and install the dependencies, you may want to include the following in the Dockerfile:
ENV http_proxy=http://your.http.proxy:port
ENV https_proxy=http://your.https.proxy:port
The release process and security checks for the current release were conducted over the following specific versions:
To install:
git clone https://github.com/Avnu/detd.git
cd detd
python3 setup.py bdist_wheel
# Uninstall in case a previous version is there
# We install detd system wide
sudo pip3 uninstall detd
sudo pip3 install dist/detd-*.whl
sudo pip3 show detd
Optionally, you may want to run the test suite on host to make sure everything is in place:
python3 -m unittest discover
.....................................ss
----------------------------------------------------------------------
Ran 39 tests in 1.458s
OK (skipped=2)
Junior tasks
Linux integration
Developer Experience
System integration
Device support
Code Quality
Other improvements
Linux integration
Developer Experience
System integration
Device support
Port PoCs