use crate::error::Result;
use crate::graph::ArrowGraph;
use crate::algorithms::{GraphAlgorithm, AlgorithmParams};
use crate::performance::profiling::{Benchmark, BenchmarkResult, PerformanceExpectation, StatisticalSummary};
use arrow::array::{StringArray, Float64Array, UInt64Array};
use arrow::record_batch::RecordBatch;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use std::sync::Arc;
use rayon::prelude::*;
use criterion::{Criterion, BenchmarkId, Throughput};
#[derive(Debug)]
pub struct GraphBenchmarkSuite {
benchmarks: Vec<Box<dyn Benchmark>>,
test_graphs: Vec<TestGraph>,
config: BenchmarkSuiteConfig,
results_history: Vec<BenchmarkSession>,
}
#[derive(Debug, Clone)]
pub struct BenchmarkSuiteConfig {
pub warmup_iterations: usize,
pub measurement_iterations: usize,
pub timeout: Duration,
pub memory_profiling: bool,
pub cpu_profiling: bool,
pub statistical_analysis: bool,
pub generate_reports: bool,
pub comparison_baseline: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TestGraph {
pub name: String,
pub num_nodes: usize,
pub num_edges: usize,
pub graph_type: GraphType,
pub density: f64,
pub clustering_coefficient: f64,
}
#[derive(Debug, Clone)]
pub enum GraphType {
Random,
SmallWorld,
ScaleFree,
Grid,
Complete,
Tree,
RealWorld(String), }
#[derive(Debug, Clone)]
pub struct BenchmarkSession {
pub session_id: String,
pub timestamp: u64,
pub environment: BenchmarkEnvironment,
pub results: Vec<BenchmarkResult>,
pub summary: SessionSummary,
}
#[derive(Debug, Clone)]
pub struct BenchmarkEnvironment {
pub cpu_model: String,
pub cpu_cores: usize,
pub memory_gb: f64,
pub os_version: String,
pub rust_version: String,
pub compiler_flags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SessionSummary {
pub total_benchmarks: usize,
pub successful_benchmarks: usize,
pub failed_benchmarks: usize,
pub total_duration: Duration,
pub performance_regression: Option<f64>,
pub performance_improvement: Option<f64>,
}
#[derive(Debug)]
pub struct PageRankBenchmark {
iterations: usize,
damping_factor: f64,
tolerance: f64,
}
#[derive(Debug)]
pub struct ConnectedComponentsBenchmark {
algorithm_type: ComponentsAlgorithm,
}
#[derive(Debug)]
pub struct TriangleCountBenchmark {
method: TriangleCountMethod,
}
#[derive(Debug)]
pub struct ShortestPathBenchmark {
algorithm: PathAlgorithm,
source_nodes: Vec<String>,
target_nodes: Option<Vec<String>>,
}
#[derive(Debug)]
pub struct CentralityBenchmark {
measure: CentralityMeasure,
normalized: bool,
}
#[derive(Debug)]
pub struct GraphConstructionBenchmark {
data_format: DataFormat,
index_types: Vec<IndexType>,
}
#[derive(Debug)]
pub struct MemoryUsageBenchmark {
operation_type: MemoryOperation,
data_size: usize,
}
#[derive(Debug)]
pub struct IOPerformanceBenchmark {
operation: IOOperation,
file_size: usize,
buffer_size: usize,
}
#[derive(Debug)]
pub struct ParallelAlgorithmsBenchmark {
thread_counts: Vec<usize>,
algorithm: ParallelAlgorithm,
}
#[derive(Debug)]
pub struct SIMDBenchmark {
operation: SIMDOperation,
vector_size: usize,
}
#[derive(Debug, Clone)]
pub enum ComponentsAlgorithm {
UnionFind,
DFS,
BFS,
Tarjan,
}
#[derive(Debug, Clone)]
pub enum TriangleCountMethod {
NodeIterator,
EdgeIterator,
Matrix,
Approximate,
}
#[derive(Debug, Clone)]
pub enum PathAlgorithm {
Dijkstra,
BellmanFord,
Floyd,
APSP,
}
#[derive(Debug, Clone)]
pub enum CentralityMeasure {
Betweenness,
Closeness,
Eigenvector,
Katz,
}
#[derive(Debug, Clone)]
pub enum DataFormat {
CSV,
JSON,
Arrow,
Parquet,
}
#[derive(Debug, Clone)]
pub enum IndexType {
BTree,
Hash,
Spatial,
Inverted,
}
#[derive(Debug, Clone)]
pub enum MemoryOperation {
Allocation,
Deallocation,
Access,
Copy,
}
#[derive(Debug, Clone)]
pub enum IOOperation {
Read,
Write,
Seek,
Flush,
}
#[derive(Debug, Clone)]
pub enum ParallelAlgorithm {
ParallelPageRank,
ParallelBFS,
ParallelConnectedComponents,
ParallelShortestPath,
}
#[derive(Debug, Clone)]
pub enum SIMDOperation {
VectorAdd,
VectorMultiply,
DotProduct,
MatrixMultiply,
}
impl GraphBenchmarkSuite {
pub fn new(config: BenchmarkSuiteConfig) -> Self {
Self {
benchmarks: Vec::new(),
test_graphs: Self::create_default_test_graphs(),
config,
results_history: Vec::new(),
}
}
pub fn add_benchmark(&mut self, benchmark: Box<dyn Benchmark>) {
self.benchmarks.push(benchmark);
}
pub fn run_full_suite(&mut self, graph: &ArrowGraph) -> Result<BenchmarkSession> {
let session_id = format!("session_{}", chrono::Utc::now().timestamp_millis());
let start_time = Instant::now();
println!("Starting benchmark suite: {}", session_id);
let mut results = Vec::new();
let mut successful = 0;
let mut failed = 0;
let total_benchmarks = self.benchmarks.len();
for i in 0..total_benchmarks {
let benchmark_name = self.benchmarks[i].name().to_string();
println!("Running benchmark {}/{}: {}", i + 1, total_benchmarks, benchmark_name);
match self.run_single_benchmark_by_index(i, graph) {
Ok(result) => {
if result.success {
successful += 1;
} else {
failed += 1;
}
results.push(result);
}
Err(e) => {
failed += 1;
let failed_result = BenchmarkResult {
benchmark_name: benchmark.name().to_string(),
duration: Duration::from_secs(0),
memory_peak: 0,
memory_average: 0,
cpu_usage: 0.0,
throughput: 0.0,
success: false,
error_message: Some(e.to_string()),
iterations: 0,
statistical_summary: None,
};
results.push(failed_result);
}
}
}
let total_duration = start_time.elapsed();
let session = BenchmarkSession {
session_id: session_id.clone(),
timestamp: chrono::Utc::now().timestamp_millis() as u64,
environment: Self::collect_environment_info(),
results: results.clone(),
summary: SessionSummary {
total_benchmarks: total_benchmarks,
successful_benchmarks: successful,
failed_benchmarks: failed,
total_duration,
performance_regression: None,
performance_improvement: None,
},
};
self.results_history.push(session.clone());
if self.config.generate_reports {
self.generate_benchmark_report(&session)?;
}
println!("Benchmark suite completed in {:?}", total_duration);
println!("Successful: {}, Failed: {}", successful, failed);
Ok(session)
}
fn run_single_benchmark_by_index(&mut self, index: usize, graph: &ArrowGraph) -> Result<BenchmarkResult> {
self.benchmarks[index].setup(graph)?;
for _ in 0..self.config.warmup_iterations {
let _ = self.benchmarks[index].run(graph);
}
let mut durations = Vec::new();
let mut memory_peaks = Vec::new();
let mut cpu_usages = Vec::new();
let mut successful_runs = 0;
let mut last_error = None;
for _ in 0..self.config.measurement_iterations {
let start_memory = Self::get_memory_usage();
let start_time = Instant::now();
match self.benchmarks[index].run(graph) {
Ok(mut result) => {
let duration = start_time.elapsed();
let end_memory = Self::get_memory_usage();
durations.push(duration);
memory_peaks.push(end_memory - start_memory);
cpu_usages.push(result.cpu_usage);
result.duration = duration;
result.memory_peak = end_memory - start_memory;
successful_runs += 1;
}
Err(e) => {
last_error = Some(e.to_string());
}
}
}
self.benchmarks[index].teardown()?;
let success = successful_runs > 0;
let avg_duration = if !durations.is_empty() {
Duration::from_nanos(durations.iter().map(|d| d.as_nanos()).sum::<u128>() / durations.len() as u128)
} else {
Duration::from_secs(0)
};
let memory_peak = memory_peaks.iter().max().copied().unwrap_or(0);
let memory_average = if !memory_peaks.is_empty() {
memory_peaks.iter().sum::<usize>() / memory_peaks.len()
} else {
0
};
let avg_cpu = if !cpu_usages.is_empty() {
cpu_usages.iter().sum::<f64>() / cpu_usages.len() as f64
} else {
0.0
};
let throughput = if avg_duration.as_secs_f64() > 0.0 {
graph.node_count() as f64 / avg_duration.as_secs_f64()
} else {
0.0
};
let statistical_summary = if self.config.statistical_analysis && !durations.is_empty() {
Some(Self::calculate_statistics(&durations))
} else {
None
};
Ok(BenchmarkResult {
benchmark_name: self.benchmarks[index].name().to_string(),
duration: avg_duration,
memory_peak,
memory_average,
cpu_usage: avg_cpu,
throughput,
success,
error_message: last_error,
iterations: successful_runs,
statistical_summary,
})
}
fn create_default_test_graphs() -> Vec<TestGraph> {
vec![
TestGraph {
name: "small_random".to_string(),
num_nodes: 1000,
num_edges: 5000,
graph_type: GraphType::Random,
density: 0.01,
clustering_coefficient: 0.1,
},
TestGraph {
name: "medium_scale_free".to_string(),
num_nodes: 10000,
num_edges: 50000,
graph_type: GraphType::ScaleFree,
density: 0.001,
clustering_coefficient: 0.3,
},
TestGraph {
name: "large_small_world".to_string(),
num_nodes: 100000,
num_edges: 500000,
graph_type: GraphType::SmallWorld,
density: 0.0001,
clustering_coefficient: 0.6,
},
TestGraph {
name: "sparse_grid".to_string(),
num_nodes: 10000,
num_edges: 20000,
graph_type: GraphType::Grid,
density: 0.0004,
clustering_coefficient: 0.0,
},
]
}
fn collect_environment_info() -> BenchmarkEnvironment {
BenchmarkEnvironment {
cpu_model: "Unknown".to_string(), cpu_cores: num_cpus::get(),
memory_gb: 16.0, os_version: std::env::consts::OS.to_string(),
rust_version: "1.70+".to_string(), compiler_flags: vec!["-O3".to_string(), "-C target-cpu=native".to_string()],
}
}
fn generate_benchmark_report(&self, session: &BenchmarkSession) -> Result<()> {
let report_path = format!("benchmark_report_{}.html", session.session_id);
let mut html = String::new();
html.push_str("<!DOCTYPE html><html><head><title>Arrow Graph Benchmark Report</title>");
html.push_str("<style>body{font-family:Arial,sans-serif}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ddd;padding:8px;text-align:left}th{background-color:#f2f2f2}</style>");
html.push_str("</head><body>");
html.push_str(&format!("<h1>Benchmark Report - {}</h1>", session.session_id));
html.push_str(&format!("<p>Generated: {}</p>", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")));
html.push_str("<h2>Environment</h2>");
html.push_str("<table>");
html.push_str(&format!("<tr><th>CPU Cores</th><td>{}</td></tr>", session.environment.cpu_cores));
html.push_str(&format!("<tr><th>Memory</th><td>{:.1} GB</td></tr>", session.environment.memory_gb));
html.push_str(&format!("<tr><th>OS</th><td>{}</td></tr>", session.environment.os_version));
html.push_str("</table>");
html.push_str("<h2>Benchmark Results</h2>");
html.push_str("<table>");
html.push_str("<tr><th>Benchmark</th><th>Duration</th><th>Memory Peak</th><th>CPU Usage</th><th>Throughput</th><th>Success</th></tr>");
for result in &session.results {
html.push_str("<tr>");
html.push_str(&format!("<td>{}</td>", result.benchmark_name));
html.push_str(&format!("<td>{:.2}ms</td>", result.duration.as_millis()));
html.push_str(&format!("<td>{:.2}MB</td>", result.memory_peak as f64 / 1024.0 / 1024.0));
html.push_str(&format!("<td>{:.1}%</td>", result.cpu_usage));
html.push_str(&format!("<td>{:.0} ops/s</td>", result.throughput));
html.push_str(&format!("<td>{}</td>", if result.success { "✓" } else { "✗" }));
html.push_str("</tr>");
}
html.push_str("</table>");
html.push_str("</body></html>");
std::fs::write(report_path, html)?;
Ok(())
}
fn calculate_statistics(durations: &[Duration]) -> StatisticalSummary {
if durations.is_empty() {
return StatisticalSummary {
mean: Duration::from_secs(0),
median: Duration::from_secs(0),
std_dev: Duration::from_secs(0),
min: Duration::from_secs(0),
max: Duration::from_secs(0),
percentile_95: Duration::from_secs(0),
percentile_99: Duration::from_secs(0),
coefficient_of_variation: 0.0,
};
}
let mut sorted = durations.to_vec();
sorted.sort();
let mean = Duration::from_nanos(
durations.iter().map(|d| d.as_nanos()).sum::<u128>() / durations.len() as u128
);
let median = sorted[durations.len() / 2];
let min = sorted[0];
let max = sorted[durations.len() - 1];
let p95_idx = (durations.len() * 95 / 100).min(durations.len() - 1);
let p99_idx = (durations.len() * 99 / 100).min(durations.len() - 1);
let percentile_95 = sorted[p95_idx];
let percentile_99 = sorted[p99_idx];
let mean_nanos = mean.as_nanos() as f64;
let variance = durations.iter()
.map(|d| {
let diff = d.as_nanos() as f64 - mean_nanos;
diff * diff
})
.sum::<f64>() / durations.len() as f64;
let std_dev = Duration::from_nanos(variance.sqrt() as u64);
let coefficient_of_variation = if mean_nanos > 0.0 {
variance.sqrt() / mean_nanos
} else {
0.0
};
StatisticalSummary {
mean,
median,
std_dev,
min,
max,
percentile_95,
percentile_99,
coefficient_of_variation,
}
}
fn get_memory_usage() -> usize {
1024 * 1024 }
pub fn create_criterion_benchmarks(c: &mut Criterion) {
let graph = create_test_graph(1000, 5000);
let mut group = c.benchmark_group("pagerank");
for &size in &[100, 1000, 10000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(
BenchmarkId::new("standard", size),
&size,
|b, &size| {
let test_graph = create_test_graph(size, size * 5);
b.iter(|| {
std::thread::sleep(Duration::from_micros(100));
})
},
);
}
group.finish();
let mut group = c.benchmark_group("connected_components");
for &size in &[100, 1000, 10000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(
BenchmarkId::new("union_find", size),
&size,
|b, &size| {
let test_graph = create_test_graph(size, size * 3);
b.iter(|| {
std::thread::sleep(Duration::from_micros(50));
})
},
);
}
group.finish();
}
}
fn create_test_graph(num_nodes: usize, num_edges: usize) -> ArrowGraph {
ArrowGraph::new()
}
impl Benchmark for PageRankBenchmark {
fn name(&self) -> &str {
"PageRank"
}
fn setup(&mut self, _graph: &ArrowGraph) -> Result<()> {
Ok(())
}
fn run(&self, graph: &ArrowGraph) -> Result<BenchmarkResult> {
let start = Instant::now();
std::thread::sleep(Duration::from_millis(10));
let duration = start.elapsed();
Ok(BenchmarkResult {
benchmark_name: self.name().to_string(),
duration,
memory_peak: 1024 * 1024, memory_average: 512 * 1024, cpu_usage: 85.0,
throughput: graph.node_count() as f64 / duration.as_secs_f64(),
success: true,
error_message: None,
iterations: self.iterations,
statistical_summary: None,
})
}
fn teardown(&mut self) -> Result<()> {
Ok(())
}
fn expected_performance(&self) -> Option<PerformanceExpectation> {
Some(PerformanceExpectation {
max_duration: Duration::from_secs(30),
max_memory: 100 * 1024 * 1024, min_throughput: 1000.0,
max_cpu_usage: 95.0,
})
}
}
impl Benchmark for ConnectedComponentsBenchmark {
fn name(&self) -> &str {
"Connected Components"
}
fn setup(&mut self, _graph: &ArrowGraph) -> Result<()> {
Ok(())
}
fn run(&self, graph: &ArrowGraph) -> Result<BenchmarkResult> {
let start = Instant::now();
std::thread::sleep(Duration::from_millis(5));
let duration = start.elapsed();
Ok(BenchmarkResult {
benchmark_name: self.name().to_string(),
duration,
memory_peak: 512 * 1024, memory_average: 256 * 1024, cpu_usage: 70.0,
throughput: graph.node_count() as f64 / duration.as_secs_f64(),
success: true,
error_message: None,
iterations: 1,
statistical_summary: None,
})
}
fn teardown(&mut self) -> Result<()> {
Ok(())
}
fn expected_performance(&self) -> Option<PerformanceExpectation> {
Some(PerformanceExpectation {
max_duration: Duration::from_secs(10),
max_memory: 50 * 1024 * 1024, min_throughput: 5000.0,
max_cpu_usage: 90.0,
})
}
}
impl Default for BenchmarkSuiteConfig {
fn default() -> Self {
Self {
warmup_iterations: 3,
measurement_iterations: 10,
timeout: Duration::from_secs(300),
memory_profiling: true,
cpu_profiling: true,
statistical_analysis: true,
generate_reports: true,
comparison_baseline: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_benchmark_suite_creation() {
let config = BenchmarkSuiteConfig::default();
let suite = GraphBenchmarkSuite::new(config);
assert_eq!(suite.benchmarks.len(), 0);
assert!(suite.test_graphs.len() > 0);
}
#[test]
fn test_pagerank_benchmark() {
let benchmark = PageRankBenchmark {
iterations: 10,
damping_factor: 0.85,
tolerance: 1e-6,
};
assert_eq!(benchmark.name(), "PageRank");
assert!(benchmark.expected_performance().is_some());
}
#[test]
fn test_statistical_calculation() {
let durations = vec![
Duration::from_millis(100),
Duration::from_millis(110),
Duration::from_millis(120),
Duration::from_millis(130),
Duration::from_millis(140),
];
let stats = GraphBenchmarkSuite::calculate_statistics(&durations);
assert_eq!(stats.min, Duration::from_millis(100));
assert_eq!(stats.max, Duration::from_millis(140));
assert_eq!(stats.median, Duration::from_millis(120));
}
#[test]
fn test_environment_collection() {
let env = GraphBenchmarkSuite::collect_environment_info();
assert!(env.cpu_cores > 0);
assert!(!env.os_version.is_empty());
}
}