diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3582a88..f16c5d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,10 +27,16 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: cargo test - run: cargo test + run: cargo test --locked - name: cargo clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy --all-targets --locked -- -D warnings + + - name: cargo build (test-doomgram example) + run: cargo build --examples --features test-doomgram --locked + + - name: cargo doc + run: cargo doc --no-deps --locked - name: rustfmt run: ./scripts/fmt --check @@ -41,6 +47,9 @@ jobs: - name: RUST_TEST_NAMING checker run: python3 scripts/check_test_names.py + - name: DERIVE_LAYOUT checker + run: python3 scripts/check_derives.py + msrv: name: MSRV (1.74) runs-on: ubuntu-latest @@ -52,4 +61,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: cargo check (library) + # Full `cargo test` needs dev-deps (criterion → clap_lex 2024 edition), + # which exceeds MSRV 1.74; the stable job runs the full test suite. run: cargo check --lib --locked diff --git a/CHANGES.md b/CHANGES.md index f887c03..1f3e485 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Diagnosticism.Rust - CHANGES +## 0.4.0 - 30th June 2026 + +* added `DoomGram::to_mmm()` and `DoomGram::to_nmmm()` — compact min/mean/max duration summaries using `nanoseconds_to_string()`; + + ## 0.3.2 - 28th June 2026 * optimisation of `nanoseconds_to_string()` — uses a custom return type `NanosecondsStr` for highly efficient conversion in vast majority of cases; @@ -11,7 +16,7 @@ * internal implementation improvements; -## 0.3.0 - 27th June 2026 +## 0.3.0 - 28th June 2026 * added `nanoseconds_to_string()` — compact human-readable duration formatting (behaviour matches **Diagnosticism.Python** 0.16.0); diff --git a/Cargo.lock b/Cargo.lock index d6c08b5..6dd1eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,7 +212,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "diagnosticism" -version = "0.3.2" +version = "0.4.0" dependencies = [ "base-traits", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 1ddb285..9a3c442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ name = "diagnosticism" readme = "README.md" repository = "https://github.com/synesissoftware/Diagnosticism.Rust" rust-version = "1.74" -version = "0.3.2" +version = "0.4.0" # ########################################################## diff --git a/EXAMPLES.md b/EXAMPLES.md index c717e5b..1ae8a18 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -3,7 +3,7 @@ |Name|Source & Description|Summary| |---|---|---| |**debug_squeezer**|[examples/debug_squeezer.rs](/examples/debug_squeezer.rs)
[examples/debug_squeezer.md](/examples/debug_squeezer.md)|An example using **Diagnosticism.Rust**'s `DebugSqueezer` type to simplify the `Debug` form of a user-defined type.| -|**doomgram**|[examples/doomgram.rs](/examples/doomgram.rs)
[examples/doomgram.md](/examples/doomgram.md)|An example using **Diagnosticism.Rust**'s `DoomGram` type to represent the performance of some time-consuming operations.| +|**doomgram**|[examples/doomgram.rs](/examples/doomgram.rs)
[examples/doomgram.md](/examples/doomgram.md)|An example using **Diagnosticism.Rust**'s `DoomGram` type to represent the performance of some time-consuming operations, including `to_strip()`, `to_mmm()`, and `to_nmmm()`.| |**ellipsis**|[examples/ellipsis.rs](/examples/ellipsis.rs)
[examples/ellipsis.md](/examples/ellipsis.md)|An example using **Diagnosticism.Rust**'s `Ellipsis` type to shorten the `Debug` form of a user-defined type.| |**password**|[examples/password.rs](/examples/password.rs)
[examples/password.md](/examples/password.md)|An example using **Diagnosticism.Rust**'s `Password` type to secure the `Debug` form of a user-defined type.| diff --git a/README.md b/README.md index 0d6e7ea..3722325 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Other facilities (that are not directly related to `Debug`) will be added to the - [Functions](#functions) - [Macros](#macros) - [Structures](#structures) + - [Redacting `Debug` output (`Ellipsis` and `Password`)](#redacting-debug-output-ellipsis-and-password) - [Traits](#traits) - [Examples](#examples) - [Example - `DoomGram`](#example---doomgram) @@ -108,7 +109,7 @@ The following macros are defined at the crate root (e.g. `use diagnosticism::fil The following structures are re-exported at the crate root (and defined in the [`diagnostics`](https://docs.rs/diagnosticism/latest/diagnosticism/diagnostics/index.html) module): * `DebugSqueezer` - used to assist with restricting the length of `Debug` forms of fields within a given width. See the example [**examples/debug_squeezer.md**](./examples/debug_squeezer.md); -* `DoomGram` - a **D**ecimal **O**rder-**O**f-**M**agnitude histo**G**ram structure that records efficiently duration values in the orders of magnitude 1ns+, 10ns+, 100ns+, 1µs+, ..., 10s+, 100s+ and provides a mechanism for displaying this histogram in a simple single 12-character display, which is useful for logging cumulative execution costs of components in long-running performance-sensitive applications. See the example [**examples/doomgram.md**](./examples/doomgram.md); +* `DoomGram` - a **D**ecimal **O**rder-**O**f-**M**agnitude histo**G**ram structure that records efficiently duration values in the orders of magnitude 1ns+, 10ns+, 100ns+, 1µs+, ..., 10s+, 100s+ and provides a mechanism for displaying this histogram in a simple single 12-character display (`to_strip()`), plus compact min/mean/max duration summaries (`to_mmm()` and `to_nmmm()`), which is useful for logging cumulative execution costs of components in long-running performance-sensitive applications. See the example [**examples/doomgram.md**](./examples/doomgram.md); * `NanosecondsStr` - compact storage for a formatted duration string; returned by `nanoseconds_to_string()`; typical outputs fit in 15 inline UTF-8 bytes without heap allocation; implements `Display`, `Deref` to `str`, and `AsRef`; * `Ellipsis` - provides the string `"..."` to be used for fields whose `Debug` forms are not to be expressed in terse (non-`#alternate()`) output. See the example [**examples/ellipsis.md**](./examples/ellipsis.md); * `Password` - provides strings such as `"********"` to be used for fields that are sensitive and whose `Debug` forms are not to be expressed. See the example [**examples/password.md**](./examples/password.md); @@ -134,16 +135,19 @@ Examples are provided in the ```examples``` directory, along with a markdown des ### Example - `DoomGram` -The example program **doomgram** (in **examples** directory, built with feature `test-doomgram`), illustrates use of `DoomGram` to capture the order-of-magnitude histogram of a large number of small random delays. The program source is: +The example program **doomgram** (in **examples** directory, built with feature `test-doomgram`), illustrates use of `DoomGram` to capture the order-of-magnitude histogram of a large number of small random delays, and to format min/mean/max duration summaries. The program source is: ```Rust // examples/doomgram.rs : example program illustrating use of `DoomGram` -use diagnosticism::DoomGram; +use diagnosticism::{ + doom_scope, + DoomGram, +}; use rand::{ rngs::StdRng, - RngCore, + Rng, SeedableRng, }; @@ -172,19 +176,15 @@ fn main() { } } - let before = Instant::now(); - - if 0 != i % 2000 { - thread::sleep(Duration::from_nanos(v as u64)); - } else { - // no wait, so should be very low ns - - thread::sleep(Duration::from_secs(0)); - } - - let after = Instant::now(); + doom_scope(&mut dg, || { + if 0 != i % 2000 { + thread::sleep(Duration::from_nanos(v as u64)); + } else { + // no wait, so should be very low ns - dg.push_event_duration(after - before); + thread::sleep(Duration::from_secs(0)); + } + }); } // output results on second run through @@ -194,7 +194,19 @@ fn main() { let after = Instant::now(); eprintln!("`#to_strip()` : {strip} (in {:?})", after - before); - eprintln!(""); + + let before = Instant::now(); + let mmm = dg.to_mmm(); + let after = Instant::now(); + + eprintln!("`#to_mmm()` : {mmm} (in {:?})", after - before); + + let before = Instant::now(); + let nmmm = dg.to_nmmm(); + let after = Instant::now(); + + eprintln!("`#to_nmmm()` : {nmmm} (in {:?})", after - before); + eprintln!(); eprintln!("dg={dg:#?}"); } @@ -207,6 +219,8 @@ and a typical output is: ```plaintext `#to_strip()` : _aabdedba___ (in 1.763µs) +`#to_mmm()` : 59ns-644µs-197.3ms (in 245ns) +`#to_nmmm()` : 20000:59ns-644µs-197.3ms (in 312ns) dg=DoomGram { event_count: 20000, @@ -233,14 +247,14 @@ dg=DoomGram { } ``` -showing the exploded `Debug` form of the `DoomGram` instance and its timing strip that, for particular execution, obtains the value `"_aabdedba___"` that indicates that there have been: +showing the exploded `Debug` form of the `DoomGram` instance, its timing strip, and compact min/mean/max summaries. For this execution, the strip obtains the value `"_aabdedba___"`, which indicates that there have been: - 0 events in the 1ns+, 1s+, 10s+, 100s+ magnitudes; - 1-9 events in 10ns+, 100ns+, 100ms+ magnitudes; - 10-99 events in 1µs+, 10ms+ magnitudes; - 1000-9999 events in 10µs+, 10ms+ magnitudes; - 10000-99999 events in the 100µs+ magnitude; -Naturally, in a live system one would not be employing the exploded `Debug` view, relying only on the terse and efficient timing strip format. +Naturally, in a live system one would not be employing the exploded `Debug` view, relying instead on the terse and efficient `to_strip()`, `to_mmm()`, and `to_nmmm()` formats. ### Example - `Ellipsis` diff --git a/benches/doomgram.rs b/benches/doomgram.rs index 54dd5a5..32b17d1 100644 --- a/benches/doomgram.rs +++ b/benches/doomgram.rs @@ -13,15 +13,104 @@ use criterion::{ }; -#[rustfmt::skip] -mod constants { +fn doomgram_empty() -> DoomGram { + DoomGram::default() +} + + +fn doomgram_single() -> DoomGram { + let mut dg = DoomGram::default(); + + dg.push_event_time_ms(13); + + dg +} + + +fn doomgram_uniform() -> DoomGram { + let mut dg = DoomGram::default(); + + dg.push_event_time_s(1); + dg.push_event_time_s(1); + dg.push_event_time_s(1); + + dg +} + + +fn doomgram_min_mean_max() -> DoomGram { + let mut dg = DoomGram::default(); + + dg.push_event_time_s(1); + dg.push_event_time_s(2); + + dg +} + + +fn doomgram_uniform_spread() -> DoomGram { + let mut dg = DoomGram::default(); + + dg.push_event_time_ns(9); + dg.push_event_time_ns(80); + dg.push_event_time_ns(700); + dg.push_event_time_us(6); + dg.push_event_time_us(50); + dg.push_event_time_us(400); + dg.push_event_time_ms(3); + dg.push_event_time_ms(20); + dg.push_event_time_ms(100); + dg.push_event_time_s(9); + dg.push_event_time_s(80); + dg.push_event_time_s(700); + + dg +} + +fn doomgram_overflowed() -> DoomGram { + let mut dg = DoomGram::default(); + + dg.push_event_time_us(18446744073709550); + dg.push_event_time_us(1); + dg.push_event_time_us(0); + dg.push_event_time_us(1); + + dg } -#[rustfmt::skip] -mod implementation { +fn bench_to_mmm( + c : &mut Criterion, + dg : DoomGram, + label : &str, +) { + let id = format!("`DoomGram::to_mmm()` [{label}]"); + c.bench_function(&id, |b| { + b.iter(|| { + let s = black_box(black_box(&dg).to_mmm()); + + black_box(s) + }) + }); +} + + +fn bench_to_nmmm( + c : &mut Criterion, + dg : DoomGram, + label : &str, +) { + let id = format!("`DoomGram::to_nmmm()` [{label}]"); + + c.bench_function(&id, |b| { + b.iter(|| { + let s = black_box(black_box(&dg).to_nmmm()); + + black_box(s) + }) + }); } @@ -86,6 +175,26 @@ pub fn BENCHMARK_DoomGram_push_s(c : &mut Criterion) { } +pub fn BENCHMARK_DoomGram_to_mmm(c : &mut Criterion) { + bench_to_mmm(c, doomgram_empty(), "empty"); + bench_to_mmm(c, doomgram_single(), "single"); + bench_to_mmm(c, doomgram_uniform(), "uniform"); + bench_to_mmm(c, doomgram_min_mean_max(), "min-mean-max"); + bench_to_mmm(c, doomgram_uniform_spread(), "uniform spread"); + bench_to_mmm(c, doomgram_overflowed(), "overflow"); +} + + +pub fn BENCHMARK_DoomGram_to_nmmm(c : &mut Criterion) { + bench_to_nmmm(c, doomgram_empty(), "empty"); + bench_to_nmmm(c, doomgram_single(), "single"); + bench_to_nmmm(c, doomgram_uniform(), "uniform"); + bench_to_nmmm(c, doomgram_min_mean_max(), "min-mean-max"); + bench_to_nmmm(c, doomgram_uniform_spread(), "uniform spread"); + bench_to_nmmm(c, doomgram_overflowed(), "overflow"); +} + + criterion_group!( benches, // construction @@ -96,5 +205,8 @@ criterion_group!( BENCHMARK_DoomGram_push_us, BENCHMARK_DoomGram_push_ms, BENCHMARK_DoomGram_push_s, + // formatting + BENCHMARK_DoomGram_to_mmm, + BENCHMARK_DoomGram_to_nmmm, ); criterion_main!(benches); diff --git a/examples/doomgram.md b/examples/doomgram.md index a19d462..0bdd824 100644 --- a/examples/doomgram.md +++ b/examples/doomgram.md @@ -2,7 +2,7 @@ ## Summary -An example using **Diagnosticism.Rust**'s `DoomGram` type to represent the performance of some time-consuming operations. +An example using **Diagnosticism.Rust**'s `DoomGram` type to represent the performance of some time-consuming operations, including the 12-character histogram strip ([`to_strip()`](https://docs.rs/diagnosticism/latest/diagnosticism/struct.DoomGram.html#method.to_strip)) and compact min/mean/max summaries ([`to_mmm()`](https://docs.rs/diagnosticism/latest/diagnosticism/struct.DoomGram.html#method.to_mmm), [`to_nmmm()`](https://docs.rs/diagnosticism/latest/diagnosticism/struct.DoomGram.html#method.to_nmmm)). ## Source @@ -10,11 +10,14 @@ An example using **Diagnosticism.Rust**'s `DoomGram` type to represent the perfo ```Rust // examples/doomgram.rs : example program illustrating use of `DoomGram` -use diagnosticism::DoomGram; +use diagnosticism::{ + doom_scope, + DoomGram, +}; use rand::{ rngs::StdRng, - RngCore, + Rng, SeedableRng, }; @@ -43,19 +46,15 @@ fn main() { } } - let before = Instant::now(); - - if 0 != i % 2000 { - thread::sleep(Duration::from_nanos(v as u64)); - } else { - // no wait, so should be very low ns - - thread::sleep(Duration::from_secs(0)); - } - - let after = Instant::now(); + doom_scope(&mut dg, || { + if 0 != i % 2000 { + thread::sleep(Duration::from_nanos(v as u64)); + } else { + // no wait, so should be very low ns - dg.push_event_duration(after - before); + thread::sleep(Duration::from_secs(0)); + } + }); } // output results on second run through @@ -65,7 +64,19 @@ fn main() { let after = Instant::now(); eprintln!("`#to_strip()` : {strip} (in {:?})", after - before); - eprintln!(""); + + let before = Instant::now(); + let mmm = dg.to_mmm(); + let after = Instant::now(); + + eprintln!("`#to_mmm()` : {mmm} (in {:?})", after - before); + + let before = Instant::now(); + let nmmm = dg.to_nmmm(); + let after = Instant::now(); + + eprintln!("`#to_nmmm()` : {nmmm} (in {:?})", after - before); + eprintln!(); eprintln!("dg={dg:#?}"); } @@ -87,6 +98,8 @@ it gives the output: ``` `#to_strip()` : _aacdeda____ (in 18.765µs) +`#to_mmm()` : 66ns-593.2µs-13.55ms (in 245ns) +`#to_nmmm()` : 20000:66ns-593.2µs-13.55ms (in 312ns) dg=DoomGram { event_count: 20000, @@ -114,5 +127,12 @@ dg=DoomGram { ``` +The three formatted lines use complementary views of the same data: + +* **`to_strip()`** — 12-character order-of-magnitude histogram (each position counts events in a decade band); +* **`to_mmm()`** — min, mean, and max event durations as compact strings via [`nanoseconds_to_string()`](https://docs.rs/diagnosticism/latest/diagnosticism/fn.nanoseconds_to_string.html); +* **`to_nmmm()`** — the same min/mean/max summary prefixed with the event count (`"{count}:{min}-{mean}-{max}"`). + + diff --git a/examples/doomgram.rs b/examples/doomgram.rs index 856e6a7..cd28a5c 100644 --- a/examples/doomgram.rs +++ b/examples/doomgram.rs @@ -7,7 +7,7 @@ use diagnosticism::{ use rand::{ rngs::StdRng, - RngCore, + Rng, SeedableRng, }; @@ -54,6 +54,18 @@ fn main() { let after = Instant::now(); eprintln!("`#to_strip()` : {strip} (in {:?})", after - before); + + let before = Instant::now(); + let mmm = dg.to_mmm(); + let after = Instant::now(); + + eprintln!("`#to_mmm()` : {mmm} (in {:?})", after - before); + + let before = Instant::now(); + let nmmm = dg.to_nmmm(); + let after = Instant::now(); + + eprintln!("`#to_nmmm()` : {nmmm} (in {:?})", after - before); eprintln!(); eprintln!("dg={dg:#?}"); } diff --git a/scripts/check_derives.py b/scripts/check_derives.py old mode 100644 new mode 100755 diff --git a/src/diagnostics/doomgram.rs b/src/diagnostics/doomgram.rs index e122885..707e0f1 100644 --- a/src/diagnostics/doomgram.rs +++ b/src/diagnostics/doomgram.rs @@ -314,6 +314,29 @@ impl DoomGram { self.num_events_ge_100s } + /// Returns min, mean, and max event times as a compact duration string. + /// + /// Each duration is formatted by [`crate::nanoseconds_to_string`]. When + /// [`Self::event_count()`] is zero, returns an empty string. When + /// [`Self::has_overflowed()`] is true, returns `"OVERFLOW"`. When there + /// is one event, or min and max are equal, returns a single formatted + /// duration; otherwise returns `min-mean-max` separated by `-`. + /// + /// Mean is [`Self::event_time_total_raw()`] divided by + /// [`Self::event_count()`]. + pub fn to_mmm(&self) -> String { + self.to_mmm_impl_() + } + + /// Like [`Self::to_mmm()`], prefixed with the event count and `:`. + /// + /// When [`Self::event_count()`] is zero, returns `"0:"`. When + /// [`Self::has_overflowed()`] is true, returns + /// `":OVERFLOW"`. + pub fn to_nmmm(&self) -> String { + self.to_nmmm_impl_() + } + /// Returns a fixed 12-character ASCII strip for the histogram. /// /// Each position encodes the order-of-magnitude of the event count in @@ -437,6 +460,78 @@ impl DoomGram { } } + fn to_mmm_impl_( + &self, + ) -> String { + use super::time_format::nanoseconds_to_string; + + const OVERFLOW : &str = "OVERFLOW"; + + let count = self.event_count(); + + if 0 == count { + return String::new(); + } + + if self.has_overflowed() { + return OVERFLOW.into(); + } + + let min_ns = self.min_event_time().unwrap() as i64; + let max_ns = self.max_event_time().unwrap() as i64; + + let body = if 1 == count || min_ns == max_ns { + format!("{}", nanoseconds_to_string(min_ns, "")) + } else { + let mean_ns = (self.event_time_total_raw() / count as u64) as i64; + + format!( + "{}-{}-{}", + nanoseconds_to_string(min_ns, ""), + nanoseconds_to_string(mean_ns, ""), + nanoseconds_to_string(max_ns, ""), + ) + }; + + body + } + + fn to_nmmm_impl_( + &self, + ) -> String { + use super::time_format::nanoseconds_to_string; + + const OVERFLOW : &str = "OVERFLOW"; + + let count = self.event_count(); + + if 0 == count { + return "0:".into(); + } + + if self.has_overflowed() { + return format!("{count}:{OVERFLOW}"); + } + + let min_ns = self.min_event_time().unwrap() as i64; + let max_ns = self.max_event_time().unwrap() as i64; + + let body = if 1 == count || min_ns == max_ns { + format!("{count}:{}", nanoseconds_to_string(min_ns, "")) + } else { + let mean_ns = (self.event_time_total_raw() / count as u64) as i64; + + format!( + "{count}:{}-{}-{}", + nanoseconds_to_string(min_ns, ""), + nanoseconds_to_string(mean_ns, ""), + nanoseconds_to_string(max_ns, ""), + ) + }; + + body + } + fn try_add_ns_to_total_and_update_minmax_and_count_( &mut self, time_in_ns : u64, @@ -890,6 +985,82 @@ mod tests { assert_eq!("_a_aa___aa_a", dg.to_strip()); } + + #[test] + fn TEST_DoomGram_to_mmm_EMPTY() { + let dg = DoomGram::default(); + + assert_eq!("", dg.to_mmm()); + assert_eq!("0:", dg.to_nmmm()); + } + + + #[test] + fn TEST_DoomGram_to_mmm_SINGLE() { + let mut dg = DoomGram::default(); + + dg.push_event_time_ms(13); + + assert_eq!("13ms", dg.to_mmm()); + assert_eq!("1:13ms", dg.to_nmmm()); + } + + + #[test] + fn TEST_DoomGram_to_mmm_UNIFORM() { + let mut dg = DoomGram::default(); + + dg.push_event_time_s(1); + dg.push_event_time_s(1); + dg.push_event_time_s(1); + + assert_eq!("1s", dg.to_mmm()); + assert_eq!("3:1s", dg.to_nmmm()); + } + + + #[test] + fn TEST_DoomGram_to_mmm_MIN_MEAN_MAX() { + let mut dg = DoomGram::default(); + + dg.push_event_time_s(1); + dg.push_event_time_s(2); + + assert_eq!("1s-1.500s-2s", dg.to_mmm()); + assert_eq!("2:1s-1.500s-2s", dg.to_nmmm()); + } + + + #[test] + fn TEST_DoomGram_to_mmm_ZERO_EVENTS_ALL_SAME() { + let mut dg = DoomGram::default(); + + dg.push_event_time_ns(0); + dg.push_event_time_us(0); + + assert_eq!("0s", dg.to_mmm()); + assert_eq!("2:0s", dg.to_nmmm()); + } + + + #[test] + fn TEST_DoomGram_to_mmm_OVERFLOW() { + let mut dg = DoomGram::default(); + + dg.push_event_time_us(18446744073709550); + dg.push_event_time_us(1); + dg.push_event_time_us(0); + + assert!(!dg.push_event_time_us(1)); + + assert!(dg.has_overflowed()); + assert_eq!(3, dg.event_count()); + + assert_eq!("OVERFLOW", dg.to_mmm()); + assert_eq!("3:OVERFLOW", dg.to_nmmm()); + } + + #[test] fn TEST_DoomGram_OVERFLOW_BY_SECONDS() { diff --git a/src/diagnostics/time_format/mod.rs b/src/diagnostics/time_format/mod.rs index db5d713..82cdb64 100644 --- a/src/diagnostics/time_format/mod.rs +++ b/src/diagnostics/time_format/mod.rs @@ -3,11 +3,10 @@ // NOTE: this work was brought in from **asynkio** via **Diagnosticism.Python** // 0.16.0 -mod format; -mod nanoseconds_str; +use crate::macros::declare_and_publish; -pub use format::nanoseconds_to_string; -pub use nanoseconds_str::NanosecondsStr; +declare_and_publish!(format, nanoseconds_to_string); +declare_and_publish!(nanoseconds_str, NanosecondsStr); // ///////////////////////////// end of file //////////////////////////// // diff --git a/src/diagnostics/time_format/nanoseconds_str.rs b/src/diagnostics/time_format/nanoseconds_str.rs index 500c41d..17590db 100644 --- a/src/diagnostics/time_format/nanoseconds_str.rs +++ b/src/diagnostics/time_format/nanoseconds_str.rs @@ -14,7 +14,7 @@ const INLINE_CAP : usize = 15; /// Compact storage for a formatted nanosecond duration string. /// -/// Obtain values from [`nanoseconds_to_string`]. Most outputs fit in +/// Obtain values from [`crate::nanoseconds_to_string`]. Most outputs fit in /// [`INLINE_CAP`] UTF-8 bytes and are stored inline without heap /// allocation. Longer results use a [`String`] variant. #[derive(Clone)] diff --git a/src/lib.rs b/src/lib.rs index 8602aae..a6d8154 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,8 @@ //! * [`DebugSqueezer`] — restrict the length of //! [`Debug`](std::fmt::Debug) output for individual fields; //! * [`DoomGram`] — decimal order-of-magnitude histogram with a compact -//! 12-character strip for logging; +//! 12-character strip for logging, plus [`DoomGram::to_mmm`] and +//! [`DoomGram::to_nmmm`] min/mean/max duration summaries; //! * [`Ellipsis`] — emit `"..."` for redacted //! [`Debug`](std::fmt::Debug) fields; //! * [`Password`] — emit a run of `*` characters for sensitive