ssb 0.1.1

Simple benchmarking for Rust, with hierarchical call tree, based on fastrace.
Documentation
//! # ssb
//!
//! An instrumented benchmarking library for Rust, powered by [`fastrace`].
//!
//! Unlike traditional benchmark tools that only measure total function time,
//! `ssb` collects span-level timing from your `fastrace`-instrumented code,
//! letting you see how each named phase performs across hundreds of iterations
//! and compare results between runs.
//!
//! ## Quick start
//!
//! Add to `Cargo.toml`:
//! ```toml
//! [dev-dependencies]
//! ssb = "*"
//! fastrace = { version = "0.7", features = ["enable"] }
//!
//! [[bench]]
//! name = "my_bench"
//! harness = false
//! ```
//!
//! Instrument your code with `fastrace` spans:
//!
//! ```rust,no_run
//! #[fastrace::trace]
//! fn phase1(data: &[u8]) -> Vec<u8> {
//!     # vec![]
//!     // ... your work
//! }
//!
//! #[fastrace::trace]
//! fn phase2(data: Vec<u8>) -> Vec<u8> {
//!     # data
//!     // ... your work
//! }
//!
//! fn process(input: &[u8]) -> Vec<u8> {
//!     phase2(phase1(input))
//! }
//! ```
//!
//! Write `benches/my_bench.rs`:
//!
//! ```rust,no_run
//! use ssb::Bench;
//!
//! fn bench_process() {
//!     let input = vec![0u8; 1024];
//!     // On the first run: prints stats and saves to
//!     //   target/ssb/process/baseline.json
//!     // On subsequent runs: loads that file, prints comparison, updates it.
//!     Bench::new("process")
//!         .iterations(1000)
//!         .warmup(100)
//!         .run(|| process(&input));
//! }
//!
//! ssb::bench_main!(bench_process);
//! # fn process(_: &[u8]) -> Vec<u8> { vec![] }
//! ```
//!
//! Run with `cargo bench` (or `cargo run --example ...` for examples).
//! The first run prints stats; every subsequent run compares against the
//! previous result automatically.
//!
//! ## Grouping benchmarks
//!
//! Use [`Bench::group`] to organise benchmarks into subdirectories (mirrors
//! criterion's `criterion_group!` layout):
//!
//! ```rust,no_run
//! use ssb::Bench;
//!
//! fn bench_parse() {
//!     # let data = vec![0u8; 1024];
//!     // stored at target/ssb/parsing/parse/baseline.json
//!     Bench::new("parse").group("parsing").run(|| parse(&data));
//! }
//! # fn parse(_: &[u8]) {}
//! # let data = vec![0u8; 1024];
//! ```
//!
//! ## Manual persistence
//!
//! Call [`Bench::no_auto_save`] to skip the automatic I/O and handle the
//! [`BenchReport`] yourself:
//!
//! ```rust,no_run
//! use ssb::{Bench, BenchReport};
//!
//! # let input = vec![0u8; 1024];
//! let report = Bench::new("process")
//!     .no_auto_save()
//!     .run(|| process(&input));
//!
//! // Save to a custom path (e.g. a CI artifact directory)
//! report.save("/tmp/ci-results/process.json").unwrap();
//!
//! // Compare with a baseline loaded from anywhere
//! let baseline = BenchReport::load("previous.json").unwrap();
//! report.compare(&baseline).print();
//! # fn process(_: &[u8]) -> Vec<u8> { vec![] }
//! ```
//!
//! ## Span nesting
//!
//! `Span::enter_with_local_parent` records the current thread-local span as
//! the parent, but does **not** automatically make the new span the
//! thread-local parent for subsequent calls. For proper nesting either:
//!
//! - Use `#[fastrace::trace]` (recommended — handles this automatically).
//! - Or call `span.set_local_parent()` manually.
//!
//! Without one of these, all spans in a call chain appear as siblings under
//! the benchmark root rather than as a nested tree.
//!
//! ## Concurrency
//!
//! ssb uses a global reporter. Do not run multiple `Bench::run` calls
//! concurrently (e.g. from parallel test threads). Use
//! `cargo test -- --test-threads=1` when running benchmarks as tests.

mod bench;
mod collector;
mod report;
mod stats;

pub use bench::{Bench, BenchGroup};
pub use report::{BenchReport, Comparison};
pub use stats::SpanStats;

/// Generate a `main` function that runs the given benchmark functions.
///
/// Each function is called with no arguments and is expected to create its
/// own [`Bench`] internally. Results are stored automatically under
/// `target/ssb/` by each [`Bench::run`] call.
///
/// # Example
///
/// ```rust,no_run
/// use ssb::Bench;
///
/// fn bench_foo() {
///     // ...
///     Bench::new("foo").run(|| { /* work */ });
/// }
///
/// fn bench_bar() {
///     Bench::new("bar").group("my_group").run(|| { /* work */ });
/// }
///
/// ssb::bench_main!(bench_foo, bench_bar);
/// ```
#[macro_export]
macro_rules! bench_main {
    ($($bench_fn:ident),+ $(,)?) => {
        fn main() {
            use $crate::{WrapFn, BenchFn};

            $(
            (&WrapFn($bench_fn)).run(stringify!($bench_fn));
            )+
        }
    };
}

/// Helper type to implement BenchFn for both `Fn() -> Any` and `Fn(&mut Bench)`.
pub struct WrapFn<T: ?Sized>(pub T);
pub trait BenchFn {
    fn run(&self, name: &'static str);
}

impl<F, Any> BenchFn for &WrapFn<F>
where
    F: Fn() -> Any,
{
    fn run(&self, name: &'static str) {
        let mut bench = Bench::new(name);
        bench.run(|| (self.0)());
    }
}

impl<F> BenchFn for WrapFn<F>
where
    F: Fn(&mut Bench),
{
    fn run(&self, name: &'static str) {
        let mut bench = Bench::new(name);
        (self.0)(&mut bench);
    }
}