optirs_bench/regression_tester/
database.rs1use crate::error::Result;
7use crate::regression_tester::types::{DatabaseMetadata, PerformanceRecord};
8use scirs2_core::numeric::Float;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, VecDeque};
11use std::fmt::Debug;
12use std::fs;
13use std::path::Path;
14use std::time::{SystemTime, UNIX_EPOCH};
15
16#[derive(Debug, Serialize, Deserialize)]
18pub struct PerformanceDatabase<A: Float> {
19 history: HashMap<String, VecDeque<PerformanceRecord<A>>>,
21 metadata: DatabaseMetadata,
23}
24
25impl<A: Float + Debug + Serialize + for<'de> Deserialize<'de> + Send + Sync>
26 PerformanceDatabase<A>
27{
28 pub fn new() -> Self {
30 Self {
31 history: HashMap::new(),
32 metadata: DatabaseMetadata {
33 version: "1.0".to_string(),
34 created_at: SystemTime::now()
35 .duration_since(UNIX_EPOCH)
36 .unwrap_or_default()
37 .as_secs(),
38 last_updated: SystemTime::now()
39 .duration_since(UNIX_EPOCH)
40 .unwrap_or_default()
41 .as_secs(),
42 total_records: 0,
43 },
44 }
45 }
46
47 pub fn load(basedir: &Path) -> Result<Self> {
49 let db_path = basedir.join("performance_db.json");
50 if db_path.exists() {
51 let data = fs::read_to_string(&db_path)?;
52 let db = serde_json::from_str(&data)?;
53 Ok(db)
54 } else {
55 Ok(Self::new())
56 }
57 }
58
59 pub fn save(&self, basedir: &Path) -> Result<()> {
61 fs::create_dir_all(basedir)?;
62 let db_path = basedir.join("performance_db.json");
63 let data = serde_json::to_string_pretty(self)?;
64 fs::write(&db_path, data)?;
65 Ok(())
66 }
67
68 pub fn add_record(&mut self, key: String, record: PerformanceRecord<A>) {
70 self.add_record_with_limit(key, record, 1000);
71 }
72
73 pub fn add_record_with_limit(
75 &mut self,
76 key: String,
77 record: PerformanceRecord<A>,
78 max_history: usize,
79 ) {
80 let history = self.history.entry(key).or_default();
81 history.push_back(record);
82
83 while history.len() > max_history {
85 history.pop_front();
86 }
87
88 self.metadata.total_records += 1;
89 self.metadata.last_updated = SystemTime::now()
90 .duration_since(UNIX_EPOCH)
91 .unwrap_or_default()
92 .as_secs();
93 }
94
95 pub fn get_history(&self, key: &str) -> Option<&VecDeque<PerformanceRecord<A>>> {
97 self.history.get(key)
98 }
99
100 pub fn get_history_mut(&mut self, key: &str) -> Option<&mut VecDeque<PerformanceRecord<A>>> {
102 self.history.get_mut(key)
103 }
104
105 pub fn get_keys(&self) -> Vec<&String> {
107 self.history.keys().collect()
108 }
109
110 pub fn get_recent_records(&self, key: &str, count: usize) -> Vec<&PerformanceRecord<A>> {
112 if let Some(history) = self.history.get(key) {
113 history.iter().rev().take(count).collect()
114 } else {
115 Vec::new()
116 }
117 }
118
119 pub fn cleanup_old_records(&mut self, cutoff_timestamp: u64) -> usize {
121 let mut removed_count = 0;
122
123 for history in self.history.values_mut() {
124 let original_len = history.len();
125 history.retain(|record| record.timestamp >= cutoff_timestamp);
126 removed_count += original_len - history.len();
127 }
128
129 self.history.retain(|_, history| !history.is_empty());
131
132 self.metadata.total_records = self.metadata.total_records.saturating_sub(removed_count);
134 self.metadata.last_updated = SystemTime::now()
135 .duration_since(UNIX_EPOCH)
136 .unwrap_or_default()
137 .as_secs();
138
139 removed_count
140 }
141
142 pub fn total_records(&self) -> usize {
144 self.history.values().map(|h| h.len()).sum()
145 }
146
147 pub fn key_count(&self) -> usize {
149 self.history.len()
150 }
151
152 pub fn metadata(&self) -> &DatabaseMetadata {
154 &self.metadata
155 }
156
157 pub fn is_empty(&self) -> bool {
159 self.history.is_empty()
160 }
161
162 pub fn get_records_by_commit(
164 &self,
165 commit_hash: &str,
166 ) -> Vec<(&String, &PerformanceRecord<A>)> {
167 let mut results = Vec::new();
168 for (key, history) in &self.history {
169 for record in history {
170 if let Some(ref hash) = record.commit_hash {
171 if hash == commit_hash {
172 results.push((key, record));
173 }
174 }
175 }
176 }
177 results
178 }
179
180 pub fn get_records_by_branch(&self, branch: &str) -> Vec<(&String, &PerformanceRecord<A>)> {
182 let mut results = Vec::new();
183 for (key, history) in &self.history {
184 for record in history {
185 if let Some(ref record_branch) = record.branch {
186 if record_branch == branch {
187 results.push((key, record));
188 }
189 }
190 }
191 }
192 results
193 }
194
195 pub fn get_records_by_time_range(
197 &self,
198 start_timestamp: u64,
199 end_timestamp: u64,
200 ) -> Vec<(&String, &PerformanceRecord<A>)> {
201 let mut results = Vec::new();
202 for (key, history) in &self.history {
203 for record in history {
204 if record.timestamp >= start_timestamp && record.timestamp <= end_timestamp {
205 results.push((key, record));
206 }
207 }
208 }
209 results
210 }
211
212 pub fn export_json(&self) -> Result<String> {
214 Ok(serde_json::to_string_pretty(self)?)
215 }
216
217 pub fn import_json(json_data: &str) -> Result<Self> {
219 Ok(serde_json::from_str(json_data)?)
220 }
221
222 pub fn merge(&mut self, other: PerformanceDatabase<A>) {
224 for (key, mut other_history) in other.history {
225 let history = self.history.entry(key).or_default();
226
227 history.append(&mut other_history);
229 let mut sorted_records: Vec<_> = history.drain(..).collect();
230 sorted_records.sort_by_key(|record| record.timestamp);
231
232 *history = sorted_records.into();
234 }
235
236 self.metadata.total_records = self.total_records();
238 self.metadata.last_updated = SystemTime::now()
239 .duration_since(UNIX_EPOCH)
240 .unwrap_or_default()
241 .as_secs();
242 }
243
244 pub fn compact(&mut self) -> usize {
246 let mut removed_count = 0;
247
248 for history in self.history.values_mut() {
249 let original_len = history.len();
250
251 let mut seen = std::collections::HashSet::new();
253 history.retain(|record| {
254 let key = (record.timestamp, record.commit_hash.clone());
255 if seen.contains(&key) {
256 false
257 } else {
258 seen.insert(key);
259 true
260 }
261 });
262
263 removed_count += original_len - history.len();
264 }
265
266 self.metadata.total_records = self.total_records();
268 self.metadata.last_updated = SystemTime::now()
269 .duration_since(UNIX_EPOCH)
270 .unwrap_or_default()
271 .as_secs();
272
273 removed_count
274 }
275}
276
277impl<A: Float + Debug + Serialize + for<'de> Deserialize<'de> + Send + Sync> Default
278 for PerformanceDatabase<A>
279{
280 fn default() -> Self {
281 Self::new()
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::regression_tester::config::TestEnvironment;
289 use crate::regression_tester::types::PerformanceMetrics;
290
291 fn create_test_record() -> PerformanceRecord<f64> {
292 PerformanceRecord {
293 timestamp: 1000000,
294 commit_hash: Some("abc123".to_string()),
295 branch: Some("main".to_string()),
296 environment: TestEnvironment::default(),
297 metrics: PerformanceMetrics::default(),
298 metadata: HashMap::new(),
299 }
300 }
301
302 #[test]
303 fn test_database_creation() {
304 let db: PerformanceDatabase<f64> = PerformanceDatabase::new();
305 assert!(db.is_empty());
306 assert_eq!(db.total_records(), 0);
307 assert_eq!(db.key_count(), 0);
308 }
309
310 #[test]
311 fn test_add_record() {
312 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
313 let record = create_test_record();
314
315 db.add_record("test_key".to_string(), record);
316
317 assert!(!db.is_empty());
318 assert_eq!(db.total_records(), 1);
319 assert_eq!(db.key_count(), 1);
320 assert!(db.get_history("test_key").is_some());
321 }
322
323 #[test]
324 fn test_history_limit() {
325 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
326
327 for i in 0..1005 {
329 let mut record = create_test_record();
330 record.timestamp = i;
331 db.add_record_with_limit("test_key".to_string(), record, 1000);
332 }
333
334 assert_eq!(
336 db.get_history("test_key").expect("unwrap failed").len(),
337 1000
338 );
339 }
340
341 #[test]
342 fn test_get_recent_records() {
343 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
344
345 for i in 0..5 {
347 let mut record = create_test_record();
348 record.timestamp = i;
349 db.add_record("test_key".to_string(), record);
350 }
351
352 let recent = db.get_recent_records("test_key", 3);
353 assert_eq!(recent.len(), 3);
354 assert_eq!(recent[0].timestamp, 4);
356 assert_eq!(recent[1].timestamp, 3);
357 assert_eq!(recent[2].timestamp, 2);
358 }
359
360 #[test]
361 fn test_cleanup_old_records() {
362 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
363
364 for i in 0..10 {
366 let mut record = create_test_record();
367 record.timestamp = i * 1000;
368 db.add_record("test_key".to_string(), record);
369 }
370
371 let removed = db.cleanup_old_records(5000);
373 assert_eq!(removed, 5); assert_eq!(db.total_records(), 5); }
376
377 #[test]
378 fn test_get_records_by_commit() {
379 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
380
381 let mut record1 = create_test_record();
382 record1.commit_hash = Some("commit1".to_string());
383 db.add_record("key1".to_string(), record1);
384
385 let mut record2 = create_test_record();
386 record2.commit_hash = Some("commit2".to_string());
387 db.add_record("key2".to_string(), record2);
388
389 let results = db.get_records_by_commit("commit1");
390 assert_eq!(results.len(), 1);
391 assert_eq!(results[0].0, "key1");
392 }
393
394 #[test]
395 fn test_compact_database() {
396 let mut db: PerformanceDatabase<f64> = PerformanceDatabase::new();
397
398 for _ in 0..3 {
400 let record = create_test_record();
401 db.add_record("test_key".to_string(), record);
402 }
403
404 assert_eq!(db.total_records(), 3);
405
406 let removed = db.compact();
407 assert_eq!(removed, 2); assert_eq!(db.total_records(), 1); }
410}