#[cfg(test)]
mod performance_test {
use inklog::{InklogConfig, LoggerManager};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tempfile::TempDir;
use tokio::sync::Barrier;
#[tokio::test]
async fn test_log_throughput_single_thread() {
let temp_dir = TempDir::new().unwrap();
let log_path = temp_dir.path().join("throughput.log");
let config = InklogConfig {
file_sink: Some(inklog::FileSinkConfig {
enabled: true,
path: log_path,
max_size: "1GB".into(),
batch_size: 1000,
flush_interval_ms: 100,
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let message_count = 10000;
let start = Instant::now();
for i in 0..message_count {
log::info!(target: "throughput_test", "Throughput test message #{}", i);
}
let elapsed = start.elapsed();
let throughput = message_count as f64 / elapsed.as_secs_f64();
println!("Single-thread throughput: {:.0} msg/s", throughput);
assert!(throughput > 1000, "Throughput too low: {:.0} msg/s", throughput);
}
#[tokio::test]
async fn test_log_throughput_multi_thread() {
let temp_dir = TempDir::new().unwrap();
let log_path = temp_dir.path().join("concurrent_throughput.log");
let config = InklogConfig {
file_sink: Some(inklog::FileSinkConfig {
enabled: true,
path: log_path,
max_size: "1GB".into(),
batch_size: 2000,
flush_interval_ms: 50,
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
performance: inklog::PerformanceConfig {
worker_threads: 8,
channel_capacity: 50000,
..Default::default()
},
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let thread_count = 10;
let messages_per_thread = 5000;
let barrier = Arc::new(Barrier::new(thread_count));
let counter = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let handles: Vec<_> = (0..thread_count)
.map(|_| {
let barrier = barrier.clone();
let counter = counter.clone();
tokio::spawn(async move {
barrier.wait().await;
for i in 0..messages_per_thread {
log::info!(target: "concurrent_throughput", "Concurrent test #{}", i);
counter.fetch_add(1, Ordering::SeqCst);
}
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
let elapsed = start.elapsed();
let total_messages = thread_count * messages_per_thread;
let throughput = total_messages as f64 / elapsed.as_secs_f64();
println!("Multi-thread throughput: {:.0} msg/s", throughput);
assert!(throughput > 3000, "Multi-thread throughput too low: {:.0} msg/s", throughput);
}
#[tokio::test]
async fn test_log_latency() {
let config = InklogConfig {
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: true,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let iterations = 1000;
let latencies: Vec<Duration> = (0..iterations)
.map(|_| {
let start = Instant::now();
log::info!(target: "latency_test", "Latency test message");
start.elapsed()
})
.collect();
let avg_latency: Duration = latencies.iter().sum();
let avg_latency = avg_latency / iterations as u32;
let max_latency = latencies.iter().max().unwrap();
let min_latency = latencies.iter().min().unwrap();
println!("Latency stats:");
println!(" Average: {:?}", avg_latency);
println!(" Min: {:?}", min_latency);
println!(" Max: {:?}", max_latency);
assert!(avg_latency.as_millis() < 1, "Average latency too high: {:?}", avg_latency);
}
#[tokio::test]
async fn test_memory_usage_during_burst() {
let temp_dir = TempDir::new().unwrap();
let log_path = temp_dir.path().join("memory_test.log");
let config = InklogConfig {
file_sink: Some(inklog::FileSinkConfig {
enabled: true,
path: log_path,
max_size: "1GB".into(),
batch_size: 5000,
flush_interval_ms: 200,
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
performance: inklog::PerformanceConfig {
channel_capacity: 100000,
..Default::default()
},
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let burst_size = 50000;
for i in 0..burst_size {
log::info!(target: "memory_test", "Burst test message #{}", i);
}
tokio::time::sleep(Duration::from_millis(1000)).await;
assert!(log_path.exists() || burst_size > 0);
println!("Memory burst test completed successfully");
}
#[tokio::test]
async fn test_batch_write_performance() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("batch_test.db");
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
{
let db_url = format!("sqlite://{}", db_path.display());
let config = InklogConfig {
database_sink: Some(inklog::DatabaseSinkConfig {
enabled: true,
driver: inklog::config::DatabaseDriver::SQLite,
url: db_url,
pool_size: 5,
batch_size: 500,
flush_interval_ms: 100,
table_name: "logs".to_string(),
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let batch_sizes = vec![100, 500, 1000];
for batch_size in batch_sizes {
let start = Instant::now();
for i in 0..batch_size {
log::info!(target: "batch_performance", "Batch test message #{}", i);
}
tokio::time::sleep(Duration::from_millis(200)).await;
let elapsed = start.elapsed();
let throughput = batch_size as f64 / elapsed.as_secs_f64();
println!("Batch size {}: {:.0} msg/s", batch_size, throughput);
}
}
#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
{
println!("Skipping test: requires --features \"dbnexus\"");
}
}
#[tokio::test]
async fn test_connection_pool_performance() {
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
{
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("pool_test.db");
let db_url = format!("sqlite://{}", db_path.display());
let pool_sizes = vec![2, 5, 10];
for pool_size in pool_sizes {
let config = InklogConfig {
database_sink: Some(inklog::DatabaseSinkConfig {
enabled: true,
driver: inklog::config::DatabaseDriver::SQLite,
url: db_url.clone(),
pool_size,
batch_size: 100,
flush_interval_ms: 50,
table_name: "logs".to_string(),
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let thread_count = pool_size as usize * 2;
let messages_per_thread = 500;
let barrier = Arc::new(Barrier::new(thread_count));
let start = Instant::now();
let handles: Vec<_> = (0..thread_count)
.map(|_| {
let barrier = barrier.clone();
tokio::spawn(async move {
barrier.wait().await;
for i in 0..messages_per_thread {
log::info!(target: "pool_test", "Pool test message #{}", i);
}
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
let elapsed = start.elapsed();
let total = thread_count * messages_per_thread;
let throughput = total as f64 / elapsed.as_secs_f64();
println!("Pool size {}: {:.0} msg/s", pool_size, throughput);
}
}
#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
{
println!("Skipping test: requires --features \"dbnexus\"");
}
}
#[tokio::test]
async fn test_sustained_load() {
let temp_dir = TempDir::new().unwrap();
let log_path = temp_dir.path().join("sustained_test.log");
let config = InklogConfig {
file_sink: Some(inklog::FileSinkConfig {
enabled: true,
path: log_path,
max_size: "100MB".into(),
batch_size: 1000,
flush_interval_ms: 100,
..Default::default()
}),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
performance: inklog::PerformanceConfig {
channel_capacity: 50000,
worker_threads: 4,
..Default::default()
},
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
let duration_secs = 5;
let batch_interval_ms = 100;
let batch_size = 100;
let start = Instant::now();
let mut interval = tokio::time::interval(Duration::from_millis(batch_interval_ms));
let mut total_messages = 0;
while start.elapsed().as_secs() < duration_secs {
interval.tick().await;
for i in 0..batch_size {
log::info!(target: "sustained_load", "Sustained load test #{}", i);
}
total_messages += batch_size;
}
let elapsed = start.elapsed();
let throughput = total_messages as f64 / elapsed.as_secs_f64();
println!("Sustained load: {} messages in {:.2}s = {:.0} msg/s",
total_messages, elapsed.as_secs_f64(), throughput);
assert!(throughput > 500, "Sustained throughput too low: {:.0} msg/s", throughput);
}
}