anya_core/testing/performance/
database.rs

1/// Database access pattern performance testing
2use crate::testing::performance::{
3    MetricType, PerformanceTestable, Result, TestConfig, TestResult, Timer,
4};
5use rand::{thread_rng, Rng};
6use std::collections::HashMap;
7
8/// Database operation type
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum DbOperation {
11    /// Read operation
12    Read,
13
14    /// Write operation
15    Write,
16
17    /// Update operation
18    Update,
19
20    /// Delete operation
21    Delete,
22}
23
24/// Database configuration
25#[derive(Debug, Clone)]
26pub struct DbConfig {
27    /// Database type
28    pub db_type: String,
29
30    /// Connection string
31    pub connection_string: String,
32
33    /// Batch size
34    pub batch_size: usize,
35
36    /// Use prepared statements
37    pub use_prepared_statements: bool,
38
39    /// Cache size in MB
40    pub cache_size_mb: usize,
41}
42
43impl Default for DbConfig {
44    fn default() -> Self {
45        Self {
46            db_type: "sqlite".to_string(),
47            connection_string: ":memory:".to_string(),
48            batch_size: 100,
49            use_prepared_statements: true,
50            cache_size_mb: 10,
51        }
52    }
53}
54
55/// Simple mock database for testing
56#[derive(Debug)]
57pub struct MockDatabase {
58    /// Configuration
59    config: DbConfig,
60
61    /// Data
62    data: HashMap<String, Vec<u8>>,
63
64    /// Cache
65    cache: HashMap<String, Vec<u8>>,
66
67    /// Stats
68    stats: DbStats,
69}
70
71/// Database statistics
72#[derive(Debug, Default, Clone)]
73pub struct DbStats {
74    /// Read operations
75    pub reads: usize,
76
77    /// Write operations
78    pub writes: usize,
79
80    /// Update operations
81    pub updates: usize,
82
83    /// Delete operations
84    pub deletes: usize,
85
86    /// Cache hits
87    pub cache_hits: usize,
88
89    /// Cache misses
90    pub cache_misses: usize,
91
92    /// Total time spent in read operations (ms)
93    pub read_time_ms: u64,
94
95    /// Total time spent in write operations (ms)
96    pub write_time_ms: u64,
97
98    /// Total time spent in update operations (ms)
99    pub update_time_ms: u64,
100
101    /// Total time spent in delete operations (ms)
102    pub delete_time_ms: u64,
103}
104
105impl MockDatabase {
106    /// Create a new mock database
107    pub fn new(config: DbConfig) -> Self {
108        Self {
109            config,
110            data: HashMap::new(),
111            cache: HashMap::new(),
112            stats: DbStats::default(),
113        }
114    }
115
116    /// Read data
117    pub fn read(&mut self, key: &str) -> Option<Vec<u8>> {
118        let mut timer = Timer::new();
119        timer.start();
120
121        // Check cache first
122        if let Some(value) = self.cache.get(key) {
123            self.stats.cache_hits += 1;
124            timer.stop();
125            if let Ok(elapsed) = timer.elapsed_ms() {
126                self.stats.read_time_ms += elapsed;
127            }
128            self.stats.reads += 1;
129            return Some(value.clone());
130        }
131
132        self.stats.cache_misses += 1;
133
134        // Check data store
135        let result = self.data.get(key).cloned();
136
137        // Update cache if found
138        if let Some(value) = &result {
139            self.cache.insert(key.to_string(), value.clone());
140
141            // Simple cache size management
142            if self.cache.len() > self.config.cache_size_mb * 1024 * 1024 / 100 {
143                // Remove a random entry to simulate eviction
144                if let Some(key) = self.cache.keys().next().cloned() {
145                    self.cache.remove(&key);
146                }
147            }
148        }
149
150        timer.stop();
151        if let Ok(elapsed) = timer.elapsed_ms() {
152            self.stats.read_time_ms += elapsed;
153        }
154        self.stats.reads += 1;
155
156        result
157    }
158
159    /// Write data
160    pub fn write(&mut self, key: &str, value: Vec<u8>) {
161        let mut timer = Timer::new();
162        timer.start();
163
164        self.data.insert(key.to_string(), value.clone());
165        self.cache.insert(key.to_string(), value);
166
167        timer.stop();
168        if let Ok(elapsed) = timer.elapsed_ms() {
169            self.stats.write_time_ms += elapsed;
170        }
171        self.stats.writes += 1;
172    }
173
174    /// Update data
175    pub fn update(&mut self, key: &str, value: Vec<u8>) -> bool {
176        let mut timer = Timer::new();
177        timer.start();
178
179        let result = self.data.insert(key.to_string(), value.clone()).is_some();
180        self.cache.insert(key.to_string(), value);
181
182        timer.stop();
183        if let Ok(elapsed) = timer.elapsed_ms() {
184            self.stats.update_time_ms += elapsed;
185        }
186        self.stats.updates += 1;
187
188        result
189    }
190
191    /// Delete data
192    pub fn delete(&mut self, key: &str) -> bool {
193        let mut timer = Timer::new();
194        timer.start();
195
196        let result = self.data.remove(key).is_some();
197        self.cache.remove(key);
198
199        timer.stop();
200        if let Ok(elapsed) = timer.elapsed_ms() {
201            self.stats.delete_time_ms += elapsed;
202        }
203        self.stats.deletes += 1;
204
205        result
206    }
207
208    /// Get stats
209    pub fn get_stats(&self) -> DbStats {
210        self.stats.clone()
211    }
212
213    /// Reset stats
214    pub fn reset_stats(&mut self) {
215        self.stats = DbStats::default();
216    }
217}
218
219/// Database access pattern test
220pub struct DatabaseAccessTest {
221    /// Mock database
222    db: MockDatabase,
223
224    /// Operations to test
225    operations: Vec<DbOperation>,
226
227    /// Key space size
228    key_space_size: usize,
229
230    /// Value size in bytes
231    value_size: usize,
232}
233
234impl DatabaseAccessTest {
235    /// Create a new database access test
236    pub fn new(
237        config: DbConfig,
238        operations: Vec<DbOperation>,
239        key_space_size: usize,
240        value_size: usize,
241    ) -> Self {
242        Self {
243            db: MockDatabase::new(config),
244            operations,
245            key_space_size,
246            value_size,
247        }
248    }
249
250    /// Generate a random key
251    fn random_key(&self) -> String {
252        let mut rng = thread_rng();
253        format!("key_{}", rng.gen_range(0..self.key_space_size))
254    }
255
256    /// Generate random data
257    fn random_data(&self) -> Vec<u8> {
258        let mut rng = thread_rng();
259        let mut data = Vec::with_capacity(self.value_size);
260        for _ in 0..self.value_size {
261            data.push(rng.gen());
262        }
263        data
264    }
265
266    /// Run a test for a specific operation
267    fn run_operation_test(&mut self, operation: DbOperation, iterations: usize) -> Result<f64> {
268        self.db.reset_stats();
269
270        let mut timer = Timer::new();
271        timer.start();
272
273        for _ in 0..iterations {
274            match operation {
275                DbOperation::Read => {
276                    let key = self.random_key();
277                    let _ = self.db.read(&key);
278                }
279                DbOperation::Write => {
280                    let key = self.random_key();
281                    let data = self.random_data();
282                    self.db.write(&key, data);
283                }
284                DbOperation::Update => {
285                    let key = self.random_key();
286                    let data = self.random_data();
287                    self.db.update(&key, data);
288                }
289                DbOperation::Delete => {
290                    let key = self.random_key();
291                    let _ = self.db.delete(&key);
292                }
293            }
294        }
295
296        timer.stop();
297
298        let ops_per_second = (iterations as f64) / (timer.elapsed_secs()?);
299
300        Ok(ops_per_second)
301    }
302}
303
304impl PerformanceTestable for DatabaseAccessTest {
305    fn run_test(&self, config: &TestConfig) -> Result<TestResult> {
306        let iterations = config.iterations;
307        let warmup_iterations = config.warmup_iterations;
308
309        // Clone self to allow mutation during the test
310        let mut test = DatabaseAccessTest {
311            db: MockDatabase::new(self.db.config.clone()),
312            operations: self.operations.clone(),
313            key_space_size: self.key_space_size,
314            value_size: self.value_size,
315        };
316
317        // Parameters
318        let mut parameters = HashMap::new();
319        parameters.insert("db_type".to_string(), test.db.config.db_type.clone());
320        parameters.insert(
321            "batch_size".to_string(),
322            test.db.config.batch_size.to_string(),
323        );
324        parameters.insert(
325            "use_prepared_statements".to_string(),
326            test.db.config.use_prepared_statements.to_string(),
327        );
328        parameters.insert(
329            "cache_size_mb".to_string(),
330            test.db.config.cache_size_mb.to_string(),
331        );
332        parameters.insert(
333            "key_space_size".to_string(),
334            test.key_space_size.to_string(),
335        );
336        parameters.insert("value_size".to_string(), test.value_size.to_string());
337
338        // Warmup
339        println!("Warming up database for {warmup_iterations} iterations...");
340        // Populate some data for reads/updates/deletes
341        for _ in 0..warmup_iterations / 10 {
342            let key = test.random_key();
343            let data = test.random_data();
344            test.db.write(&key, data);
345        }
346
347        // Reset stats after warmup
348        test.db.reset_stats();
349
350        // Actual test
351        println!("Running database test for {iterations} iterations...");
352
353        let mut timer = Timer::new();
354        timer.start();
355
356        let mut metrics = HashMap::new();
357        let mut metric_types = HashMap::new();
358
359        // Clone the operations to avoid borrow checker issues
360        let operations = test.operations.clone();
361
362        for operation in &operations {
363            println!("Testing {operation:?} operations...");
364            let ops_per_second =
365                test.run_operation_test(*operation, iterations / operations.len())?;
366
367            let metric_name = match operation {
368                DbOperation::Read => "read_ops_per_second",
369                DbOperation::Write => "write_ops_per_second",
370                DbOperation::Update => "update_ops_per_second",
371                DbOperation::Delete => "delete_ops_per_second",
372            };
373
374            metrics.insert(metric_name.to_string(), ops_per_second);
375            metric_types.insert(metric_name.to_string(), MetricType::DbOpsPerSecond);
376        }
377
378        // Overall metrics
379        let stats = test.db.get_stats();
380
381        // Cache hit rate
382        let total_reads = stats.cache_hits + stats.cache_misses;
383        let cache_hit_rate = if total_reads > 0 {
384            (stats.cache_hits as f64) / (total_reads as f64) * 100.0
385        } else {
386            0.0
387        };
388
389        metrics.insert("cache_hit_rate".to_string(), cache_hit_rate);
390        metric_types.insert("cache_hit_rate".to_string(), MetricType::CacheHitRate);
391
392        // Average operation times
393        if stats.reads > 0 {
394            metrics.insert(
395                "avg_read_time_ms".to_string(),
396                (stats.read_time_ms as f64) / (stats.reads as f64),
397            );
398            metric_types.insert("avg_read_time_ms".to_string(), MetricType::LatencyMs);
399        }
400
401        if stats.writes > 0 {
402            metrics.insert(
403                "avg_write_time_ms".to_string(),
404                (stats.write_time_ms as f64) / (stats.writes as f64),
405            );
406            metric_types.insert("avg_write_time_ms".to_string(), MetricType::LatencyMs);
407        }
408
409        if stats.updates > 0 {
410            metrics.insert(
411                "avg_update_time_ms".to_string(),
412                (stats.update_time_ms as f64) / (stats.updates as f64),
413            );
414            metric_types.insert("avg_update_time_ms".to_string(), MetricType::LatencyMs);
415        }
416
417        if stats.deletes > 0 {
418            metrics.insert(
419                "avg_delete_time_ms".to_string(),
420                (stats.delete_time_ms as f64) / (stats.deletes as f64),
421            );
422            metric_types.insert("avg_delete_time_ms".to_string(), MetricType::LatencyMs);
423        }
424
425        timer.stop();
426
427        Ok(TestResult {
428            name: self.name().to_string(),
429            timestamp: chrono::Utc::now().to_rfc3339(),
430            duration_ms: timer.elapsed_ms()?,
431            metrics,
432            metric_types,
433            parameters,
434        })
435    }
436
437    fn name(&self) -> &str {
438        "database_access"
439    }
440}