#![cfg(all(feature = "perf", target_os = "linux"))]
use tacet::measurement::perf::{LinuxPerfTimer, PerfError};
#[test]
#[cfg(target_os = "linux")]
fn test_perf_timer_initialization() {
match LinuxPerfTimer::new() {
Ok(timer) => {
eprintln!("perf timer initialized successfully");
eprintln!(" cycles_per_ns: {:.2}", timer.cycles_per_ns());
eprintln!(" resolution_ns: {:.2}", timer.resolution_ns());
assert!(
timer.cycles_per_ns() > 0.5 && timer.cycles_per_ns() < 10.0,
"cycles_per_ns should be reasonable: {}",
timer.cycles_per_ns()
);
assert!(
timer.resolution_ns() > 0.1 && timer.resolution_ns() < 5.0,
"resolution_ns should be sub-nanosecond to few nanoseconds: {}",
timer.resolution_ns()
);
}
Err(PerfError::PermissionDenied) => {
eprintln!("perf timer requires elevated permissions (expected on some systems)");
eprintln!("Run with: sudo -E cargo test --features perf --test test_perf");
}
Err(e) => {
panic!("Unexpected perf error: {}", e);
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_perf_timer_measurement() {
let mut timer = match LinuxPerfTimer::new() {
Ok(t) => t,
Err(PerfError::PermissionDenied) => {
eprintln!("Skipping measurement test - requires elevated permissions");
return;
}
Err(e) => panic!("Unexpected error: {}", e),
};
let cycles = timer
.measure_cycles(|| {
let mut sum = 0u64;
for i in 0..10000 {
sum = sum.wrapping_add(std::hint::black_box(i));
}
std::hint::black_box(sum)
})
.unwrap();
eprintln!("Measured {} cycles for 10k additions", cycles);
assert!(cycles > 0, "Should measure non-zero cycles");
let ns = timer.cycles_to_ns(cycles);
eprintln!(" = {:.1} ns", ns);
assert!(ns > 0.0, "Should convert to positive nanoseconds");
}
#[test]
#[cfg(target_os = "linux")]
fn test_perf_timer_consistency() {
let mut timer = match LinuxPerfTimer::new() {
Ok(t) => t,
Err(PerfError::PermissionDenied) => {
eprintln!("Skipping consistency test - requires elevated permissions");
return;
}
Err(e) => panic!("Unexpected error: {}", e),
};
let mut samples = Vec::with_capacity(100);
for _ in 0..100 {
let cycles = timer
.measure_cycles(|| {
let mut sum = 0u64;
for i in 0..500 {
sum = sum.wrapping_add(std::hint::black_box(i));
}
std::hint::black_box(sum)
})
.unwrap();
samples.push(cycles as f64);
}
assert!(
samples.iter().all(|&c| c > 0.0),
"All measurements should be non-zero"
);
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
let variance = samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
let std_dev = variance.sqrt();
let cv = std_dev / mean;
eprintln!(
"Measurement stats: mean={:.0} cycles, std={:.0}, CV={:.2}",
mean, std_dev, cv
);
assert!(
cv < 0.5,
"CV {} should be < 0.5 for consistent measurements (mean={:.0}, std={:.0})",
cv,
mean,
std_dev
);
}
#[test]
#[cfg(not(target_os = "linux"))]
fn test_perf_unsupported_platform() {
let result = LinuxPerfTimer::new();
assert!(
matches!(result, Err(PerfError::UnsupportedPlatform)),
"Should return UnsupportedPlatform on non-Linux"
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_perf_timer_zero_work() {
let mut timer = match LinuxPerfTimer::new() {
Ok(t) => t,
Err(PerfError::PermissionDenied) => {
eprintln!("Skipping zero-work test - requires elevated permissions");
return;
}
Err(e) => panic!("Unexpected error: {}", e),
};
let cycles = timer.measure_cycles(|| std::hint::black_box(42)).unwrap();
eprintln!("Measured {} cycles for minimal work", cycles);
assert!(
cycles < 10000,
"Minimal work should take < 10k cycles, got {}",
cycles
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_perf_cycles_to_ns_conversion() {
let timer = match LinuxPerfTimer::new() {
Ok(t) => t,
Err(PerfError::PermissionDenied) => {
eprintln!("Skipping conversion test - requires elevated permissions");
return;
}
Err(e) => panic!("Unexpected error: {}", e),
};
let cycles_per_ns = timer.cycles_per_ns();
let test_cycles = 1000u64;
let ns = timer.cycles_to_ns(test_cycles);
let expected_ns = test_cycles as f64 / cycles_per_ns;
assert!(
(ns - expected_ns).abs() < 0.001,
"cycles_to_ns({}) = {} ns, expected {} ns",
test_cycles,
ns,
expected_ns
);
eprintln!(
"{} cycles = {:.2} ns (at {:.2} cycles/ns)",
test_cycles, ns, cycles_per_ns
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_perf_vs_standard_timer() {
use tacet::measurement::Timer;
eprintln!("\n=== Perf vs Standard Timer Comparison ===\n");
let std_timer = Timer::new();
eprintln!("Standard timer:");
eprintln!(" Cycles per ns: {:.4}", std_timer.cycles_per_ns());
eprintln!(" Resolution: {:.2} ns", std_timer.resolution_ns());
match LinuxPerfTimer::new() {
Ok(mut perf_timer) => {
eprintln!("\nPerf timer (perf_event):");
eprintln!(" Cycles per ns: {:.4}", perf_timer.cycles_per_ns());
eprintln!(" Resolution: {:.4} ns", perf_timer.resolution_ns());
let iterations = 1000;
let mut perf_samples: Vec<u64> = Vec::with_capacity(iterations);
for _ in 0..iterations {
let cycles = perf_timer
.measure_cycles(|| std::hint::black_box(42u64.wrapping_mul(17)))
.unwrap();
perf_samples.push(cycles);
}
let mut std_samples: Vec<u64> = Vec::with_capacity(iterations);
for _ in 0..iterations {
let cycles = std_timer
.measure_cycles(|| std::hint::black_box(42u64.wrapping_mul(17)))
.unwrap();
std_samples.push(cycles);
}
perf_samples.sort();
let perf_min = perf_samples[0];
let perf_max = perf_samples[iterations - 1];
let perf_median = perf_samples[iterations / 2];
let perf_unique: std::collections::HashSet<_> = perf_samples.iter().collect();
std_samples.sort();
let std_min = std_samples[0];
let std_max = std_samples[iterations - 1];
let std_median = std_samples[iterations / 2];
let std_unique: std::collections::HashSet<_> = std_samples.iter().collect();
eprintln!("\nMeasurement comparison ({} samples):", iterations);
eprintln!("\n Perf timer:");
eprintln!(
" Min: {} cycles ({:.2} ns)",
perf_min,
perf_timer.cycles_to_ns(perf_min)
);
eprintln!(
" Median: {} cycles ({:.2} ns)",
perf_median,
perf_timer.cycles_to_ns(perf_median)
);
eprintln!(
" Max: {} cycles ({:.2} ns)",
perf_max,
perf_timer.cycles_to_ns(perf_max)
);
eprintln!(" Unique values: {}", perf_unique.len());
eprintln!("\n Standard timer:");
eprintln!(
" Min: {} cycles ({:.2} ns)",
std_min,
std_timer.cycles_to_ns(std_min)
);
eprintln!(
" Median: {} cycles ({:.2} ns)",
std_median,
std_timer.cycles_to_ns(std_median)
);
eprintln!(
" Max: {} cycles ({:.2} ns)",
std_max,
std_timer.cycles_to_ns(std_max)
);
eprintln!(" Unique values: {}", std_unique.len());
if perf_timer.resolution_ns() < std_timer.resolution_ns() {
let improvement = std_timer.resolution_ns() / perf_timer.resolution_ns();
eprintln!("\n Resolution improvement: {:.1}x", improvement);
} else {
eprintln!(
"\n Note: Standard timer has similar or better resolution on this system"
);
}
eprintln!("\n===========================================\n");
}
Err(PerfError::PermissionDenied) => {
eprintln!("\nPerf timer error: Permission denied");
eprintln!("(Run with sudo to enable perf access)");
eprintln!("\n===========================================\n");
}
Err(e) => panic!("Unexpected perf error: {}", e),
}
}