Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
386
examples/scipix/tests/integration/performance_tests.rs
Normal file
386
examples/scipix/tests/integration/performance_tests.rs
Normal file
@@ -0,0 +1,386 @@
|
||||
// Performance validation tests
|
||||
//
|
||||
// Tests latency, memory usage, throughput, and ensures no memory leaks
|
||||
|
||||
use super::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_latency_within_bounds() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
let image = images::generate_simple_equation("x + y");
|
||||
image.save("/tmp/perf_latency.png").unwrap();
|
||||
|
||||
// Measure latency
|
||||
let start = Instant::now();
|
||||
let result = test_server
|
||||
.process_image("/tmp/perf_latency.png", OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
let latency = start.elapsed();
|
||||
|
||||
println!("Latency: {:?}", latency);
|
||||
println!("Confidence: {}", result.confidence);
|
||||
|
||||
// Assert latency is within bounds (<100ms for simple equation)
|
||||
assert!(latency.as_millis() < 100, "Latency too high: {:?}", latency);
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_memory_usage_limits() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
// Get initial memory usage
|
||||
let initial_memory = get_memory_usage();
|
||||
|
||||
// Process multiple images
|
||||
for i in 0..100 {
|
||||
let eq = format!("x + {}", i);
|
||||
let image = images::generate_simple_equation(&eq);
|
||||
let path = format!("/tmp/perf_mem_{}.png", i);
|
||||
image.save(&path).unwrap();
|
||||
|
||||
test_server
|
||||
.process_image(&path, OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
|
||||
// Clean up
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
|
||||
// Get final memory usage
|
||||
let final_memory = get_memory_usage();
|
||||
let memory_increase = final_memory - initial_memory;
|
||||
|
||||
println!("Memory increase: {} MB", memory_increase / 1024 / 1024);
|
||||
|
||||
// Assert memory usage is reasonable (<100MB increase)
|
||||
assert!(
|
||||
memory_increase < 100 * 1024 * 1024,
|
||||
"Memory usage too high: {} bytes",
|
||||
memory_increase
|
||||
);
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_no_memory_leaks() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
let image = images::generate_simple_equation("leak test");
|
||||
image.save("/tmp/leak_test.png").unwrap();
|
||||
|
||||
// Process same image many times
|
||||
let iterations = 1000;
|
||||
let mut memory_samples = Vec::new();
|
||||
|
||||
for i in 0..iterations {
|
||||
test_server
|
||||
.process_image("/tmp/leak_test.png", OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
|
||||
// Sample memory every 100 iterations
|
||||
if i % 100 == 0 {
|
||||
memory_samples.push(get_memory_usage());
|
||||
}
|
||||
}
|
||||
|
||||
// Check for linear memory growth (leak indicator)
|
||||
let first_sample = memory_samples[0];
|
||||
let last_sample = *memory_samples.last().unwrap();
|
||||
let growth_rate = (last_sample - first_sample) as f64 / iterations as f64;
|
||||
|
||||
println!("Memory growth rate: {} bytes/iteration", growth_rate);
|
||||
println!("Samples: {:?}", memory_samples);
|
||||
|
||||
// Growth rate should be minimal (<1KB per iteration)
|
||||
assert!(
|
||||
growth_rate < 1024.0,
|
||||
"Possible memory leak detected: {} bytes/iteration",
|
||||
growth_rate
|
||||
);
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_throughput() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
// Create test images
|
||||
let image_count = 50;
|
||||
for i in 0..image_count {
|
||||
let eq = format!("throughput_{}", i);
|
||||
let image = images::generate_simple_equation(&eq);
|
||||
image.save(&format!("/tmp/throughput_{}.png", i)).unwrap();
|
||||
}
|
||||
|
||||
// Measure throughput
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..image_count {
|
||||
test_server
|
||||
.process_image(&format!("/tmp/throughput_{}.png", i), OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
let throughput = image_count as f64 / duration.as_secs_f64();
|
||||
|
||||
println!("Throughput: {:.2} images/second", throughput);
|
||||
println!("Total time: {:?} for {} images", duration, image_count);
|
||||
|
||||
// Assert reasonable throughput (>5 images/second)
|
||||
assert!(
|
||||
throughput > 5.0,
|
||||
"Throughput too low: {:.2} images/s",
|
||||
throughput
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
for i in 0..image_count {
|
||||
std::fs::remove_file(&format!("/tmp/throughput_{}.png", i)).unwrap();
|
||||
}
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_concurrent_throughput() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
// Create test image
|
||||
let image = images::generate_simple_equation("concurrent");
|
||||
image.save("/tmp/concurrent_throughput.png").unwrap();
|
||||
|
||||
let concurrent_requests = 20;
|
||||
let start = Instant::now();
|
||||
|
||||
// Spawn concurrent requests
|
||||
let mut handles = vec![];
|
||||
for _ in 0..concurrent_requests {
|
||||
let server = test_server.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
server
|
||||
.process_image("/tmp/concurrent_throughput.png", OutputFormat::LaTeX)
|
||||
.await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Wait for all to complete
|
||||
let results = futures::future::join_all(handles).await;
|
||||
let duration = start.elapsed();
|
||||
|
||||
let success_count = results.iter().filter(|r| r.is_ok()).count();
|
||||
let throughput = concurrent_requests as f64 / duration.as_secs_f64();
|
||||
|
||||
println!("Concurrent throughput: {:.2} req/second", throughput);
|
||||
println!("Success rate: {}/{}", success_count, concurrent_requests);
|
||||
|
||||
assert!(
|
||||
success_count == concurrent_requests,
|
||||
"All requests should succeed"
|
||||
);
|
||||
assert!(
|
||||
throughput > 10.0,
|
||||
"Concurrent throughput too low: {:.2}",
|
||||
throughput
|
||||
);
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_latency_percentiles() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
let iterations = 100;
|
||||
let mut latencies = Vec::new();
|
||||
|
||||
for i in 0..iterations {
|
||||
let eq = format!("p{}", i);
|
||||
let image = images::generate_simple_equation(&eq);
|
||||
let path = format!("/tmp/percentile_{}.png", i);
|
||||
image.save(&path).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
test_server
|
||||
.process_image(&path, OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
let latency = start.elapsed();
|
||||
|
||||
latencies.push(latency.as_micros());
|
||||
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
|
||||
// Sort latencies
|
||||
latencies.sort();
|
||||
|
||||
// Calculate percentiles
|
||||
let p50 = latencies[50];
|
||||
let p95 = latencies[95];
|
||||
let p99 = latencies[99];
|
||||
|
||||
println!("Latency percentiles:");
|
||||
println!(" P50: {} μs ({} ms)", p50, p50 / 1000);
|
||||
println!(" P95: {} μs ({} ms)", p95, p95 / 1000);
|
||||
println!(" P99: {} μs ({} ms)", p99, p99 / 1000);
|
||||
|
||||
// Assert percentile targets
|
||||
assert!(p50 < 100_000, "P50 latency too high: {} μs", p50); // <100ms
|
||||
assert!(p95 < 200_000, "P95 latency too high: {} μs", p95); // <200ms
|
||||
assert!(p99 < 500_000, "P99 latency too high: {} μs", p99); // <500ms
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_batch_efficiency() {
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
|
||||
// Create test images
|
||||
let batch_size = 10;
|
||||
let mut paths = Vec::new();
|
||||
|
||||
for i in 0..batch_size {
|
||||
let eq = format!("batch_{}", i);
|
||||
let image = images::generate_simple_equation(&eq);
|
||||
let path = format!("/tmp/batch_eff_{}.png", i);
|
||||
image.save(&path).unwrap();
|
||||
paths.push(path);
|
||||
}
|
||||
|
||||
// Measure sequential processing
|
||||
let start_sequential = Instant::now();
|
||||
for path in &paths {
|
||||
test_server
|
||||
.process_image(path, OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
}
|
||||
let sequential_time = start_sequential.elapsed();
|
||||
|
||||
// Measure batch processing
|
||||
let start_batch = Instant::now();
|
||||
test_server
|
||||
.process_batch(
|
||||
&paths.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
|
||||
OutputFormat::LaTeX,
|
||||
)
|
||||
.await
|
||||
.expect("Batch processing failed");
|
||||
let batch_time = start_batch.elapsed();
|
||||
|
||||
println!("Sequential time: {:?}", sequential_time);
|
||||
println!("Batch time: {:?}", batch_time);
|
||||
println!(
|
||||
"Speedup: {:.2}x",
|
||||
sequential_time.as_secs_f64() / batch_time.as_secs_f64()
|
||||
);
|
||||
|
||||
// Batch should be faster
|
||||
assert!(
|
||||
batch_time < sequential_time,
|
||||
"Batch processing should be faster"
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
for path in paths {
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_cold_start_warmup() {
|
||||
// Measure cold start
|
||||
let start_cold = Instant::now();
|
||||
let test_server = TestServer::start()
|
||||
.await
|
||||
.expect("Failed to start test server");
|
||||
let cold_start_time = start_cold.elapsed();
|
||||
|
||||
println!("Cold start time: {:?}", cold_start_time);
|
||||
|
||||
// First request (warmup)
|
||||
let image = images::generate_simple_equation("warmup");
|
||||
image.save("/tmp/warmup.png").unwrap();
|
||||
|
||||
let start_first = Instant::now();
|
||||
test_server
|
||||
.process_image("/tmp/warmup.png", OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
let first_request_time = start_first.elapsed();
|
||||
|
||||
// Second request (warmed up)
|
||||
let start_second = Instant::now();
|
||||
test_server
|
||||
.process_image("/tmp/warmup.png", OutputFormat::LaTeX)
|
||||
.await
|
||||
.expect("Processing failed");
|
||||
let second_request_time = start_second.elapsed();
|
||||
|
||||
println!("First request time: {:?}", first_request_time);
|
||||
println!("Second request time: {:?}", second_request_time);
|
||||
|
||||
// Cold start should be reasonable (<5s)
|
||||
assert!(
|
||||
cold_start_time.as_secs() < 5,
|
||||
"Cold start too slow: {:?}",
|
||||
cold_start_time
|
||||
);
|
||||
|
||||
// Second request should be faster (model loaded)
|
||||
assert!(
|
||||
second_request_time < first_request_time,
|
||||
"Warmed up request should be faster"
|
||||
);
|
||||
|
||||
test_server.shutdown().await;
|
||||
}
|
||||
|
||||
// Helper function to get current memory usage
|
||||
fn get_memory_usage() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Read from /proc/self/statm
|
||||
if let Ok(content) = std::fs::read_to_string("/proc/self/statm") {
|
||||
if let Some(rss) = content.split_whitespace().nth(1) {
|
||||
if let Ok(pages) = rss.parse::<usize>() {
|
||||
// Convert pages to bytes (assuming 4KB pages)
|
||||
return pages * 4096;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for other platforms or if reading fails
|
||||
0
|
||||
}
|
||||
Reference in New Issue
Block a user