sent_at is now captured immediately after a successful UDP send returns, instead of before timeout finalization and the socket send.
Raw RTT and the userspace upstream-OWD fallback no longer include the socket send-call interval (or the small amount of local work before it).
The operational probe timeout deadline remains anchored to a pre-send timestamp, unchanged in behavior.
Linux kernel TX plausibility keeps a separate pre-send lower bound, so a valid TX_SOFTWARE timestamp generated while the send syscall is still in flight remains usable even though it can now legitimately precede the post-send sent_at.
finalize_probe_commit still performs the only fallible work in the transaction (timeout deadline checked_add, which can produce DurationOverflow) and still runs before the send. commit_probe_sent is now infallible and only runs after the send succeeded. This preserves the existing contract:
DurationOverflow still sends nothing and commits nothing (blocking_timeout_finalization_overflow_does_not_send_or_commit).
A Tokio WouldBlocktry_send still samples no sent_at, commits nothing to the machine or schedule, and retains the PreparedProbe for retry; each retry gets a fresh send_anchor, and only a successful attempt's anchor/sent_at are retained.
Socket send failure (blocking or Tokio) still leaves the machine and schedule untouched.
The existing short-send fault-injection behavior (commit occurs before the presentation-length error) is unchanged; the retained sent_at in that case is the new post-send sample.
Timing semantics
sent_at (public): paired wall/monotonic ClientTimestamp captured immediately after the successful socket send. RTT endpoint, userspace upstream-OWD fallback endpoint, timer_error basis, and the value on EchoSent/EchoReply/EchoLoss/LateReply.
Pre-send anchor (private, send_anchor in the adapters / PendingProbe::tx_not_before_wall + timeout_at's basis in the machine): timeout deadline arithmetic and the kernel TX plausibility lower bound. Never exposed publicly.
send_call: unchanged — elapsed time strictly inside the send syscall (send_finished_at - send_call_start), excluding post-send timestamp-capture overhead.
timer_error: now abs(sent_at.mono - scheduled_at) using the post-send sent_at, describing deviation between scheduled send time and completed send, rather than time-before-syscall-entry.
Kernel TX interaction
Old lower bound: sent_at.wall <= kernel_tx <= received_at.wall (worked only because sent_at was pre-send).
New lower bound: tx_not_before_wall <= kernel_tx <= received_at.wall, where tx_not_before_wall is the pre-send anchor's wall sample.
Fallback (no/implausible kernel TX): now the post-send sent_at.wall instead of the pre-send sample.
New regression test kernel_tx_accepted_even_though_earlier_than_post_send_sent_at: pre-send anchor=1000ms, kernel TX=1005ms, post-send sent_at=1008ms, server recv=1020ms, client recv=1040ms → kernel TX is accepted and client_to_server == 15ms (not rejected for preceding the post-send sample, and not silently falling back to 1020-1008=12ms).
New test kernel_tx_below_pre_send_anchor_falls_back_to_post_send_not_pre_send: kernel TX below even the pre-send anchor (999ms) is rejected and falls back to the post-send sample (1008ms → 12ms), not the pre-send anchor (which would give 20ms).
RTT
Before: raw RTT = received.mono - pre_send.mono (included send-call interval).
After: raw RTT = received.mono - post_send_sent_at.mono.
New deterministic test raw_rtt_uses_post_send_sent_at_not_pre_send_anchor pins raw == 50ms for a post-send sample 10ms later than what a pre-send anchor would have given (60ms).
Adjusted/effective RTT inherit the new raw endpoint unchanged; server-processing subtraction is untouched.
Timeout
timeout_at is deliberately not derived from the new sent_at.mono. It remains pre_send_anchor.mono + probe_timeout, exactly as before this change in effect (just renamed/relocated internally). EchoLoss.sent_at reports the post-send measurement while the loss itself still fires at the pre-send-anchored deadline — pinned explicitly in blocking_probe_separates_permission_committed_send_and_send_call_timing.
Compatibility
No public type/field-shape changes. ClientEvent, RttSample, OneWayDelaySample, PendingProbe's public API surface via events are unchanged in shape; only the semantics of existing sent_at fields move from pre-send to post-send, documented in rustdoc.
Non-Linux platforms get the same RTT/OWD-fallback accuracy improvement (kernel TX is Linux/ancillary-only and was already optional).
Kernel TX remains the preferred upstream-OWD endpoint when locally plausible; nothing about TX capture, MSG_ERRQUEUE parsing, drain budget/locations, or the OPT_ID correlation invariant changed.
crates/irtt-server and Cargo.lock/Cargo.toml are untouched (empty diff).
Downstream (server_to_client) OWD and its kernel-RX-preferred selection are untouched.
Scope note: IPDV
crates/irtt-stats/src/normalization.rs's ReplySample::from_reply_parts feeds the public EchoReply/LateReply::sent_at directly into IpdvSample.client_send_mono / client_send_wall_ns, which crates/irtt-stats/src/ipdv.rs's send_ipdv_ns uses for the upstream/send-direction IPDV component. Since sent_at's meaning moves from pre-send to post-send, that IPDV component naturally shifts along with it (jitter is now measured between successive post-send samples instead of pre-send samples, differing by each probe's send-call-duration variance). Per the review scope, this is called out rather than compensated for — irtt-stats production code is untouched by this PR.
Scope
Out of scope, and not touched: Linux TX timestamp capture/correlation (SO_TIMESTAMPING flags, MSG_ERRQUEUE parsing, drain locations/budget, OPT_ID correlation), kernel RX handling, ProbeSchedule mathematics, timeout/loss classification policy, server timing (crates/irtt-server), irtt-stats production code/IPDV redesign, short-send policy redesign.
Verification
All run from the repo root:
cargo test -p irtt-client --all-features --locked — pass (341 unit tests + integration suites)
cargo test -p irtt-stats --all-features --locked — pass
cargo test -p irtt-cli --all-features --locked — pass
## Summary
- `sent_at` is now captured immediately after a successful UDP send returns, instead of before timeout finalization and the socket send.
- Raw RTT and the userspace upstream-OWD fallback no longer include the socket send-call interval (or the small amount of local work before it).
- The operational probe timeout deadline remains anchored to a pre-send timestamp, unchanged in behavior.
- Linux kernel TX plausibility keeps a separate pre-send lower bound, so a valid `TX_SOFTWARE` timestamp generated while the send syscall is still in flight remains usable even though it can now legitimately precede the post-send `sent_at`.
## Send transaction
New order (blocking and Tokio):
```
permission -> prepare -> machine preflight -> schedule preflight -> event reserve
send_anchor = ClientTimestamp::now() // PRIVATE pre-send anchor
finalize_probe_commit(preflight, send_anchor)? // fallible: timeout checked_add, tx_not_before_wall
send_call_start = Instant::now()
socket.send(...) / try_send(...)
send_finished_at = Instant::now()
sent_at = ClientTimestamp::now() // PUBLIC post-send measurement
commit_probe_sent(commit, sent_at, bytes) // infallible
schedule.commit(...)
```
`finalize_probe_commit` still performs the only fallible work in the transaction (timeout deadline `checked_add`, which can produce `DurationOverflow`) and still runs before the send. `commit_probe_sent` is now infallible and only runs after the send succeeded. This preserves the existing contract:
- `DurationOverflow` still sends nothing and commits nothing (`blocking_timeout_finalization_overflow_does_not_send_or_commit`).
- A Tokio `WouldBlock` `try_send` still samples no `sent_at`, commits nothing to the machine or schedule, and retains the `PreparedProbe` for retry; each retry gets a fresh `send_anchor`, and only a successful attempt's anchor/`sent_at` are retained.
- Socket send failure (blocking or Tokio) still leaves the machine and schedule untouched.
- The existing short-send fault-injection behavior (commit occurs before the presentation-length error) is unchanged; the retained `sent_at` in that case is the new post-send sample.
## Timing semantics
- `sent_at` (public): paired wall/monotonic `ClientTimestamp` captured immediately after the successful socket send. RTT endpoint, userspace upstream-OWD fallback endpoint, `timer_error` basis, and the value on `EchoSent`/`EchoReply`/`EchoLoss`/`LateReply`.
- Pre-send anchor (private, `send_anchor` in the adapters / `PendingProbe::tx_not_before_wall` + `timeout_at`'s basis in the machine): timeout deadline arithmetic and the kernel TX plausibility lower bound. Never exposed publicly.
- `send_call`: unchanged — elapsed time strictly inside the send syscall (`send_finished_at - send_call_start`), excluding post-send timestamp-capture overhead.
- `timer_error`: now `abs(sent_at.mono - scheduled_at)` using the post-send `sent_at`, describing deviation between scheduled send time and completed send, rather than time-before-syscall-entry.
## Kernel TX interaction
- Old lower bound: `sent_at.wall <= kernel_tx <= received_at.wall` (worked only because `sent_at` was pre-send).
- New lower bound: `tx_not_before_wall <= kernel_tx <= received_at.wall`, where `tx_not_before_wall` is the pre-send anchor's wall sample.
- Fallback (no/implausible kernel TX): now the post-send `sent_at.wall` instead of the pre-send sample.
- New regression test `kernel_tx_accepted_even_though_earlier_than_post_send_sent_at`: pre-send anchor=1000ms, kernel TX=1005ms, post-send `sent_at`=1008ms, server recv=1020ms, client recv=1040ms → kernel TX is accepted and `client_to_server == 15ms` (not rejected for preceding the post-send sample, and not silently falling back to `1020-1008=12ms`).
- New test `kernel_tx_below_pre_send_anchor_falls_back_to_post_send_not_pre_send`: kernel TX below even the pre-send anchor (999ms) is rejected and falls back to the post-send sample (1008ms → 12ms), not the pre-send anchor (which would give 20ms).
## RTT
- Before: `raw RTT = received.mono - pre_send.mono` (included send-call interval).
- After: `raw RTT = received.mono - post_send_sent_at.mono`.
- New deterministic test `raw_rtt_uses_post_send_sent_at_not_pre_send_anchor` pins `raw == 50ms` for a post-send sample 10ms later than what a pre-send anchor would have given (60ms).
- Adjusted/effective RTT inherit the new raw endpoint unchanged; server-processing subtraction is untouched.
## Timeout
`timeout_at` is deliberately **not** derived from the new `sent_at.mono`. It remains `pre_send_anchor.mono + probe_timeout`, exactly as before this change in effect (just renamed/relocated internally). `EchoLoss.sent_at` reports the post-send measurement while the loss itself still fires at the pre-send-anchored deadline — pinned explicitly in `blocking_probe_separates_permission_committed_send_and_send_call_timing`.
## Compatibility
- No public type/field-shape changes. `ClientEvent`, `RttSample`, `OneWayDelaySample`, `PendingProbe`'s public API surface via events are unchanged in shape; only the *semantics* of existing `sent_at` fields move from pre-send to post-send, documented in rustdoc.
- Non-Linux platforms get the same RTT/OWD-fallback accuracy improvement (kernel TX is Linux/`ancillary`-only and was already optional).
- Kernel TX remains the preferred upstream-OWD endpoint when locally plausible; nothing about TX capture, `MSG_ERRQUEUE` parsing, drain budget/locations, or the `OPT_ID` correlation invariant changed.
- `crates/irtt-server` and `Cargo.lock`/`Cargo.toml` are untouched (empty diff).
- Downstream (`server_to_client`) OWD and its kernel-RX-preferred selection are untouched.
## Scope note: IPDV
`crates/irtt-stats/src/normalization.rs`'s `ReplySample::from_reply_parts` feeds the public `EchoReply`/`LateReply::sent_at` directly into `IpdvSample.client_send_mono` / `client_send_wall_ns`, which `crates/irtt-stats/src/ipdv.rs`'s `send_ipdv_ns` uses for the upstream/send-direction IPDV component. Since `sent_at`'s meaning moves from pre-send to post-send, that IPDV component naturally shifts along with it (jitter is now measured between successive post-send samples instead of pre-send samples, differing by each probe's send-call-duration variance). Per the review scope, this is called out rather than compensated for — `irtt-stats` production code is untouched by this PR.
## Scope
Out of scope, and not touched: Linux TX timestamp capture/correlation (`SO_TIMESTAMPING` flags, `MSG_ERRQUEUE` parsing, drain locations/budget, `OPT_ID` correlation), kernel RX handling, `ProbeSchedule` mathematics, timeout/loss classification policy, server timing (`crates/irtt-server`), `irtt-stats` production code/IPDV redesign, short-send policy redesign.
## Verification
All run from the repo root:
- `cargo test -p irtt-client --all-features --locked` — pass (341 unit tests + integration suites)
- `cargo test -p irtt-stats --all-features --locked` — pass
- `cargo test -p irtt-cli --all-features --locked` — pass
- `cargo fmt --all -- --check` — pass
- `cargo check --workspace --all-targets --all-features --locked` — pass
- `cargo test --workspace --all-targets --all-features --locked` — pass
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` — pass
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked` — pass
- `cargo check -p irtt-cli --no-default-features --features client --all-targets --locked` — pass
- `cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked` — pass
- `cargo check -p irtt-cli --no-default-features --features server --all-targets --locked` — pass
- `git diff --check` — pass
- Cross-target `cargo check -p irtt-client --all-features --locked --target <T>` (via rustup rustc, `RUSTC` override) for `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-musl`, `x86_64-unknown-freebsd`, `x86_64-pc-windows-gnu`, `i686-unknown-linux-gnu` — all pass
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
### 💡 Codex Review
Here are some automated review suggestions for this pull request.
**Reviewed commit:** `a3896376da`
<details> <summary>ℹ️ About Codex in GitHub</summary>
<br/>
[Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
</details>
When probe send-call durations vary, moving public sent_at from before the send to after it changes send-direction IPDV: ReplySample::from_reply_parts copies this value into client_send_mono/client_send_wall_ns, and send_ipdv_ns compares those fields across probes. The commit description acknowledges this semantic shift, so retaining “IPDV is likewise unaffected” as repository guidance establishes a false invariant; document the shift here, or preserve a separate pre-send timestamp if IPDV truly must remain unchanged.
**<sub><sub></sub></sub> Correct the claim that IPDV is unaffected**
When probe send-call durations vary, moving public `sent_at` from before the send to after it changes send-direction IPDV: `ReplySample::from_reply_parts` copies this value into `client_send_mono`/`client_send_wall_ns`, and `send_ipdv_ns` compares those fields across probes. The commit description acknowledges this semantic shift, so retaining “IPDV is likewise unaffected” as repository guidance establishes a false invariant; document the shift here, or preserve a separate pre-send timestamp if IPDV truly must remain unchanged.
AGENTS.md reference: [crates/irtt-client/AGENTS.md:L153-L157](https://github.com/Lochnair/irtt-rs/blob/a3896376da62585e35b31ece8d0edbe6d58a5a84/crates/irtt-client/AGENTS.md#L153-L157)
Useful? React with 👍 / 👎.
Fixed — AGENTS.md now documents that the upstream IPDV component (send_ipdv_ns, fed by ReplySample.client_send_mono/client_send_wall_ns from sent_at) shifts endpoint along with sent_at itself, rather than claiming IPDV is unaffected. Pushed in e7b0632.
Fixed — AGENTS.md now documents that the upstream IPDV component (`send_ipdv_ns`, fed by `ReplySample.client_send_mono`/`client_send_wall_ns` from `sent_at`) shifts endpoint along with `sent_at` itself, rather than claiming IPDV is unaffected. Pushed in e7b0632.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
### 💡 Codex Review
Here are some automated review suggestions for this pull request.
**Reviewed commit:** `e7b0632239`
<details> <summary>ℹ️ About Codex in GitHub</summary>
<br/>
[Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
</details>
sent_at is not an input to the downstream (server_to_client) one-way delay: compute_one_way derives that direction from meta.preferred_receive_wall(received_at.wall) and the server send timestamp. Retaining this statement in repository guidance establishes a false timing invariant and can cause future changes or tests to couple downstream delay to the probe's send endpoint; document sent_at as the RTT and upstream-fallback endpoint instead.
Useful? React with 👍 / 👎.
**<sub><sub></sub></sub> Remove sent_at from downstream-delay guidance**
`sent_at` is not an input to the downstream (`server_to_client`) one-way delay: `compute_one_way` derives that direction from `meta.preferred_receive_wall(received_at.wall)` and the server send timestamp. Retaining this statement in repository guidance establishes a false timing invariant and can cause future changes or tests to couple downstream delay to the probe's send endpoint; document `sent_at` as the RTT and upstream-fallback endpoint instead.
Useful? React with 👍 / 👎.
Preserve event ordering with pre-send timeout deadlines
When a successful send takes longer than the configured probe_timeout—which may be any nonzero Duration—this stores a pre-send timeout_at that is earlier than the post-send sent_at.mono. Normal processing can therefore emit EchoSent(at = sent_at) followed by EchoLoss(at = timeout_at) with decreasing timestamps; irtt-stats/src/rolling.rs::push assumes its time-event deque is timestamp-ordered and stops eviction at the first newer front entry, so an expired loss behind that entry remains in rolling-time snapshots too long. Account for this reversed ordering in the event or rolling-window semantics while retaining the required pre-send overflow check.
**<sub><sub></sub></sub> Preserve event ordering with pre-send timeout deadlines**
When a successful send takes longer than the configured `probe_timeout`—which may be any nonzero `Duration`—this stores a pre-send `timeout_at` that is earlier than the post-send `sent_at.mono`. Normal processing can therefore emit `EchoSent(at = sent_at)` followed by `EchoLoss(at = timeout_at)` with decreasing timestamps; `irtt-stats/src/rolling.rs::push` assumes its time-event deque is timestamp-ordered and stops eviction at the first newer front entry, so an expired loss behind that entry remains in rolling-time snapshots too long. Account for this reversed ordering in the event or rolling-window semantics while retaining the required pre-send overflow check.
AGENTS.md reference: [crates/irtt-client/AGENTS.md:L117-L127](https://github.com/Lochnair/irtt-rs/blob/e7b06322396ca837079a7b59dc601299c736fe28/crates/irtt-client/AGENTS.md#L117-L127)
Useful? React with 👍 / 👎.
Fixed — normalize_event now clamps StatsEvent::Loss's windowing timestamp to max(timeout_at, sent_at.mono) so rolling-window eviction's non-decreasing-order assumption holds, without changing the public EchoLoss.timeout_at field or the pre-send timeout semantics. Added a targeted unit test (loss_windowing_timestamp_is_clamped_to_its_own_sent_at) pinning this. Pushed in aac5247.
Fixed — `normalize_event` now clamps `StatsEvent::Loss`'s windowing timestamp to `max(timeout_at, sent_at.mono)` so rolling-window eviction's non-decreasing-order assumption holds, without changing the public `EchoLoss.timeout_at` field or the pre-send timeout semantics. Added a targeted unit test (`loss_windowing_timestamp_is_clamped_to_its_own_sent_at`) pinning this. Pushed in aac5247.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Fixed — reworded to state sent_at is the RTT endpoint and the upstream (client_to_server) OWD userspace fallback only; downstream (server_to_client) OWD is called out explicitly as unaffected, derived solely from ReceiveMeta::preferred_receive_wall and the negotiated server send timestamp. Pushed in aac5247.
Fixed — reworded to state sent_at is the RTT endpoint and the upstream (client_to_server) OWD userspace fallback only; downstream (server_to_client) OWD is called out explicitly as unaffected, derived solely from ReceiveMeta::preferred_receive_wall and the negotiated server send timestamp. Pushed in aac5247.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
sent_atis now captured immediately after a successful UDP send returns, instead of before timeout finalization and the socket send.TX_SOFTWAREtimestamp generated while the send syscall is still in flight remains usable even though it can now legitimately precede the post-sendsent_at.Send transaction
New order (blocking and Tokio):
finalize_probe_commitstill performs the only fallible work in the transaction (timeout deadlinechecked_add, which can produceDurationOverflow) and still runs before the send.commit_probe_sentis now infallible and only runs after the send succeeded. This preserves the existing contract:DurationOverflowstill sends nothing and commits nothing (blocking_timeout_finalization_overflow_does_not_send_or_commit).WouldBlocktry_sendstill samples nosent_at, commits nothing to the machine or schedule, and retains thePreparedProbefor retry; each retry gets a freshsend_anchor, and only a successful attempt's anchor/sent_atare retained.sent_atin that case is the new post-send sample.Timing semantics
sent_at(public): paired wall/monotonicClientTimestampcaptured immediately after the successful socket send. RTT endpoint, userspace upstream-OWD fallback endpoint,timer_errorbasis, and the value onEchoSent/EchoReply/EchoLoss/LateReply.send_anchorin the adapters /PendingProbe::tx_not_before_wall+timeout_at's basis in the machine): timeout deadline arithmetic and the kernel TX plausibility lower bound. Never exposed publicly.send_call: unchanged — elapsed time strictly inside the send syscall (send_finished_at - send_call_start), excluding post-send timestamp-capture overhead.timer_error: nowabs(sent_at.mono - scheduled_at)using the post-sendsent_at, describing deviation between scheduled send time and completed send, rather than time-before-syscall-entry.Kernel TX interaction
sent_at.wall <= kernel_tx <= received_at.wall(worked only becausesent_atwas pre-send).tx_not_before_wall <= kernel_tx <= received_at.wall, wheretx_not_before_wallis the pre-send anchor's wall sample.sent_at.wallinstead of the pre-send sample.kernel_tx_accepted_even_though_earlier_than_post_send_sent_at: pre-send anchor=1000ms, kernel TX=1005ms, post-sendsent_at=1008ms, server recv=1020ms, client recv=1040ms → kernel TX is accepted andclient_to_server == 15ms(not rejected for preceding the post-send sample, and not silently falling back to1020-1008=12ms).kernel_tx_below_pre_send_anchor_falls_back_to_post_send_not_pre_send: kernel TX below even the pre-send anchor (999ms) is rejected and falls back to the post-send sample (1008ms → 12ms), not the pre-send anchor (which would give 20ms).RTT
raw RTT = received.mono - pre_send.mono(included send-call interval).raw RTT = received.mono - post_send_sent_at.mono.raw_rtt_uses_post_send_sent_at_not_pre_send_anchorpinsraw == 50msfor a post-send sample 10ms later than what a pre-send anchor would have given (60ms).Timeout
timeout_atis deliberately not derived from the newsent_at.mono. It remainspre_send_anchor.mono + probe_timeout, exactly as before this change in effect (just renamed/relocated internally).EchoLoss.sent_atreports the post-send measurement while the loss itself still fires at the pre-send-anchored deadline — pinned explicitly inblocking_probe_separates_permission_committed_send_and_send_call_timing.Compatibility
ClientEvent,RttSample,OneWayDelaySample,PendingProbe's public API surface via events are unchanged in shape; only the semantics of existingsent_atfields move from pre-send to post-send, documented in rustdoc.ancillary-only and was already optional).MSG_ERRQUEUEparsing, drain budget/locations, or theOPT_IDcorrelation invariant changed.crates/irtt-serverandCargo.lock/Cargo.tomlare untouched (empty diff).server_to_client) OWD and its kernel-RX-preferred selection are untouched.Scope note: IPDV
crates/irtt-stats/src/normalization.rs'sReplySample::from_reply_partsfeeds the publicEchoReply/LateReply::sent_atdirectly intoIpdvSample.client_send_mono/client_send_wall_ns, whichcrates/irtt-stats/src/ipdv.rs'ssend_ipdv_nsuses for the upstream/send-direction IPDV component. Sincesent_at's meaning moves from pre-send to post-send, that IPDV component naturally shifts along with it (jitter is now measured between successive post-send samples instead of pre-send samples, differing by each probe's send-call-duration variance). Per the review scope, this is called out rather than compensated for —irtt-statsproduction code is untouched by this PR.Scope
Out of scope, and not touched: Linux TX timestamp capture/correlation (
SO_TIMESTAMPINGflags,MSG_ERRQUEUEparsing, drain locations/budget,OPT_IDcorrelation), kernel RX handling,ProbeSchedulemathematics, timeout/loss classification policy, server timing (crates/irtt-server),irtt-statsproduction code/IPDV redesign, short-send policy redesign.Verification
All run from the repo root:
cargo test -p irtt-client --all-features --locked— pass (341 unit tests + integration suites)cargo test -p irtt-stats --all-features --locked— passcargo test -p irtt-cli --all-features --locked— passcargo fmt --all -- --check— passcargo check --workspace --all-targets --all-features --locked— passcargo test --workspace --all-targets --all-features --locked— passcargo clippy --workspace --all-targets --all-features --locked -- -D warnings— passRUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked— passcargo check -p irtt-cli --no-default-features --features client --all-targets --locked— passcargo check -p irtt-cli --no-default-features --features tui --all-targets --locked— passcargo check -p irtt-cli --no-default-features --features server --all-targets --locked— passgit diff --check— passcargo check -p irtt-client --all-features --locked --target <T>(via rustup rustc,RUSTCoverride) forx86_64-unknown-linux-gnu,aarch64-unknown-linux-musl,x86_64-unknown-freebsd,x86_64-pc-windows-gnu,i686-unknown-linux-gnu— all pass💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:
a3896376daℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
@@ -96,33 +96,75 @@ socket.## Retained stateWhen probe send-call durations vary, moving public
sent_atfrom before the send to after it changes send-direction IPDV:ReplySample::from_reply_partscopies this value intoclient_send_mono/client_send_wall_ns, andsend_ipdv_nscompares those fields across probes. The commit description acknowledges this semantic shift, so retaining “IPDV is likewise unaffected” as repository guidance establishes a false invariant; document the shift here, or preserve a separate pre-send timestamp if IPDV truly must remain unchanged.AGENTS.md reference: crates/irtt-client/AGENTS.md:L153-L157
Useful? React with 👍 / 👎.
@@ -96,33 +96,75 @@ socket.## Retained stateFixed — AGENTS.md now documents that the upstream IPDV component (
send_ipdv_ns, fed byReplySample.client_send_mono/client_send_wall_nsfromsent_at) shifts endpoint along withsent_atitself, rather than claiming IPDV is unaffected. Pushed ine7b0632.🤖 Addressed by Claude Code
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:
e7b0632239ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
@@ -96,33 +96,75 @@ socket.## Retained statesent_atis not an input to the downstream (server_to_client) one-way delay:compute_one_wayderives that direction frommeta.preferred_receive_wall(received_at.wall)and the server send timestamp. Retaining this statement in repository guidance establishes a false timing invariant and can cause future changes or tests to couple downstream delay to the probe's send endpoint; documentsent_atas the RTT and upstream-fallback endpoint instead.Useful? React with 👍 / 👎.
When a successful send takes longer than the configured
probe_timeout—which may be any nonzeroDuration—this stores a pre-sendtimeout_atthat is earlier than the post-sendsent_at.mono. Normal processing can therefore emitEchoSent(at = sent_at)followed byEchoLoss(at = timeout_at)with decreasing timestamps;irtt-stats/src/rolling.rs::pushassumes its time-event deque is timestamp-ordered and stops eviction at the first newer front entry, so an expired loss behind that entry remains in rolling-time snapshots too long. Account for this reversed ordering in the event or rolling-window semantics while retaining the required pre-send overflow check.AGENTS.md reference: crates/irtt-client/AGENTS.md:L117-L127
Useful? React with 👍 / 👎.
Fixed —
normalize_eventnow clampsStatsEvent::Loss's windowing timestamp tomax(timeout_at, sent_at.mono)so rolling-window eviction's non-decreasing-order assumption holds, without changing the publicEchoLoss.timeout_atfield or the pre-send timeout semantics. Added a targeted unit test (loss_windowing_timestamp_is_clamped_to_its_own_sent_at) pinning this. Pushed inaac5247.🤖 Addressed by Claude Code
@@ -96,33 +96,75 @@ socket.## Retained stateFixed — reworded to state sent_at is the RTT endpoint and the upstream (client_to_server) OWD userspace fallback only; downstream (server_to_client) OWD is called out explicitly as unaffected, derived solely from ReceiveMeta::preferred_receive_wall and the negotiated server send timestamp. Pushed in
aac5247.🤖 Addressed by Claude Code