#![allow(dead_code)] //! Cache Performance Benchmark //! //! Measures caching layer performance for: //! - JWT validation cache (decoded tokens) //! - RBAC permission cache //! - Revocation list cache //! - User session cache //! //! Targets: //! - Cache hit: <100ns //! - Cache miss: <1μs (with lookup) use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; /// Simple LRU cache implementation struct LruCache { cache: HashMap, capacity: usize, ttl: Duration, } impl LruCache { fn new(capacity: usize, ttl: Duration) -> Self { Self { cache: HashMap::with_capacity(capacity), capacity, ttl, } } fn get(&mut self, key: &K) -> Option { if let Some((value, timestamp)) = self.cache.get(key) { // Check TTL if timestamp.elapsed() < self.ttl { return Some(value.clone()); } else { // Expired self.cache.remove(key); } } None } fn put(&mut self, key: K, value: V) { // Evict if at capacity if self.cache.len() >= self.capacity { // Simple eviction: remove first entry if let Some(k) = self.cache.keys().next().cloned() { self.cache.remove(&k); } } self.cache.insert(key, (value, Instant::now())); } } /// Thread-safe cache wrapper struct ThreadSafeCache { cache: Arc>>, } impl ThreadSafeCache { fn new(capacity: usize, ttl: Duration) -> Self { Self { cache: Arc::new(RwLock::new(LruCache::new(capacity, ttl))), } } fn get(&self, key: &K) -> Option { self.cache.write().expect("INVARIANT: RwLock should not be poisoned").get(key) } fn put(&self, key: K, value: V) { self.cache.write().expect("INVARIANT: RwLock should not be poisoned").put(key, value); } } /// JWT cache entry #[derive(Clone, Debug)] struct JwtCacheEntry { user_id: String, roles: Vec, permissions: Vec, exp: u64, } /// RBAC cache entry #[derive(Clone, Debug)] struct RbacCacheEntry { permissions: Vec, } /// Benchmark 1: JWT cache hit (TARGET: <100ns) fn bench_jwt_cache_hit(c: &mut Criterion) { let mut cache = LruCache::new(10_000, Duration::from_secs(300)); // Prepopulate cache for i in 0..1000 { let entry = JwtCacheEntry { user_id: format!("user{}", i), roles: vec!["trader".to_string()], permissions: vec!["trade:read".to_string(), "trade:write".to_string()], exp: 1234567890, }; cache.put(format!("jwt_token_{}", i), entry); } c.bench_function("jwt_cache_hit", |b| { b.iter(|| { let key = format!("jwt_token_{}", black_box(500)); let entry = cache.get(&key); black_box(entry); }); }); } /// Benchmark 2: JWT cache miss (with decode simulation) fn bench_jwt_cache_miss(c: &mut Criterion) { let mut cache = LruCache::new(10_000, Duration::from_secs(300)); c.bench_function("jwt_cache_miss_with_decode", |b| { b.iter(|| { let key = format!("jwt_token_{}", black_box(9999)); let entry = cache.get(&key); if entry.is_none() { // Simulate JWT decode (expensive operation) let decoded = JwtCacheEntry { user_id: "user9999".to_string(), roles: vec!["trader".to_string()], permissions: vec!["trade:read".to_string()], exp: 1234567890, }; cache.put(key.clone(), decoded.clone()); black_box(decoded); } }); }); } /// Benchmark 3: RBAC cache hit (TARGET: <100ns) fn bench_rbac_cache_hit(c: &mut Criterion) { let mut cache = LruCache::new(10_000, Duration::from_secs(300)); // Prepopulate with permissions for i in 0..1000 { let entry = RbacCacheEntry { permissions: vec![ "trade:read".to_string(), "trade:write".to_string(), "portfolio:read".to_string(), ], }; cache.put(format!("user{}", i), entry); } c.bench_function("rbac_cache_hit", |b| { b.iter(|| { let key = format!("user{}", black_box(500)); let entry = cache.get(&key); black_box(entry); }); }); } /// Benchmark 4: Different cache sizes fn bench_cache_sizes(c: &mut Criterion) { let mut group = c.benchmark_group("cache_size_impact"); for size in &[100, 1_000, 10_000, 100_000] { let mut cache = LruCache::new(*size, Duration::from_secs(300)); // Prepopulate to capacity for i in 0..*size { cache.put(format!("key{}", i), format!("value{}", i)); } group.bench_with_input(BenchmarkId::new("cache_lookup", size), size, |b, &n| { b.iter(|| { let key = format!("key{}", black_box(n / 2)); let value = cache.get(&key); black_box(value); }); }); } group.finish(); } /// Benchmark 5: Cache eviction overhead fn bench_cache_eviction(c: &mut Criterion) { let mut cache = LruCache::new(100, Duration::from_secs(300)); // Fill to capacity for i in 0..100 { cache.put(format!("key{}", i), format!("value{}", i)); } c.bench_function("cache_eviction_on_insert", |b| { let mut counter = 100; b.iter(|| { cache.put( format!("key{}", black_box(counter)), format!("value{}", counter), ); counter += 1; }); }); } /// Benchmark 6: TTL expiration check overhead fn bench_ttl_expiration(c: &mut Criterion) { let mut cache_short = LruCache::new(1000, Duration::from_millis(1)); let mut cache_long = LruCache::new(1000, Duration::from_secs(300)); // Prepopulate for i in 0..100 { cache_short.put(format!("key{}", i), format!("value{}", i)); cache_long.put(format!("key{}", i), format!("value{}", i)); } let mut group = c.benchmark_group("ttl_expiration"); group.bench_function("short_ttl_1ms", |b| { b.iter(|| { let key = format!("key{}", black_box(50)); let value = cache_short.get(&key); black_box(value); }); }); group.bench_function("long_ttl_300s", |b| { b.iter(|| { let key = format!("key{}", black_box(50)); let value = cache_long.get(&key); black_box(value); }); }); group.finish(); } /// Benchmark 7: Thread-safe cache with RwLock fn bench_thread_safe_cache(c: &mut Criterion) { let cache = ThreadSafeCache::new(10_000, Duration::from_secs(300)); // Prepopulate for i in 0..1000 { cache.put(format!("key{}", i), format!("value{}", i)); } let mut group = c.benchmark_group("thread_safe_cache"); group.bench_function("read_lock_cache_hit", |b| { b.iter(|| { let key = format!("key{}", black_box(500)); let value = cache.get(&key); black_box(value); }); }); group.bench_function("write_lock_cache_miss", |b| { let mut counter = 1000; b.iter(|| { let key = format!("key{}", black_box(counter)); let value = cache.get(&key); if value.is_none() { cache.put(key.clone(), format!("value{}", counter)); } counter += 1; }); }); group.finish(); } /// Benchmark 8: Hot/cold cache patterns fn bench_hot_cold_patterns(c: &mut Criterion) { let mut group = c.benchmark_group("hot_cold_patterns"); // Hot cache: Small working set let mut hot_cache = LruCache::new(10_000, Duration::from_secs(300)); for i in 0..10 { hot_cache.put(format!("hot_key{}", i), format!("value{}", i)); } group.bench_function("hot_cache_10_keys", |b| { b.iter(|| { let key = format!("hot_key{}", black_box(5)); let value = hot_cache.get(&key); black_box(value); }); }); // Cold cache: Large working set with misses let mut cold_cache = LruCache::new(100, Duration::from_secs(300)); for i in 0..100 { cold_cache.put(format!("cold_key{}", i), format!("value{}", i)); } let mut counter = 0; group.bench_function("cold_cache_100_keys_rotating", |b| { b.iter(|| { counter += 1; let key = format!("cold_key{}", black_box(counter % 150)); let value = cold_cache.get(&key); if value.is_none() { cold_cache.put(key.clone(), format!("value{}", counter)); } black_box(value); }); }); group.finish(); } /// Benchmark 9: Multi-tier cache (L1 + L2) fn bench_multi_tier_cache(c: &mut Criterion) { let l1_cache = Arc::new(RwLock::new(LruCache::::new( 100, Duration::from_secs(60), ))); let l2_cache = Arc::new(RwLock::new(LruCache::::new( 10_000, Duration::from_secs(300), ))); // Prepopulate L2 for i in 0..1000 { l2_cache .write() .unwrap() .put(format!("key{}", i), format!("value{}", i)); } // Prepopulate L1 with hot keys for i in 0..50 { l1_cache .write() .unwrap() .put(format!("key{}", i), format!("value{}", i)); } c.bench_function("multi_tier_cache_l1_hit", |b| { b.iter(|| { let key = format!("key{}", black_box(25)); // Check L1 let value = l1_cache.write().expect("INVARIANT: RwLock should not be poisoned").get(&key); if value.is_none() { // Check L2 if let Some(v) = l2_cache.write().expect("INVARIANT: RwLock should not be poisoned").get(&key) { l1_cache.write().expect("INVARIANT: RwLock should not be poisoned").put(key, v.clone()); black_box(v); } } else { black_box(value); } }); }); } /// Benchmark 10: Revocation list lookup fn bench_revocation_list(c: &mut Criterion) { // Simulate revocation list as HashSet let mut revoked_tokens = std::collections::HashSet::new(); for i in 0..10000 { revoked_tokens.insert(format!("revoked_token_{}", i)); } let mut group = c.benchmark_group("revocation_list"); group.bench_function("revoked_token_hit", |b| { b.iter(|| { let jti = format!("revoked_token_{}", black_box(5000)); let is_revoked = revoked_tokens.contains(&jti); black_box(is_revoked); }); }); group.bench_function("revoked_token_miss", |b| { b.iter(|| { let jti = format!("valid_token_{}", black_box(5000)); let is_revoked = revoked_tokens.contains(&jti); black_box(is_revoked); }); }); group.finish(); } criterion_group!( cache_benches, bench_jwt_cache_hit, bench_jwt_cache_miss, bench_rbac_cache_hit, bench_cache_sizes, bench_cache_eviction, bench_ttl_expiration, bench_thread_safe_cache, bench_hot_cold_patterns, bench_multi_tier_cache, bench_revocation_list ); criterion_main!(cache_benches);