use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use super::{MetricType, Runtime};
use std::time::{Duration, Instant};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
pub name: String,
pub runtime: Runtime,
pub start_time: Instant,
pub end_time: Instant,
pub total_duration: Duration,
pub actual_iterations: u32,
pub iteration_durations: Vec<Duration>,
pub metrics: PerformanceMetrics,
pub statistics: Statistics,
pub metadata: HashMap<String, String>,
pub error: Option<String>,
pub warnings: Vec<String>,
pub success: bool,
}
impl BenchmarkResult {
pub fn new(name: &str, runtime: Runtime) -> Self {
Self {
name: name.to_string(),
runtime,
start_time: Instant::now(),
end_time: Instant::now(),
total_duration: Duration::from_secs(0),
actual_iterations: 0,
iteration_durations: Vec::new(),
metrics: PerformanceMetrics::default(),
statistics: Statistics::default(),
metadata: HashMap::new(),
error: None,
warnings: Vec::new(),
success: false,
}
}
pub fn start(&mut self) {
self.start_time = Instant::now();
}
pub fn finish(&mut self) {
self.end_time = Instant::now();
self.total_duration = self.end_time.duration_since(self.start_time);
self.statistics = Statistics::from_iterations(&self.iteration_durations);
self.success = self.error.is_none();
}
pub fn add_iteration(&mut self, duration: Duration) {
self.iteration_durations.push(duration);
self.actual_iterations += 1;
}
pub fn add_error(&mut self, error: &str) {
self.error = Some(error.to_string());
}
pub fn add_warning(&mut self, warning: &str) {
self.warnings.push(warning.to_string());
}
pub fn add_metadata(&mut self, key: &str, value: &str) {
self.metadata.insert(key.to_string(), value.to_string());
}
pub fn average_duration(&self) -> Duration {
self.statistics.mean
}
pub fn median_duration(&self) -> Duration {
self.statistics.median
}
pub fn min_duration(&self) -> Duration {
self.statistics.min
}
pub fn max_duration(&self) -> Duration {
self.statistics.max
}
pub fn standard_deviation(&self) -> Duration {
self.statistics.std_dev
}
pub fn coefficient_of_variation(&self) -> f64 {
if self.statistics.mean.as_nanos() == 0 {
0.0
} else {
self.statistics.std_dev.as_nanos() as f64 / self.statistics.mean.as_nanos() as f64
}
}
pub fn throughput(&self) -> f64 {
if self.total_duration.as_secs_f64() == 0.0 {
0.0
} else {
self.actual_iterations as f64 / self.total_duration.as_secs_f64()
}
}
pub fn is_statistically_significant(&self, other: &Self, alpha: f64) -> bool {
self.statistics.is_significantly_different(&other.statistics, alpha)
}
pub fn performance_improvement(&self, baseline: &Self) -> f64 {
if baseline.average_duration().as_nanos() == 0 {
0.0
} else {
let baseline_ns: _ = baseline.average_duration().as_nanos() as f64;
let current_ns: _ = self.average_duration().as_nanos() as f64;
((baseline_ns - current_ns) / baseline_ns) * 100.0
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PerformanceMetrics {
pub execution_time_ms: f64,
pub memory_usage_bytes: u64,
pub memory_peak_bytes: u64,
pub cpu_usage_percent: f64,
pub throughput_ops_per_sec: f64,
pub latency_ms: f64,
pub io_operations: u64,
pub network_bytes: u64,
pub gc_time_ms: f64,
pub compilation_time_ms: f64,
pub jit_compilations: u32,
pub cache_hit_rate: f64,
pub custom_metrics: HashMap<String, serde_json::Value>>,
}
impl PerformanceMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn execution_time(mut self, time: Duration) -> Self {
self.execution_time_ms = time.as_secs_f64() * 1000.0;
self
}
pub fn memory_usage(mut self, usage: u64) -> Self {
self.memory_usage_bytes = usage;
self
}
pub fn memory_peak(mut self, peak: u64) -> Self {
self.memory_peak_bytes = peak;
self
}
pub fn cpu_usage(mut self, usage: f64) -> Self {
self.cpu_usage_percent = usage;
self
}
pub fn throughput(mut self, throughput: f64) -> Self {
self.throughput_ops_per_sec = throughput;
self
}
pub fn latency(mut self, latency: Duration) -> Self {
self.latency_ms = latency.as_secs_f64() * 1000.0;
self
}
pub fn io_operations(mut self, operations: u64) -> Self {
self.io_operations = operations;
self
}
pub fn network_bytes(mut self, bytes: u64) -> Self {
self.network_bytes = bytes;
self
}
pub fn gc_time(mut self, time: Duration) -> Self {
self.gc_time_ms = time.as_secs_f64() * 1000.0;
self
}
pub fn compilation_time(mut self, time: Duration) -> Self {
self.compilation_time_ms = time.as_secs_f64() * 1000.0;
self
}
pub fn jit_compilations(mut self, count: u32) -> Self {
self.jit_compilations = count;
self
}
pub fn cache_hit_rate(mut self, rate: f64) -> Self {
self.cache_hit_rate = rate;
self
}
pub fn add_custom_metric(mut self, key: &str, value: serde_json::Value) -> Self {
self.custom_metrics.insert(key.to_string(), value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Statistics {
pub sample_count: usize,
pub mean: Duration,
pub median: Duration,
pub min: Duration,
pub max: Duration,
pub std_dev: Duration,
pub variance: Duration,
pub p25: Duration,
pub p75: Duration,
pub p90: Duration,
pub p95: Duration,
pub p99: Duration,
pub iqr: Duration,
pub skewness: f64,
pub kurtosis: f64,
pub confidence_interval_lower: Duration,
pub confidence_interval_upper: Duration,
}
impl Default for Statistics {
fn default() -> Self {
Self {
sample_count: 0,
mean: Duration::from_secs(0),
median: Duration::from_secs(0),
min: Duration::from_secs(0),
max: Duration::from_secs(0),
std_dev: Duration::from_secs(0),
variance: Duration::from_secs(0),
p25: Duration::from_secs(0),
p75: Duration::from_secs(0),
p90: Duration::from_secs(0),
p95: Duration::from_secs(0),
p99: Duration::from_secs(0),
iqr: Duration::from_secs(0),
skewness: 0.0,
kurtosis: 0.0,
confidence_interval_lower: Duration::from_secs(0),
confidence_interval_upper: Duration::from_secs(0),
}
}
}
impl Statistics {
pub fn from_iterations(iterations: &[Duration]) -> Self {
if iterations.is_empty() {
return Self::default();
}
let mut values: Vec<u64> = iterations.iter().map(|d| d.as_nanos() as u64).collect();
values.sort();
let sample_count: _ = values.len();
let sum: u64 = values.iter().sum();
let mean_ns: _ = sum / sample_count as u64;
let median_ns: _ = if sample_count % 2 == 0 {
(values[sample_count / 2 - 1] + values[sample_count / 2]) / 2
} else {
values[sample_count / 2]
};
let min_ns: _ = values[0];
let max_ns: _ = values[sample_count - 1];
let variance_ns: u64 = values.iter()
.map(|&x| {
let diff: _ = x as i128 - mean_ns as i128;
(diff * diff) as u64
})
.sum::<u64>() / sample_count as u64;
let std_dev_ns: _ = (variance_ns as f64).sqrt() as u64;
let p25_ns: _ = calculate_percentile(&values, 25.0);
let p75_ns: _ = calculate_percentile(&values, 75.0);
let p90_ns: _ = calculate_percentile(&values, 90.0);
let p95_ns: _ = calculate_percentile(&values, 95.0);
let p99_ns: _ = calculate_percentile(&values, 99.0);
let iqr_ns: _ = p75_ns - p25_ns;
let (skewness, kurtosis) = calculate_higher_moments(&values, mean_ns);
let confidence_interval: _ = calculate_confidence_interval(std_dev_ns, sample_count);
let confidence_interval_lower_ns: _ = if mean_ns > confidence_interval {
mean_ns - confidence_interval
} else {
0
};
let confidence_interval_upper_ns: _ = mean_ns + confidence_interval;
Self {
sample_count,
mean: Duration::from_nanos(mean_ns),
median: Duration::from_nanos(median_ns),
min: Duration::from_nanos(min_ns),
max: Duration::from_nanos(max_ns),
std_dev: Duration::from_nanos(std_dev_ns),
variance: Duration::from_nanos(variance_ns),
p25: Duration::from_nanos(p25_ns),
p75: Duration::from_nanos(p75_ns),
p90: Duration::from_nanos(p90_ns),
p95: Duration::from_nanos(p95_ns),
p99: Duration::from_nanos(p99_ns),
iqr: Duration::from_nanos(iqr_ns),
skewness,
kurtosis,
confidence_interval_lower: Duration::from_nanos(confidence_interval_lower_ns),
confidence_interval_upper: Duration::from_nanos(confidence_interval_upper_ns),
}
}
pub fn is_significantly_different(&self, other: &Self, alpha: f64) -> bool {
self.confidence_interval_upper < other.confidence_interval_lower ||
other.confidence_interval_upper < self.confidence_interval_lower
}
pub fn coefficient_of_variation(&self) -> f64 {
if self.mean.as_nanos() == 0 {
0.0
} else {
self.std_dev.as_nanos() as f64 / self.mean.as_nanos() as f64
}
}
pub fn relative_iqr(&self) -> f64 {
if self.median.as_nanos() == 0 {
0.0
} else {
self.iqr.as_nanos() as f64 / self.median.as_nanos() as f64
}
}
}
fn calculate_percentile(values: &[u64], percentile: f64) -> u64 {
if values.is_empty() {
return 0;
}
let index: _ = (percentile / 100.0) * (values.len() - 1) as f64;
let lower: _ = index.floor() as usize;
let upper: _ = index.ceil() as usize;
if lower == upper {
values[lower]
} else {
let weight: _ = index - lower as f64;
(values[lower] as f64 * (1.0 - weight) + values[upper] as f64 * weight) as u64
}
}
fn calculate_higher_moments(values: &[u64], mean: u64) -> (f64, f64) {
if values.len() < 2 {
return (0.0, 0.0);
}
let n: _ = values.len() as f64;
let mean_f: _ = mean as f64;
let mut m3 = 0.0;
for &value in values {
let diff: _ = value as f64 - mean_f;
m3 += diff * diff * diff;
}
m3 /= n;
let m3: _ = m3 / (mean_f * mean_f * mean_f).max(1.0);
let mut m4 = 0.0;
for &value in values {
let diff: _ = value as f64 - mean_f;
m4 += diff * diff * diff * diff;
}
m4 /= n;
m4 = m4 / (mean_f * mean_f * mean_f * mean_f).max(1.0);
let skewness: _ = m3.signum() * m3.abs().sqrt();
let kurtosis: _ = m4 - 3.0;
(skewness, kurtosis)
}
fn calculate_confidence_interval(std_dev: u64, sample_size: usize) -> u64 {
if sample_size < 2 {
return 0;
}
let t_value: _ = if sample_size < 30 {
2.0
} else {
1.96
};
let standard_error: _ = std_dev as f64 / (sample_size as f64).sqrt();
(t_value * standard_error) as u64
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResultSet {
pub suite_name: String,
pub run_time: Instant,
pub results: Vec<BenchmarkResult>,
pub global_metadata: HashMap<String, String>,
pub environment: EnvironmentInfo,
}
impl BenchmarkResultSet {
pub fn new(suite_name: &str) -> Self {
Self {
suite_name: suite_name.to_string(),
run_time: Instant::now(),
results: Vec::new(),
global_metadata: HashMap::new(),
environment: EnvironmentInfo::default(),
}
}
pub fn add_result(&mut self, result: BenchmarkResult) {
self.results.push(result);
}
pub fn add_metadata(&mut self, key: &str, value: &str) {
self.global_metadata.insert(key.to_string(), value.to_string());
}
pub fn environment(mut self, env: EnvironmentInfo) -> Self {
self.environment = env;
self
}
pub fn group_by_runtime(&self) -> HashMap<Runtime, Vec<&BenchmarkResult> {
let mut groups: HashMap<Runtime, Vec<&BenchmarkResult> = HashMap::new();
for result in &self.results {
groups.entry(result.runtime).or_insert_with(Vec::new).push(result);
}
groups
}
pub fn get_average_performance(&self) -> HashMap<Runtime, Duration> {
let groups: _ = self.group_by_runtime();
let mut averages: HashMap<Runtime, Duration> = HashMap::new();
for (runtime, results) in groups {
let total_duration: Duration = results.iter()
.map(|r| r.average_duration())
.sum();
let avg_duration: _ = total_duration / results.len() as u32;
averages.insert(runtime, avg_duration);
}
averages
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvironmentInfo {
pub os: String,
pub architecture: String,
pub cpu_model: String,
pub cpu_cores: u32,
pub memory_size: u64,
pub rust_version: String,
pub v8_version: Option<String>,
pub build_time: String,
pub custom_env: HashMap<String, String>,
}
impl EnvironmentInfo {
pub fn new() -> Self {
Self::default()
}
pub fn os(mut self, os: &str) -> Self {
self.os = os.to_string();
self
}
pub fn architecture(mut self, arch: &str) -> Self {
self.architecture = arch.to_string();
self
}
pub fn cpu(mut self, model: &str, cores: u32) -> Self {
self.cpu_model = model.to_string();
self.cpu_cores = cores;
self
}
pub fn memory(mut self, size: u64) -> Self {
self.memory_size = size;
self
}
pub fn rust_version(mut self, version: &str) -> Self {
self.rust_version = version.to_string();
self
}
pub fn v8_version(mut self, version: &str) -> Self {
self.v8_version = Some(version.to_string());
self
}
pub fn add_env(mut self, key: &str, value: &str) -> Self {
self.custom_env.insert(key.to_string(), value.to_string());
self
}
}