use std::sync::atomic::{AtomicU64, Ordering};
#[allow(unused_imports)]
use std::sync::OnceLock;
#[allow(unused_imports)]
use std::time::{Duration, Instant};
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
use std::sync::atomic::AtomicBool;
#[cfg(all(target_os = "linux", target_arch = "aarch64", feature = "perf"))]
use crate::measurement::perf::LinuxPerfTimer;
#[cfg(all(target_os = "linux", target_arch = "aarch64", feature = "perf"))]
fn get_effect_timer() -> Option<&'static LinuxPerfTimer> {
use crate::measurement::perf::LinuxPerfTimer;
use std::sync::OnceLock;
static EFFECT_TIMER: OnceLock<Option<LinuxPerfTimer>> = OnceLock::new();
EFFECT_TIMER
.get_or_init(|| {
LinuxPerfTimer::new().ok()
})
.as_ref()
}
use std::cell::{Cell, LazyCell};
thread_local! {
static EFFECT_INITIALIZED: Cell<bool> = const { Cell::new(false) };
static BUNDLE_CALIBRATION: LazyCell<Option<BundleCalibration>> = LazyCell::new(|| {
#[cfg(all(target_os = "linux", target_arch = "aarch64", feature = "perf"))]
if let Some(timer) = get_effect_timer() {
return Some(BundleCalibration::calibrate_with_perf(timer));
}
Some(BundleCalibration::calibrate_with_instant())
});
}
#[derive(Clone, Debug)]
#[allow(dead_code)] struct BundleCalibration {
cost_per_unit: f64,
}
impl BundleCalibration {
#[cfg(all(target_os = "linux", target_arch = "aarch64", feature = "perf"))]
fn calibrate_with_perf(timer: &LinuxPerfTimer) -> Self {
const LARGE_SAMPLE_COUNT: usize = 100_000;
const MAX_REASONABLE_CYCLES: u64 = 10_000_000_000;
let mut calibration_timer = LinuxPerfTimer::new()
.unwrap_or_else(|_| panic!("Failed to create perf timer during calibration"));
for _attempt in 0..3 {
let result = calibration_timer.measure_cycles(|| {
spin_bundle(LARGE_SAMPLE_COUNT as u64);
});
let total_cycles = match result {
Ok(cycles) => cycles,
Err(_) => continue,
};
if total_cycles > MAX_REASONABLE_CYCLES {
continue;
}
let avg_cycles = total_cycles as f64 / LARGE_SAMPLE_COUNT as f64;
let cost_per_unit = avg_cycles / timer.cycles_per_ns();
return Self { cost_per_unit };
}
Self::calibrate_with_instant()
}
fn calibrate_with_instant() -> Self {
const LARGE_SAMPLE_COUNT: usize = 100_000;
spin_bundle(LARGE_SAMPLE_COUNT as u64);
let start = Instant::now();
spin_bundle(LARGE_SAMPLE_COUNT as u64);
let elapsed_ns = start.elapsed().as_nanos() as f64;
let cost_per_unit = elapsed_ns / LARGE_SAMPLE_COUNT as f64;
Self { cost_per_unit }
}
#[allow(dead_code)] fn iterations_for_target(&self, target_ns: u64) -> u64 {
if target_ns == 0 || self.cost_per_unit <= 0.0 {
return 0;
}
let exact_iters = (target_ns as f64) / self.cost_per_unit;
exact_iters.round().max(0.0) as u64
}
}
#[inline(never)]
fn spin_bundle(iterations: u64) {
for _ in 0..iterations {
std::hint::spin_loop();
std::hint::black_box(());
}
}
pub fn init_effect_injection() {
EFFECT_INITIALIZED.with(|initialized| initialized.set(true));
BUNDLE_CALIBRATION.with(|_| {
});
validate_busy_wait_once();
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
static FALLBACK_WARNING_PRINTED: AtomicBool = AtomicBool::new(false);
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
fn warn_imprecise_fallback() {
if !FALLBACK_WARNING_PRINTED.swap(true, Ordering::Relaxed) {
eprintln!(
"[tacet::effect] WARNING: Using Instant::now() fallback for busy_wait_ns. \
This has ~50ns overhead and poor precision. For accurate effect injection, \
use aarch64 or x86_64 on Linux/macOS."
);
}
}
pub fn timer_backend_name() -> &'static str {
#[cfg(all(target_os = "linux", target_arch = "aarch64", feature = "perf"))]
{
if get_effect_timer().is_some() {
"perf_event"
} else {
"cntvct_el0"
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
"cntvct_el0"
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
"rdtsc"
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
{
"instant_fallback"
}
}
pub fn using_precise_timer() -> bool {
#[cfg(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
))]
{
true
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
{
false
}
}
pub use tacet_core::timer::counter_frequency_hz;
pub use tacet_core::timer::timer_resolution_ns;
pub fn min_injectable_effect_ns() -> f64 {
let resolution = timer_resolution_ns();
let floor = resolution * 5.0;
floor.clamp(20.0, 500.0)
}
#[cfg(target_arch = "aarch64")]
fn aarch64_counter_freq() -> u64 {
counter_frequency_hz()
}
#[cfg(target_arch = "aarch64")]
#[inline]
fn ns_to_ticks_aarch64(ns: u64) -> u64 {
let freq = aarch64_counter_freq();
if freq == 1_000_000_000 {
ns
} else if freq == 24_000_000 {
(ns * 24).div_ceil(1000)
} else {
((ns as u128 * freq as u128).div_ceil(1_000_000_000)) as u64
}
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
fn x86_64_tsc_freq() -> u64 {
counter_frequency_hz()
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
#[inline]
fn rdtsc() -> u64 {
let lo: u32;
let hi: u32;
unsafe {
core::arch::asm!(
"rdtsc",
out("eax") lo,
out("edx") hi,
options(nostack, nomem)
);
}
((hi as u64) << 32) | (lo as u64)
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
#[inline]
fn ns_to_ticks_x86_64(ns: u64) -> u64 {
let freq = x86_64_tsc_freq();
((ns as u128 * freq as u128).div_ceil(1_000_000_000)) as u64
}
static GLOBAL_MAX_DELAY_NS: AtomicU64 = AtomicU64::new(1_000_000);
thread_local! {
static BUSY_WAIT_VALIDATED: Cell<bool> = const { Cell::new(false) };
}
fn validate_busy_wait_once() {
BUSY_WAIT_VALIDATED.with(|validated| {
if validated.get() {
return;
}
validated.set(true);
if !using_precise_timer() {
return;
}
const TARGET_NS: u64 = 10_000;
#[cfg(target_arch = "aarch64")]
{
busy_wait_ns_aarch64(100);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
busy_wait_ns_x86_64(100);
}
let start = Instant::now();
#[cfg(target_arch = "aarch64")]
{
busy_wait_ns_aarch64(TARGET_NS);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
busy_wait_ns_x86_64(TARGET_NS);
}
let actual_ns = start.elapsed().as_nanos() as u64;
let ratio = actual_ns as f64 / TARGET_NS as f64;
if !(0.3..=10.0).contains(&ratio) {
eprintln!(
"[tacet::effect] WARNING: busy_wait_ns accuracy issue detected!\n\
Requested: {}ns, Actual: {}ns, Ratio: {:.2}x\n\
Detected frequency: {} Hz ({})\n\
This may indicate incorrect counter frequency detection.\n\
Effect injection timing may be inaccurate.",
TARGET_NS,
actual_ns,
ratio,
counter_frequency_hz(),
timer_backend_name()
);
}
});
}
pub fn set_global_max_delay_ns(max_ns: u64) {
let clamped = max_ns.min(10_000_000);
GLOBAL_MAX_DELAY_NS.store(clamped, Ordering::Relaxed);
}
pub fn global_max_delay_ns() -> u64 {
GLOBAL_MAX_DELAY_NS.load(Ordering::Relaxed)
}
#[inline(never)]
pub fn busy_wait_ns(ns: u64) {
EFFECT_INITIALIZED.with(|initialized| {
if !initialized.get() {
panic!(
"Effect injection not initialized! Call init_effect_injection() before using busy_wait_ns().\n\
\n\
Example:\n\
use tacet::helpers::init_effect_injection;\n\
\n\
init_effect_injection(); // Required before first use\n\
busy_wait_ns(100); // Now safe to use\n\
\n\
This ensures calibration doesn't interfere with timing measurements."
);
}
});
let max = GLOBAL_MAX_DELAY_NS.load(Ordering::Relaxed);
let clamped = ns.min(max);
if clamped == 0 {
return;
}
#[cfg(target_arch = "aarch64")]
{
busy_wait_ns_aarch64(clamped);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
busy_wait_ns_x86_64(clamped);
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
{
warn_imprecise_fallback();
busy_wait_ns_instant(clamped);
}
}
#[cfg(target_arch = "aarch64")]
#[inline(never)]
fn busy_wait_ns_aarch64(ns: u64) {
if ns == 0 {
return;
}
let used_bundles = BUNDLE_CALIBRATION.with(|cal| {
if let Some(cal) = cal.as_ref() {
let iterations = cal.iterations_for_target(ns);
if iterations > 0 {
spin_bundle(iterations);
}
true
} else {
false
}
});
if used_bundles {
return;
}
let freq = aarch64_counter_freq();
if freq > 0 {
let ticks = ns_to_ticks_aarch64(ns);
let start: u64;
unsafe {
core::arch::asm!("mrs {}, cntvct_el0", out(reg) start);
}
loop {
let now: u64;
unsafe {
core::arch::asm!("mrs {}, cntvct_el0", out(reg) now);
}
if now.wrapping_sub(start) >= ticks {
break;
}
std::hint::spin_loop();
}
return;
}
let start = Instant::now();
let target = Duration::from_nanos(ns);
while start.elapsed() < target {
std::hint::spin_loop();
}
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
#[inline(never)]
fn busy_wait_ns_x86_64(ns: u64) {
let ticks = ns_to_ticks_x86_64(ns);
let start = rdtsc();
loop {
let now = rdtsc();
if now.wrapping_sub(start) >= ticks {
break;
}
std::hint::spin_loop();
}
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
#[inline(never)]
fn busy_wait_ns_instant(ns: u64) {
let start = Instant::now();
let target = Duration::from_nanos(ns);
while start.elapsed() < target {
std::hint::spin_loop();
}
}
#[derive(Debug, Clone)]
pub struct EffectInjector {
max_ns: u64,
}
impl Default for EffectInjector {
fn default() -> Self {
Self::new()
}
}
impl EffectInjector {
pub fn new() -> Self {
init_effect_injection();
Self { max_ns: 100_000 }
}
pub fn with_max_ns(max_ns: u64) -> Self {
init_effect_injection();
Self { max_ns }
}
#[inline]
pub fn delay_ns(&self, ns: u64) {
let clamped = ns.min(self.max_ns);
busy_wait_ns(clamped);
}
#[inline]
pub fn conditional_delay(&self, condition: bool, delay_ns: u64) {
if condition {
self.delay_ns(delay_ns);
}
std::hint::black_box(());
}
#[inline]
pub fn proportional_delay(&self, value: u64, ns_per_unit: f64) {
let delay = (value as f64 * ns_per_unit) as u64;
self.delay_ns(delay);
}
#[inline]
pub fn hamming_weight_delay(&self, data: &[u8], ns_per_bit: f64) {
let weight: u32 = data.iter().map(|b| b.count_ones()).sum();
let delay = (weight as f64 * ns_per_bit) as u64;
self.delay_ns(delay);
}
pub fn max_ns(&self) -> u64 {
self.max_ns
}
}
#[derive(Debug, Clone, Copy)]
pub enum BenchmarkEffect {
Null,
FixedDelay {
delay_ns: u64,
},
ThetaMultiple {
multiplier: f64,
theta_ns: f64,
},
EarlyExit {
max_delay_ns: u64,
},
HammingWeight {
ns_per_bit: f64,
},
Bimodal {
slow_prob: f64,
slow_delay_ns: u64,
},
VariableDelay {
mean_ns: u64,
std_ns: u64,
},
TailEffect {
base_delay_ns: u64,
tail_prob: f64,
tail_mult: f64,
},
}
impl BenchmarkEffect {
pub fn expected_difference_ns(&self) -> Option<f64> {
match self {
BenchmarkEffect::Null => Some(0.0),
BenchmarkEffect::FixedDelay { delay_ns } => Some(*delay_ns as f64),
BenchmarkEffect::ThetaMultiple {
multiplier,
theta_ns,
} => Some(multiplier * theta_ns),
BenchmarkEffect::EarlyExit { max_delay_ns } => Some(-(*max_delay_ns as f64)), BenchmarkEffect::HammingWeight { .. } => None, BenchmarkEffect::Bimodal {
slow_prob,
slow_delay_ns,
} => Some(slow_prob * (*slow_delay_ns as f64)),
BenchmarkEffect::VariableDelay { mean_ns, .. } => Some(*mean_ns as f64),
BenchmarkEffect::TailEffect {
base_delay_ns,
tail_prob,
tail_mult,
} => {
let base = *base_delay_ns as f64;
Some(base * (1.0 + tail_prob * (tail_mult - 1.0)))
}
}
}
pub fn bimodal(slow_prob: f64, slow_delay_ns: u64) -> Self {
BenchmarkEffect::Bimodal {
slow_prob,
slow_delay_ns,
}
}
pub fn bimodal_default() -> Self {
Self::bimodal(0.05, 10_000)
}
pub fn variable_delay(mean_ns: u64, std_ns: u64) -> Self {
BenchmarkEffect::VariableDelay { mean_ns, std_ns }
}
pub fn tail_effect(base_delay_ns: u64, tail_prob: f64, tail_mult: f64) -> Self {
BenchmarkEffect::TailEffect {
base_delay_ns,
tail_prob,
tail_mult,
}
}
pub fn tail_effect_default() -> Self {
Self::tail_effect(100, 0.05, 10.0)
}
pub fn at_theta_multiple(multiplier: f64, theta_ns: f64) -> Self {
BenchmarkEffect::ThetaMultiple {
multiplier,
theta_ns,
}
}
pub fn adjacent_network_effects() -> Vec<(f64, Self)> {
vec![
(0.5, Self::FixedDelay { delay_ns: 50 }),
(1.0, Self::FixedDelay { delay_ns: 100 }),
(2.0, Self::FixedDelay { delay_ns: 200 }),
(5.0, Self::FixedDelay { delay_ns: 500 }),
(10.0, Self::FixedDelay { delay_ns: 1000 }),
]
}
pub fn research_effects() -> Vec<(f64, Self)> {
vec![
(1.0, Self::FixedDelay { delay_ns: 50 }),
(2.0, Self::FixedDelay { delay_ns: 100 }),
(5.0, Self::FixedDelay { delay_ns: 250 }),
(10.0, Self::FixedDelay { delay_ns: 500 }),
(20.0, Self::FixedDelay { delay_ns: 1000 }),
]
}
}
pub fn apply_effect(effect: BenchmarkEffect) -> impl Fn(&bool) {
use rand::Rng;
use rand_distr::{Distribution, Normal};
use std::cell::RefCell;
let injector = EffectInjector::new();
thread_local! {
static RNG: RefCell<rand::rngs::ThreadRng> = RefCell::new(rand::rng());
}
move |&is_sample: &bool| {
match effect {
BenchmarkEffect::Null => {
std::hint::black_box(());
}
BenchmarkEffect::FixedDelay { delay_ns } => {
injector.conditional_delay(is_sample, delay_ns);
}
BenchmarkEffect::ThetaMultiple {
multiplier,
theta_ns,
} => {
let delay = (multiplier * theta_ns) as u64;
injector.conditional_delay(is_sample, delay);
}
BenchmarkEffect::EarlyExit { max_delay_ns } => {
injector.conditional_delay(!is_sample, max_delay_ns);
}
BenchmarkEffect::HammingWeight { .. } => {
std::hint::black_box(());
}
BenchmarkEffect::Bimodal {
slow_prob,
slow_delay_ns,
} => {
if is_sample {
RNG.with(|rng| {
if rng.borrow_mut().random::<f64>() < slow_prob {
injector.delay_ns(slow_delay_ns);
}
});
}
}
BenchmarkEffect::VariableDelay { mean_ns, std_ns } => {
if is_sample {
RNG.with(|rng| {
let normal = Normal::new(mean_ns as f64, std_ns as f64)
.unwrap_or_else(|_| Normal::new(0.0, 1.0).unwrap());
let delay: f64 = normal.sample(&mut *rng.borrow_mut());
let delay = delay.max(0.0) as u64;
injector.delay_ns(delay);
});
}
}
BenchmarkEffect::TailEffect {
base_delay_ns,
tail_prob,
tail_mult,
} => {
if is_sample {
RNG.with(|rng| {
let is_tail = rng.borrow_mut().random::<f64>() < tail_prob;
let delay = if is_tail {
(base_delay_ns as f64 * tail_mult) as u64
} else {
base_delay_ns
};
injector.delay_ns(delay);
});
}
}
}
}
}
pub fn apply_effect_bytes(effect: BenchmarkEffect) -> impl Fn(&[u8; 32]) {
let injector = EffectInjector::new();
move |data: &[u8; 32]| {
match effect {
BenchmarkEffect::HammingWeight { ns_per_bit } => {
injector.hamming_weight_delay(data, ns_per_bit);
}
BenchmarkEffect::FixedDelay { delay_ns } => {
let is_sample = data.iter().any(|&b| b != 0);
injector.conditional_delay(is_sample, delay_ns);
}
_ => {
std::hint::black_box(data);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_busy_wait_returns_quickly_for_zero() {
init_effect_injection();
let start = Instant::now();
busy_wait_ns(0);
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(10),
"busy_wait_ns(0) took {:?}, should be nearly instant",
elapsed
);
}
#[test]
#[ignore] fn test_busy_wait_respects_global_limit() {
init_effect_injection();
let old_max = global_max_delay_ns();
set_global_max_delay_ns(1000);
let start = Instant::now();
busy_wait_ns(1_000_000); let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(1));
set_global_max_delay_ns(old_max);
}
#[test]
fn test_busy_wait_approximate_accuracy() {
init_effect_injection();
let start = Instant::now();
busy_wait_ns(10_000);
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_micros(1),
"Too fast: {:?}",
elapsed
);
assert!(
elapsed < Duration::from_millis(10),
"Too slow: {:?}",
elapsed
);
}
#[test]
#[ignore] fn test_effect_injector_conditional() {
let injector = EffectInjector::with_max_ns(100_000);
let mut sum_false = Duration::ZERO;
let mut sum_true = Duration::ZERO;
for _ in 0..10 {
let start = Instant::now();
injector.conditional_delay(false, 50_000);
sum_false += start.elapsed();
let start = Instant::now();
injector.conditional_delay(true, 50_000);
sum_true += start.elapsed();
}
assert!(
sum_true > sum_false,
"Expected true ({:?}) > false ({:?})",
sum_true,
sum_false
);
}
#[test]
fn test_effect_injector_clamping() {
let injector = EffectInjector::with_max_ns(1000);
let start = Instant::now();
injector.delay_ns(1_000_000); let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(10),
"Clamped delay took {:?}, expected < 10ms (clamped to 1μs)",
elapsed
);
}
#[test]
fn test_benchmark_effect_expected_difference() {
assert_eq!(BenchmarkEffect::Null.expected_difference_ns(), Some(0.0));
assert_eq!(
BenchmarkEffect::FixedDelay { delay_ns: 100 }.expected_difference_ns(),
Some(100.0)
);
assert_eq!(
BenchmarkEffect::ThetaMultiple {
multiplier: 2.0,
theta_ns: 100.0
}
.expected_difference_ns(),
Some(200.0)
);
assert_eq!(
BenchmarkEffect::EarlyExit { max_delay_ns: 500 }.expected_difference_ns(),
Some(-500.0)
);
assert_eq!(
BenchmarkEffect::HammingWeight { ns_per_bit: 1.0 }.expected_difference_ns(),
None
);
}
#[test]
fn test_timer_backend_name() {
let name = timer_backend_name();
assert!(
name == "cntvct_el0"
|| name == "rdtsc"
|| name == "instant_fallback"
|| name == "perf_event",
"Unknown timer backend: {}",
name
);
}
#[test]
fn test_using_precise_timer() {
let precise = using_precise_timer();
let backend = timer_backend_name();
if backend == "instant_fallback" {
assert!(!precise, "Fallback should not be precise");
} else {
assert!(precise, "Hardware timer should be precise");
}
}
#[test]
fn test_platform_detection_consistency() {
let backend = timer_backend_name();
let precise = using_precise_timer();
#[cfg(target_arch = "aarch64")]
{
#[cfg(all(target_os = "linux", feature = "perf"))]
assert!(
backend == "cntvct_el0" || backend == "perf_event",
"Expected cntvct_el0 or perf_event on Linux ARM64, got: {}",
backend
);
#[cfg(not(all(target_os = "linux", feature = "perf")))]
assert_eq!(backend, "cntvct_el0");
assert!(precise);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
assert_eq!(backend, "rdtsc");
assert!(precise);
}
}
#[test]
fn test_effect_injector_default() {
let injector = EffectInjector::default();
assert_eq!(injector.max_ns(), 100_000); }
#[test]
fn test_effect_injector_custom_max() {
let injector = EffectInjector::with_max_ns(5000);
assert_eq!(injector.max_ns(), 5000);
}
#[test]
fn test_hamming_weight_calculation() {
let injector = EffectInjector::with_max_ns(100_000);
let all_zeros = [0u8; 32];
let all_ones = [0xFFu8; 32]; let half_set = [0xAAu8; 32];
let hw_zeros: u32 = all_zeros.iter().map(|b| b.count_ones()).sum();
let hw_ones: u32 = all_ones.iter().map(|b| b.count_ones()).sum();
let hw_half: u32 = half_set.iter().map(|b| b.count_ones()).sum();
assert_eq!(hw_zeros, 0);
assert_eq!(hw_ones, 256);
assert_eq!(hw_half, 128);
injector.hamming_weight_delay(&all_zeros, 1.0);
injector.hamming_weight_delay(&all_ones, 1.0);
injector.hamming_weight_delay(&half_set, 1.0);
}
#[test]
fn test_proportional_delay_calculation() {
let injector = EffectInjector::with_max_ns(100_000);
injector.proportional_delay(0, 10.0);
injector.proportional_delay(100, 10.0);
injector.proportional_delay(1000, 0.1);
}
#[test]
fn test_benchmark_effect_at_theta_multiple() {
let effect = BenchmarkEffect::at_theta_multiple(2.0, 100.0);
assert_eq!(effect.expected_difference_ns(), Some(200.0));
let effect = BenchmarkEffect::at_theta_multiple(0.5, 100.0);
assert_eq!(effect.expected_difference_ns(), Some(50.0));
}
#[test]
fn test_benchmark_effect_presets() {
let adjacent = BenchmarkEffect::adjacent_network_effects();
assert!(!adjacent.is_empty());
for window in adjacent.windows(2) {
assert!(window[0].0 < window[1].0, "Multipliers should increase");
}
let research = BenchmarkEffect::research_effects();
assert!(!research.is_empty());
}
#[test]
fn test_apply_effect_null() {
let effect_fn = apply_effect(BenchmarkEffect::Null);
effect_fn(&false);
effect_fn(&true);
}
#[test]
fn test_apply_effect_fixed_delay() {
let effect_fn = apply_effect(BenchmarkEffect::FixedDelay { delay_ns: 100 });
effect_fn(&false);
effect_fn(&true);
}
#[test]
fn test_apply_effect_bytes_hamming() {
let effect_fn = apply_effect_bytes(BenchmarkEffect::HammingWeight { ns_per_bit: 1.0 });
let zeros = [0u8; 32];
let ones = [0xFFu8; 32];
effect_fn(&zeros);
effect_fn(&ones);
}
#[test]
fn test_benchmark_effect_bimodal() {
let effect = BenchmarkEffect::bimodal(0.1, 1000);
match effect {
BenchmarkEffect::Bimodal {
slow_prob,
slow_delay_ns,
} => {
assert!((slow_prob - 0.1).abs() < 1e-9);
assert_eq!(slow_delay_ns, 1000);
}
_ => panic!("Expected Bimodal variant"),
}
assert_eq!(effect.expected_difference_ns(), Some(100.0));
let default = BenchmarkEffect::bimodal_default();
match default {
BenchmarkEffect::Bimodal {
slow_prob,
slow_delay_ns,
} => {
assert!((slow_prob - 0.05).abs() < 1e-9);
assert_eq!(slow_delay_ns, 10_000);
}
_ => panic!("Expected Bimodal variant"),
}
assert_eq!(default.expected_difference_ns(), Some(500.0)); }
#[test]
fn test_benchmark_effect_variable_delay() {
let effect = BenchmarkEffect::variable_delay(500, 100);
match effect {
BenchmarkEffect::VariableDelay { mean_ns, std_ns } => {
assert_eq!(mean_ns, 500);
assert_eq!(std_ns, 100);
}
_ => panic!("Expected VariableDelay variant"),
}
assert_eq!(effect.expected_difference_ns(), Some(500.0));
}
#[test]
fn test_benchmark_effect_tail_effect() {
let effect = BenchmarkEffect::tail_effect(100, 0.1, 5.0);
match effect {
BenchmarkEffect::TailEffect {
base_delay_ns,
tail_prob,
tail_mult,
} => {
assert_eq!(base_delay_ns, 100);
assert!((tail_prob - 0.1).abs() < 1e-9);
assert!((tail_mult - 5.0).abs() < 1e-9);
}
_ => panic!("Expected TailEffect variant"),
}
assert_eq!(effect.expected_difference_ns(), Some(140.0));
let default = BenchmarkEffect::tail_effect_default();
match default {
BenchmarkEffect::TailEffect {
base_delay_ns,
tail_prob,
tail_mult,
} => {
assert_eq!(base_delay_ns, 100);
assert!((tail_prob - 0.05).abs() < 1e-9);
assert!((tail_mult - 10.0).abs() < 1e-9);
}
_ => panic!("Expected TailEffect variant"),
}
assert_eq!(default.expected_difference_ns(), Some(145.0));
}
#[test]
fn test_apply_effect_bimodal() {
let effect_fn = apply_effect(BenchmarkEffect::Bimodal {
slow_prob: 0.1,
slow_delay_ns: 1000,
});
for _ in 0..20 {
effect_fn(&false);
effect_fn(&true);
}
}
#[test]
fn test_apply_effect_variable_delay() {
let effect_fn = apply_effect(BenchmarkEffect::VariableDelay {
mean_ns: 500,
std_ns: 100,
});
for _ in 0..20 {
effect_fn(&false);
effect_fn(&true);
}
}
#[test]
fn test_apply_effect_tail_effect() {
let effect_fn = apply_effect(BenchmarkEffect::TailEffect {
base_delay_ns: 100,
tail_prob: 0.1,
tail_mult: 5.0,
});
for _ in 0..20 {
effect_fn(&false);
effect_fn(&true);
}
}
#[test]
fn test_stochastic_effects_are_stochastic() {
use std::time::Instant;
let effect_fn = apply_effect(BenchmarkEffect::Bimodal {
slow_prob: 0.5, slow_delay_ns: 10_000,
});
let mut times = Vec::with_capacity(50);
for _ in 0..50 {
let start = Instant::now();
effect_fn(&true);
times.push(start.elapsed().as_nanos() as f64);
}
let min_time = times.iter().cloned().fold(f64::INFINITY, f64::min);
let max_time = times.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
assert!(
max_time > min_time * 1.5,
"Bimodal effect should show variation: min={:.0}ns, max={:.0}ns",
min_time,
max_time
);
}
#[test]
#[ignore] fn test_busy_wait_multiple_calls() {
init_effect_injection();
let delay_ns = 5000; let iterations = 10;
let mut total_elapsed = Duration::ZERO;
for _ in 0..iterations {
let start = Instant::now();
busy_wait_ns(delay_ns);
total_elapsed += start.elapsed();
}
let avg_elapsed_ns = total_elapsed.as_nanos() as f64 / iterations as f64;
assert!(
avg_elapsed_ns >= delay_ns as f64 * 0.5,
"Average too fast: {}ns for {}ns request",
avg_elapsed_ns,
delay_ns
);
assert!(
avg_elapsed_ns < delay_ns as f64 * 100.0,
"Average too slow: {}ns for {}ns request",
avg_elapsed_ns,
delay_ns
);
}
#[test]
#[ignore] fn test_busy_wait_monotonicity() {
init_effect_injection();
let delays = [1000u64, 5000, 10000, 50000]; let mut times = Vec::new();
for &delay in &delays {
let start = Instant::now();
for _ in 0..5 {
busy_wait_ns(delay);
}
times.push(start.elapsed().as_nanos() as f64 / 5.0);
}
for i in 1..times.len() {
assert!(
times[i] > times[i - 1] * 0.5, "Delay {} ({:.0}ns) should be longer than {} ({:.0}ns)",
delays[i],
times[i],
delays[i - 1],
times[i - 1]
);
}
}
#[test]
fn test_global_max_delay_hard_limit() {
let old_max = global_max_delay_ns();
set_global_max_delay_ns(100_000_000);
assert_eq!(global_max_delay_ns(), 10_000_000);
set_global_max_delay_ns(old_max);
}
#[test]
fn test_global_max_delay_normal() {
let old_max = global_max_delay_ns();
set_global_max_delay_ns(500_000); assert_eq!(global_max_delay_ns(), 500_000);
set_global_max_delay_ns(old_max);
}
#[test]
fn test_counter_frequency_reasonable() {
let freq = counter_frequency_hz();
let backend = timer_backend_name();
if backend == "instant_fallback" {
assert_eq!(freq, 0, "Fallback should report 0 frequency");
} else {
assert!(
freq >= 1_000_000,
"Frequency {} Hz is too low for {}",
freq,
backend
);
assert!(
freq <= 10_000_000_000,
"Frequency {} Hz is too high for {}",
freq,
backend
);
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
assert!(
freq == 24_000_000 || freq == 1_000_000_000,
"Apple Silicon should be 24MHz (M1/M2) or 1GHz (M3+), got {} Hz",
freq
);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
assert!(
freq >= 1_000_000_000,
"x86_64 TSC {} Hz seems too low",
freq
);
}
}
}
#[test]
fn test_timer_resolution_reasonable() {
let resolution = timer_resolution_ns();
let backend = timer_backend_name();
if backend == "instant_fallback" {
assert!(
resolution.is_infinite(),
"Fallback should report infinite resolution"
);
} else {
assert!(
resolution > 0.0,
"Resolution should be positive for {}",
backend
);
assert!(
resolution < 1000.0,
"Resolution {} ns is too coarse for {}",
resolution,
backend
);
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
let is_m1_m2 = (resolution - 41.67).abs() < 1.0;
let is_m3_plus = resolution < 2.0;
assert!(
is_m1_m2 || is_m3_plus,
"Apple Silicon resolution should be ~42ns (M1/M2) or ~1ns (M3+), got {}",
resolution
);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
assert!(
resolution < 10.0,
"x86_64 resolution {} ns seems too coarse",
resolution
);
}
}
}
#[test]
fn test_frequency_consistency() {
let freq1 = counter_frequency_hz();
let freq2 = counter_frequency_hz();
assert_eq!(freq1, freq2, "Frequency should be consistent (cached)");
let res1 = timer_resolution_ns();
let res2 = timer_resolution_ns();
assert_eq!(res1, res2, "Resolution should be consistent");
}
#[test]
fn test_min_injectable_effect_reasonable() {
let min_effect = min_injectable_effect_ns();
assert!(min_effect >= 20.0, "Min effect should be at least 20ns");
assert!(min_effect <= 500.0, "Min effect should not exceed 500ns");
}
#[test]
fn test_min_injectable_effect_above_resolution() {
let resolution = timer_resolution_ns();
let min_effect = min_injectable_effect_ns();
assert!(
min_effect >= resolution * 3.0,
"Min effect should be at least 3× timer resolution for reliable measurement"
);
}
#[test]
#[ignore]
fn test_frequency_accuracy_validation() {
let backend = timer_backend_name();
if backend == "instant_fallback" {
return;
}
let freq = counter_frequency_hz();
if freq == 0 {
return;
}
const TARGET_DELAY_MS: u64 = 50;
const TARGET_DELAY_NS: u64 = TARGET_DELAY_MS * 1_000_000;
let expected_ticks = ((TARGET_DELAY_NS as u128 * freq as u128) / 1_000_000_000) as u64;
let (start_ticks, end_ticks) = measure_ticks_during_sleep(TARGET_DELAY_MS);
let actual_ticks = end_ticks.wrapping_sub(start_ticks);
let ratio = actual_ticks as f64 / expected_ticks as f64;
assert!(
ratio > 0.8 && ratio < 1.2,
"Frequency validation failed!\n\
Backend: {}\n\
Detected frequency: {} Hz ({:.2} MHz)\n\
Target delay: {} ms\n\
Expected ticks: {}\n\
Actual ticks: {}\n\
Ratio: {:.3} (should be ~1.0)\n\
This suggests the detected frequency is incorrect.",
backend,
freq,
freq as f64 / 1e6,
TARGET_DELAY_MS,
expected_ticks,
actual_ticks,
ratio
);
}
#[cfg(target_arch = "aarch64")]
fn measure_ticks_during_sleep(sleep_ms: u64) -> (u64, u64) {
let start: u64;
unsafe {
core::arch::asm!("mrs {}, cntvct_el0", out(reg) start);
}
std::thread::sleep(Duration::from_millis(sleep_ms));
let end: u64;
unsafe {
core::arch::asm!("mrs {}, cntvct_el0", out(reg) end);
}
(start, end)
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
fn measure_ticks_during_sleep(sleep_ms: u64) -> (u64, u64) {
let start = rdtsc();
std::thread::sleep(Duration::from_millis(sleep_ms));
let end = rdtsc();
(start, end)
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
)))]
fn measure_ticks_during_sleep(_sleep_ms: u64) -> (u64, u64) {
(0, 0) }
#[test]
#[ignore] fn test_bundle_calibration_accuracy() {
init_effect_injection();
let has_bundles = BUNDLE_CALIBRATION.with(|cal| cal.as_ref().is_some());
if !has_bundles {
eprintln!("Skipping bundle calibration test - bundles not available");
return;
}
let calibration = BUNDLE_CALIBRATION.with(|cal| cal.as_ref().unwrap().clone());
let test_cases = [1000, 5000, 10000, 50000];
for iterations in test_cases {
let start = Instant::now();
spin_bundle(iterations);
let actual_ns = start.elapsed().as_nanos() as f64;
let expected_ns = calibration.cost_per_unit * iterations as f64;
let ratio = actual_ns / expected_ns;
assert!(
ratio > 0.5 && ratio < 2.0,
"Bundle calibration accuracy failed for {} iterations!\n\
Calibrated cost_per_unit: {:.2}ns\n\
Expected total time: {:.2}ns\n\
Actual total time: {:.2}ns\n\
Ratio: {:.2}x (should be ~1.0)\n\
This suggests calibration is measuring the wrong metric.",
iterations,
calibration.cost_per_unit,
expected_ns,
actual_ns,
ratio
);
}
}
#[test]
#[ignore] fn test_busy_wait_ns_scaling() {
init_effect_injection();
if timer_backend_name() == "instant_fallback" {
eprintln!("Skipping scaling test - fallback timer not accurate enough");
return;
}
let test_delays = [1_000, 10_000, 50_000];
for target_ns in test_delays {
let mut total_elapsed_ns = 0u128;
const RUNS: usize = 10;
for _ in 0..RUNS {
let start = Instant::now();
busy_wait_ns(target_ns);
total_elapsed_ns += start.elapsed().as_nanos();
}
let avg_elapsed_ns = (total_elapsed_ns / RUNS as u128) as f64;
let ratio = avg_elapsed_ns / target_ns as f64;
assert!(
ratio > 0.5 && ratio < 3.0,
"busy_wait_ns scaling failed for {}ns target!\n\
Backend: {}\n\
Frequency: {} Hz\n\
Target delay: {}ns ({:.1}μs)\n\
Average actual delay: {:.0}ns ({:.1}μs)\n\
Ratio: {:.2}x (should be ~1.0)\n\
Runs: {}\n\
This suggests effect injection is not scaling correctly.",
target_ns,
timer_backend_name(),
counter_frequency_hz(),
target_ns,
target_ns as f64 / 1000.0,
avg_elapsed_ns,
avg_elapsed_ns / 1000.0,
ratio,
RUNS
);
}
}
#[test]
#[ignore] fn test_busy_wait_ns_consistency() {
init_effect_injection();
if timer_backend_name() == "instant_fallback" {
eprintln!("Skipping consistency test - fallback timer not accurate enough");
return;
}
const TARGET_NS: u64 = 10_000; const ITERATIONS: usize = 50;
let mut measurements = Vec::with_capacity(ITERATIONS);
for _ in 0..ITERATIONS {
let start = Instant::now();
busy_wait_ns(TARGET_NS);
let elapsed_ns = start.elapsed().as_nanos() as u64;
measurements.push(elapsed_ns);
}
measurements.sort_unstable();
let median = measurements[ITERATIONS / 2];
let ratio = median as f64 / TARGET_NS as f64;
assert!(
ratio > 0.5 && ratio < 3.0,
"busy_wait_ns consistency failed!\n\
Backend: {}\n\
Frequency: {} Hz\n\
Target delay: {}ns\n\
Median actual delay: {}ns\n\
Ratio: {:.2}x (should be ~1.0)\n\
Min: {}ns, Max: {}ns\n\
Measurements: {} iterations\n\
This suggests effect injection has accuracy issues.",
timer_backend_name(),
counter_frequency_hz(),
TARGET_NS,
median,
ratio,
measurements[0],
measurements[ITERATIONS - 1],
ITERATIONS
);
let outliers: Vec<_> = measurements
.iter()
.filter(|&&m| {
let individual_ratio = m as f64 / TARGET_NS as f64;
!(0.3..=5.0).contains(&individual_ratio)
})
.collect();
assert!(
outliers.len() < ITERATIONS / 10, "Too many outlier measurements: {}/{} measurements outside 0.3x-5.0x range",
outliers.len(),
ITERATIONS
);
}
#[test]
#[ignore] fn test_busy_wait_accuracy_validation() {
init_effect_injection();
let backend = timer_backend_name();
if backend == "instant_fallback" {
return;
}
const TARGET_NS: u64 = 10_000;
const ITERATIONS: usize = 20;
let mut total_elapsed_ns: u128 = 0;
for _ in 0..ITERATIONS {
let start = Instant::now();
busy_wait_ns(TARGET_NS);
total_elapsed_ns += start.elapsed().as_nanos();
}
let avg_elapsed_ns = total_elapsed_ns / ITERATIONS as u128;
let ratio = avg_elapsed_ns as f64 / TARGET_NS as f64;
assert!(
ratio > 0.5 && ratio < 5.0,
"busy_wait_ns accuracy validation failed!\n\
Backend: {}\n\
Detected frequency: {} Hz\n\
Target delay: {} ns\n\
Average actual delay: {} ns\n\
Ratio: {:.2} (should be 1.0-2.0 typically)\n\
This suggests the counter frequency is incorrect.",
backend,
counter_frequency_hz(),
TARGET_NS,
avg_elapsed_ns,
ratio
);
}
#[test]
#[ignore] fn test_raw_spin_bundle_timing() {
let iterations = 10_000u64;
spin_bundle(1000);
let start = Instant::now();
spin_bundle(iterations);
let elapsed_ns = start.elapsed().as_nanos() as f64;
let ns_per_iter = elapsed_ns / iterations as f64;
eprintln!(
"spin_bundle({}) took {:.0}ns total, {:.4}ns/iter",
iterations, elapsed_ns, ns_per_iter
);
assert!(
ns_per_iter > 0.1,
"spin_bundle too fast: {:.4}ns/iter suggests it's being optimized away",
ns_per_iter
);
assert!(
ns_per_iter < 1000.0,
"spin_bundle too slow: {:.4}ns/iter is unreasonable",
ns_per_iter
);
}
#[test]
#[ignore] fn test_calibration_cold_vs_warm() {
const ITERATIONS: u64 = 100_000;
let start1 = Instant::now();
spin_bundle(ITERATIONS);
let cold_ns = start1.elapsed().as_nanos() as f64;
let start2 = Instant::now();
spin_bundle(ITERATIONS);
let warm_ns = start2.elapsed().as_nanos() as f64;
let start3 = Instant::now();
spin_bundle(ITERATIONS);
let warm2_ns = start3.elapsed().as_nanos() as f64;
eprintln!(
"Cold: {:.0}ns total ({:.4}ns/iter)\n\
Warm1: {:.0}ns total ({:.4}ns/iter)\n\
Warm2: {:.0}ns total ({:.4}ns/iter)\n\
Cold/Warm ratio: {:.2}x",
cold_ns,
cold_ns / ITERATIONS as f64,
warm_ns,
warm_ns / ITERATIONS as f64,
warm2_ns,
warm2_ns / ITERATIONS as f64,
cold_ns / warm_ns
);
let ratio = cold_ns / warm_ns;
assert!(
ratio > 0.3 && ratio < 3.0,
"Cold/warm ratio {:.2}x is abnormal - suggests calibration issue",
ratio
);
}
#[test]
#[ignore] fn test_print_calibration_state() {
eprintln!("\n=== Calibration State ===");
eprintln!("Timer backend: {}", timer_backend_name());
eprintln!("Counter frequency: {} Hz", counter_frequency_hz());
eprintln!("Timer resolution: {:.4}ns", timer_resolution_ns());
eprintln!("Using precise timer: {}", using_precise_timer());
init_effect_injection();
let cal_info = BUNDLE_CALIBRATION.with(|cal| cal.as_ref().map(|c| c.cost_per_unit));
eprintln!("Calibration cost_per_unit: {:?}", cal_info);
if let Some(cost) = cal_info {
eprintln!("\nIteration calculations:");
for target in [1_000u64, 5_000, 10_000, 50_000, 100_000] {
let iterations = BUNDLE_CALIBRATION
.with(|cal| cal.as_ref().map(|c| c.iterations_for_target(target)));
eprintln!(
" {}ns → {:?} iterations (expected: {:.0})",
target,
iterations,
target as f64 / cost
);
}
}
eprintln!("\nDirect spin_bundle measurement:");
for iterations in [1_000u64, 10_000, 100_000] {
let start = Instant::now();
spin_bundle(iterations);
let elapsed_ns = start.elapsed().as_nanos() as f64;
eprintln!(
" spin_bundle({}) → {:.0}ns ({:.4}ns/iter)",
iterations,
elapsed_ns,
elapsed_ns / iterations as f64
);
}
eprintln!("=========================\n");
}
}