#[cfg(all(target_os = "macos", feature = "metal-native"))]
mod metal_stress_tests {
use hive_gpu::error::HiveGpuError;
use hive_gpu::metal::MetalNativeContext;
use hive_gpu::traits::GpuContext;
use hive_gpu::types::{GpuDistanceMetric, GpuVector};
use std::time::{Duration, Instant};
fn create_test_vectors(count: usize, dimension: usize, offset: usize) -> Vec<GpuVector> {
(0..count)
.map(|i| {
let data: Vec<f32> = (0..dimension)
.map(|d| ((offset + i) * dimension + d) as f32)
.collect();
GpuVector::new(format!("vec_{}_{}", offset, i), data)
})
.collect()
}
#[test]
fn test_sustained_load() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
let vectors_per_batch = 500;
let num_batches = 10;
let duration_target = Duration::from_secs(5);
println!("✅ Sustained Load Test:");
println!(" Duration: {:?}", duration_target);
println!(" Batches: {}", num_batches);
println!(" Vectors per batch: {}", vectors_per_batch);
println!();
let start = Instant::now();
let mut total_vectors = 0;
let mut batch_times = Vec::new();
for batch in 0..num_batches {
if start.elapsed() >= duration_target {
break;
}
let vectors =
create_test_vectors(vectors_per_batch, dimension, batch * vectors_per_batch);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
let batch_start = Instant::now();
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
let batch_time = batch_start.elapsed();
batch_times.push(batch_time);
total_vectors += vectors_per_batch;
if batch % 2 == 0 {
println!(
" Batch {}: {:?} ({} vectors)",
batch + 1,
batch_time,
total_vectors
);
}
}
let total_time = start.elapsed();
let avg_batch_time = batch_times.iter().sum::<Duration>() / batch_times.len() as u32;
let throughput = total_vectors as f64 / total_time.as_secs_f64();
println!();
println!(" Total vectors processed: {}", total_vectors);
println!(" Total time: {:?}", total_time);
println!(" Avg batch time: {:?}", avg_batch_time);
println!(" Throughput: {:.2} vectors/sec", throughput);
println!(" ✅ Sustained high load completed successfully");
assert!(
total_vectors >= vectors_per_batch * 5,
"Should process at least 5 batches"
);
}
#[test]
fn test_maximum_vector_count() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 128;
let target_count = 10_000;
println!("✅ Maximum Vector Count Test:");
println!(" Target: {} vectors × {}D", target_count, dimension);
println!();
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
let batch_size = 1000;
let num_batches = target_count / batch_size;
let start = Instant::now();
for batch in 0..num_batches {
let vectors = create_test_vectors(batch_size, dimension, batch * batch_size);
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
if batch % 2 == 0 {
let elapsed = start.elapsed();
let processed = (batch + 1) * batch_size;
println!(
" Progress: {}/{} vectors ({:?})",
processed, target_count, elapsed
);
}
}
let total_time = start.elapsed();
let throughput = target_count as f64 / total_time.as_secs_f64();
println!();
println!(" Total time: {:?}", total_time);
println!(" Throughput: {:.2} vectors/sec", throughput);
println!(" ✅ Successfully handled {} vectors", target_count);
let query = vec![1.0; dimension];
let results = storage.search(&query, 10).expect("Search should work");
assert_eq!(
results.len(),
10,
"Should return 10 results from large dataset"
);
}
#[test]
fn test_memory_pressure() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
println!("✅ Memory Pressure Test:");
let device_info = context.device_info().expect("Failed to get device info");
let total_vram = device_info.total_vram_bytes;
let initial_available = device_info.available_vram_bytes;
println!(
" Total VRAM: {:.2} GB",
total_vram as f64 / 1024.0 / 1024.0 / 1024.0
);
println!(
" Initially available: {:.2} GB",
initial_available as f64 / 1024.0 / 1024.0 / 1024.0
);
println!();
let dimension = 512;
let sizes = vec![1000, 2000, 5000, 10000];
for size in sizes {
let data_size_mb = (size * dimension * 4) as f64 / 1024.0 / 1024.0;
let vectors = create_test_vectors(size, dimension, 0);
println!(" Attempting {} vectors ({:.2} MB)...", size, data_size_mb);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
match storage.add_vectors(&vectors) {
Ok(_) => {
let device_info = context.device_info().expect("Failed to get device info");
let used_vram = device_info.total_vram_bytes - device_info.available_vram_bytes;
println!(" ✅ Allocated successfully");
println!(
" VRAM used: {:.2} MB",
used_vram as f64 / 1024.0 / 1024.0
);
}
Err(e) => {
println!(" ⚠️ Allocation failed: {}", e);
break;
}
}
}
println!();
println!(" ✅ Memory pressure handling validated");
}
#[test]
fn test_rapid_allocation_cycles() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
let vectors_per_cycle = 200;
let num_cycles = 50;
println!("✅ Rapid Allocation Cycles Test:");
println!(" Cycles: {}", num_cycles);
println!(" Vectors per cycle: {}", vectors_per_cycle);
println!();
let start = Instant::now();
let mut cycle_times = Vec::new();
for cycle in 0..num_cycles {
let vectors =
create_test_vectors(vectors_per_cycle, dimension, cycle * vectors_per_cycle);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
let cycle_start = Instant::now();
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
let cycle_time = cycle_start.elapsed();
cycle_times.push(cycle_time);
if cycle % 10 == 0 && cycle > 0 {
let avg = cycle_times.iter().sum::<Duration>() / cycle_times.len() as u32;
println!(" After {} cycles: avg {:?}/cycle", cycle, avg);
}
}
let total_time = start.elapsed();
let avg_cycle_time = cycle_times.iter().sum::<Duration>() / cycle_times.len() as u32;
let min_cycle_time = cycle_times.iter().min().unwrap();
let max_cycle_time = cycle_times.iter().max().unwrap();
println!();
println!(" Total time: {:?}", total_time);
println!(" Avg cycle: {:?}", avg_cycle_time);
println!(" Min cycle: {:?}", min_cycle_time);
println!(" Max cycle: {:?}", max_cycle_time);
println!(" ✅ All cycles completed successfully");
let max_avg_ratio = max_cycle_time.as_secs_f64() / avg_cycle_time.as_secs_f64();
assert!(
max_avg_ratio < 5.0,
"Max cycle time too high vs avg: {:.2}x",
max_avg_ratio
);
}
#[test]
fn test_concurrent_high_load() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 192;
let vectors_per_storage = 500;
let num_storages = 10;
println!("✅ Concurrent High Load Test:");
println!(" Storages: {}", num_storages);
println!(" Vectors per storage: {}", vectors_per_storage);
println!();
let start = Instant::now();
let mut storages = Vec::new();
for i in 0..num_storages {
let vectors =
create_test_vectors(vectors_per_storage, dimension, i * vectors_per_storage);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
storages.push(storage);
if i % 2 == 0 {
println!(" Created storage {}/{}", i + 1, num_storages);
}
}
let creation_time = start.elapsed();
let search_start = Instant::now();
let query = vec![1.0; dimension];
let mut total_results = 0;
for (i, storage) in storages.iter().enumerate() {
let results = storage.search(&query, 10).expect("Search failed");
total_results += results.len();
if i % 2 == 0 {
println!(" Searched storage {}/{}", i + 1, num_storages);
}
}
let search_time = search_start.elapsed();
let total_time = start.elapsed();
println!();
println!(" Creation time: {:?}", creation_time);
println!(" Search time: {:?}", search_time);
println!(" Total time: {:?}", total_time);
println!(" Total results: {}", total_results);
println!(" ✅ Concurrent operations completed");
assert_eq!(
total_results,
num_storages * 10,
"Should get 10 results from each storage"
);
}
#[test]
fn test_sustained_search_load() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
let vector_count = 2000;
let vectors = create_test_vectors(vector_count, dimension, 0);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
println!("✅ Sustained Search Load Test:");
println!(" Dataset: {} vectors", vector_count);
println!(" Search duration: 2 seconds");
println!();
let duration = Duration::from_secs(2);
let query = vec![1.0; dimension];
let start = Instant::now();
let mut num_searches = 0;
let mut total_results = 0;
while start.elapsed() < duration {
let results = storage.search(&query, 10).expect("Search failed");
total_results += results.len();
num_searches += 1;
}
let elapsed = start.elapsed();
let qps = num_searches as f64 / elapsed.as_secs_f64();
println!(" Searches performed: {}", num_searches);
println!(" Total results: {}", total_results);
println!(" QPS: {:.0} queries/sec", qps);
println!(" ✅ Sustained search load completed");
assert!(qps > 1000.0, "QPS too low: {:.0}", qps);
}
#[test]
fn test_mixed_workload() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
let initial_vectors = 1000;
let num_iterations = 20;
let vectors_per_iteration = 100;
println!("✅ Mixed Workload Test:");
println!(" Initial vectors: {}", initial_vectors);
println!(" Iterations: {}", num_iterations);
println!();
let vectors = create_test_vectors(initial_vectors, dimension, 0);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
let start = Instant::now();
let query = vec![1.0; dimension];
let mut total_searches = 0;
let mut total_additions = 0;
for i in 0..num_iterations {
for _ in 0..10 {
storage.search(&query, 5).expect("Search failed");
total_searches += 1;
}
let new_vectors = create_test_vectors(
vectors_per_iteration,
dimension,
initial_vectors + i * vectors_per_iteration,
);
storage
.add_vectors(&new_vectors)
.expect("Failed to add vectors");
total_additions += vectors_per_iteration;
if i % 5 == 0 {
println!(
" Iteration {}: {} searches, {} vectors added",
i + 1,
total_searches,
total_additions
);
}
}
let elapsed = start.elapsed();
println!();
println!(" Total time: {:?}", elapsed);
println!(" Total searches: {}", total_searches);
println!(" Total additions: {}", total_additions);
println!(" ✅ Mixed workload completed");
assert_eq!(total_searches, num_iterations * 10);
assert_eq!(total_additions, num_iterations * vectors_per_iteration);
}
#[test]
fn test_long_running_stability() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
let duration = Duration::from_secs(5); let batch_size = 200;
println!("✅ Long Running Stability Test:");
println!(" Duration: {:?}", duration);
println!();
let start = Instant::now();
let mut batches_processed = 0;
let mut total_vectors = 0;
while start.elapsed() < duration {
let vectors =
create_test_vectors(batch_size, dimension, batches_processed * batch_size);
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
storage
.add_vectors(&vectors)
.expect("Failed to add vectors");
let query = vec![1.0; dimension];
storage.search(&query, 10).expect("Search failed");
batches_processed += 1;
total_vectors += batch_size;
}
let elapsed = start.elapsed();
println!(" Batches processed: {}", batches_processed);
println!(" Total vectors: {}", total_vectors);
println!(" Elapsed: {:?}", elapsed);
println!(" ✅ System remained stable");
assert!(batches_processed >= 5, "Should process at least 5 batches");
}
#[test]
fn test_recovery_after_errors() {
let context = match MetalNativeContext::new() {
Ok(ctx) => ctx,
Err(HiveGpuError::NoDeviceAvailable) => {
println!("⚠️ Metal not available, skipping test");
return;
}
Err(e) => panic!("Failed to create Metal context: {}", e),
};
let dimension = 256;
println!("✅ Recovery After Errors Test:");
println!();
let mut storage = context
.create_storage(dimension, GpuDistanceMetric::Cosine)
.expect("Failed to create storage");
let empty_vectors: Vec<GpuVector> = vec![];
match storage.add_vectors(&empty_vectors) {
Ok(_) => {} Err(e) => println!(" Expected error for empty vectors: {}", e),
}
let valid_vectors = create_test_vectors(100, dimension, 0);
storage
.add_vectors(&valid_vectors)
.expect("Should work after empty vector attempt");
println!(" ✅ Added 100 valid vectors after error");
let query = vec![1.0; dimension];
let results = storage
.search(&query, 10)
.expect("Search should work after error recovery");
println!(" ✅ Search succeeded: {} results", results.len());
let more_vectors = create_test_vectors(50, dimension, 100);
storage
.add_vectors(&more_vectors)
.expect("Should continue adding vectors");
println!(" ✅ Added 50 more vectors");
let results2 = storage
.search(&query, 10)
.expect("Search should still work");
println!(" ✅ Second search succeeded: {} results", results2.len());
println!(" ✅ System recovered and remained stable");
assert_eq!(results.len(), 10);
assert_eq!(results2.len(), 10);
}
}