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
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ exclude = [
"example/server/",
]

# Combined library + binary package. The library (`src/lib.rs`) is the reusable
# load-testing engine; the binary (`src/main.rs`) is the CLI shell that drives
# it. Targets are declared explicitly so the boundary and binary name are
# unambiguous and so integration tests / benchmarks can `use driller::`.
[lib]
name = "driller"
path = "src/lib.rs"

[[bin]]
name = "driller"
path = "src/main.rs"

[dependencies]
clap = { version = "4", features = ["derive"] }
colored = "3"
Expand Down
13 changes: 12 additions & 1 deletion src/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,16 @@ pub struct RunOptions {
pub tags: Tags,
}

/// Aggregated outcome of a benchmark run, returned by [`crate::run`].
///
/// Bundles the per-iteration reports, the total wall-clock time, and the
/// assertion-failure tally so a caller (the CLI, a test, or a benchmark) can
/// render stats and decide an exit status without re-deriving them.
pub struct BenchmarkResult {
/// Per-iteration results: one inner `Vec<Report>` per completed iteration,
/// each holding one [`Report`] per executed step.
pub reports: Vec<Reports>,
/// Total wall-clock duration of the run, in seconds.
pub duration: f64,
/// Number of `assert` checks that failed during the run. Non-zero drives a
/// non-zero process exit code in `main`, so a failed assertion is detectable
Expand Down Expand Up @@ -94,7 +102,10 @@ fn build_synthetic_plan(path: &str) -> Benchmark {
}

/// Executes a benchmark run using the provided options.
pub fn execute(options: &RunOptions) -> BenchmarkResult {
///
/// Crate-internal: external callers go through [`crate::run`], which is the
/// library's single public entry point for executing a run.
pub(crate) fn execute(options: &RunOptions) -> BenchmarkResult {
let config = Arc::new(Config::new(options));

// Held outside the runtime so the failure tally survives after `config` is
Expand Down
9 changes: 6 additions & 3 deletions src/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ use crate::reader;
///
/// Returns `Ok(())` when every named request stays within `threshold`
/// milliseconds of its baseline mean, or `Err(n)` where `n` is the number of
/// names that regressed. Exits with a clean error if the baseline file is empty
/// or is not a list of records; records missing a `name` or numeric `duration`
/// are skipped rather than panicking.
/// names that regressed. Records missing a `name` or numeric `duration` are
/// skipped rather than panicking.
///
/// Note for library callers: a missing/empty/malformed baseline file is treated
/// as fatal and **terminates the process** via `std::process::exit(1)` rather
/// than returning an `Err` -- pre-validate the file if that is not acceptable.
pub fn compare(list_reports: &[Vec<Report>], filepath: &str, threshold: f64) -> Result<(), i32> {
let docs = reader::read_file_as_yml(filepath);
let items = match docs.first().and_then(|doc| doc.as_sequence()) {
Expand Down
63 changes: 63 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! driller -- HTTP load-testing engine with Ansible-style YAML plans.
//!
//! This crate is the reusable engine behind the `driller` binary. The binary
//! (`src/main.rs`) owns the command-line interface -- argument parsing, version
//! strings, the stats/compare presentation, and process exit codes -- while
//! everything that actually loads a plan and runs a benchmark lives here, so
//! integration tests, benchmarks, and sibling tools can drive the engine
//! directly without shelling out to the binary.
//!
//! # Entry point
//!
//! [`run`] is the single typed entry point: build a [`RunOptions`] and call it.
//!
//! ```no_run
//! use driller::{run, RunOptions};
//!
//! # fn build_options() -> RunOptions { unimplemented!() }
//! let options: RunOptions = build_options();
//! let result = run(&options);
//! println!("{} assertion(s) failed", result.assertion_failures);
//! ```
//!
//! # Module surface
//!
//! The engine modules are exposed at module level for in-repo consumers
//! (tests, benchmarks, and sibling crates); their members keep their original
//! visibility. `interpolator` and `writer` stay crate-private as they appear in
//! no public interface.

pub mod actions;
pub mod benchmark;
pub mod checker;
pub mod config;
pub mod expandable;
pub mod reader;
pub mod tags;

mod interpolator;
mod writer;

pub use benchmark::{BenchmarkResult, RunOptions};

/// Runs a benchmark to completion and returns its aggregated result.
///
/// This is the library's single entry point for executing a run. The `driller`
/// binary constructs a [`RunOptions`] from parsed command-line arguments and
/// calls this; tests and benchmarks can do the same without going through the
/// CLI layer.
///
/// The call builds its own tokio runtime (current-thread when `worker_threads`
/// is `None`/`1`, multi-thread when it is `2` or more) and blocks until the run
/// finishes, so it is safe to call from a synchronous context.
///
/// # Process exit on fatal input
///
/// Some invalid inputs are currently treated as fatal and terminate the
/// process via `std::process::exit(1)` rather than returning -- an empty plan,
/// and the unreadable/malformed-file paths in [`reader`]. A caller embedding
/// the engine should pre-validate its [`RunOptions`] accordingly. Migrating
/// these paths to returned errors is tracked as a follow-up.
pub fn run(options: &RunOptions) -> BenchmarkResult {
benchmark::execute(options)
}
17 changes: 4 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
mod actions;
mod benchmark;
mod checker;
mod config;
mod expandable;
mod interpolator;
mod reader;
mod tags;
mod writer;

use crate::actions::Report;
use crate::benchmark::RunOptions;
use clap::{Args, Parser, Subcommand};
use colored::*;
use driller::actions::Report;
use driller::tags;
use driller::{RunOptions, checker};
use hdrhistogram::Histogram;
use linked_hash_map::LinkedHashMap;
use std::collections::BTreeMap;
Expand Down Expand Up @@ -298,7 +289,7 @@ fn main() {
}
};

let benchmark_result = benchmark::execute(&options);
let benchmark_result = driller::run(&options);
let assertion_failures = benchmark_result.assertion_failures;
let list_reports = benchmark_result.reports;
let duration = benchmark_result.duration;
Expand Down
Loading