use crate::analysis;
use crate::benchmark::PartialBenchmarkConfig;
use crate::connection::OutgoingMessage;
use crate::measurement::Measurement;
use crate::report::BenchmarkId as InternalBenchmarkId;
use crate::report::Report;
use crate::report::ReportContext;
use crate::routine::{Function, Routine};
use crate::{Bencher, Criterion, Mode, PlotConfiguration, SamplingMode, Throughput};
use std::time::Duration;
pub struct BenchmarkGroup<'a, M: Measurement> {
criterion: &'a mut Criterion<M>,
group_name: String,
all_ids: Vec<InternalBenchmarkId>,
any_matched: bool,
partial_config: PartialBenchmarkConfig,
throughput: Option<Throughput>,
}
impl<'a, M: Measurement> BenchmarkGroup<'a, M> {
pub fn sample_size(&mut self, n: usize) -> &mut Self {
assert!(n >= 10);
self.partial_config.sample_size = Some(n);
self
}
pub fn warm_up_time(&mut self, dur: Duration) -> &mut Self {
assert!(dur.as_nanos() > 0);
self.partial_config.warm_up_time = Some(dur);
self
}
pub fn measurement_time(&mut self, dur: Duration) -> &mut Self {
assert!(dur.as_nanos() > 0);
self.partial_config.measurement_time = Some(dur);
self
}
pub fn nresamples(&mut self, n: usize) -> &mut Self {
assert!(n > 0);
if n <= 1000 {
eprintln!("\nWarning: It is not recommended to reduce nresamples below 1000.");
}
self.partial_config.nresamples = Some(n);
self
}
pub fn noise_threshold(&mut self, threshold: f64) -> &mut Self {
assert!(threshold >= 0.0);
self.partial_config.noise_threshold = Some(threshold);
self
}
pub fn confidence_level(&mut self, cl: f64) -> &mut Self {
assert!(cl > 0.0 && cl < 1.0);
if cl < 0.5 {
eprintln!("\nWarning: It is not recommended to reduce confidence level below 0.5.");
}
self.partial_config.confidence_level = Some(cl);
self
}
pub fn significance_level(&mut self, sl: f64) -> &mut Self {
assert!(sl > 0.0 && sl < 1.0);
self.partial_config.significance_level = Some(sl);
self
}
pub fn plot_config(&mut self, new_config: PlotConfiguration) -> &mut Self {
self.partial_config.plot_config = new_config;
self
}
pub fn throughput(&mut self, throughput: Throughput) -> &mut Self {
self.throughput = Some(throughput);
self
}
pub fn sampling_mode(&mut self, new_mode: SamplingMode) -> &mut Self {
self.partial_config.sampling_mode = Some(new_mode);
self
}
pub(crate) fn new(criterion: &mut Criterion<M>, group_name: String) -> BenchmarkGroup<'_, M> {
BenchmarkGroup {
criterion,
group_name,
all_ids: vec![],
any_matched: false,
partial_config: PartialBenchmarkConfig::default(),
throughput: None,
}
}
pub fn bench_function<ID: IntoBenchmarkId, F>(&mut self, id: ID, mut f: F) -> &mut Self
where
F: FnMut(&mut Bencher<'_, M>),
{
self.run_bench(id.into_benchmark_id(), &(), |b, _| f(b));
self
}
pub fn bench_with_input<ID: IntoBenchmarkId, F, I>(
&mut self,
id: ID,
input: &I,
f: F,
) -> &mut Self
where
F: FnMut(&mut Bencher<'_, M>, &I),
I: ?Sized,
{
self.run_bench(id.into_benchmark_id(), input, f);
self
}
fn run_bench<F, I>(&mut self, id: BenchmarkId, input: &I, f: F)
where
F: FnMut(&mut Bencher<'_, M>, &I),
I: ?Sized,
{
let config = self.partial_config.to_complete(&self.criterion.config);
let report_context = ReportContext {
output_directory: self.criterion.output_directory.clone(),
plot_config: self.partial_config.plot_config.clone(),
};
let mut id = InternalBenchmarkId::new(
self.group_name.clone(),
id.function_name,
id.parameter,
self.throughput.clone(),
);
assert!(
!self.all_ids.contains(&id),
"Benchmark IDs must be unique within a group. Encountered duplicated benchmark ID {}",
&id
);
id.ensure_directory_name_unique(&self.criterion.all_directories);
self.criterion
.all_directories
.insert(id.as_directory_name().to_owned());
id.ensure_title_unique(&self.criterion.all_titles);
self.criterion.all_titles.insert(id.as_title().to_owned());
let do_run = self.criterion.filter_matches(id.id());
self.any_matched |= do_run;
let mut func = Function::new(f);
match &self.criterion.mode {
Mode::Benchmark => {
if let Some(conn) = &self.criterion.connection {
if do_run {
conn.send(&OutgoingMessage::BeginningBenchmark { id: (&id).into() })
.unwrap();
} else {
conn.send(&OutgoingMessage::SkippingBenchmark { id: (&id).into() })
.unwrap();
}
}
if do_run {
analysis::common(
&id,
&mut func,
&config,
self.criterion,
&report_context,
input,
self.throughput.clone(),
);
}
}
Mode::List(_) => {
if do_run {
println!("{}: benchmark", id);
}
}
Mode::Test => {
if do_run {
self.criterion.report.test_start(&id, &report_context);
func.test(&self.criterion.measurement, input);
self.criterion.report.test_pass(&id, &report_context);
}
}
&Mode::Profile(duration) => {
if do_run {
func.profile(
&self.criterion.measurement,
&id,
self.criterion,
&report_context,
duration,
input,
);
}
}
}
self.all_ids.push(id);
}
pub fn finish(self) {
::std::mem::drop(self);
}
}
impl<'a, M: Measurement> Drop for BenchmarkGroup<'a, M> {
fn drop(&mut self) {
if let Some(conn) = &mut self.criterion.connection {
conn.send(&OutgoingMessage::FinishedBenchmarkGroup {
group: &self.group_name,
})
.unwrap();
conn.serve_value_formatter(self.criterion.measurement.formatter())
.unwrap();
}
if self.all_ids.len() > 1 && self.any_matched && self.criterion.mode.is_benchmark() {
let report_context = ReportContext {
output_directory: self.criterion.output_directory.clone(),
plot_config: self.partial_config.plot_config.clone(),
};
self.criterion.report.summarize(
&report_context,
&self.all_ids,
self.criterion.measurement.formatter(),
);
}
if self.any_matched && !self.criterion.mode.is_terse() {
self.criterion.report.group_separator();
}
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct BenchmarkId {
pub(crate) function_name: Option<String>,
pub(crate) parameter: Option<String>,
}
impl BenchmarkId {
pub fn new<S: Into<String>, P: ::std::fmt::Display>(
function_name: S,
parameter: P,
) -> BenchmarkId {
BenchmarkId {
function_name: Some(function_name.into()),
parameter: Some(format!("{}", parameter)),
}
}
pub fn from_parameter<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId {
BenchmarkId {
function_name: None,
parameter: Some(format!("{}", parameter)),
}
}
pub(crate) fn no_function() -> BenchmarkId {
BenchmarkId {
function_name: None,
parameter: None,
}
}
pub(crate) fn no_function_with_input<P: ::std::fmt::Display>(parameter: P) -> BenchmarkId {
BenchmarkId {
function_name: None,
parameter: Some(format!("{}", parameter)),
}
}
}
mod private {
pub trait Sealed {}
impl Sealed for super::BenchmarkId {}
impl<S: Into<String>> Sealed for S {}
}
pub trait IntoBenchmarkId: private::Sealed {
fn into_benchmark_id(self) -> BenchmarkId;
}
impl IntoBenchmarkId for BenchmarkId {
fn into_benchmark_id(self) -> BenchmarkId {
self
}
}
impl<S: Into<String>> IntoBenchmarkId for S {
fn into_benchmark_id(self) -> BenchmarkId {
let function_name = self.into();
assert!(
!function_name.is_empty(),
"Function name must not be empty."
);
BenchmarkId {
function_name: Some(function_name),
parameter: None,
}
}
}