diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..89e6186 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1] - 2025-12-18 + +### Added +- GitHub Actions CI workflow for automated build and test +- MIT LICENSE file in repository root +- Runnable examples in `examples/` directory (`cargo run --example `) +- Criterion benchmarks in `benches/` directory (`cargo bench`) + +### Changed +- Reorganized repository: moved helper scripts to `scripts/` directory +- Made main `dozr` binary explicit in Cargo.toml for clarity +- Updated `examples.md` to use current subcommand syntax +- Updated `TEST.md` to document benchmarks and runnable examples + +## [0.4.0] - 2025-01-15 + +### Added +- Uniform distribution support (`uniform` / `u` command) +- Triangular distribution support (`triangular` / `t` command) +- Subcommand-based CLI architecture with short aliases +- Testing documentation (`TEST.md`) + +### Changed +- Refactored CLI to use subcommands instead of flags +- Encapsulated WaitCondition creation in CLI module +- Improved code organization and reduced code smells + +### Removed +- Weibull distribution (replaced by more commonly used distributions) + +## [0.3.0] - 2025-01-10 + +### Added +- Normal distribution support (`normal` / `n` command) +- Exponential distribution support (`exponential` / `e` command) +- Log-normal distribution support (`log-normal` / `ln` command) +- Pareto distribution support (`pareto` / `par` command) +- Adaptive verbose mode that adjusts update frequency based on remaining time +- Comprehensive test suite with unit and integration tests + +### Fixed +- Verbose output timing accuracy improvements +- Adaptive verbose output alignment to time markers + +## [0.2.0] - 2025-01-05 + +### Added +- `--until` / `at` command to wait until a specific time of day +- `--probability` flag for probabilistic execution +- Time alignment with `align` command (snap to intervals) +- Customizable verbose update period +- Jitter support with `--jitter` flag + +### Changed +- Refactored to library-first architecture +- Decoupled verbose output from wait logic +- Improved CLI argument validation using clap groups + +## [0.1.0] - 2025-01-01 + +### Added +- Initial release +- Fixed duration waits with human-readable time parsing +- `--verbose` flag with ETA countdown +- Adaptive ETA display with human-readable formatting +- Support for duration units: seconds, minutes, hours, days, milliseconds + +[Unreleased]: https://github.com/ShaneIsley/dozr/compare/v0.4.1...HEAD +[0.4.1]: https://github.com/ShaneIsley/dozr/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/ShaneIsley/dozr/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/ShaneIsley/dozr/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/ShaneIsley/dozr/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/ShaneIsley/dozr/releases/tag/v0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 474e7c9..348df1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,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 = "0.6.19" @@ -127,6 +133,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[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.30" @@ -156,6 +168,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.5.41" @@ -208,6 +247,73 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[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 = "difflib" version = "0.4.0" @@ -228,12 +334,13 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dozr" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "assert_cmd", "chrono", "clap", + "criterion", "humantime", "mockall", "predicates", @@ -241,6 +348,12 @@ dependencies = [ "rand_distr", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "float-cmp" version = "0.10.0" @@ -268,12 +381,29 @@ dependencies = [ "wasi", ] +[[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 = "heck" 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 = "humantime" version = "2.2.0" @@ -304,12 +434,38 @@ dependencies = [ "cc", ] +[[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", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.77" @@ -405,6 +561,40 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[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 = "ppv-lite86" version = "0.2.21" @@ -507,6 +697,26 @@ dependencies = [ "rand", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +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 = "regex" version = "1.11.1" @@ -542,6 +752,21 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[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 = "serde" version = "1.0.219" @@ -562,6 +787,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -591,6 +828,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[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 = "unicode-ident" version = "1.0.18" @@ -612,6 +859,16 @@ dependencies = [ "libc", ] +[[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.14.2+wasi-0.2.4" @@ -679,6 +936,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-core" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 7862fa9..1649267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dozr" -version = "0.4.0" +version = "0.4.1" edition = "2024" description = "A flexible `sleep`-like command-line utility for pausing execution with fun timing features." license = "MIT" @@ -17,6 +17,14 @@ rust-version = "1.85.0" # Minimum Rust version required name = "dozr" path = "src/lib.rs" +[[bin]] +name = "dozr" +path = "src/main.rs" + +[[bin]] +name = "dist_sampler" +path = "src/bin/dist_sampler.rs" + [dependencies] anyhow = "1.0.98" clap = { version = "4.5.40", features = ["derive"] } @@ -25,11 +33,12 @@ rand = "0.9.1" rand_distr = "0.5" chrono = "0.4" -[[bin]] -name = "dist_sampler" -path = "src/bin/dist_sampler.rs" - [dev-dependencies] assert_cmd = "2.0.17" predicates = "3.1.0" mockall = "0.12.1" +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "wait_conditions" +harness = false diff --git a/TEST.md b/TEST.md index 5a4d445..40b4265 100644 --- a/TEST.md +++ b/TEST.md @@ -49,3 +49,30 @@ cargo test ``` This command will compile and run all the unit and integration tests, providing a comprehensive overview of the project's health. + +## Benchmarks + +Performance benchmarks are located in the `benches/` directory and use the [Criterion](https://github.com/bheisler/criterion.rs) framework. These benchmarks measure the computational overhead of calculating wait durations for each distribution type. + +To run the benchmarks: + +```bash +cargo bench +``` + +Benchmark results are saved to `target/criterion/` with HTML reports for visualization. + +## Runnable Examples + +The `examples/` directory contains runnable examples demonstrating library usage: + +```bash +# Basic duration waits with jitter +cargo run --example basic_wait + +# Statistical distribution sampling +cargo run --example distributions + +# Verbose progress output demonstrations +cargo run --example verbose_progress +``` diff --git a/benches/wait_conditions.rs b/benches/wait_conditions.rs new file mode 100644 index 0000000..49abe4b --- /dev/null +++ b/benches/wait_conditions.rs @@ -0,0 +1,144 @@ +//! Benchmarks for wait condition calculations. +//! +//! Run with: `cargo bench` +//! +//! These benchmarks measure the computational overhead of calculating +//! wait durations, not the actual waiting time. + +use std::time::Duration; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use dozr::conditions::{ + DurationWait, ExponentialWait, GammaWait, LogNormalWait, NormalWait, + ParetoWait, TriangularWait, UniformWait, WaitCondition, +}; + +fn bench_duration_calculation(c: &mut Criterion) { + let wait = DurationWait { + duration: Duration::from_secs(1), + verbose: None, + jitter: None, + }; + + c.bench_function("duration_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_duration_with_jitter(c: &mut Criterion) { + let wait = DurationWait { + duration: Duration::from_secs(1), + verbose: None, + jitter: Some(Duration::from_millis(100)), + }; + + c.bench_function("duration_with_jitter_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_normal_distribution(c: &mut Criterion) { + let wait = NormalWait { + mean: Duration::from_secs(1), + std_dev: 0.1, + verbose: None, + jitter: None, + }; + + c.bench_function("normal_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_exponential_distribution(c: &mut Criterion) { + let wait = ExponentialWait { + lambda: 1.0, + verbose: None, + jitter: None, + }; + + c.bench_function("exponential_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_uniform_distribution(c: &mut Criterion) { + let wait = UniformWait { + min: Duration::from_millis(500), + max: Duration::from_secs(2), + verbose: None, + jitter: None, + }; + + c.bench_function("uniform_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_lognormal_distribution(c: &mut Criterion) { + let wait = LogNormalWait { + mean: Duration::from_secs(1), + std_dev: 0.5, + verbose: None, + jitter: None, + }; + + c.bench_function("lognormal_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_pareto_distribution(c: &mut Criterion) { + let wait = ParetoWait { + scale: 1.0, + shape: 2.0, + verbose: None, + jitter: None, + }; + + c.bench_function("pareto_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_triangular_distribution(c: &mut Criterion) { + let wait = TriangularWait { + min: 0.5, + max: 2.0, + mode: 1.0, + verbose: None, + jitter: None, + }; + + c.bench_function("triangular_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +fn bench_gamma_distribution(c: &mut Criterion) { + let wait = GammaWait { + shape: 2.0, + scale: 0.5, + verbose: None, + jitter: None, + }; + + c.bench_function("gamma_distribution_calculate", |b| { + b.iter(|| black_box(wait.calculate_wait_duration())) + }); +} + +criterion_group!( + benches, + bench_duration_calculation, + bench_duration_with_jitter, + bench_normal_distribution, + bench_exponential_distribution, + bench_uniform_distribution, + bench_lognormal_distribution, + bench_pareto_distribution, + bench_triangular_distribution, + bench_gamma_distribution, +); + +criterion_main!(benches); diff --git a/examples.md b/examples.md index 0bb799c..f247c10 100644 --- a/examples.md +++ b/examples.md @@ -9,13 +9,13 @@ This document provides various examples of how to use the `dozr` command-line ut Wait for 10 seconds: ```bash -dozr --duration 10s +dozr d 10s ``` Wait for 1 minute and 30 seconds: ```bash -dozr --duration 1m30s +dozr d 1m30s ``` ### Waiting with Jitter @@ -25,7 +25,7 @@ Add a random delay to your wait. The jitter value specifies the *maximum* additi Wait for 5 seconds, plus a random duration between 0 and 2 seconds: ```bash -dozr --duration 5s --jitter 2s +dozr d 5s -j 2s ``` This can be useful for distributing load or simulating more natural delays in scripts. @@ -37,13 +37,13 @@ Use the `--verbose` or `-v` flag to see real-time status updates, including the Wait for 30 seconds with verbose output: ```bash -dozr --duration 30s --verbose +dozr d 30s -v ``` Combine verbose output with jitter: ```bash -dozr --duration 1m --jitter 10s -v +dozr d 1m -j 10s -v ``` ### Custom Verbose Update Period @@ -51,13 +51,13 @@ dozr --duration 1m --jitter 10s -v Specify a custom update period for verbose messages (e.g., every 250 milliseconds): ```bash -dozr --duration 5s --verbose 250ms +dozr d 5s -v 250ms ``` Set verbose messages to update every 5 seconds: ```bash -dozr --duration 1m --verbose 5s +dozr d 1m -v 5s ``` ### Time Alignment @@ -67,19 +67,33 @@ Align execution to the next even time interval. This is useful for synchronizing Wait until the next even 5-second mark: ```bash -dozr --align 5s +dozr a 5s ``` Wait until the next even 10-second mark, with verbose output: ```bash -dozr --align 10s --verbose +dozr a 10s -v ``` Combine with verbose output and a custom update period: ```bash -dozr --align 15s --verbose 1s +dozr a 15s -v 1s +``` + +### Wait Until a Specific Time + +Wait until a specific time of day: + +```bash +dozr at 22:30 +``` + +Wait until 2:30 PM with verbose output: + +```bash +dozr at 14:30:00 -v ``` ### Probabilistic Delay @@ -89,25 +103,25 @@ Execute a wait with a given probability. This is useful for simulating intermitt Wait for 5 seconds with a 50% chance: ```bash -dozr --duration 5s --probability 0.5 +dozr d 5s -p 0.5 ``` -Wait for 10 seconds with a 100% chance (equivalent to `dozr 10s`): +Wait for 10 seconds with a 100% chance (equivalent to `dozr d 10s`): ```bash -dozr --duration 10s --probability 1.0 +dozr d 10s -p 1.0 ``` Wait for 10 seconds with a 0% chance (will not wait): ```bash -dozr --duration 10s --probability 0.0 +dozr d 10s -p 0.0 ``` Combine with verbose output: ```bash -dozr --duration 3s --probability 0.75 --verbose +dozr d 3s -p 0.75 -v ``` ### Using `dozr` in Pipelines @@ -118,14 +132,14 @@ Run a command, wait, then run another command, showing `dozr`'s progress: ```bash echo "Starting process..." -dozr --duration 5s -v +dozr d 5s -v echo "Process complete." ``` Redirect `dozr`'s verbose output to a log file: ```bash -dozr --duration 1m --jitter 5s -v 2> dozr_progress.log +dozr d 1m -j 5s -v 2> dozr_progress.log cat dozr_progress.log ``` @@ -133,10 +147,10 @@ cat dozr_progress.log ### Normal Distribution -Wait for a duration sampled from a Normal distribution with a mean of 1 second and a standard deviation of 100 milliseconds: +Wait for a duration sampled from a Normal distribution with a mean of 1 second and a standard deviation of 0.1: ```bash -dozr --normal-mean 1s --normal-std-dev 100ms +dozr n 1s 0.1 ``` ### Exponential Distribution @@ -144,49 +158,60 @@ dozr --normal-mean 1s --normal-std-dev 100ms Wait for a duration sampled from an Exponential distribution with a lambda (rate parameter) of 0.5: ```bash -dozr --exponential-lambda 0.5 +dozr e 0.5 ``` ### Log-Normal Distribution -Wait for a duration sampled from a Log-Normal distribution with a mean of 1 second and a standard deviation of 100 milliseconds: +Wait for a duration sampled from a Log-Normal distribution with a mean of 1 second and a standard deviation of 0.5: ```bash -dozr --log-normal-mean 1s --log-normal-std-dev 100ms +dozr ln 1s 0.5 ``` ### Pareto Distribution -Wait for a duration sampled from a Pareto distribution with a scale of 1 second and a shape of 1.5: +Wait for a duration sampled from a Pareto distribution with a scale of 1.0 and a shape of 2.0: ```bash -dozr --pareto-scale 1s --pareto-shape 1.5 +dozr par 1.0 2.0 ``` - - ### Uniform Distribution Wait for a duration sampled from a Uniform distribution between 1 second and 5 seconds: ```bash -dozr --uniform-min 1s --uniform-max 5s +dozr u 1s 5s ``` ### Triangular Distribution -Wait for a duration sampled from a Triangular distribution with a minimum of 0.0, a maximum of 1.0, and a mode of 0.5: +Wait for a duration sampled from a Triangular distribution with a minimum of 0.0, a maximum of 10.0, and a mode of 5.0: ```bash -dozr --triangular-min 0.0 --triangular-max 1.0 --triangular-mode 0.5 +dozr t 0.0 10.0 5.0 ``` ### Gamma Distribution -Wait for a duration sampled from a Gamma distribution with a shape of 2.0 and a scale of 1.0: +Wait for a duration sampled from a Gamma distribution with a shape of 2.0 and a scale of 1.5: ```bash -dozr --gamma-shape 2.0 --gamma-scale 1.0 +dozr g 2.0 1.5 ``` +## Library Usage + +For programmatic usage, see the runnable examples in the `examples/` directory: +```bash +# Basic duration waits +cargo run --example basic_wait + +# Statistical distribution sampling +cargo run --example distributions + +# Verbose progress output +cargo run --example verbose_progress +``` diff --git a/examples/basic_wait.rs b/examples/basic_wait.rs new file mode 100644 index 0000000..9b3b485 --- /dev/null +++ b/examples/basic_wait.rs @@ -0,0 +1,36 @@ +//! Basic example demonstrating simple duration waits. +//! +//! Run with: `cargo run --example basic_wait` + +use std::time::Duration; + +use dozr::conditions::{DurationWait, WaitCondition}; + +fn main() -> anyhow::Result<()> { + println!("Starting a 2-second wait..."); + + let wait = DurationWait { + duration: Duration::from_secs(2), + verbose: None, + jitter: None, + }; + + wait.wait()?; + + println!("Wait complete!"); + + // Example with jitter: wait 1 second plus up to 500ms random jitter + println!("\nStarting a 1-second wait with up to 500ms jitter..."); + + let wait_with_jitter = DurationWait { + duration: Duration::from_secs(1), + verbose: None, + jitter: Some(Duration::from_millis(500)), + }; + + wait_with_jitter.wait()?; + + println!("Wait with jitter complete!"); + + Ok(()) +} diff --git a/examples/distributions.rs b/examples/distributions.rs new file mode 100644 index 0000000..bb1242d --- /dev/null +++ b/examples/distributions.rs @@ -0,0 +1,56 @@ +//! Example demonstrating statistical distribution sampling. +//! +//! Run with: `cargo run --example distributions` + +use std::time::Duration; + +use dozr::conditions::{ + ExponentialWait, GammaWait, NormalWait, UniformWait, WaitCondition, +}; + +fn main() -> anyhow::Result<()> { + // Normal distribution: mean of 500ms, std dev of 100ms + println!("Sampling from Normal distribution (mean=500ms, std_dev=0.1)..."); + let normal = NormalWait { + mean: Duration::from_millis(500), + std_dev: 0.1, + verbose: None, + jitter: None, + }; + let duration = normal.calculate_wait_duration()?; + println!(" Sampled duration: {:?}\n", duration); + + // Uniform distribution: between 200ms and 800ms + println!("Sampling from Uniform distribution (200ms to 800ms)..."); + let uniform = UniformWait { + min: Duration::from_millis(200), + max: Duration::from_millis(800), + verbose: None, + jitter: None, + }; + let duration = uniform.calculate_wait_duration()?; + println!(" Sampled duration: {:?}\n", duration); + + // Exponential distribution: lambda = 2.0 + println!("Sampling from Exponential distribution (lambda=2.0)..."); + let exponential = ExponentialWait { + lambda: 2.0, + verbose: None, + jitter: None, + }; + let duration = exponential.calculate_wait_duration()?; + println!(" Sampled duration: {:?}\n", duration); + + // Gamma distribution: shape = 2.0, scale = 0.5 + println!("Sampling from Gamma distribution (shape=2.0, scale=0.5)..."); + let gamma = GammaWait { + shape: 2.0, + scale: 0.5, + verbose: None, + jitter: None, + }; + let duration = gamma.calculate_wait_duration()?; + println!(" Sampled duration: {:?}\n", duration); + + Ok(()) +} diff --git a/examples/verbose_progress.rs b/examples/verbose_progress.rs new file mode 100644 index 0000000..bc4cb17 --- /dev/null +++ b/examples/verbose_progress.rs @@ -0,0 +1,39 @@ +//! Example demonstrating verbose progress output. +//! +//! Run with: `cargo run --example verbose_progress` + +use std::time::Duration; + +use dozr::{adaptive_verbose_wait, verbose_wait}; + +fn main() { + // Fixed update period verbose wait + println!("Verbose wait with 500ms update period (3 seconds total):"); + println!("---"); + + verbose_wait( + Duration::from_secs(3), + Duration::from_millis(500), + |remaining| { + if remaining.is_zero() { + println!(" Complete!"); + } else { + println!(" Time remaining: {:.1}s", remaining.as_secs_f64()); + } + }, + ); + + println!(); + + // Adaptive verbose wait (adjusts update frequency based on remaining time) + println!("Adaptive verbose wait (5 seconds total):"); + println!("---"); + + adaptive_verbose_wait(Duration::from_secs(5), |remaining| { + if remaining.is_zero() { + println!(" Complete!"); + } else { + println!(" Time remaining: {}s", remaining.as_secs()); + } + }); +} diff --git a/tests/cli.rs b/tests/cli.rs index 2ab6646..114c7e5 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -69,30 +69,32 @@ fn test_verbose_custom_update_period() { #[test] fn test_verbose_adaptive_short_wait() { let mut cmd = Command::cargo_bin("dozr").unwrap(); - // Test with a 1.5s wait (adaptive 500ms update). + // Test with a 5s wait - falls in 0-20s bucket (1s update period) let assert = cmd - .args(&["d", "1s500ms", "-v"]) + .args(&["d", "5s", "-v"]) .assert() .success(); let output = assert.get_output(); let stderr_str = String::from_utf8_lossy(&output.stderr); + // Should have multiple 1-second updates assert!(str::contains("[DOZR] Time remaining:").eval(&stderr_str)); } #[test] fn test_verbose_adaptive_long_wait() { let mut cmd = Command::cargo_bin("dozr").unwrap(); - // Test with a 5s wait (adaptive 1s update). + // Test with a 21s wait - falls in 21-60s bucket (5s update period) let assert = cmd - .args(&["d", "5s", "-v"]) + .args(&["d", "21s", "-v"]) .assert() .success(); let output = assert.get_output(); let stderr_str = String::from_utf8_lossy(&output.stderr); + // Should have updates at 5-second intervals assert!(str::contains("[DOZR] Time remaining:").eval(&stderr_str)); }