Files
wifi-densepose/examples/scipix/tests/integration/performance_tests.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

387 lines
11 KiB
Rust

// 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
}