Skip to content

bench(client): receive one message from the decoded stanza to the event - #1392

Merged
jlucaso1 merged 4 commits into
mainfrom
claude/whatsapp-rust-perf-recv-bench
Sep 2, 2026
Merged

bench(client): receive one message from the decoded stanza to the event#1392
jlucaso1 merged 4 commits into
mainfrom
claude/whatsapp-rust-perf-recv-bench

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

The client crate had a benchmark for the send path (client_group_send) and none for the receive path, which is where #1389 and #1390 spent most of their effort. This adds client_receive, the inbound twin of the group-send harness, so that work is regression-gated on CodSpeed from here on.

  • ReceiveHarness (bench_support, behind bench-harness): a second in-memory client is the peer. It holds an acknowledged pairwise session (reusing the same two-way X3DH the send harness needs for its companion, which now returns the peer instead of dropping it) and a sender key installed through the real SKDM handler, and it encrypts every measured stanza. The receiving client takes each stanza at handle_incoming_message, which is what the chat-lane worker awaits per message, so the queue hop is excluded by construction and the per-message work is not: classification, the signal-cache checkout, dedup and retry bookkeeping, plaintext handling, dispatch, and the delivery receipt marshalled and noise-encrypted onto the sink socket. The receipt rides a detached worker, so receive flushes the outbound scope before returning; without that the receipt's cost leaked into the next iteration (cubic caught this).
  • Two benches, dm_receive and group_receive. Each asserts that the subscribed handler saw every message it received, because a failed decrypt is fast and would otherwise pass for a receive.
  • handle_incoming_message (test-only before) is also available under bench-harness.

The CodSpeed client shard already builds the whole crate with --features bench-harness, so the new target runs without a workflow change.

Numbers

Current-thread runtime, in-memory backend, this container:

bench client-level, receipt included without the receipt pure decrypt (wacore)
dm_receive 20.6 µs 13.5 µs 10.5 µs
group_receive 72.6 µs 50.8 µs 42.8 µs

Up to the dispatched event the client is within a few microseconds of its crypto floor: under callgrind, handle_incoming_message_scoped is 73% session decrypt for a DM and 79% XEdDSA verification for a group message, and the client's own share is about 25k instructions per DM. The delivery receipt is the one thing this benchmark found that was not already known: 7 µs on a DM and 22 µs on a group message, which is the next thing to look at, and now has a number to move.

The "left out" items from #1390 measure below the noise (a SenderKeyRecord clone is 149 ns against a 44 µs group decrypt; a precomputed per-sender verification table changes nothing, since the 256 doublings dominate).

Validation

  • cargo fmt --all, cargo clippy -p whatsapp-rust --features bench-harness --all-targets -- -D warnings, cargo check -p whatsapp-rust --lib (feature off), RUSTDOCFLAGS="-D warnings" cargo doc -p whatsapp-rust --all-features --no-deps clean.
  • cargo bench -p whatsapp-rust --features bench-harness --bench client_receive runs both benches with their delivery assertions passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_0172fpxasGTrouFyYH5UGmjN

`wacore`'s `send_receive_benchmark` covers the pure decrypt, and both DM
and group receives there sit within a few microseconds of their crypto
floor. Everything around the decrypt lives in the client crate and had
no benchmark: classification, the signal-cache checkout, dedup and
retry bookkeeping, plaintext handling, the event bus, the delivery
receipt. `ReceiveHarness` is the inbound twin of `GroupSendHarness`: a
second in-memory client is the peer, holding an acknowledged pairwise
session and a sender key installed through the real SKDM handler, and
encrypts every measured stanza; the receiving client takes it at
`handle_incoming_message`, which is what the chat-lane worker awaits per
message, so the queue hop is excluded and the per-message work is not.
Each bench asserts the subscriber saw every message, since a failed
decrypt is fast and would otherwise pass for a receive.

First numbers, current-thread runtime, in-memory backend:

    dm_receive     13.5 µs   (pure decrypt: 10.5 µs)
    group_receive  50.8 µs   (pure decrypt: 42.8 µs)

Under callgrind, `handle_incoming_message_scoped` is 73% session
decrypt for a DM and 79% signature verification for a group message;
the client's own share is about 25k instructions per DM.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172fpxasGTrouFyYH5UGmjN
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Team

