Skip to main content

bench_cam_tx/
bench_cam_tx.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT)
3
4//! Benchmark: Maximum CAM TX throughput.
5//!
6//! Sends CAM packets as fast as possible (100 ms ETSI rate-limiter bypassed)
7//! and reports packets/s and UPER encode latency per packet in µs.
8//!
9//! # Usage
10//! ```text
11//! sudo cargo run --release --example bench_cam_tx -- [interface] [duration_s]
12//! # defaults: lo, 10 s
13//! ```
14
15use rustflexstack::btp::router::{BTPRouterHandle, Router as BTPRouter};
16use rustflexstack::btp::service_access_point::BTPDataRequest;
17use rustflexstack::facilities::ca_basic_service::cam_coder::{
18    cam_header, generation_delta_time_now, AccelerationComponent, AccelerationConfidence,
19    AccelerationValue, Altitude, AltitudeConfidence, AltitudeValue, BasicContainer,
20    BasicVehicleContainerHighFrequency, Cam, CamCoder, CamParameters, CamPayload, Curvature,
21    CurvatureCalculationMode, CurvatureConfidence, CurvatureValue, DriveDirection, Heading,
22    HeadingConfidence, HeadingValue, HighFrequencyContainer, Latitude, Longitude,
23    PositionConfidenceEllipse, ReferencePositionWithConfidence, SemiAxisLength, Speed,
24    SpeedConfidence, SpeedValue, TrafficParticipantType, VehicleLength,
25    VehicleLengthConfidenceIndication, VehicleLengthValue, VehicleWidth, Wgs84AngleValue, YawRate,
26    YawRateConfidence, YawRateValue,
27};
28use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST};
29use rustflexstack::geonet::mib::Mib;
30use rustflexstack::geonet::position_vector::LongPositionVector;
31use rustflexstack::geonet::router::{Router as GNRouter, RouterHandle};
32use rustflexstack::geonet::service_access_point::{
33    Area, CommonNH, CommunicationProfile, GNDataIndication, GNDataRequest, HeaderSubType,
34    HeaderType, PacketTransportType, TopoBroadcastHST, TrafficClass,
35};
36use rustflexstack::link_layer::raw_link_layer::RawLinkLayer;
37use rustflexstack::security::sn_sap::SecurityProfile;
38
39use std::env;
40use std::sync::mpsc;
41use std::thread;
42use std::time::{Duration, Instant};
43
44fn main() {
45    let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string());
46    let duration_s = env::args()
47        .nth(2)
48        .and_then(|s| s.parse::<u64>().ok())
49        .unwrap_or(10);
50
51    println!("=== Benchmark: Maximum CAM TX throughput ===");
52    println!("Interface : {iface}");
53    println!("Duration  : {duration_s} s\n");
54
55    // ── MAC / MIB ─────────────────────────────────────────────────────────────
56    let mac = random_mac();
57    let mut mib = Mib::new();
58    mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::PassengerCar, MID::new(mac));
59    mib.itsGnBeaconServiceRetransmitTimer = 0;
60
61    // ── Routers + link layer ──────────────────────────────────────────────────
62    let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None);
63    let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib);
64
65    let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::<Vec<u8>>();
66    RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac).start();
67    wire_routers(
68        &gn_handle,
69        &btp_handle,
70        ll_to_gn_rx,
71        gn_to_btp_rx,
72        btp_to_gn_rx,
73    );
74
75    // Seed GN position vector so packets are accepted downstream.
76    let mut epv = LongPositionVector::decode([0u8; 24]);
77    epv.update_from_gps(41.552, 2.134, 0.0, 0.0, true);
78    gn_handle.update_position_vector(epv);
79    thread::sleep(Duration::from_millis(50));
80
81    // ── Coder + template CAM ──────────────────────────────────────────────────
82    let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]);
83    let coder = CamCoder::new();
84    let template = make_cam(station_id);
85
86    // ── Benchmark loop ────────────────────────────────────────────────────────
87    println!("Sending CAMs as fast as possible…\n");
88    println!(
89        "{:>7}  {:>10}  {:>12}  {:>12}",
90        "time(s)", "total_sent", "rate(pkt/s)", "avg_enc(µs)"
91    );
92
93    let mut total_sent: u64 = 0;
94    let mut total_enc_us: u128 = 0;
95    let bench_start = Instant::now();
96    let bench_end = bench_start + Duration::from_secs(duration_s);
97    let mut win_start = Instant::now();
98    let mut win_sent: u64 = 0;
99
100    while Instant::now() < bench_end {
101        let t0 = Instant::now();
102        let data = match coder.encode(&template) {
103            Ok(d) => d,
104            Err(e) => {
105                eprintln!("[TX] Encode error: {e}");
106                continue;
107            }
108        };
109        let enc_us = t0.elapsed().as_micros();
110
111        btp_handle.send_btp_data_request(cam_btp_request(data));
112
113        total_sent += 1;
114        total_enc_us += enc_us;
115        win_sent += 1;
116
117        let win_elapsed = win_start.elapsed();
118        if win_elapsed >= Duration::from_secs(1) {
119            let pps = win_sent as f64 / win_elapsed.as_secs_f64();
120            let avg_enc = total_enc_us / total_sent.max(1) as u128;
121            println!(
122                "{:>7.1}  {:>10}  {:>12.1}  {:>12}",
123                bench_start.elapsed().as_secs_f64(),
124                total_sent,
125                pps,
126                avg_enc
127            );
128            win_start = Instant::now();
129            win_sent = 0;
130        }
131    }
132
133    // ── Summary ───────────────────────────────────────────────────────────────
134    let elapsed = bench_start.elapsed().as_secs_f64();
135    let avg_rate = total_sent as f64 / elapsed;
136    let avg_encode = total_enc_us / total_sent.max(1) as u128;
137
138    println!();
139    println!("=== CAM TX Results ===");
140    println!("  Total sent      : {total_sent}");
141    println!("  Elapsed         : {elapsed:.3} s");
142    println!("  Average rate    : {avg_rate:.1} pkt/s");
143    println!("  Avg encode time : {avg_encode} µs");
144}
145
146// ─── Helpers ─────────────────────────────────────────────────────────────────
147
148fn make_cam(station_id: u32) -> Cam {
149    let hf = BasicVehicleContainerHighFrequency::new(
150        Heading::new(HeadingValue(900), HeadingConfidence(127)),
151        Speed::new(SpeedValue(0), SpeedConfidence(127)),
152        DriveDirection::unavailable,
153        VehicleLength::new(
154            VehicleLengthValue(1023),
155            VehicleLengthConfidenceIndication::unavailable,
156        ),
157        VehicleWidth(62),
158        AccelerationComponent::new(AccelerationValue(161), AccelerationConfidence(102)),
159        Curvature::new(CurvatureValue(1023), CurvatureConfidence::unavailable),
160        CurvatureCalculationMode::unavailable,
161        YawRate::new(YawRateValue(32767), YawRateConfidence::unavailable),
162        None,
163        None,
164        None,
165        None,
166        None,
167        None,
168        None,
169    );
170    Cam::new(
171        cam_header(station_id),
172        CamPayload::new(
173            generation_delta_time_now(),
174            CamParameters::new(
175                BasicContainer::new(
176                    TrafficParticipantType(5),
177                    ReferencePositionWithConfidence::new(
178                        Latitude(415_520_000),
179                        Longitude(21_340_000),
180                        PositionConfidenceEllipse::new(
181                            SemiAxisLength(4095),
182                            SemiAxisLength(4095),
183                            Wgs84AngleValue(3601),
184                        ),
185                        Altitude::new(AltitudeValue(12000), AltitudeConfidence::unavailable),
186                    ),
187                ),
188                HighFrequencyContainer::basicVehicleContainerHighFrequency(hf),
189                None,
190                None,
191                None,
192            ),
193        ),
194    )
195}
196
197fn cam_btp_request(data: Vec<u8>) -> BTPDataRequest {
198    BTPDataRequest {
199        btp_type: CommonNH::BtpB,
200        source_port: 0,
201        destination_port: 2001,
202        destination_port_info: 0,
203        gn_packet_transport_type: PacketTransportType {
204            header_type: HeaderType::Tsb,
205            header_sub_type: HeaderSubType::TopoBroadcast(TopoBroadcastHST::SingleHop),
206        },
207        gn_destination_address: GNAddress {
208            m: M::GnMulticast,
209            st: ST::Unknown,
210            mid: MID::new([0xFF; 6]),
211        },
212        communication_profile: CommunicationProfile::Unspecified,
213        gn_area: Area {
214            latitude: 0,
215            longitude: 0,
216            a: 0,
217            b: 0,
218            angle: 0,
219        },
220        traffic_class: TrafficClass {
221            scf: false,
222            channel_offload: false,
223            tc_id: 0,
224        },
225        security_profile: SecurityProfile::NoSecurity,
226        its_aid: 36,
227        security_permissions: vec![],
228        gn_max_hop_limit: 1,
229        gn_max_packet_lifetime: None,
230        gn_repetition_interval: None,
231        gn_max_repetition_time: None,
232        destination: None,
233        length: data.len() as u16,
234        data,
235    }
236}
237
238fn random_mac() -> [u8; 6] {
239    use std::time::{SystemTime, UNIX_EPOCH};
240    let s = SystemTime::now()
241        .duration_since(UNIX_EPOCH)
242        .unwrap()
243        .subsec_nanos();
244    [
245        0x02,
246        (s >> 24) as u8,
247        (s >> 16) as u8,
248        (s >> 8) as u8,
249        s as u8,
250        0xBB,
251    ]
252}
253
254fn wire_routers(
255    gn: &RouterHandle,
256    btp: &BTPRouterHandle,
257    ll_rx: mpsc::Receiver<Vec<u8>>,
258    gn_btp_rx: mpsc::Receiver<GNDataIndication>,
259    btp_gn_rx: mpsc::Receiver<GNDataRequest>,
260) {
261    let g1 = gn.clone();
262    thread::spawn(move || {
263        while let Ok(p) = ll_rx.recv() {
264            g1.send_incoming_packet(p);
265        }
266    });
267    let b1 = btp.clone();
268    thread::spawn(move || {
269        while let Ok(i) = gn_btp_rx.recv() {
270            b1.send_gn_data_indication(i);
271        }
272    });
273    let g2 = gn.clone();
274    thread::spawn(move || {
275        while let Ok(r) = btp_gn_rx.recv() {
276            g2.send_gn_data_request(r);
277        }
278    });
279}