use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, mpsc};
use std::time::Instant;
use hdrhistogram::Histogram;
use crate::ixit::{Environment, Ixit};
use crate::perf::{
ClassVerdict, Measurement, OperationMeasurement, PerfOp, PerformanceCase, class_verdict,
};
use crate::perf_run::client::PerfPrincipals;
use crate::perf_run::corpus::SeededCorpus;
use crate::perf_run::execute::{CaptureStore, perform};
use crate::perf_run::pack::JourneyPack;
use crate::perf_run::schedule::{JourneyWorkload, build_schedule};
const HDR_MAX_US: u64 = 600_000_000;
const FAILURE_SAMPLES: u32 = 16;
struct OpRecorder {
histogram: Histogram<u64>,
errors: u64,
}
struct Completion {
op: PerfOp,
latency_us: u64,
ok: bool,
recorded: bool,
}
#[derive(Debug)]
pub struct WindowOutcome {
pub offered_load_sustained: f64,
pub operations: Vec<OperationMeasurement>,
pub generator_bound: bool,
}
#[expect(
clippy::too_many_lines,
reason = "one measured-window procedure: schedule → collect → aggregate"
)]
pub fn run_window(
principals: &PerfPrincipals,
corpus: &SeededCorpus,
workload: &JourneyWorkload<'_>,
rate: f64,
warmup_s: u64,
duration_s: u64,
progress: &(dyn Fn(String) + Sync),
) -> Result<WindowOutcome, String> {
let schedule = build_schedule(workload, rate, warmup_s, duration_s, corpus.ward.len())?;
if !schedule.dropped_journeys.is_empty() {
progress(format!(
"journeys not scheduled (the ixit declares no principal for them; the remaining \
shares were renormalized): {}",
schedule.dropped_journeys.join(", ")
));
}
let total = schedule.arrivals.len();
let captures = CaptureStore::new();
let (tx, rx) = mpsc::channel::<Completion>();
let collector = std::thread::spawn(move || {
let mut recorders: Vec<(PerfOp, OpRecorder)> = Vec::new();
let mut generator_faults: u64 = 0;
for done in rx {
if !done.recorded {
continue;
}
if done.latency_us == u64::MAX {
generator_faults = generator_faults.saturating_add(1);
continue;
}
let index = if let Some(index) = recorders.iter().position(|(op, _)| *op == done.op) {
index
} else {
let Ok(histogram) = Histogram::new_with_bounds(1, HDR_MAX_US, 3) else {
generator_faults = generator_faults.saturating_add(1);
continue;
};
recorders.push((
done.op,
OpRecorder {
histogram,
errors: 0,
},
));
recorders.len() - 1
};
if let Some((_, recorder)) = recorders.get_mut(index) {
let value = done.latency_us.clamp(1, HDR_MAX_US);
let _saturated = recorder.histogram.record(value);
if !done.ok {
recorder.errors = recorder.errors.saturating_add(1);
}
}
}
(recorders, generator_faults)
});
let start = Instant::now();
let dispatched_measured = Arc::new(AtomicU64::new(0));
let failure_samples = Arc::new(AtomicU32::new(0));
progress(format!(
"open-loop schedule: {total} arrivals ({} measured) at {rate}/s aggregate \
({warmup_s}s warmup + {duration_s}s measured, {} journeys interleaved)",
schedule.planned_measured,
schedule
.arrivals
.last()
.map_or(0, |a| a.journey.saturating_add(1))
));
let dispatch_span = std::thread::scope(|scope| {
for (i, planned_arrival) in schedule.arrivals.iter().enumerate() {
let planned = start + planned_arrival.at;
let now = Instant::now();
if planned > now {
std::thread::sleep(planned - now);
}
if planned_arrival.recorded {
dispatched_measured.fetch_add(1, Ordering::Relaxed);
}
let tx = tx.clone();
let principals = principals.clone();
let captures = &captures;
let arrival_index = u64::try_from(i).unwrap_or(u64::MAX);
let failure_samples = Arc::clone(&failure_samples);
scope.spawn(move || {
let mut observed: Option<u16> = None;
let outcome = perform(
&principals,
arrival_index,
planned_arrival,
corpus,
workload.pack,
captures,
&mut observed,
);
let latency = planned.elapsed();
let latency_us = u64::try_from(latency.as_micros().min(u128::from(HDR_MAX_US)))
.unwrap_or(HDR_MAX_US);
let ok = match &outcome {
Ok(ok) => *ok,
Err(_) => false,
};
if !ok && failure_samples.fetch_add(1, Ordering::Relaxed) < FAILURE_SAMPLES {
let detail = match (&outcome, observed) {
(Err(reason), _) => reason.clone(),
(Ok(_), Some(status)) => format!("unexpected wire status {status}"),
(Ok(_), None) => "no wire observation".to_owned(),
};
progress(format!(
"arrival failure sample: {} journey {} at {:?}: {detail}",
planned_arrival.op.as_str(),
planned_arrival.journey,
planned_arrival.at,
));
}
let _closed = tx.send(Completion {
op: planned_arrival.op,
latency_us,
ok,
recorded: planned_arrival.recorded,
});
});
if i % 1000 == 999 {
progress(format!("dispatched {}/{total} arrivals", i + 1));
}
}
drop(tx);
start.elapsed()
});
let (recorders, generator_faults) = collector.join().map_err(|payload| {
let detail = payload
.downcast_ref::<&str>()
.map(|message| (*message).to_owned())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "non-string panic payload".to_owned());
format!("collector thread panicked: {detail}")
})?;
if generator_faults > 0 {
return Err(format!("{generator_faults} generator faults"));
}
let planned_span_s = warmup_s.saturating_add(duration_s);
#[expect(
clippy::as_conversions,
clippy::cast_precision_loss,
reason = "spans/counts << 2^52"
)]
let (offered_load_sustained, generator_bound) = {
let actual_span = dispatch_span.as_secs_f64().max(planned_span_s as f64);
let measured_span = actual_span - warmup_s as f64;
let dispatched = dispatched_measured.load(Ordering::Relaxed) as f64;
let fidelity = (dispatched / schedule.planned_measured.max(1) as f64)
.min(planned_span_s as f64 / actual_span);
let offered = match workload.curve {
crate::perf::ArrivalCurve::Uniform => {
if measured_span > 0.0 {
dispatched / measured_span
} else {
0.0
}
}
crate::perf::ArrivalCurve::Diurnal => schedule.planned_busy_hour * fidelity,
};
let lagged = dispatch_span.as_secs_f64() > planned_span_s as f64 * 1.02;
(offered, lagged)
};
let mut operations: Vec<OperationMeasurement> = Vec::new();
for (op, recorder) in &recorders {
operations.push(OperationMeasurement::from_histogram(
op.as_str(),
&recorder.histogram,
recorder.errors,
)?);
}
operations.sort_by(|a, b| a.operation.cmp(&b.operation));
Ok(WindowOutcome {
offered_load_sustained,
operations,
generator_bound,
})
}
#[expect(clippy::too_many_arguments, reason = "the one case-drive seam")]
pub fn drive_case(
case: &PerformanceCase,
principals: &PerfPrincipals,
corpus: &SeededCorpus,
journey_pack: &JourneyPack,
catalogue: &crate::perf::JourneyCatalogue,
environment: &Environment,
warmup_s: u64,
duration_s: u64,
progress: &(dyn Fn(String) + Sync),
) -> Result<Measurement, String> {
case.check_invariants()?;
let workload = JourneyWorkload {
catalogue,
shares: &case.workload.journeys,
pack: journey_pack,
curve: case.workload.arrival_curve,
principals,
};
let window = run_window(
principals,
corpus,
&workload,
case.workload.arrival_rate.0,
warmup_s,
duration_s,
progress,
)?;
let (verdict, violations) =
class_verdict(case, window.offered_load_sustained, &window.operations)?;
Ok(Measurement {
case: case.id.clone(),
class: case.class,
environment: environment.clone(),
offered_load_sustained: window.offered_load_sustained,
warmup_s,
duration_s,
operations: window.operations,
verdict,
violations,
resources: None,
})
}
pub fn measured_run_context(ixit: &Ixit) -> Result<(PerfPrincipals, &Environment), String> {
let principals = PerfPrincipals::from_ixit(ixit)?;
let environment = ixit.environment.as_ref().ok_or_else(|| {
"ixit has no environment block (mandatory for performance runs)".to_owned()
})?;
Ok((principals, environment))
}
pub fn rederive_verdict(
case: &PerformanceCase,
measurement: &Measurement,
) -> Result<(ClassVerdict, Vec<String>), String> {
class_verdict(
case,
measurement.offered_load_sustained,
&measurement.operations,
)
}