codex_memory/performance/
benchmarks.rs

1//! Performance benchmarks for establishing baseline metrics
2
3use crate::memory::models::{CreateMemoryRequest, SearchRequest, UpdateMemoryRequest};
4use crate::memory::{MemoryRepository, MemoryTier};
5use anyhow::Result;
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use tracing::info;
9
10/// Benchmark results for a specific operation
11#[derive(Debug, Clone)]
12pub struct BenchmarkResult {
13    pub operation: String,
14    pub iterations: u32,
15    pub total_duration: Duration,
16    pub avg_duration: Duration,
17    pub min_duration: Duration,
18    pub max_duration: Duration,
19    pub ops_per_second: f64,
20}
21
22/// Benchmark suite for memory operations
23pub struct MemoryBenchmarks {
24    repository: Arc<MemoryRepository>,
25    iterations: u32,
26}
27
28impl MemoryBenchmarks {
29    pub fn new(repository: Arc<MemoryRepository>, iterations: u32) -> Self {
30        Self {
31            repository,
32            iterations,
33        }
34    }
35
36    /// Run all benchmarks and return results
37    pub async fn run_all(&self) -> Result<Vec<BenchmarkResult>> {
38        let mut results = Vec::new();
39
40        info!("Running memory operation benchmarks...");
41
42        // Benchmark create operation
43        results.push(self.benchmark_create().await?);
44
45        // Benchmark read operation
46        results.push(self.benchmark_read().await?);
47
48        // Benchmark update operation
49        results.push(self.benchmark_update().await?);
50
51        // Benchmark delete operation
52        results.push(self.benchmark_delete().await?);
53
54        // Benchmark search operation
55        results.push(self.benchmark_search().await?);
56
57        // Benchmark bulk operations
58        results.push(self.benchmark_bulk_insert().await?);
59
60        // Benchmark concurrent reads
61        results.push(self.benchmark_concurrent_reads().await?);
62
63        Ok(results)
64    }
65
66    /// Benchmark memory creation
67    async fn benchmark_create(&self) -> Result<BenchmarkResult> {
68        let mut durations = Vec::new();
69
70        for i in 0..self.iterations {
71            let content = format!("Benchmark create content {i}");
72            let request = CreateMemoryRequest {
73                content,
74                embedding: None,
75                tier: Some(MemoryTier::Working),
76                importance_score: Some(0.5),
77                metadata: Some(serde_json::json!({"benchmark": true})),
78                parent_id: None,
79                expires_at: None,
80            };
81
82            let start = Instant::now();
83            self.repository.create_memory(request).await?;
84            let duration = start.elapsed();
85
86            durations.push(duration);
87        }
88
89        Ok(self.calculate_result("Create", durations))
90    }
91
92    /// Benchmark memory read
93    async fn benchmark_read(&self) -> Result<BenchmarkResult> {
94        // First create test memories and store their IDs
95        let mut memory_ids = Vec::new();
96        for i in 0..self.iterations {
97            let content = format!("Benchmark read content {i}");
98            let request = CreateMemoryRequest {
99                content,
100                embedding: None,
101                tier: Some(MemoryTier::Working),
102                importance_score: Some(0.5),
103                metadata: Some(serde_json::json!({"benchmark": true})),
104                parent_id: None,
105                expires_at: None,
106            };
107            let memory = self.repository.create_memory(request).await?;
108            memory_ids.push(memory.id);
109        }
110
111        let mut durations = Vec::new();
112
113        for id in memory_ids {
114            let start = Instant::now();
115            self.repository.get_memory(id).await?;
116            let duration = start.elapsed();
117
118            durations.push(duration);
119        }
120
121        Ok(self.calculate_result("Read", durations))
122    }
123
124    /// Benchmark memory update
125    async fn benchmark_update(&self) -> Result<BenchmarkResult> {
126        // First create test memories and store their IDs
127        let mut memory_ids = Vec::new();
128        for i in 0..self.iterations {
129            let content = format!("Original content {i}");
130            let request = CreateMemoryRequest {
131                content,
132                embedding: None,
133                tier: Some(MemoryTier::Working),
134                importance_score: Some(0.5),
135                metadata: Some(serde_json::json!({"benchmark": true})),
136                parent_id: None,
137                expires_at: None,
138            };
139            let memory = self.repository.create_memory(request).await?;
140            memory_ids.push(memory.id);
141        }
142
143        let mut durations = Vec::new();
144
145        for (i, id) in memory_ids.iter().enumerate() {
146            let update_request = UpdateMemoryRequest {
147                content: Some(format!("Updated content {i}")),
148                embedding: None,
149                tier: None,
150                importance_score: Some(0.7),
151                metadata: None,
152                expires_at: None,
153            };
154
155            let start = Instant::now();
156            self.repository.update_memory(*id, update_request).await?;
157            let duration = start.elapsed();
158
159            durations.push(duration);
160        }
161
162        Ok(self.calculate_result("Update", durations))
163    }
164
165    /// Benchmark memory deletion
166    async fn benchmark_delete(&self) -> Result<BenchmarkResult> {
167        // First create test memories and store their IDs
168        let mut memory_ids = Vec::new();
169        for i in 0..self.iterations {
170            let content = format!("Content to delete {i}");
171            let request = CreateMemoryRequest {
172                content,
173                embedding: None,
174                tier: Some(MemoryTier::Working),
175                importance_score: Some(0.5),
176                metadata: Some(serde_json::json!({"benchmark": true})),
177                parent_id: None,
178                expires_at: None,
179            };
180            let memory = self.repository.create_memory(request).await?;
181            memory_ids.push(memory.id);
182        }
183
184        let mut durations = Vec::new();
185
186        for id in memory_ids {
187            let start = Instant::now();
188            self.repository.delete_memory(id).await?;
189            let duration = start.elapsed();
190
191            durations.push(duration);
192        }
193
194        Ok(self.calculate_result("Delete", durations))
195    }
196
197    /// Benchmark search operation
198    async fn benchmark_search(&self) -> Result<BenchmarkResult> {
199        // Create diverse test data for searching
200        for i in 0..100 {
201            let content = format!(
202                "Search benchmark content with keyword{} and topic{}",
203                i % 10,
204                i % 5
205            );
206            let request = CreateMemoryRequest {
207                content,
208                embedding: None,
209                tier: Some(MemoryTier::Working),
210                importance_score: Some(0.5),
211                metadata: Some(serde_json::json!({"benchmark": true})),
212                parent_id: None,
213                expires_at: None,
214            };
215            self.repository.create_memory(request).await?;
216        }
217
218        let mut durations = Vec::new();
219        let queries = ["keyword1", "topic2", "benchmark", "content", "search"];
220
221        for i in 0..self.iterations {
222            let query = queries[i as usize % queries.len()];
223            let search_request = SearchRequest {
224                query_text: Some(query.to_string()),
225                query_embedding: None,
226                search_type: None,
227                hybrid_weights: None,
228                tier: None,
229                date_range: None,
230                importance_range: None,
231                metadata_filters: None,
232                tags: None,
233                limit: Some(10),
234                offset: None,
235                cursor: None,
236                similarity_threshold: None,
237                include_metadata: None,
238                include_facets: None,
239                ranking_boost: None,
240                explain_score: None,
241            };
242
243            let start = Instant::now();
244            self.repository
245                .search_memories_simple(search_request)
246                .await?;
247            let duration = start.elapsed();
248
249            durations.push(duration);
250        }
251
252        Ok(self.calculate_result("Search", durations))
253    }
254
255    /// Benchmark bulk insert operations
256    async fn benchmark_bulk_insert(&self) -> Result<BenchmarkResult> {
257        let mut durations = Vec::new();
258        let batch_size = 100;
259
260        for batch in 0..(self.iterations / batch_size).max(1) {
261            let start = Instant::now();
262
263            for i in 0..batch_size {
264                let content = format!("Bulk insert batch {batch} item {i}");
265                let request = CreateMemoryRequest {
266                    content,
267                    embedding: None,
268                    tier: Some(MemoryTier::Working),
269                    importance_score: Some(0.5),
270                    metadata: Some(serde_json::json!({"benchmark": true, "batch": batch})),
271                    parent_id: None,
272                    expires_at: None,
273                };
274                self.repository.create_memory(request).await?;
275            }
276
277            let duration = start.elapsed();
278            durations.push(duration);
279        }
280
281        Ok(self.calculate_result("Bulk Insert (100 items)", durations))
282    }
283
284    /// Benchmark concurrent read operations
285    async fn benchmark_concurrent_reads(&self) -> Result<BenchmarkResult> {
286        // Create test data and store IDs
287        let mut memory_ids = Vec::new();
288        for i in 0..100 {
289            let content = format!("Concurrent read content {i}");
290            let request = CreateMemoryRequest {
291                content,
292                embedding: None,
293                tier: Some(MemoryTier::Working),
294                importance_score: Some(0.5),
295                metadata: Some(serde_json::json!({"benchmark": true})),
296                parent_id: None,
297                expires_at: None,
298            };
299            let memory = self.repository.create_memory(request).await?;
300            memory_ids.push(memory.id);
301        }
302
303        let mut durations = Vec::new();
304        let concurrent_reads = 10;
305
306        for _ in 0..(self.iterations / concurrent_reads).max(1) {
307            let start = Instant::now();
308
309            let mut handles = Vec::new();
310            for i in 0..concurrent_reads {
311                let repo = Arc::clone(&self.repository);
312                let id = memory_ids[i as usize % memory_ids.len()];
313
314                let handle = tokio::spawn(async move { repo.get_memory(id).await });
315
316                handles.push(handle);
317            }
318
319            // Wait for all reads to complete
320            for handle in handles {
321                handle.await??;
322            }
323
324            let duration = start.elapsed();
325            durations.push(duration);
326        }
327
328        Ok(self.calculate_result("Concurrent Reads (10)", durations))
329    }
330
331    /// Calculate benchmark result from duration samples
332    fn calculate_result(&self, operation: &str, durations: Vec<Duration>) -> BenchmarkResult {
333        let total_duration: Duration = durations.iter().sum();
334        let avg_duration = total_duration / durations.len() as u32;
335        let min_duration = durations.iter().min().cloned().unwrap_or(Duration::ZERO);
336        let max_duration = durations.iter().max().cloned().unwrap_or(Duration::ZERO);
337
338        let ops_per_second = if total_duration.as_secs_f64() > 0.0 {
339            durations.len() as f64 / total_duration.as_secs_f64()
340        } else {
341            0.0
342        };
343
344        BenchmarkResult {
345            operation: operation.to_string(),
346            iterations: durations.len() as u32,
347            total_duration,
348            avg_duration,
349            min_duration,
350            max_duration,
351            ops_per_second,
352        }
353    }
354
355    /// Print benchmark results in a formatted table
356    pub fn print_results(results: &[BenchmarkResult]) {
357        println!("\n=== Performance Benchmark Results ===\n");
358        println!(
359            "{:<20} {:>10} {:>15} {:>15} {:>15} {:>10}",
360            "Operation", "Iterations", "Avg (ms)", "Min (ms)", "Max (ms)", "Ops/sec"
361        );
362        println!("{:-<90}", "");
363
364        for result in results {
365            println!(
366                "{:<20} {:>10} {:>15.2} {:>15.2} {:>15.2} {:>10.2}",
367                result.operation,
368                result.iterations,
369                result.avg_duration.as_secs_f64() * 1000.0,
370                result.min_duration.as_secs_f64() * 1000.0,
371                result.max_duration.as_secs_f64() * 1000.0,
372                result.ops_per_second
373            );
374        }
375
376        println!();
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[tokio::test]
385    async fn test_calculate_result() {
386        let benchmarks = MemoryBenchmarks {
387            repository: Arc::new(MemoryRepository::new(
388                sqlx::PgPool::connect_lazy("postgresql://localhost/test").unwrap(),
389            )),
390            iterations: 100,
391        };
392
393        let durations = vec![
394            Duration::from_millis(10),
395            Duration::from_millis(20),
396            Duration::from_millis(15),
397            Duration::from_millis(25),
398            Duration::from_millis(30),
399        ];
400
401        let result = benchmarks.calculate_result("Test", durations);
402
403        assert_eq!(result.operation, "Test");
404        assert_eq!(result.iterations, 5);
405        assert_eq!(result.min_duration, Duration::from_millis(10));
406        assert_eq!(result.max_duration, Duration::from_millis(30));
407        assert_eq!(result.avg_duration, Duration::from_millis(20));
408    }
409}