// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Benchmarks comparing fast lock vs old lock performance #[cfg(test)] #[allow(dead_code)] // Temporarily disable benchmark tests mod benchmarks { use super::super::*; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::task; /// Benchmark single-threaded lock operations #[tokio::test] async fn bench_single_threaded_fast_locks() { let manager = Arc::new(FastObjectLockManager::new()); let iterations = 10000; // Warm up for i in 0..100 { let _guard = manager .acquire_write_lock("bucket", &format!("warm_{}", i), "owner") .await .unwrap(); } // Benchmark write locks let start = Instant::now(); for i in 0..iterations { let _guard = manager .acquire_write_lock("bucket", &format!("object_{}", i), "owner") .await .unwrap(); } let duration = start.elapsed(); println!("Fast locks: {} write locks in {:?}", iterations, duration); println!("Average: {:?} per lock", duration / iterations); let metrics = manager.get_metrics(); println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0); // Should be much faster than old implementation assert!(duration.as_millis() < 1000, "Should complete 10k locks in <1s"); assert!(metrics.shard_metrics.fast_path_rate() > 0.95, "Should have >95% fast path rate"); } /// Benchmark concurrent lock operations #[tokio::test] async fn bench_concurrent_fast_locks() { let manager = Arc::new(FastObjectLockManager::new()); let concurrent_tasks = 100; let iterations_per_task = 100; let start = Instant::now(); let mut handles = Vec::new(); for task_id in 0..concurrent_tasks { let manager_clone = manager.clone(); let handle = task::spawn(async move { for i in 0..iterations_per_task { let object_name = format!("obj_{}_{}", task_id, i); let _guard = manager_clone .acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id)) .await .unwrap(); // Simulate some work tokio::task::yield_now().await; } }); handles.push(handle); } // Wait for all tasks for handle in handles { handle.await.unwrap(); } let duration = start.elapsed(); let total_ops = concurrent_tasks * iterations_per_task; println!("Concurrent fast locks: {} operations across {} tasks in {:?}", total_ops, concurrent_tasks, duration); println!("Throughput: {:.2} ops/sec", total_ops as f64 / duration.as_secs_f64()); let metrics = manager.get_metrics(); println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0); println!("Contention events: {}", metrics.shard_metrics.contention_events); // Should maintain high throughput even with concurrency assert!(duration.as_millis() < 5000, "Should complete concurrent ops in <5s"); } /// Benchmark contended lock operations #[tokio::test] async fn bench_contended_locks() { let manager = Arc::new(FastObjectLockManager::new()); let concurrent_tasks = 50; let shared_objects = 10; // High contention on few objects let iterations_per_task = 50; let start = Instant::now(); let mut handles = Vec::new(); for task_id in 0..concurrent_tasks { let manager_clone = manager.clone(); let handle = task::spawn(async move { for i in 0..iterations_per_task { let object_name = format!("shared_{}", i % shared_objects); // Mix of read and write operations if i % 3 == 0 { // Write operation if let Ok(_guard) = manager_clone .acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id)) .await { tokio::task::yield_now().await; } } else { // Read operation if let Ok(_guard) = manager_clone .acquire_read_lock("bucket", &object_name, &format!("owner_{}", task_id)) .await { tokio::task::yield_now().await; } } } }); handles.push(handle); } // Wait for all tasks for handle in handles { handle.await.unwrap(); } let duration = start.elapsed(); println!("Contended locks: {} tasks on {} objects in {:?}", concurrent_tasks, shared_objects, duration); let metrics = manager.get_metrics(); println!("Total acquisitions: {}", metrics.shard_metrics.total_acquisitions()); println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0); println!("Average wait time: {:?}", metrics.shard_metrics.avg_wait_time()); println!("Timeout rate: {:.2}%", metrics.shard_metrics.timeout_rate() * 100.0); // Even with contention, should maintain reasonable performance assert!(metrics.shard_metrics.timeout_rate() < 0.1, "Should have <10% timeout rate"); assert!(metrics.shard_metrics.avg_wait_time() < Duration::from_millis(100), "Avg wait should be <100ms"); } /// Benchmark batch operations #[tokio::test] async fn bench_batch_operations() { let manager = FastObjectLockManager::new(); let batch_sizes = vec![10, 50, 100, 500]; for batch_size in batch_sizes { // Create batch request let mut batch = BatchLockRequest::new("batch_owner"); for i in 0..batch_size { batch = batch.add_write_lock("bucket", &format!("batch_obj_{}", i)); } let start = Instant::now(); let result = manager.acquire_locks_batch(batch).await; let duration = start.elapsed(); assert!(result.all_acquired, "Batch should succeed"); println!("Batch size {}: {:?} ({:.2} μs per lock)", batch_size, duration, duration.as_micros() as f64 / batch_size as f64); // Batch should be much faster than individual acquisitions assert!(duration.as_millis() < batch_size as u128 / 10, "Batch should be 10x+ faster than individual locks"); } } /// Benchmark version-specific locks #[tokio::test] async fn bench_versioned_locks() { let manager = Arc::new(FastObjectLockManager::new()); let objects = 100; let versions_per_object = 10; let start = Instant::now(); let mut handles = Vec::new(); for obj_id in 0..objects { let manager_clone = manager.clone(); let handle = task::spawn(async move { for version in 0..versions_per_object { let _guard = manager_clone .acquire_write_lock_versioned( "bucket", &format!("obj_{}", obj_id), &format!("v{}", version), "version_owner" ) .await .unwrap(); } }); handles.push(handle); } for handle in handles { handle.await.unwrap(); } let duration = start.elapsed(); let total_ops = objects * versions_per_object; println!("Versioned locks: {} version locks in {:?}", total_ops, duration); println!("Throughput: {:.2} locks/sec", total_ops as f64 / duration.as_secs_f64()); let metrics = manager.get_metrics(); println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0); // Versioned locks should not interfere with each other assert!(metrics.shard_metrics.fast_path_rate() > 0.9, "Should maintain high fast path rate"); } /// Compare with theoretical maximum performance #[tokio::test] async fn bench_theoretical_maximum() { let manager = Arc::new(FastObjectLockManager::new()); let iterations = 100000; // Measure pure fast path performance (no contention) let start = Instant::now(); for i in 0..iterations { let _guard = manager .acquire_write_lock("bucket", &format!("unique_{}", i), "owner") .await .unwrap(); } let duration = start.elapsed(); println!("Theoretical maximum: {} unique locks in {:?}", iterations, duration); println!("Rate: {:.2} locks/sec", iterations as f64 / duration.as_secs_f64()); println!("Latency: {:?} per lock", duration / iterations); let metrics = manager.get_metrics(); println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0); // Should achieve very high performance with no contention assert!(metrics.shard_metrics.fast_path_rate() > 0.99, "Should be nearly 100% fast path"); assert!(duration.as_secs_f64() / (iterations as f64) < 0.0001, "Should be <100μs per lock"); } /// Performance regression test #[tokio::test] async fn performance_regression_test() { let manager = Arc::new(FastObjectLockManager::new()); // This test ensures we maintain performance targets let test_cases = vec![ ("single_thread", 1, 10000), ("low_contention", 10, 1000), ("high_contention", 100, 100), ]; for (test_name, threads, ops_per_thread) in test_cases { let start = Instant::now(); let mut handles = Vec::new(); for thread_id in 0..threads { let manager_clone = manager.clone(); let handle = task::spawn(async move { for op_id in 0..ops_per_thread { let object = if threads == 1 { format!("obj_{}_{}", thread_id, op_id) } else { format!("obj_{}", op_id % 100) // Create contention }; let owner = format!("owner_{}", thread_id); let _guard = manager_clone .acquire_write_lock("bucket", object, owner) .await .unwrap(); } }); handles.push(handle); } for handle in handles { handle.await.unwrap(); } let duration = start.elapsed(); let total_ops = threads * ops_per_thread; let ops_per_sec = total_ops as f64 / duration.as_secs_f64(); println!("{}: {:.2} ops/sec", test_name, ops_per_sec); // Performance targets (adjust based on requirements) match test_name { "single_thread" => assert!(ops_per_sec > 50000.0, "Single thread should exceed 50k ops/sec"), "low_contention" => assert!(ops_per_sec > 20000.0, "Low contention should exceed 20k ops/sec"), "high_contention" => assert!(ops_per_sec > 5000.0, "High contention should exceed 5k ops/sec"), _ => {} } } } }