diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d6272ac --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: + - master + - dev + - boilerplate + - idiomatic + - rc1 + - rc2 + - rc3 + pull_request: + +permissions: + contents: read + +defaults: + run: + shell: bash + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Stable checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Install pinned nightly rustfmt + run: rustup toolchain install nightly-2026-08-08 --profile minimal --component rustfmt + + - uses: Swatinem/rust-cache@v2 + + - name: cargo test (all targets) + run: cargo test --all-targets --locked + + - name: cargo test (no default features) + run: cargo test --no-default-features --locked + + - name: cargo test (binary scaling) + run: cargo test --features binary-scaling --locked + + - name: cargo clippy + run: cargo clippy --all-targets --locked -- -D warnings + + - name: cargo doc + run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked + + - name: rustfmt + run: RUSTUP_TOOLCHAIN=nightly-2026-08-08 ./scripts/fmt --check + + - name: DOC_76 checker + run: python3 scripts/check_doc_76.py + + - name: RUST_TEST_NAMING checker + run: python3 scripts/check_test_names.py + + - name: DERIVE_LAYOUT checker + run: python3 scripts/check_derives.py + + - name: cargo build (example) + run: cargo build --example build_histogram --locked + + - name: cargo publish (dry run) + run: cargo publish --dry-run --locked + + msrv: + name: MSRV (1.74) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@1.74.0 + + - uses: Swatinem/rust-cache@v2 + + - name: cargo check (library) + run: cargo +1.74.0 check --lib --locked diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 440e8a3..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "master", "dev", "gha" ] - pull_request: - branches: [ "master", "dev", "gha" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/CHANGES.md b/CHANGES.md index e2bc688..f36f1a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -# p99.Rust CHANGES +# p99.Rust - Changes ## 0.0.3 - 14th July 2026 diff --git a/Cargo.lock b/Cargo.lock index 0626e91..499e0bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -40,15 +40,18 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base-traits" -version = "0.0.13" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb2de59cca22f51f3301a09c549b0b4a32f56e8a007d0d44b02a1ce42975b37" +checksum = "69068402cce0b92d771a83fb17d617a975ce1b8122968fdd025e1ed6c7bd0d5e" +dependencies = [ + "bt-rs", +] [[package]] name = "bt-rs" -version = "0.0.1" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4985bd0bdfcd2a13bdeaa1798240f2168998107a7939a39384c4c7608392a26" +checksum = "66c801a3e540d22c99661706133db245edae489c872cfdb2f5925b2077170285" [[package]] name = "cast" @@ -58,9 +61,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "shlex", @@ -101,18 +104,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstyle", "clap_lex", @@ -165,15 +168,15 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "either" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "half" @@ -203,9 +206,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "memchr" @@ -248,27 +251,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -278,9 +281,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -304,9 +307,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -314,29 +317,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -353,9 +356,20 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -364,9 +378,9 @@ dependencies = [ [[package]] name = "test_help-rs" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "860ba0c1f6ec76a108a38b90425e38a84d024597cfe4deacb4f19f5a9839541d" +checksum = "2279bf04d9491e2b3bcd59cbc12eb9d7ac914c81290c8e54cdc92225dbb52cbb" dependencies = [ "base-traits", "bt-rs", @@ -446,26 +460,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 70d9f92..5833e4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,24 +8,29 @@ authors = [ "Matt Wilson ", ] categories = [ - "development-tools::profiling", "algorithms", + "development-tools::profiling", ] description = "Low-cost generation of performance percentiles (p50, p90, p99, etc.)" +documentation = "https://docs.rs/p99" edition = "2021" homepage = "https://github.com/synesissoftware/p99.Rust" keywords = [ - "percentiles", - "metrics", "latency", + "metrics", "performance", + "percentiles", ] license = "BSD-3-Clause" name = "p99" readme = "README.md" repository = "https://github.com/synesissoftware/p99.Rust" +rust-version = "1.74" version = "0.0.3" +[package.metadata.docs.rs] +all-features = true + # ########################################################## # Targets @@ -79,7 +84,7 @@ binary-scaling = [ criterion = { version = "0.8", default-features = false, features = [ ] } -test_help-rs = { version = "0.1", default-features = false, features = [ +test_help-rs = { version = "0.2.1", default-features = false, features = [ ] } diff --git a/EXAMPLES.md b/EXAMPLES.md index f9dd78d..535c36d 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,9 +1,9 @@ -# p99.Rust Examples +# p99.Rust - Examples |Name|Source & Description|Summary| |---|---|---| -|**build_histogram**|[examples/build_histogram/main.rs](/examples/build_histogram/main.rs)
[examples/build_histogram.md](/examples/build_histogram.md)|Lists files in the current directory whose paths match one or more shell wildcard pattern(s), using `shwild::matches()`.| +|**build_histogram**|[examples/build_histogram.rs](./examples/build_histogram.rs)|Builds a `Histogram` from simulated event delays and displays its recorded statistics and approximated percentiles.| diff --git a/NEWS.md b/NEWS.md index 790b5ad..69b1966 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# p99.Rust - NEWS +# p99.Rust - News | Date | News Item | | --------------------- | ----------------------------------------- | diff --git a/README.md b/README.md index 254c738..678430c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # p99.Rust +Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.). + ![Language](https://img.shields.io/badge/Rust-000000?style=flat&logo=rust&logoColor=white) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![Crates.io](https://img.shields.io/crates/v/p99.svg)](https://crates.io/crates/p99) [![GitHub release](https://img.shields.io/github/v/release/synesissoftware/p99.Rust.svg)](https://github.com/synesissoftware/p99.Rust/releases/latest) [![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/p99.Rust)](https://github.com/synesissoftware/p99.Rust/commits/master) -[![Crates.io](https://img.shields.io/crates/v/p99.svg)](https://crates.io/crates/p99) - -Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.). +[![MSRV](https://img.shields.io/badge/MSRV-1.74-lightgrey)](https://github.com/rust-lang/rust/releases/tag/1.74.0) +[![CI](https://github.com/synesissoftware/p99.Rust/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/p99.Rust/actions/workflows/ci.yml) +[![docs.rs](https://docs.rs/p99/badge.svg)](https://docs.rs/p99) ## Table of Contents @@ -35,7 +38,7 @@ Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.). - [Where to get help](#where-to-get-help) - [Contribution guidelines](#contribution-guidelines) - [Dependencies](#dependencies) - - [Dev Dependencies](#dev-dependencies) + - [Development Dependencies](#development-dependencies) - [License](#license) @@ -64,10 +67,13 @@ Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.). ### Performance Claims * **Zero Allocation**: `Histogram` does not allocate memory on the heap during creation, event insertion, or percentile queries. It is a compact (~576-byte) structure that can reside entirely on the stack or be embedded in other structures. -* **Ultra-Low Latency Insertion**: Recording a latency measurement (`push_event_time_ns`) takes approximately **11 nanoseconds** (about 35 CPU cycles on modern hardware). -* **Blazing-Fast Queries**: Querying percentiles (such as `value_at_p99()`) takes only **11 to 17 nanoseconds**, depending on the distribution of events across the buckets. +* **Low-Latency Operations**: The fixed bucket layout is designed for low-latency insertion and percentile queries. Actual timings depend on the processor, compiler, build profile, and workload. * **Instruction-Cache Friendly**: The query methods are designed with a "thin caller / heavy worker" pattern to prevent instruction-cache bloat and maintain high CPU cache locality under real-world workloads. +The statements above describe implementation characteristics or design goals, +not guaranteed timings. Measured results from the checked-in Criterion +benchmark are reported below. + ### Trade-offs & Sacrifices * **Logarithmic Precision**: To achieve zero allocation and constant-time operations, `Histogram` sacrifices exact precision. It does not store individual event times. Instead, values are grouped into logarithmic buckets. @@ -77,16 +83,16 @@ Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.). ## Installation -Reference in **Cargo.toml** in the usual way: +Reference the current release in **Cargo.toml** in the usual way: ```toml -p99 = { version = "0" } +p99 = { version = "0.0.3" } ``` To enable the optional binary-scaling optimization: ```toml -p99 = { version = "0", features = ["binary-scaling"] } +p99 = { version = "0.0.3", features = ["binary-scaling"] } ``` @@ -117,7 +123,7 @@ Add the feature in your **Cargo.toml**: ```toml [dependencies] -p99 = { version = "0", features = ["binary-scaling"] } +p99 = { version = "0.0.3", features = ["binary-scaling"] } ``` Or, when building from the command line: @@ -132,7 +138,17 @@ cargo run --example build_histogram --features binary-scaling #### Benchmark Results -Measured with [**criterion**](https://github.com/bheisler/criterion.rs) on 100k events (Apple M-series, release profile). Only the integer-based percentile methods are affected; the generic `value_at_percentile(f64)` method is unchanged. +Measured with [**criterion**](https://github.com/bheisler/criterion.rs) on +100,000 events per workload, using the release profile on an Apple M-series +machine. Criterion's default measurement configuration was used. The exact +machine, operating-system, compiler, and Criterion output are not recorded, +so these results are illustrative rather than guaranteed. Only the +integer-based percentile methods are affected; the generic +`value_at_percentile(f64)` method is unchanged. + +Run the benchmark in the release profile with +`cargo bench --bench histogram --release`, then repeat with +`--features binary-scaling` to reproduce the comparison. | Method | Default | `binary-scaling` | Improvement | |---|---:|---:|---:| @@ -252,13 +268,16 @@ Defect reports, feature requests, and pull requests are welcome on https://githu **p99.Rust** has no (non-development) dependencies. -#### Dev Dependencies +#### Development Dependencies Crates upon which **p99.Rust** has development dependencies: * [**criterion**](https://github.com/bheisler/criterion.rs); * [**test_help-rs**](https://github.com/synesissoftware/test_help-rs); +**Cargo.lock** is retained so local and CI validation use a reproducible +dependency graph. Commands that consume the lockfile use `--locked`. + ### License diff --git a/TODO.md b/TODO.md index df6990d..69668ad 100644 --- a/TODO.md +++ b/TODO.md @@ -10,13 +10,13 @@ ## Functional improvements -- [x] ~~~`Debug` form~~~; -- [x] ~~~binary scaling~~~; +* [x] ~~~`Debug` form~~~; +* [x] ~~~binary scaling~~~; ## Performance improvements -- [x] ~~~binary scaling~~~; +* [x] ~~~binary scaling~~~; ## Packaging improvements diff --git a/benches/histogram.rs b/benches/histogram.rs index 282300e..2d2e49a 100644 --- a/benches/histogram.rs +++ b/benches/histogram.rs @@ -41,14 +41,14 @@ fn build_wide_range_histogram() -> Histogram { } fn bench_percentile_comparison( - c: &mut Criterion, - h: &Histogram, - percentile_name: &str, - workload_name: &str, - float_val: f64, - int_method: F, + c : &mut Criterion, + h : &Histogram, + percentile_name : &str, + workload_name : &str, + float_val : f64, + int_method : F, ) where - F: Fn(&Histogram) -> Option + Copy, + F : Fn(&Histogram) -> Option + Copy, { let float_str = if float_val.fract() == 0.0 { format!("{:.1}", float_val) @@ -73,8 +73,8 @@ fn bench_percentile_comparison( // Benchmarks -fn BENCHMARK_bucket_index_SMALL(c: &mut Criterion) { - let id = format!("`Histogram::bucket_index(1)`"); +fn BENCHMARK_bucket_index_SMALL(c : &mut Criterion) { + let id = "`Histogram::bucket_index(1)`".to_string(); c.bench_function(&id, |b| { b.iter(|| { @@ -85,8 +85,8 @@ fn BENCHMARK_bucket_index_SMALL(c: &mut Criterion) { }); } -fn BENCHMARK_bucket_index_LARGE(c: &mut Criterion) { - let id = format!("`Histogram::bucket_index(u64::MAX)`"); +fn BENCHMARK_bucket_index_LARGE(c : &mut Criterion) { + let id = "`Histogram::bucket_index(u64::MAX)`".to_string(); c.bench_function(&id, |b| { b.iter(|| { @@ -97,12 +97,12 @@ fn BENCHMARK_bucket_index_LARGE(c: &mut Criterion) { }); } -fn BENCHMARK_push_event_time_ns(c: &mut Criterion) { - let id = format!("`Histogram::push_event_time_ns()`"); +fn BENCHMARK_push_event_time_ns(c : &mut Criterion) { + let id = "`Histogram::push_event_time_ns()`".to_string(); c.bench_function(&id, |b| { b.iter_batched_ref( - || Histogram::default(), + Histogram::default, |h| { std_hint::black_box(h.push_event_time_ns(std_hint::black_box(12_345))); }, @@ -111,8 +111,8 @@ fn BENCHMARK_push_event_time_ns(c: &mut Criterion) { }); } -fn BENCHMARK_clear(c: &mut Criterion) { - let id = format!("`Histogram::clear()`"); +fn BENCHMARK_clear(c : &mut Criterion) { + let id = "`Histogram::clear()`".to_string(); c.bench_function(&id, |b| { b.iter_batched_ref( @@ -124,14 +124,15 @@ fn BENCHMARK_clear(c: &mut Criterion) { h }, |h| { - std_hint::black_box(h.clear()); + h.clear(); + std_hint::black_box(h); }, BatchSize::SmallInput, ) }); } -fn BENCHMARK_percentile_queries(c: &mut Criterion) { +fn BENCHMARK_percentile_queries(c : &mut Criterion) { // 1. Benchmark under dense sequential 100k events let seq_h = build_sequential_histogram(); bench_percentile_comparison(c, &seq_h, "99", "100k events", 99.0, |h| h.value_at_p99()); diff --git a/examples/build_histogram.rs b/examples/build_histogram.rs index 2165146..e07eed2 100644 --- a/examples/build_histogram.rs +++ b/examples/build_histogram.rs @@ -10,14 +10,16 @@ use std::{ }; struct SimpleRng { - state: u64, + state : u64, } // API functions impl SimpleRng { - fn new(seed: u64) -> Self { - Self { state: seed } + fn new(seed : u64) -> Self { + Self { + state : seed + } } } @@ -35,24 +37,31 @@ impl SimpleRng { fn main() { let range = match std_env::var("P99_RANGE") { - Ok(val) => match val.parse::() { - Ok(num) => num, - Err(_) => { - eprintln!("Warning: failed to parse P99_RANGE value '{}', defaulting to 1,000,000", val); - - 1_000_000 - }, + Ok(val) => { + match val.parse::() { + Ok(num) => num, + Err(_) => { + eprintln!( + "Warning: failed to parse P99_RANGE value '{}', defaulting to 1,000,000", + val + ); + + 1_000_000 + }, + } }, Err(_) => 1_000_000, }; let tries = match std_env::var("P99_TRIES") { - Ok(val) => match val.parse::() { - Ok(num) => num, - Err(_) => { - eprintln!("Warning: failed to parse P99_TRIES value '{}', defaulting to 100", val); - - 100 - }, + Ok(val) => { + match val.parse::() { + Ok(num) => num, + Err(_) => { + eprintln!("Warning: failed to parse P99_TRIES value '{}', defaulting to 100", val); + + 100 + }, + } }, Err(_) => 100, }; @@ -68,13 +77,11 @@ fn main() { let start = std_time::Instant::now(); if delay_ns < 1_000_000 { - let busy_wait_start = std_time::Instant::now(); while busy_wait_start.elapsed().as_nanos() < delay_ns as u128 { std::hint::black_box(1); } } else { - std_thread::sleep(std_time::Duration::from_micros(delay_ns)); } diff --git a/rustfmt.toml b/rustfmt.toml index 9dbe787..b912750 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,13 +1,15 @@ # rustfmt.toml for p99 (p99.Rust) # -# Requires nightly rustfmt with unstable features enabled, e.g.: +# Requires the pinned nightly rustfmt toolchain used by scripts/fmt: # # ./scripts/fmt # # Or: # -# RUSTFMT="$(rustup which --toolchain nightly rustfmt)" cargo fmt -- --unstable-features +# RUSTFMT="$(rustup which --toolchain nightly-2026-08-08 rustfmt)" cargo fmt -- --unstable-features +# +# The wrapper uses nightly-2026-08-08 by default. # # Based on rustfmt.toml circa cargo-fmt 1.4.37 era. # edition/style_edition updated for Rust 2021. diff --git a/scripts/check_derives.py b/scripts/check_derives.py new file mode 100755 index 0000000..c34617a --- /dev/null +++ b/scripts/check_derives.py @@ -0,0 +1,128 @@ +""" +Verify DERIVE_LAYOUT: multi-trait `#[derive(...)]` macros must be split +into separate single-trait lines, ordered alphabetically by trait name, +except tightly coupled groups (Eq/PartialEq, Ord/PartialOrd). +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +COUPLED_TRAIT_GROUPS = [ + ["Eq", "PartialEq"], + ["Ord", "PartialOrd"], +] + +PASS = "\N{WHITE HEAVY CHECK MARK}" # ✅ +FAIL = "\N{CROSS MARK}" # ❌ + + +def lint_file(filepath: Path) -> list[str]: + errors: list[str] = [] + + lines = filepath.read_text(encoding="utf-8").splitlines() + i = 0 + + while i < len(lines): + line = lines[i] + + if not re.match(r"^\s*#\[derive\(", line): + i += 1 + continue + + derive_block: list[tuple[int, str]] = [] + start_line_num = i + 1 + + while i < len(lines) and re.match(r"^\s*#\[derive\(", lines[i]): + derive_block.append((i + 1, lines[i])) + i += 1 + + parsed_lines: list[tuple[int, str, str]] = [] + block_has_error = False + + for line_num, line_str in derive_block: + match = re.search(r"#\[derive\((.*?)\)\]", line_str) + + if not match: + continue + + traits = [ + t.strip() + for t in match.group(1).split(",") + if t.strip() + ] + + if len(traits) > 1: + if traits not in COUPLED_TRAIT_GROUPS: + block_has_error = True + allowed = ", ".join( + f"'{', '.join(group)}'" + for group in COUPLED_TRAIT_GROUPS + ) + errors.append( + f"{filepath}:{line_num}: multi-trait derive " + f"'{line_str.strip()}' is not allowed " + f"(except coupled groups: {allowed})", + ) + elif len(traits) == 0: + block_has_error = True + errors.append( + f"{filepath}:{line_num}: empty derive attribute " + f"'{line_str.strip()}'", + ) + + sort_key = traits[0] if traits else "" + parsed_lines.append((line_num, line_str, sort_key)) + + if not block_has_error and len(parsed_lines) > 1: + sort_keys = [item[2] for item in parsed_lines] + + if sort_keys != sorted(sort_keys): + actual = [item[1].strip() for item in parsed_lines] + expected = [ + item[1].strip() + for item in sorted(parsed_lines, key=lambda x: x[2]) + ] + errors.append( + f"{filepath}:{start_line_num}: derive attributes not " + f"sorted alphabetically\n" + f" actual: {actual}\n" + f" expected: {expected}", + ) + + return errors + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + errors: list[str] = [] + + for directory in ("src", "examples", "benches", "test"): + base = root / directory + + if not base.is_dir(): + continue + + for path in sorted(base.rglob("*.rs")): + if "target" in path.parts: + continue + + errors.extend(lint_file(path)) + + if errors: + print( + f"{FAIL} DERIVE_LAYOUT violations:", + file=sys.stderr, + ) + print("\n".join(f" {FAIL} {error}" for error in errors), file=sys.stderr) + return 1 + + print(f"{PASS} DERIVE_LAYOUT: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_doc_76.py b/scripts/check_doc_76.py new file mode 100755 index 0000000..5d58d5b --- /dev/null +++ b/scripts/check_doc_76.py @@ -0,0 +1,65 @@ +""" +Verify DOC_76: public documentation comment lines are at most 76 characters. + +Code blocks inside doc comments (``` ... ```) are exempt, matching Synesis +Information Systems' internal project standards. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +DOC_LINE = re.compile(r"^\s*(//!|///)") + +PASS = "\N{WHITE HEAVY CHECK MARK}" # ✅ +FAIL = "\N{CROSS MARK}" # ❌ + + +def iter_doc_violations(path: Path) -> list[str]: + violations: list[str] = [] + in_codeblock = False + + for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + stripped = line.rstrip() + + if DOC_LINE.match(stripped) and re.search(r"\s*```\s*$", stripped): + in_codeblock = not in_codeblock + continue + + if in_codeblock or not DOC_LINE.match(stripped): + continue + + if len(stripped) > 76: + violations.append( + f"{path}:{line_no} ({len(stripped)} chars): {stripped}" + ) + + return violations + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + errors: list[str] = [] + + for path in sorted(root.rglob("*.rs")): + if "target" in path.parts: + continue + errors.extend(iter_doc_violations(path)) + + if errors: + print( + f"{FAIL} DOC_76 violations (doc comment lines must be <= 76 characters):", + file=sys.stderr, + ) + print("\n".join(f" {FAIL} {error}" for error in errors), file=sys.stderr) + return 1 + + print(f"{PASS} DOC_76: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_test_names.py b/scripts/check_test_names.py new file mode 100755 index 0000000..259397e --- /dev/null +++ b/scripts/check_test_names.py @@ -0,0 +1,242 @@ +""" +Verify RUST_TEST_NAMING: test functions and test modules use TEST_ prefix +and SHOUTING_SNAKE_CASE, except words that name a specific Rust construct +(type, function, macro, field, etc.) which must preserve exact case. + +When a construct name is embedded as a SHOUTING_SNAKE_CASE constant (or +PascalCase construct), it may be delimited with an extra underscore on +each side — e.g. HAVING__IGNORE_CASE__1. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +TEST_ATTR = re.compile(r"^\s*#\[(\w+::)?test(\(\))?\]") +FN_DEF = re.compile(r"^\s*fn\s+(\w+)") +MOD_DEF = re.compile(r"^\s*mod\s+(\w+)") +SNAKE_PART = re.compile(r"^[a-z][a-z0-9]*$") + +PASS = "\N{WHITE HEAVY CHECK MARK}" # ✅ +FAIL = "\N{CROSS MARK}" # ❌ + + +def is_pascal_case_atom(atom: str) -> bool: + return ( + atom[0].isupper() + and any(c.islower() for c in atom) + and atom.isalnum() + ) + + +def atom_violation(atom: str) -> str | None: + if atom.isupper() or atom.isdigit(): + return None + + if atom[0].islower() and all(SNAKE_PART.match(part) for part in atom.split("_")): + return None + + if is_pascal_case_atom(atom): + return None + + return ( + f"segment '{atom}' must be SHOUTING_SNAKE_CASE, a PascalCase construct " + "name, or a Rust snake_case identifier" + ) + + +def parse_padded_construct( + segments: list[str], start: int +) -> tuple[str | None, int, list[str]]: + """Parse __CONSTRUCT__ padding around a shouting or PascalCase atom.""" + violations: list[str] = [] + i = start + + while i < len(segments) and not segments[i]: + i += 1 + if i >= len(segments): + return None, i, ["empty segment padding without construct"] + + seg = segments[i] + atom: str | None = None + + if seg.isupper() or seg.isdigit(): + parts = [seg] + i += 1 + while i < len(segments) and segments[i] and ( + segments[i].isupper() or segments[i].isdigit() + ): + parts.append(segments[i]) + i += 1 + atom = "_".join(parts) + reason = atom_violation(atom) + if reason: + violations.append(reason) + elif seg[0].isupper() and is_pascal_case_atom(seg): + atom = seg + reason = atom_violation(seg) + if reason: + violations.append(reason) + i += 1 + else: + return None, start, ["empty segment padding without construct"] + + while i < len(segments) and not segments[i]: + i += 1 + + return atom, i, violations + + +def parse_name_atoms(rest: str) -> tuple[list[str], list[str]]: + """Split a test name body into atoms; return (atoms, violations).""" + atoms: list[str] = [] + violations: list[str] = [] + segments = rest.split("_") + i = 0 + + while i < len(segments): + seg = segments[i] + if not seg: + start = i + atom, i, viols = parse_padded_construct(segments, i) + violations.extend(viols) + if atom: + atoms.append(atom) + elif not viols: + violations.append(f"empty segment in '{rest}'") + if i == start: + i += 1 + continue + + if seg.isupper() or seg.isdigit(): + reason = atom_violation(seg) + if reason: + violations.append(reason) + else: + atoms.append(seg) + i += 1 + continue + + if seg[0].isupper(): + reason = atom_violation(seg) + if reason: + violations.append(reason) + else: + atoms.append(seg) + i += 1 + continue + + if SNAKE_PART.match(seg): + parts = [seg] + i += 1 + while i < len(segments) and SNAKE_PART.match(segments[i]): + parts.append(segments[i]) + i += 1 + atom = "_".join(parts) + reason = atom_violation(atom) + if reason: + violations.append(reason) + else: + atoms.append(atom) + continue + + violations.append( + f"segment '{seg}' must be SHOUTING_SNAKE_CASE, a PascalCase construct " + "name, or a Rust snake_case identifier" + ) + i += 1 + + return atoms, violations + + +def iter_name_violations(name: str) -> list[str]: + if not name.startswith("TEST_"): + return ["must start with 'TEST_'"] + + rest = name[len("TEST_") :] + if not rest: + return ["must have a name after 'TEST_'"] + + _, violations = parse_name_atoms(rest) + return violations + + +def iter_test_results(path: Path, root: Path) -> list[tuple[bool, str]]: + results: list[tuple[bool, str]] = [] + pending_test = False + display = path.relative_to(root) + + for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + stripped = line.rstrip() + + if TEST_ATTR.match(stripped): + pending_test = True + continue + + if pending_test and stripped.startswith("#["): + continue + + fn_match = FN_DEF.match(stripped) + if fn_match: + name = fn_match.group(1) + if pending_test: + pending_test = False + reasons = iter_name_violations(name) + label = f"{display}:{line_no}: test function '{name}'" + if reasons: + for reason in reasons: + results.append((False, f"{label}: {reason}")) + else: + results.append((True, label)) + continue + + mod_match = MOD_DEF.match(stripped) + if mod_match: + pending_test = False + name = mod_match.group(1) + if name.startswith("TEST_"): + reasons = iter_name_violations(name) + label = f"{display}:{line_no}: test module '{name}'" + if reasons: + for reason in reasons: + results.append((False, f"{label}: {reason}")) + else: + results.append((True, label)) + continue + + if stripped and not stripped.startswith("#") and stripped.endswith("{"): + pending_test = False + + return results + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + results: list[tuple[bool, str]] = [] + + for path in sorted(root.rglob("*.rs")): + if "target" in path.parts: + continue + results.extend(iter_test_results(path, root)) + + failures = [line for ok, line in results if not ok] + if failures: + print( + f"{FAIL} RUST_TEST_NAMING violations " + "(test functions and modules must use TEST_ + SHOUTING_SNAKE_CASE):", + file=sys.stderr, + ) + for ok, line in results: + mark = PASS if ok else FAIL + print(f" {mark} {line}", file=sys.stderr) + return 1 + + print(f"{PASS} RUST_TEST_NAMING: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/fmt b/scripts/fmt new file mode 100755 index 0000000..0a06baf --- /dev/null +++ b/scripts/fmt @@ -0,0 +1,13 @@ +#! /usr/bin/env bash +set -euo pipefail + +RUSTFMT_TOOLCHAIN="${RUSTFMT_TOOLCHAIN:-nightly-2026-08-08}" +RUSTFMT="$(rustup which --toolchain "${RUSTFMT_TOOLCHAIN}" rustfmt 2>/dev/null || true)" +if [[ -z "${RUSTFMT}" ]]; then + echo "error: ${RUSTFMT_TOOLCHAIN} rustfmt is required (see rustfmt.toml)" >&2 + echo " rustup toolchain install ${RUSTFMT_TOOLCHAIN} --component rustfmt" >&2 + exit 1 +fi + +export RUSTFMT +exec cargo fmt -- --unstable-features "$@" diff --git a/src/lib.rs b/src/lib.rs index 3e07d27..abfae2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,15 +14,15 @@ use std::{ /// and suited for high-frequency low-overhead timing measurements. #[derive(Clone)] pub struct Histogram { - event_count: usize, + event_count : usize, - event_time_total: u64, - has_overflowed: bool, + event_time_total : u64, + has_overflowed : bool, - min_event_time: Option, - max_event_time: Option, + min_event_time : Option, + max_event_time : Option, - buckets: [u64; 64], + buckets : [u64; 64], } // API functions @@ -47,15 +47,15 @@ impl Histogram { /// `u64`. pub fn push_event_duration( &mut self, - duration: std_time::Duration, + duration : std_time::Duration, ) -> bool { - return self.push_event_time_ns(duration.as_nanos() as u64); + self.push_event_time_ns(duration.as_nanos() as u64) } /// Pushes an event with the given number of nanoseconds. pub fn push_event_time_ns( &mut self, - time_in_ns: u64, + time_in_ns : u64, ) -> bool { if self.try_add_ns_to_total_and_update_minmax_and_count_(time_in_ns) { self.event_count += 1; @@ -72,12 +72,10 @@ impl Histogram { /// Pushes an event with the given number of microseconds. pub fn push_event_time_us( &mut self, - time_in_us: u64, + time_in_us : u64, ) -> bool { if let Some(time_in_ns) = time_in_us.checked_mul(1_000) { - let r = self.push_event_time_ns(time_in_ns); - - r + self.push_event_time_ns(time_in_ns) } else { self.has_overflowed = true; @@ -88,12 +86,10 @@ impl Histogram { /// Pushes an event with the given number of milliseconds. pub fn push_event_time_ms( &mut self, - time_in_ms: u64, + time_in_ms : u64, ) -> bool { if let Some(time_in_ns) = time_in_ms.checked_mul(1_000_000) { - let r = self.push_event_time_ns(time_in_ns); - - r + self.push_event_time_ns(time_in_ns) } else { self.has_overflowed = true; @@ -104,12 +100,10 @@ impl Histogram { /// Pushes an event with the given number of seconds. pub fn push_event_time_s( &mut self, - time_in_s: u64, + time_in_s : u64, ) -> bool { if let Some(time_in_ns) = time_in_s.checked_mul(1_000_000_000) { - let r = self.push_event_time_ns(time_in_ns); - - r + self.push_event_time_ns(time_in_ns) } else { self.has_overflowed = true; @@ -124,7 +118,7 @@ impl Histogram { /// Returns the count of events in a specific bucket. pub fn bucket_value( &self, - index: usize, + index : usize, ) -> Option { if index < 64 { Some(self.buckets[index]) @@ -143,7 +137,8 @@ impl Histogram { self.event_count } - /// Returns the total event time in nanoseconds, if no overflow occurred. + /// Returns the total event time in nanoseconds, if no overflow + /// occurred. pub fn event_time_total(&self) -> Option { if self.has_overflowed { None @@ -186,7 +181,7 @@ impl Histogram { /// events; otherwise, returns `None`. pub fn value_at_percentile( &self, - percentile: f64, + percentile : f64, ) -> Option { if self.event_count == 0 { return None; @@ -254,9 +249,7 @@ impl Histogram { } } - let r = self.max_event_time; - - r + self.max_event_time } /// Returns the approximated duration (in nanoseconds) at the 50th @@ -265,12 +258,12 @@ impl Histogram { /// # Return /// Returns `Some(value_in_ns)` if the histogram contains one or more /// events; otherwise, returns `None`. + #[allow(clippy::identity_op)] // this to have the consistent `* 1` #[inline(always)] pub fn value_at_p50(&self) -> Option { let target_rank = (self.event_count as u128 * 1) / 2; - let r = self.value_at_target_rank_impl(target_rank as u64); - r + self.value_at_target_rank_impl(target_rank as u64) } /// Returns the approximated duration (in nanoseconds) at the 75th @@ -282,9 +275,8 @@ impl Histogram { #[inline(always)] pub fn value_at_p75(&self) -> Option { let target_rank = (self.event_count as u128 * 3) / 4; - let r = self.value_at_target_rank_impl(target_rank as u64); - r + self.value_at_target_rank_impl(target_rank as u64) } /// Returns the approximated duration (in nanoseconds) at the 90th @@ -301,9 +293,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 90) / 100) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 95th @@ -320,9 +310,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 95) / 100) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99th @@ -339,9 +327,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 99) / 100) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99.5th @@ -358,9 +344,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 995) / 1_000) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99.9th @@ -377,9 +361,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 999) / 1_000) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99.99th @@ -396,9 +378,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 9_999) / 10_000) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99.999th @@ -415,9 +395,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 99_999) / 100_000) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } /// Returns the approximated duration (in nanoseconds) at the 99.9999th @@ -434,9 +412,7 @@ impl Histogram { #[cfg(not(feature = "binary-scaling"))] let target_rank = ((self.event_count as u128 * 999_999) / 1_000_000) as u64; - let r = self.value_at_target_rank_impl(target_rank); - - r + self.value_at_target_rank_impl(target_rank) } } @@ -445,21 +421,21 @@ impl Histogram { impl std_fmt::Debug for Histogram { fn fmt( &self, - f: &mut std_fmt::Formatter<'_>, + f : &mut std_fmt::Formatter<'_>, ) -> std_fmt::Result { struct BucketsDebug<'a>(&'a [u64; 64], bool); impl std_fmt::Debug for BucketsDebug<'_> { fn fmt( &self, - f: &mut std_fmt::Formatter<'_>, + f : &mut std_fmt::Formatter<'_>, ) -> std_fmt::Result { struct PowerOfTwoKey(usize); impl std_fmt::Debug for PowerOfTwoKey { fn fmt( &self, - f: &mut std_fmt::Formatter<'_>, + f : &mut std_fmt::Formatter<'_>, ) -> std_fmt::Result { write!(f, "\"2^{}\"", self.0) } @@ -504,12 +480,12 @@ impl std_fmt::Debug for Histogram { impl Default for Histogram { fn default() -> Self { Self { - event_count: 0, - event_time_total: 0, - has_overflowed: false, - min_event_time: None, - max_event_time: None, - buckets: [0; 64], + event_count : 0, + event_time_total : 0, + has_overflowed : false, + min_event_time : None, + max_event_time : None, + buckets : [0; 64], } } } @@ -532,7 +508,7 @@ impl Histogram { /// `leading_zeros()` instruction, avoiding loop and branching logic. #[doc(hidden)] #[inline] - pub fn bucket_index(time_in_ns: u64) -> usize { + pub fn bucket_index(time_in_ns : u64) -> usize { if time_in_ns <= 1 { 0 } else { @@ -546,7 +522,7 @@ impl Histogram { /// - Index `0` represents `[0, 1]` nanoseconds; /// - Any index `i` from `1` to `63` represents `[2^i, 2^(i+1) - 1]`; #[doc(hidden)] - pub fn bucket_range(index: usize) -> Option<(u64, u64)> { + pub fn bucket_range(index : usize) -> Option<(u64, u64)> { if index >= 64 { None } else if index == 0 { @@ -565,7 +541,7 @@ impl Histogram { fn try_add_ns_to_total_and_update_minmax_and_count_( &mut self, - time_in_ns: u64, + time_in_ns : u64, ) -> bool { if self.has_overflowed { return false; @@ -609,7 +585,7 @@ impl Histogram { fn value_at_target_rank_impl( &self, - target_rank: u64, + target_rank : u64, ) -> Option { if self.event_count == 0 { return None; @@ -670,9 +646,7 @@ impl Histogram { } } - let r = self.max_event_time; - - r + self.max_event_time } } @@ -701,7 +675,6 @@ mod tests { #[test] fn TEST_Histogram_Debug() { - // empty { let h = Histogram::default(); @@ -726,7 +699,6 @@ mod tests { #[test] fn TEST_Histogram_Debug_alternate() { - // empty { let h = Histogram::default();