Run ID: adef22d1-3c0f-4d63-8b42-15dc0992d1e2

📥 Commits

Reviewing files that changed from the base of the PR and between 28ba798 and 38d4cdf.

📒 Files selected for processing (1)
  • src/bench_support.rs

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.


Important

Approval pending

CodeRabbit has no unresolved comments, but it has not reviewed the latest commit.

Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.

  • 🔍 Trigger review
📝 Summary

Summary by CodeRabbit

  • Tests

    • Added client-level receive benchmarks for direct and group messages.
    • Added coverage for processing incoming messages through delivery.
    • Added checks confirming that benchmarked messages are successfully delivered.
  • Chores

    • Enabled receive benchmarks through the benchmark-enabled build configuration.
    • Added reusable test support for measuring incoming message handling.

Walkthrough

Adds client-level DM and group receive benchmarks. The change adds a warmed in-memory encrypted-message fixture, benchmark-enabled receive handling, delivery-count validation, and Cargo benchmark registration.

Changes

Client receive benchmarks

Layer / File(s) Summary
Receive fixture setup
src/bench_support.rs, src/message/receive.rs
ReceiveHarness creates acknowledged Signal sessions, installs group sender-key state, tracks delivered messages, and configures an in-memory transport. handle_incoming_message is available with bench-harness.
Encrypted stanza flow
src/bench_support.rs
The harness generates encrypted DM and group stanzas, performs a wire round trip, receives messages, and exposes delivery counts.
Benchmark targets
Cargo.toml, benches/client_receive.rs
Cargo registers client_receive. The benchmark measures DM and group receive throughput with shared fixture caching and delivery-count assertions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 38d4c

This change adds opt-in receive benchmarks and an in-memory test fixture without changing ordinary production behavior or external interfaces. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Divan
  participant ReceiveHarness
  participant Client
  participant MessageEvents
  Divan->>ReceiveHarness: build encrypted DM or group stanza
  Divan->>Client: receive decoded stanza
  Client->>MessageEvents: dispatch message event
  Divan->>MessageEvents: verify delivered-message count
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a client receive benchmark for processing one message from a decoded stanza to the dispatched event.
Description check ✅ Passed The description directly explains the new client receive benchmark, its fixtures, measured paths, validation, and scope. It is fully related to the changeset.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/whatsapp-rust-perf-recv-bench

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a feature-gated client receive benchmark that measures direct and group messages from decoded stanza through event dispatch and receipt generation.

  • Adds dm_receive and group_receive Divan benchmarks.
  • Introduces an in-memory peer-backed receive harness with established Signal sessions and sender keys.
  • Exposes the existing inbound-message helper to the benchmark feature.
  • Registers the new benchmark target in Cargo.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
benches/client_receive.rs Adds direct-message and group-message receive benchmarks with delivery assertions.
src/bench_support.rs Adds the peer-backed receive fixture, encrypted stanza builders, event counter, and outbound flushing.
src/message/receive.rs Makes the existing test-only inbound helper available under the benchmark-harness feature.
Cargo.toml Registers the feature-gated client_receive benchmark target.

Sequence Diagram

sequenceDiagram
    participant Bench as Divan benchmark
    participant Peer as In-memory peer
    participant Client as Receiving client
    participant Signal as Signal stores
    participant Events as Event subscriber
    participant Sink as Sink transport
    Bench->>Peer: Build encrypted DM/group stanza
    Peer->>Signal: Advance session or sender-key chain
    Peer-->>Bench: Decoded message node
    Bench->>Client: handle_incoming_message(node)
    Client->>Signal: Decrypt payload
    Signal-->>Client: Plaintext message
    Client->>Events: Dispatch Event::Messages
    Client->>Sink: Queue encrypted delivery receipt
    Bench->>Client: Flush outbound receipt work
    Bench->>Events: Assert delivered count
Loading

Reviews (3): Last reviewed commit: "bench(client): keep the delivery receipt..." | Re-trigger Greptile

greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 2, 2026
The receive harness doc linked `Client::handle_incoming_message`, which is
pub(crate), and rustdoc under `-D warnings` rejects a public item linking
to a private one. A plain code span says the same thing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172fpxasGTrouFyYH5UGmjN
@greptile-apps
greptile-apps Bot dismissed their stale review September 2, 2026 21:33

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

jlucaso1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Semver Checks (informational) is red on this head and is not this PR's: the job diffs wacore, wacore-binary and waproto against the published 0.7.0, and every finding is already on main (the same set reported on #1390 and #1391). This branch adds a bench target and a bench-harness-gated fixture; its one change outside the feature gate widens a pub(crate) method's cfg, which no published API sees. The workflow marks itself non-blocking, so nothing to port.


Generated by Claude Code

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172fpxasGTrouFyYH5UGmjN

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/bench_support.rs Outdated
greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 2, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

`ack_received_message` hands the receipt to a detached worker, so
`handle_incoming_message` returned before it was marshalled and
noise-encrypted, and on the harness's current-thread runtime that work
ran during the next iteration's await instead. `receive` now flushes the
outbound scope, which pulls the receipt into the measured region:

    dm_receive     13.5 µs -> 20.6 µs
    group_receive  50.8 µs -> 72.6 µs

The difference is the receipt path's own cost, which the benchmark was
built to expose and had been hiding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172fpxasGTrouFyYH5UGmjN
@greptile-apps
greptile-apps Bot dismissed their stale review September 2, 2026 21:43

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You’re at about 96% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Auto-approved: Adds client-level receive benchmarks and supporting bench-only harness code, with no production behavior changes; the only non-test gate is widened to the bench-harness feature for an internal test helper.

Re-trigger cubic

@jlucaso1
jlucaso1 merged commit 1ee13b4 into main Sep 2, 2026
39 of 40 checks passed
@jlucaso1
jlucaso1 deleted the claude/whatsapp-rust-perf-recv-bench branch September 2, 2026 21:52
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.37 MiB 10.37 MiB 0
bin .text 8.31 MiB 8.31 MiB 0
bin allocated (text+data+bss) 10.37 MiB 10.37 MiB 0
llvm-lines wacore 567,419 567,419 0
llvm-lines wacore copies 18,610 18,610 0
llvm-lines whatsapp-rust lib 790,937 790,937 0
llvm-lines whatsapp-rust lib copies 25,177 25,177 0
deps crates (Cargo.lock) 468 468 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.91 MiB 1.91 MiB 0
.text wacore 742.87 KiB 743.09 KiB +224 B (+0.03%) 🔺
.text wacore_binary 81.88 KiB 81.88 KiB 0
.text wacore_libsignal 187.68 KiB 187.68 KiB 0
.text wacore_appstate 28.34 KiB 28.34 KiB 0
.text wacore_noise 20.92 KiB 20.92 KiB 0
.text waproto 1.79 MiB 1.79 MiB 0
.text whatsapp_rust_sqlite_storage 546.07 KiB 546.07 KiB 0
.text whatsapp_rust_tokio_transport 40.57 KiB 40.57 KiB 0
.text whatsapp_rust_ureq_http_client 12.75 KiB 12.75 KiB 0
.text std 1.01 MiB 1.01 MiB 0
.text other deps 1.94 MiB 1.94 MiB -224 B (-0.01%) 🔽

Baseline: c7860743e (latest main run) · Head: ddd32d801 · Graphs

jlucaso1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Correction to the numbers in the description, from an A/B on one binary after the merge: the delivery receipt plus its worker hand-off costs about 5 µs on a DM receive, not 7, and on a group receive the difference is within this host's noise, not 22 µs. The description's "without the receipt" column came from an earlier run on a quieter machine state; measured back to back, no-wait vs. timed flush vs. listener vs. yield loop give 15 / 20 / 20 / 19 µs for dm_receive and 62–69 / 66–72 / 66–70 / 66–68 µs for group_receive. So the wait strategy does not matter, the receipt is the same ~5 µs whichever way it is waited for, and inside the worker it is about 1 µs to build and marshal the node and 1.5–1.9 µs to noise-encrypt and hand it to the transport. The one thing the receipt path could still save is the hop itself, which a production multi-thread runtime overlaps with the next message anyway. Nothing to change in the merged code; the timed flush it ships is as good as the cheaper waits.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants