diff --git a/.github/workflows/bench-integration.yml b/.github/workflows/bench-integration.yml
new file mode 100644
index 000000000..c51263ae8
--- /dev/null
+++ b/.github/workflows/bench-integration.yml
@@ -0,0 +1,186 @@
+name: Integration Benchmark
+
+on:
+ pull_request:
+ branches: [main]
+ push:
+ branches: [main]
+
+concurrency:
+ group: bench-integration-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ CARGO_TERM_COLOR: always
+ PROTOC_VERSION: "3.25.3"
+ SCCACHE_GHA_ENABLED: "true"
+ RUSTC_WRAPPER: "sccache"
+
+jobs:
+ bench-integration-pr:
+ if: github.event_name == 'pull_request'
+ name: Integration Benchmark (PR)
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ services:
+ mock-server:
+ image: ghcr.io/whiskeysockets-devtools/bartender:latest
+ credentials:
+ username: ${{ github.actor }}
+ password: ${{ secrets.BARTENDER_GHCR_TOKEN }}
+ ports:
+ - 8080:8080
+ env:
+ CHATSTATE_TTL_SECS: "3"
+ options: --log-driver none
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: nightly-2026-04-05
+
+ - name: Install protoc
+ uses: taiki-e/install-action@v2
+ with:
+ tool: protoc@${{ env.PROTOC_VERSION }}
+
+ - name: Setup sccache
+ uses: mozilla-actions/sccache-action@v0.0.9
+
+ - name: Cache Rust registry
+ uses: Swatinem/rust-cache@v2
+ with:
+ prefix-key: ${{ runner.os }}-cargo-bench-int
+ cache-targets: "false"
+
+ - name: Wait for mock server
+ run: |
+ for i in $(seq 1 30); do
+ if curl -sk https://localhost:8080/ > /dev/null 2>&1; then
+ echo "Mock server is ready"
+ exit 0
+ fi
+ sleep 1
+ done
+ echo "Mock server failed to become ready"
+ exit 1
+
+ - name: Run integration benchmarks
+ env:
+ MOCK_SERVER_URL: "wss://127.0.0.1:8080/ws/chat"
+ RUST_LOG: warn
+ run: |
+ cargo run -p bench-integration --release \
+ > bench_integration_results.json \
+ 2> bench_integration.log
+ cat bench_integration.log >&2
+ echo "Results:"
+ cat bench_integration_results.json
+
+ - name: Upload benchmark artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: bench-integration-results
+ path: |
+ bench_integration_results.json
+ bench_integration.log
+ retention-days: 30
+
+ - name: Compare against baseline
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ name: "whatsapp-rust integration benchmarks"
+ tool: "customSmallerIsBetter"
+ output-file-path: bench_integration_results.json
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ auto-push: false
+ save-data-file: false
+ benchmark-data-dir-path: dev/bench-integration
+ summary-always: true
+
+ bench-integration-push:
+ if: github.event_name == 'push'
+ name: Integration Benchmark (push)
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ deployments: write
+ services:
+ mock-server:
+ image: ghcr.io/whiskeysockets-devtools/bartender:latest
+ credentials:
+ username: ${{ github.actor }}
+ password: ${{ secrets.BARTENDER_GHCR_TOKEN }}
+ ports:
+ - 8080:8080
+ env:
+ CHATSTATE_TTL_SECS: "3"
+ options: --log-driver none
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: nightly-2026-04-05
+
+ - name: Install protoc
+ uses: taiki-e/install-action@v2
+ with:
+ tool: protoc@${{ env.PROTOC_VERSION }}
+
+ - name: Setup sccache
+ uses: mozilla-actions/sccache-action@v0.0.9
+
+ - name: Cache Rust registry
+ uses: Swatinem/rust-cache@v2
+ with:
+ prefix-key: ${{ runner.os }}-cargo-bench-int
+ cache-targets: "false"
+
+ - name: Wait for mock server
+ run: |
+ for i in $(seq 1 30); do
+ if curl -sk https://localhost:8080/ > /dev/null 2>&1; then
+ echo "Mock server is ready"
+ exit 0
+ fi
+ sleep 1
+ done
+ echo "Mock server failed to become ready"
+ exit 1
+
+ - name: Run integration benchmarks
+ env:
+ MOCK_SERVER_URL: "wss://127.0.0.1:8080/ws/chat"
+ RUST_LOG: warn
+ run: |
+ cargo run -p bench-integration --release \
+ > bench_integration_results.json \
+ 2> bench_integration.log
+ cat bench_integration.log >&2
+ echo "Results:"
+ cat bench_integration_results.json
+
+ - name: Upload benchmark artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: bench-integration-results
+ path: |
+ bench_integration_results.json
+ bench_integration.log
+ retention-days: 30
+
+ - name: Store baseline
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ name: "whatsapp-rust integration benchmarks"
+ tool: "customSmallerIsBetter"
+ output-file-path: bench_integration_results.json
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ auto-push: true
+ benchmark-data-dir-path: dev/bench-integration
+ max-items-in-chart: 100
+ summary-always: true
diff --git a/.gitignore b/.gitignore
index 539e2d082..a157f7df1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ docs
.claude
__pycache__
.codex
+dhat-heap.json
diff --git a/Cargo.lock b/Cargo.lock
index 498b50e9c..23e7d5b7d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -144,6 +144,22 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "bench-integration"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "dhat",
+ "e2e-tests",
+ "env_logger",
+ "log",
+ "serde",
+ "serde_json",
+ "tokio",
+ "wacore",
+ "whatsapp-rust",
+]
+
[[package]]
name = "bincode"
version = "1.3.3"
@@ -608,7 +624,6 @@ dependencies = [
"wacore",
"wacore-binary",
"whatsapp-rust",
- "whatsapp-rust-sqlite-storage",
"whatsapp-rust-tokio-transport",
"whatsapp-rust-ureq-http-client",
]
@@ -2405,6 +2420,7 @@ version = "0.5.0"
dependencies = [
"async-trait",
"bincode 2.0.1",
+ "bytes",
"diesel",
"diesel_migrations",
"libsqlite3-sys",
diff --git a/Cargo.toml b/Cargo.toml
index edb16d2c7..59507d659 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ members = [
".",
"http_clients/ureq-client",
"storages/sqlite-storage",
+ "tests/bench-integration",
"tests/e2e",
"transports/tokio-transport",
"wacore",
@@ -161,3 +162,9 @@ inherits = "release"
lto = "thin"
debug = 1
strip = false
+
+# Profiling: optimized with debug symbols for DHAT / heaptrack / perf
+[profile.profiling]
+inherits = "release"
+debug = 1
+strip = false
diff --git a/src/appstate_sync.rs b/src/appstate_sync.rs
index 06556e479..0426e94e1 100644
--- a/src/appstate_sync.rs
+++ b/src/appstate_sync.rs
@@ -59,7 +59,7 @@ mod tests {
async fn store_prekey(&self, _: u32, _: &[u8], _: bool) -> StoreResult<()> {
Ok(())
}
- async fn load_prekey(&self, _: u32) -> StoreResult