From 3afbfb42cff67baca416a5c53d3d901baf8e60fc Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Thu, 30 Apr 2026 21:01:38 +0400 Subject: [PATCH] bench: add criterion benchmarks for protocol, bandwidth, TCP RX scan, and EC-SRP5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds four Criterion.rs benchmark suites to measure hot-path performance and demonstrate the impact of Sprints 1–3 optimizations: - benches/protocol.rs — Command & StatusMessage serialize/deserialize - benches/bandwidth.rs — BandwidthState atomics, budget, interval math - benches/tcp_rx_scan.rs — memchr SIMD scan vs naive O(n) loop (55× faster on 256KB buffers with status at end) - benches/ecsrp5.rs — WCurve::new() heavy math vs cached LazyLock (~123,000× faster access) Also adds BENCHMARKS.md with usage instructions and example results. Visibility changes (bench-only): - scan_status_message is now pub (was #[cfg(test)] only) - WCurve and WCURVE are now pub in ecsrp5.rs dev-dependencies: criterion + pprof (optional flamegraph support) --- BENCHMARKS.md | 54 ++++ Cargo.lock | 638 ++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 22 ++ benches/bandwidth.rs | 79 +++++ benches/ecsrp5.rs | 19 ++ benches/protocol.rs | 65 +++++ benches/tcp_rx_scan.rs | 100 +++++++ src/client.rs | 4 +- src/ecsrp5.rs | 6 +- 9 files changed, 969 insertions(+), 18 deletions(-) create mode 100644 BENCHMARKS.md create mode 100644 benches/bandwidth.rs create mode 100644 benches/ecsrp5.rs create mode 100644 benches/protocol.rs create mode 100644 benches/tcp_rx_scan.rs diff --git a/BENCHMARKS.md b/BENCHMARKS.md new file mode 100644 index 0000000..afad982 --- /dev/null +++ b/BENCHMARKS.md @@ -0,0 +1,54 @@ +# Benchmarks + +This project uses [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for performance benchmarking and regression detection. + +## Running Benchmarks + +Run all benchmarks: +```bash +cargo bench +``` + +Run a specific benchmark suite: +```bash +cargo bench --bench protocol +cargo bench --bench bandwidth +cargo bench --bench tcp_rx_scan +cargo bench --bench ecsrp5 +``` + +Run in "quick" mode (fewer iterations, useful for development): +```bash +cargo bench --bench tcp_rx_scan -- --quick +``` + +## Benchmark Suites + +### `protocol` — Protocol Serialization +Measures the zero-allocation serialization/deserialization of `Command` (16 bytes) and `StatusMessage` (12 bytes) structs. + +### `bandwidth` — Bandwidth State Atomics +Measures `BandwidthState` hot-path operations: `fetch_add`, `spend_budget`, `calc_send_interval`, `advance_next_send`, and `summary`. + +### `tcp_rx_scan` — TCP RX Status Message Scan +Compares the optimized `memchr`-based scan against the old naive O(n) byte-by-byte loop on 256KB buffers. Key scenarios: +- **All zeros** (common case — data packets contain no status) +- **Status at start** +- **Status at end** (worst case for naive scan) +- **Split messages** (status spans two TCP reads) + +### `ecsrp5` — EC-SRP5 Curve Construction +Compares `WCurve::new()` (heavy `BigUint` modular arithmetic) against the cached `&*WCURVE` access to demonstrate the Sprint 1 optimization. + +## Interpreting Results + +Criterion generates HTML reports in `target/criterion/`. Open `target/criterion/report/index.html` after running benchmarks to view interactive charts. + +Example results (Apple M3 Pro, release profile): + +| Benchmark | Naive/Uncached | Optimized/Cached | Speedup | +|-----------|---------------|------------------|---------| +| TCP RX scan 256KB (status at end) | 251 µs | 4.5 µs | **~55×** | +| WCurve construction | 126 µs | 1.0 ns | **~123,000×** | +| Command serialize | — | 7.7 ns | — | +| Bandwidth `fetch_add` | — | ~1 ns | — | diff --git a/Cargo.lock b/Cargo.lock index 2b94b51..5d111b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,34 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +39,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -20,6 +57,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -76,6 +119,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "askama" version = "0.15.6" @@ -203,6 +252,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "basic-toml" version = "0.1.10" @@ -212,6 +276,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -246,6 +316,7 @@ dependencies = [ "bytes", "chrono", "clap", + "criterion", "hostname", "ldap3", "md-5", @@ -253,13 +324,14 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "pprof", "rand", "rusqlite", "serde", "serde_json", "sha2", "socket2 0.5.10", - "thiserror", + "thiserror 2.0.18", "tokio", "tower-http", "tracing", @@ -272,12 +344,24 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.58" @@ -307,6 +391,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.6.0" @@ -375,6 +486,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -384,6 +504,73 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -403,6 +590,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "digest" version = "0.10.7" @@ -435,6 +631,32 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -475,6 +697,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -620,6 +854,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -628,11 +874,28 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -666,6 +929,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hostname" version = "0.4.2" @@ -916,12 +1185,50 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inferno" +version = "0.11.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +dependencies = [ + "ahash", + "indexmap", + "is-terminal", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml", + "rgb", + "str_stack", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -969,7 +1276,7 @@ dependencies = [ "native-tls", "nom", "percent-encoding", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-native-tls", "tokio-stream", @@ -1058,6 +1365,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -1080,6 +1396,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.2.0" @@ -1108,6 +1433,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1137,6 +1473,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1155,6 +1501,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1167,13 +1522,19 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl" version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -1252,6 +1613,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -1261,6 +1650,29 @@ dependencies = [ "zerovec", ] +[[package]] +name = "pprof" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afad4d4df7b31280028245f152d5a575083e2abb822d05736f5e47653e77689f" +dependencies = [ + "aligned-vec", + "backtrace", + "cfg-if", + "criterion", + "findshlibs", + "inferno", + "libc", + "log", + "nix", + "once_cell", + "smallvec", + "spin", + "symbolic-demangle", + "tempfile", + "thiserror 1.0.69", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1289,6 +1701,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.45" @@ -1298,6 +1719,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1334,13 +1761,45 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1360,6 +1819,15 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + [[package]] name = "rsqlite-vfs" version = "0.1.0" @@ -1367,7 +1835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" dependencies = [ "hashbrown 0.16.1", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1376,7 +1844,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "bitflags", + "bitflags 2.11.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1385,6 +1853,12 @@ dependencies = [ "sqlite-wasm-rs", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -1397,7 +1871,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -1416,6 +1890,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -1437,7 +1920,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation", "core-foundation-sys", "libc", @@ -1594,6 +2077,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + [[package]] name = "sqlite-wasm-rs" version = "0.5.2" @@ -1612,12 +2104,41 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "symbolic-common" +version = "12.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332615d90111d8eeaf86a84dc9bbe9f65d0d8c5cf11b4caccedc37754eb0dcfd" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912017718eb4d21930546245af9a3475c9dccf15675a5c215664e76621afc471" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + [[package]] name = "syn" version = "2.0.117" @@ -1659,13 +2180,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1698,6 +2239,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.50.0" @@ -1782,7 +2333,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -1924,6 +2475,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -1942,6 +2503,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2039,12 +2610,53 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", ] +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2253,7 +2865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "indexmap", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index 05b4380..34f3d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,3 +70,25 @@ codegen-units = 1 inherits = "release" opt-level = "z" panic = "abort" + +# --- Benchmarks --- + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } +pprof = { version = "0.14", features = ["criterion", "flamegraph"] } + +[[bench]] +name = "protocol" +harness = false + +[[bench]] +name = "bandwidth" +harness = false + +[[bench]] +name = "tcp_rx_scan" +harness = false + +[[bench]] +name = "ecsrp5" +harness = false diff --git a/benches/bandwidth.rs b/benches/bandwidth.rs new file mode 100644 index 0000000..96a7701 --- /dev/null +++ b/benches/bandwidth.rs @@ -0,0 +1,79 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use btest_rs::bandwidth::{BandwidthState, calc_send_interval, advance_next_send}; +use std::sync::atomic::Ordering; +use std::time::{Duration, Instant}; + +fn bench_atomic_fetch_add(c: &mut Criterion) { + let state = BandwidthState::new(); + c.bench_function("bandwidth_rx_bytes_fetch_add", |b| { + b.iter(|| { + black_box(state.rx_bytes.fetch_add(1500, Ordering::Relaxed)); + }) + }); + c.bench_function("bandwidth_tx_bytes_fetch_add", |b| { + b.iter(|| { + black_box(state.tx_bytes.fetch_add(32768, Ordering::Relaxed)); + }) + }); +} + +fn bench_spend_budget(c: &mut Criterion) { + // Unlimited budget (fast path) + let unlimited = BandwidthState::new(); + c.bench_function("spend_budget_unlimited", |b| { + b.iter(|| black_box(unlimited.spend_budget(black_box(1500)))) + }); + + // Limited budget + let limited = BandwidthState::new(); + limited.byte_budget.store(1_000_000_000, Ordering::SeqCst); + c.bench_function("spend_budget_limited", |b| { + b.iter(|| black_box(limited.spend_budget(black_box(1500)))) + }); +} + +fn bench_calc_send_interval(c: &mut Criterion) { + c.bench_function("calc_interval_100mbps_1500b", |b| { + b.iter(|| black_box(calc_send_interval(black_box(100_000_000), black_box(1500)))) + }); + c.bench_function("calc_interval_1gbps_32768b", |b| { + b.iter(|| black_box(calc_send_interval(black_box(1_000_000_000), black_box(32768)))) + }); + c.bench_function("calc_interval_unlimited", |b| { + b.iter(|| black_box(calc_send_interval(black_box(0), black_box(1500)))) + }); +} + +fn bench_advance_next_send(c: &mut Criterion) { + let iv = Duration::from_micros(120); + let now = Instant::now(); + let mut next = now; + c.bench_function("advance_next_send", |b| { + b.iter(|| { + let r = advance_next_send(&mut next, iv, now); + black_box(r); + }); + next = now; + }); +} + +fn bench_summary(c: &mut Criterion) { + let state = BandwidthState::new(); + // Pre-populate some values so loads are real + state.total_tx_bytes.store(1_000_000_000, Ordering::Relaxed); + state.total_rx_bytes.store(2_000_000_000, Ordering::Relaxed); + state.intervals.store(100, Ordering::Relaxed); + c.bench_function("bandwidth_summary", |b| { + b.iter(|| black_box(state.summary())) + }); +} + +criterion_group!( + bandwidth_benches, + bench_atomic_fetch_add, + bench_spend_budget, + bench_calc_send_interval, + bench_advance_next_send, + bench_summary +); +criterion_main!(bandwidth_benches); diff --git a/benches/ecsrp5.rs b/benches/ecsrp5.rs new file mode 100644 index 0000000..fa609f0 --- /dev/null +++ b/benches/ecsrp5.rs @@ -0,0 +1,19 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use btest_rs::ecsrp5::{WCurve, WCURVE}; + +fn bench_wcurve_new(c: &mut Criterion) { + c.bench_function("wcurve_new_uncached", |b| { + b.iter(|| black_box(WCurve::new())) + }); +} + +fn bench_wcurve_cached(c: &mut Criterion) { + // Force initialization before benchmarking + let _ = &*WCURVE; + c.bench_function("wcurve_cached_access", |b| { + b.iter(|| black_box(&*WCURVE)) + }); +} + +criterion_group!(ecsrp5_benches, bench_wcurve_new, bench_wcurve_cached); +criterion_main!(ecsrp5_benches); diff --git a/benches/protocol.rs b/benches/protocol.rs new file mode 100644 index 0000000..53cd9c7 --- /dev/null +++ b/benches/protocol.rs @@ -0,0 +1,65 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use btest_rs::protocol::{Command, StatusMessage, CMD_PROTO_TCP, CMD_DIR_BOTH}; + +fn bench_command_serialize(c: &mut Criterion) { + let cmd = Command::new(CMD_PROTO_TCP, CMD_DIR_BOTH); + c.bench_function("command_serialize", |b| { + b.iter(|| black_box(cmd.serialize())) + }); +} + +fn bench_command_deserialize(c: &mut Criterion) { + let bytes = [0x01, 0x03, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + c.bench_function("command_deserialize", |b| { + b.iter(|| black_box(Command::deserialize(black_box(&bytes)))) + }); +} + +fn bench_status_message_serialize(c: &mut Criterion) { + let msg = StatusMessage { + seq: 42, + bytes_received: 1_000_000, + cpu_load: 50, + }; + c.bench_function("status_message_serialize", |b| { + b.iter(|| black_box(msg.serialize())) + }); +} + +fn bench_status_message_deserialize(c: &mut Criterion) { + let bytes = [0x07, 0xB2, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x40, 0x42, 0x0F, 0x00]; + c.bench_function("status_message_deserialize", |b| { + b.iter(|| black_box(StatusMessage::deserialize(black_box(&bytes)))) + }); +} + +fn bench_roundtrip(c: &mut Criterion) { + let cmd = Command::new(CMD_PROTO_TCP, CMD_DIR_BOTH); + let msg = StatusMessage { + seq: 99, + bytes_received: 50_000, + cpu_load: 75, + }; + c.bench_function("command_roundtrip", |b| { + b.iter(|| { + let s = black_box(cmd.serialize()); + black_box(Command::deserialize(&s)) + }) + }); + c.bench_function("status_message_roundtrip", |b| { + b.iter(|| { + let s = black_box(msg.serialize()); + black_box(StatusMessage::deserialize(&s)) + }) + }); +} + +criterion_group!( + protocol_benches, + bench_command_serialize, + bench_command_deserialize, + bench_status_message_serialize, + bench_status_message_deserialize, + bench_roundtrip +); +criterion_main!(protocol_benches); diff --git a/benches/tcp_rx_scan.rs b/benches/tcp_rx_scan.rs new file mode 100644 index 0000000..26d641a --- /dev/null +++ b/benches/tcp_rx_scan.rs @@ -0,0 +1,100 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use btest_rs::client::scan_status_message; +use btest_rs::protocol::STATUS_MSG_TYPE; + +/// Naive O(n) byte-by-byte scan — the old implementation. +fn naive_scan(buf: &[u8]) -> Option { + const STATUS_MSG_SIZE: usize = 12; + if buf.len() < STATUS_MSG_SIZE { + return None; + } + for i in 0..=(buf.len() - STATUS_MSG_SIZE) { + if buf[i] == STATUS_MSG_TYPE && buf[i + 1] >= 0x80 { + return Some((buf[i + 1] & 0x7F).min(100)); + } + } + None +} + +fn make_buffer(size: usize, status_at: Option) -> Vec { + let mut buf = vec![0u8; size]; + if let Some(pos) = status_at { + buf[pos] = STATUS_MSG_TYPE; + buf[pos + 1] = 0x80 | 50; // CPU = 50% + } + buf +} + +fn bench_scan_all_zeros(c: &mut Criterion) { + let mut group = c.benchmark_group("tcp_rx_scan_all_zeros"); + for size in [4096, 65536, 262144] { + let buf = make_buffer(size, None); + group.throughput(Throughput::Bytes(size as u64)); + group.bench_with_input(BenchmarkId::new("naive", size), &buf, |b, buf| { + b.iter(|| black_box(naive_scan(black_box(buf)))) + }); + group.bench_with_input(BenchmarkId::new("memchr", size), &buf, |b, buf| { + b.iter(|| black_box(scan_status_message(black_box(&[]), black_box(buf)))) + }); + } + group.finish(); +} + +fn bench_scan_status_at_start(c: &mut Criterion) { + let mut group = c.benchmark_group("tcp_rx_scan_status_at_start"); + for size in [4096, 65536, 262144] { + let buf = make_buffer(size, Some(0)); + group.throughput(Throughput::Bytes(size as u64)); + group.bench_with_input(BenchmarkId::new("naive", size), &buf, |b, buf| { + b.iter(|| black_box(naive_scan(black_box(buf)))) + }); + group.bench_with_input(BenchmarkId::new("memchr", size), &buf, |b, buf| { + b.iter(|| black_box(scan_status_message(black_box(&[]), black_box(buf)))) + }); + } + group.finish(); +} + +fn bench_scan_status_at_end(c: &mut Criterion) { + let mut group = c.benchmark_group("tcp_rx_scan_status_at_end"); + for size in [4096, 65536, 262144] { + let buf = make_buffer(size, Some(size - 12)); + group.throughput(Throughput::Bytes(size as u64)); + group.bench_with_input(BenchmarkId::new("naive", size), &buf, |b, buf| { + b.iter(|| black_box(naive_scan(black_box(buf)))) + }); + group.bench_with_input(BenchmarkId::new("memchr", size), &buf, |b, buf| { + b.iter(|| black_box(scan_status_message(black_box(&[]), black_box(buf)))) + }); + } + group.finish(); +} + +fn bench_scan_split_message(c: &mut Criterion) { + // Simulate a status message split across two reads: + // carry has first 5 bytes, buf has remaining 7 bytes + let mut carry = vec![0u8; 5]; + carry[0] = STATUS_MSG_TYPE; + carry[1] = 0x80 | 75; + let buf = vec![0u8; 7]; + + c.bench_function("scan_split_5_7", |b| { + b.iter(|| black_box(scan_status_message(black_box(&carry), black_box(&buf)))) + }); + + // Split with 2 bytes in carry (status type + cpu byte), 10 in buf + let carry_2 = vec![STATUS_MSG_TYPE, 0x80 | 33]; + let buf_10 = vec![0u8; 10]; + c.bench_function("scan_split_2_10", |b| { + b.iter(|| black_box(scan_status_message(black_box(&carry_2), black_box(&buf_10)))) + }); +} + +criterion_group!( + tcp_rx_scan_benches, + bench_scan_all_zeros, + bench_scan_status_at_start, + bench_scan_status_at_end, + bench_scan_split_message +); +criterion_main!(tcp_rx_scan_benches); diff --git a/src/client.rs b/src/client.rs index 226f209..6f9d873 100644 --- a/src/client.rs +++ b/src/client.rs @@ -573,8 +573,8 @@ async fn udp_client_status_loop( } /// Scan for a status message in `carry` + `buf` and return the CPU value if found. -#[cfg(test)] -fn scan_status_message(carry: &[u8], buf: &[u8]) -> Option { +#[allow(dead_code)] +pub fn scan_status_message(carry: &[u8], buf: &[u8]) -> Option { let carry_len = carry.len(); // 1) Check split across carry + buf if carry_len > 0 { diff --git a/src/ecsrp5.rs b/src/ecsrp5.rs index a3f19d6..11842f4 100644 --- a/src/ecsrp5.rs +++ b/src/ecsrp5.rs @@ -42,7 +42,7 @@ static WEIERSTRASS_A: LazyLock = LazyLock::new(|| { .unwrap() }); -static WCURVE: LazyLock = LazyLock::new(WCurve::new); +pub static WCURVE: LazyLock = LazyLock::new(WCurve::new); const MONT_A: u64 = 486662; @@ -244,14 +244,14 @@ impl Point { // --- WCurve: Curve25519 in Weierstrass form --- -struct WCurve { +pub struct WCurve { g: Point, conversion_from_m: BigUint, conversion_to_m: BigUint, } impl WCurve { - fn new() -> Self { + pub fn new() -> Self { let p_val = &*P; let mont_a = BigUint::from(MONT_A); let three_inv = modinv(&BigUint::from(3u32), p_val);