Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Create `dpdk-stdlib-tcp/` crate (depends on `dpdk-stdlib-net` + `dpdk-stdlib`, C
`build_tcp_frame(params) -> Vec<u8>` (Eth + IPv4 + TCP; SYN/SYN-ACK frames include MSS, WScale, SACK-Perm, Timestamps). `tcp_checksum` with parameterized pseudo-header. `compute_mss(mtu, ip_hdr_len)`. `parse_tcp_packet` (validate data-offset ≥5, parse all options). `build_tcp_packet(mbuf, params)` (zero-copy DPDK path, byte-identical to `build_tcp_frame`). 7 property tests: round-trip, Mbuf equivalence, invalid frame rejection, SYN required options, MSS bound, checksum flip fails, sequence transitivity. (~500 LOC)

- Spec: `.kiro/specs/tcp-support/` · tasks `3.7`, `3.8`, `3.9`, `3.10`, `3.11`
- [ ] Complete · PR: —
- [x] Complete · PR: #79

---

Expand All @@ -149,7 +149,7 @@ Create `dpdk-stdlib-tcp/` crate (depends on `dpdk-stdlib-net` + `dpdk-stdlib`, C
`ConnectionHandle` (rx_ring/tx_ring SpscByteRing, AtomicU8 state, AtomicBool eof, Mutex error, condvar + notify_lock, AtomicWaker × 2, app_refcount, cmd_tx, key, linger). `EngineCommand` enum. `SocketOption` enum. `CommandSender` (wraps mpsc::Sender + Arc<EngineWakeup>; every send signals engine_wakeup). `OneshotSender/Receiver`. `TcpState` (11 states). `FourTuple`. `SystemClock` + `MockClock` (with `advance()`). `IsnGenerator` (RFC 6528: 128-bit per-boot secret via `getrandom`, SipHash-2-4 of FourTuple, M = elapsed µs / 4). (~400 LOC)

- Spec: `.kiro/specs/tcp-support/` · tasks `5.1`, `5.2`, `5.3`, `5.4`
- [ ] Complete · PR: —
- [x] Complete · PR: #80

---

Expand Down
71 changes: 71 additions & 0 deletions docs/perf-test-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,77 @@ Each entry captures the git context, test configuration, results, and analysis.
**Standard benchmarks** (include in every run entry):
1. **Hardware PPS** — TRex on c6in.xlarge (measures NIC + DPDK + application stack)

## Run #42: dpdk-stdlib-tcp Contract Types, TcpState, Clock, IsnGenerator — No Regression

| Field | Value |
|-------|-------|
| **Date** | 2026-06-13 |
| **Git Hash** | `22c0cd5` |
| **Branch** | `agent/tcp-contract-types-state-clock-isn` |
| **PR** | [#80](https://github.com/gspivey/dpdk-stdlib-rust/pull/80) |
| **GH Actions Run** | [27476064107](https://github.com/gspivey/dpdk-stdlib-rust/actions/runs/27476064107) |
| **Instance Type** | c6in.xlarge (4 vCPU, 6.25 Gbps baseline / 30 Gbps burst) |
| **Traffic Generator** | TRex |

### Changes Since Run #41

1. **`22c0cd5` — dpdk-stdlib-tcp contract types, TcpState, Clock, and IsnGenerator.** New modules: `state.rs` (TcpState enum + FourTuple), `clock.rs` (Clock trait + SystemClock + MockClock), `isn.rs` (IsnGenerator per RFC 6528), `contract.rs` (ConnectionHandle, EngineCommand, SocketOption, CommandSender, OneshotSender/Receiver, EngineWakeup, AtomicWaker). Zero changes to any existing data-path crate.

### Results: Hardware (TRex)

#### 64-byte packets

| Target PPS | plain-rust RX | Drop | rust-dpdk RX | Drop | tokio-dpdk RX | Drop | native-dpdk RX | Drop |
|-----------|--------------|------|-------------|------|--------------|------|---------------|------|
| 70,000 | 69,000 | 1.4% | 69,000 | 1.4% | 69,000 | 1.4% | 70,000 | 0.0% |
| 140,000 | 138,997 | 0.7% | 139,000 | 0.7% | 139,000 | 0.7% | 140,000 | 0.0% |
| 350,000 | 348,985 | 0.3% | 348,990 | 0.3% | 311,544 | 11.0% | 349,973 | 0.0% |
| 700,000 | 381,095 | 45.6% | 658,996 | 5.9% | 312,244 | 55.4% | 656,738 | 6.2% |

#### 512-byte packets

| Target PPS | plain-rust RX | Drop | rust-dpdk RX | Drop | tokio-dpdk RX | Drop | native-dpdk RX | Drop |
|-----------|--------------|------|-------------|------|--------------|------|---------------|------|
| 70,000 | 69,000 | 1.4% | 69,000 | 1.4% | 69,000 | 1.4% | 70,000 | 0.0% |
| 140,000 | 138,978 | 0.7% | 139,000 | 0.7% | 139,000 | 0.7% | 140,000 | 0.0% |
| 350,000 | 348,815 | 0.3% | 348,990 | 0.3% | 231,845 | 33.8% | 349,978 | 0.0% |
| 700,000 | 398,868 | 43.0% | 620,084 | 11.4% | 231,659 | 66.9% | 598,345 | 14.5% |

#### 1400-byte packets (near MTU)

| Target PPS | plain-rust RX | Drop | rust-dpdk RX | Drop | tokio-dpdk RX | Drop | native-dpdk RX | Drop |
|-----------|--------------|------|-------------|------|--------------|------|---------------|------|
| 70,000 | 69,000 | 1.4% | 69,000 | 1.4% | 69,000 | 1.4% | 70,000 | 0.0% |
| 140,000 | 138,990 | 0.7% | 139,000 | 0.7% | 139,000 | 0.7% | 140,000 | 0.0% |
| 350,000 | 348,770 | 0.4% | 349,000 | 0.3% | 150,532 | 57.0% | 349,998 | 0.0% |
| 700,000 | 447,288 | 6.0%* | 466,142 | 2.0%* | 160,320 | 66.3%* | 460,415 | 3.1%* |

\* TX capped at ~476K pps (ENA line-rate limit at 1400B)

#### 8500-byte packets (jumbo)

| Target PPS | plain-rust RX | Drop | rust-dpdk RX | Drop | tokio-dpdk RX | Drop | native-dpdk RX | Drop |
|-----------|--------------|------|-------------|------|--------------|------|---------------|------|
| 70,000 | 38,549 | 44.9% | 69,000 | 1.4% | 55,327 | 21.0% | 70,000 | 0.0% |
| 140,000 | 77,780 | 0.7%* | 77,724 | 0.8%* | 58,906 | 24.8%* | 78,266 | 0.0%* |
| 350,000 | 77,905 | 0.5%* | 77,900 | 0.6%* | 59,005 | 24.7%* | 77,960 | 0.4%* |

\* TX capped at ~78K pps (30 Gbps ENA burst limit at 8500B)

### Analysis

**No performance regression.** This PR adds new contract types, state machine enum, clock abstraction, and ISN generator to `dpdk-stdlib-tcp`. Zero changes to any existing data-path crate or networking code.

**rust-dpdk at 700K PPS, 64B**: 658,996 RX (5.9% drop) — consistent with Run #41's 659,356 (5.8% drop). Within normal ENA variance.

**native-dpdk at 700K PPS, 64B**: 656,738 RX (6.2% drop) — consistent with Run #41's 674,638 (3.6% drop). Normal saturation-point variance.

**rust-dpdk vs native-dpdk gap**: Near parity at 350K and below across all packet sizes. At 700K saturation, rust-dpdk tracks native-dpdk closely.

**Conclusion**: Adding TCP contract types is performance-neutral as expected.

---

## Run #41: dpdk-stdlib-tcp Crate Skeleton and Codec Types — No Regression

| Field | Value |
Expand Down
2 changes: 2 additions & 0 deletions dpdk-stdlib-tcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ description = "DPDK-accelerated TCP stack for dpdk-stdlib-rust"
dpdk-stdlib-net = { version = "0.2.0", path = "../dpdk-stdlib-net" }
dpdk = { version = "0.2.0", path = "../dpdk", package = "dpdk-stdlib" }
thiserror = { workspace = true }
getrandom = "0.2"
siphasher = "1"

[dev-dependencies]
proptest = "1.4"
100 changes: 100 additions & 0 deletions dpdk-stdlib-tcp/src/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Clock abstraction for deterministic testing of timer-driven behavior.

use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

/// Clock trait for injectable time source.
pub trait Clock: Send + Sync {
fn now(&self) -> Instant;
}

/// System clock delegating to `std::time::Instant::now()`.
pub struct SystemClock;

impl Clock for SystemClock {
#[inline]
fn now(&self) -> Instant {
Instant::now()
}
}

/// Mock clock for deterministic testing. Advances only via explicit calls.
pub struct MockClock {
inner: Arc<Mutex<Instant>>,
}

impl MockClock {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(Instant::now())),
}
}

/// Create with a specific starting instant.
pub fn with_instant(instant: Instant) -> Self {
Self {
inner: Arc::new(Mutex::new(instant)),
}
}

/// Advance the clock by the given duration.
pub fn advance(&self, duration: Duration) {
let mut t = self.inner.lock().unwrap();
*t += duration;
}

/// Set the clock to an exact instant.
pub fn set(&self, instant: Instant) {
let mut t = self.inner.lock().unwrap();
*t = instant;
}
}

impl Clock for MockClock {
fn now(&self) -> Instant {
*self.inner.lock().unwrap()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn system_clock_monotonic() {
let clock = SystemClock;
let t1 = clock.now();
let t2 = clock.now();
assert!(t2 >= t1);
}

#[test]
fn mock_clock_advance() {
let clock = MockClock::new();
let t1 = clock.now();
clock.advance(Duration::from_secs(5));
let t2 = clock.now();
assert_eq!(t2 - t1, Duration::from_secs(5));
}

#[test]
fn mock_clock_set() {
let clock = MockClock::new();
let start = clock.now();
clock.advance(Duration::from_secs(10));
let after_advance = clock.now();
assert_eq!(after_advance - start, Duration::from_secs(10));
clock.set(start);
assert_eq!(clock.now(), start);
}

#[test]
fn mock_clock_multiple_advances() {
let clock = MockClock::new();
let start = clock.now();
clock.advance(Duration::from_millis(100));
clock.advance(Duration::from_millis(200));
clock.advance(Duration::from_millis(300));
assert_eq!(clock.now() - start, Duration::from_millis(600));
}
}
Loading
Loading