codex_memory/performance/
benchmarks.rs1use 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#[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
22pub 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 pub async fn run_all(&self) -> Result<Vec<BenchmarkResult>> {
38 let mut results = Vec::new();
39
40 info!("Running memory operation benchmarks...");
41
42 results.push(self.benchmark_create().await?);
44
45 results.push(self.benchmark_read().await?);
47
48 results.push(self.benchmark_update().await?);
50
51 results.push(self.benchmark_delete().await?);
53
54 results.push(self.benchmark_search().await?);
56
57 results.push(self.benchmark_bulk_insert().await?);
59
60 results.push(self.benchmark_concurrent_reads().await?);
62
63 Ok(results)
64 }
65
66 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 async fn benchmark_read(&self) -> Result<BenchmarkResult> {
94 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 async fn benchmark_update(&self) -> Result<BenchmarkResult> {
126 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 async fn benchmark_delete(&self) -> Result<BenchmarkResult> {
167 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 async fn benchmark_search(&self) -> Result<BenchmarkResult> {
199 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 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 async fn benchmark_concurrent_reads(&self) -> Result<BenchmarkResult> {
286 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 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 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 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}