Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
7 changes: 6 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Diagnosticism.Rust - CHANGES <!-- omit in toc -->


## 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;
Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"


# ##########################################################
Expand Down
2 changes: 1 addition & 1 deletion EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
|Name|Source & Description|Summary|
|---|---|---|
|**debug_squeezer**|[examples/debug_squeezer.rs](/examples/debug_squeezer.rs)<br/>[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)<br/>[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)<br/>[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)<br/>[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)<br/>[examples/password.md](/examples/password.md)|An example using **Diagnosticism.Rust**'s `Password` type to secure the `Debug` form of a user-defined type.|

Expand Down
52 changes: 33 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<str>`;
* `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);
Expand All @@ -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,
};

Expand Down Expand Up @@ -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
Expand All @@ -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:#?}");
}

Expand All @@ -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,
Expand All @@ -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`
Expand Down
120 changes: 116 additions & 4 deletions benches/doomgram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
});
}


Expand Down Expand Up @@ -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
Expand All @@ -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);
Loading
Loading