1use crate::error::{DaemonError, Result};
4use crate::models::{FixtureState, FlakinessRecord, NativeTestNode, ScheduledTest, TestNode};
5use rmp_serde::{Deserializer, Serializer};
6use serde::{Deserialize, Serialize};
7use sled::{Db, Tree};
8use std::path::PathBuf;
9use tracing::{debug, error};
10
11const TREE_INVENTORY: &str = "inventory";
13const TREE_NATIVE_TESTS: &str = "native_tests";
14const TREE_FLAKINESS: &str = "flakiness";
15const TREE_FIXTURES: &str = "fixtures";
16const TREE_DURATION_HISTORY: &str = "duration_history";
17const TREE_CONTEXTS: &str = "contexts";
18const TREE_SCHEDULER: &str = "scheduler";
19const TREE_CONFIG: &str = "config";
20
21const STORAGE_VERSION: u32 = 1;
23
24#[derive(Clone)]
26pub struct DaemonStorage {
27 db: Db,
28 inventory: Tree,
29 native_tests: Tree,
30 flakiness: Tree,
31 fixtures: Tree,
32 duration_history: Tree,
33 contexts: Tree,
34 scheduler: Tree,
35 config: Tree,
36}
37
38impl std::fmt::Debug for DaemonStorage {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("DaemonStorage").finish_non_exhaustive()
41 }
42}
43
44impl DaemonStorage {
45 pub fn open(storage_path: &PathBuf) -> Result<Self> {
47 let config = sled::Config::default()
52 .path(storage_path)
53 .temporary(false)
54 .cache_capacity(256 * 1024 * 1024) .flush_every_ms(Some(5000)) .mode(sled::Mode::HighThroughput);
57
58 let db = config.open()?;
59
60 let inventory = db.open_tree(TREE_INVENTORY)?;
62 let native_tests = db.open_tree(TREE_NATIVE_TESTS)?;
63 let flakiness = db.open_tree(TREE_FLAKINESS)?;
64 let fixtures = db.open_tree(TREE_FIXTURES)?;
65 let duration_history = db.open_tree(TREE_DURATION_HISTORY)?;
66 let contexts = db.open_tree(TREE_CONTEXTS)?;
67 let scheduler = db.open_tree(TREE_SCHEDULER)?;
68 let config_tree = db.open_tree(TREE_CONFIG)?;
69
70 if let Some(version_bytes) = config_tree.get("version")? {
72 let version: u32 = rmp_serde::from_slice(&version_bytes)?;
73 if version != STORAGE_VERSION {
74 error!(
75 "Storage schema version mismatch: expected {}, found {}",
76 STORAGE_VERSION, version
77 );
78 return Err(DaemonError::Other(format!(
79 "Storage schema version mismatch: expected {}, found {}",
80 STORAGE_VERSION, version
81 )));
82 }
83 } else {
84 let mut version_bytes = Vec::new();
86 STORAGE_VERSION.serialize(&mut Serializer::new(&mut version_bytes))?;
87 config_tree.insert("version", version_bytes)?;
88 }
89
90 Ok(Self {
91 db,
92 inventory,
93 native_tests,
94 flakiness,
95 fixtures,
96 duration_history,
97 contexts,
98 scheduler,
99 config: config_tree,
100 })
101 }
102
103 pub fn clear_all(&self) -> Result<()> {
105 self.inventory.clear()?;
106 self.native_tests.clear()?;
107 self.flakiness.clear()?;
108 self.fixtures.clear()?;
109 self.duration_history.clear()?;
110 self.contexts.clear()?;
111 self.scheduler.clear()?;
112 Ok(())
113 }
114
115 pub fn save_test_node(&self, node: &TestNode) -> Result<()> {
119 let mut buf = Vec::new();
120 node.serialize(&mut Serializer::new(&mut buf))?;
121 self.inventory.insert(&node.node_id, buf)?;
122 Ok(())
123 }
124
125 pub fn save_test_nodes_batch(&self, nodes: &[TestNode]) -> Result<()> {
127 if nodes.is_empty() {
128 return Ok(());
129 }
130
131 let mut batch = sled::Batch::default();
132 for node in nodes {
133 let mut buf = Vec::new();
134 node.serialize(&mut Serializer::new(&mut buf))?;
135 batch.insert(node.node_id.as_bytes(), buf);
136 }
137 self.inventory.apply_batch(batch)?;
138 Ok(())
139 }
140
141 pub fn load_test_node(&self, node_id: &str) -> Result<Option<TestNode>> {
143 if let Some(bytes) = self.inventory.get(node_id)? {
144 let mut deserializer = Deserializer::new(&bytes[..]);
145 let node: TestNode = Deserialize::deserialize(&mut deserializer)?;
146 Ok(Some(node))
147 } else {
148 Ok(None)
149 }
150 }
151
152 pub fn get_all_inventory(&self) -> Result<Vec<TestNode>> {
154 let mut nodes = Vec::new();
155 for item in self.inventory.iter() {
156 let (_, bytes) = item?;
157 let mut deserializer = Deserializer::new(&bytes[..]);
158 let node: TestNode = Deserialize::deserialize(&mut deserializer)?;
159 nodes.push(node);
160 }
161 Ok(nodes)
162 }
163
164 pub fn inventory_count(&self) -> usize {
166 self.inventory.len()
167 }
168
169 pub fn clear_inventory(&self) -> Result<()> {
171 self.inventory.clear()?;
172 Ok(())
173 }
174
175 pub fn save_native_test(&self, node: &NativeTestNode) -> Result<()> {
179 let mut buf = Vec::new();
180 node.serialize(&mut Serializer::new(&mut buf))?;
181 self.native_tests.insert(&node.node_id, buf)?;
182 Ok(())
183 }
184
185 pub fn save_native_tests_batch(&self, nodes: &[NativeTestNode]) -> Result<()> {
187 if nodes.is_empty() {
188 return Ok(());
189 }
190
191 let mut batch = sled::Batch::default();
192 for node in nodes {
193 let mut buf = Vec::new();
194 node.serialize(&mut Serializer::new(&mut buf))?;
195 batch.insert(node.node_id.as_bytes(), buf);
196 }
197 self.native_tests.apply_batch(batch)?;
198 Ok(())
199 }
200
201 pub fn load_native_test(&self, node_id: &str) -> Result<Option<NativeTestNode>> {
203 if let Some(bytes) = self.native_tests.get(node_id)? {
204 let mut deserializer = Deserializer::new(&bytes[..]);
205 let node: NativeTestNode = Deserialize::deserialize(&mut deserializer)?;
206 Ok(Some(node))
207 } else {
208 Ok(None)
209 }
210 }
211
212 pub fn get_all_native_tests(&self) -> Result<Vec<NativeTestNode>> {
214 let mut nodes = Vec::new();
215 for item in self.native_tests.iter() {
216 let (_, bytes) = item?;
217 let mut deserializer = Deserializer::new(&bytes[..]);
218 let node: NativeTestNode = Deserialize::deserialize(&mut deserializer)?;
219 nodes.push(node);
220 }
221 Ok(nodes)
222 }
223
224 pub fn clear_native_tests(&self) -> Result<()> {
226 self.native_tests.clear()?;
227 Ok(())
228 }
229
230 pub fn save_flakiness_record(&self, record: &FlakinessRecord) -> Result<()> {
234 let mut buf = Vec::new();
235 record.serialize(&mut Serializer::new(&mut buf))?;
236 self.flakiness.insert(&record.node_id, buf)?;
237 Ok(())
238 }
239
240 pub fn load_flakiness_record(&self, node_id: &str) -> Result<Option<FlakinessRecord>> {
242 if let Some(bytes) = self.flakiness.get(node_id)? {
243 let mut deserializer = Deserializer::new(&bytes[..]);
244 let record: FlakinessRecord = Deserialize::deserialize(&mut deserializer)?;
245 Ok(Some(record))
246 } else {
247 Ok(None)
248 }
249 }
250
251 pub fn get_all_flakiness(&self) -> Result<Vec<FlakinessRecord>> {
253 let mut records = Vec::new();
254 for item in self.flakiness.iter() {
255 let (_, bytes) = item?;
256 let mut deserializer = Deserializer::new(&bytes[..]);
257 let record: FlakinessRecord = Deserialize::deserialize(&mut deserializer)?;
258 records.push(record);
259 }
260 Ok(records)
261 }
262
263 pub fn delete_flakiness_record(&self, node_id: &str) -> Result<()> {
265 self.flakiness.remove(node_id)?;
266 Ok(())
267 }
268
269 pub fn save_fixture(&self, context_id: &str, fixture: &FixtureState) -> Result<()> {
273 let key = format!("{}/{}", context_id, fixture.name);
274 let mut buf = Vec::new();
275 fixture.serialize(&mut Serializer::new(&mut buf))?;
276 self.fixtures.insert(key, buf)?;
277 Ok(())
278 }
279
280 pub fn load_fixture(&self, context_id: &str, name: &str) -> Result<Option<FixtureState>> {
282 let key = format!("{}/{}", context_id, name);
283 if let Some(bytes) = self.fixtures.get(key)? {
284 let mut deserializer = Deserializer::new(&bytes[..]);
285 let fixture: FixtureState = Deserialize::deserialize(&mut deserializer)?;
286 Ok(Some(fixture))
287 } else {
288 Ok(None)
289 }
290 }
291
292 pub fn get_context_fixtures(&self, context_id: &str) -> Result<Vec<FixtureState>> {
294 let prefix = format!("{}/", context_id);
295 let mut fixtures = Vec::new();
296 for item in self.fixtures.scan_prefix(prefix) {
297 let (_, bytes) = item?;
298 let mut deserializer = Deserializer::new(&bytes[..]);
299 let fixture: FixtureState = Deserialize::deserialize(&mut deserializer)?;
300 fixtures.push(fixture);
301 }
302 Ok(fixtures)
303 }
304
305 pub fn delete_fixture(&self, context_id: &str, name: &str) -> Result<()> {
307 let key = format!("{}/{}", context_id, name);
308 self.fixtures.remove(key)?;
309 Ok(())
310 }
311
312 pub fn clear_context_fixtures(&self, context_id: &str) -> Result<()> {
314 let prefix = format!("{}/", context_id);
315 for item in self.fixtures.scan_prefix(prefix) {
316 let (key, _) = item?;
317 self.fixtures.remove(key)?;
318 }
319 Ok(())
320 }
321
322 pub fn save_duration_history(&self, node_id: &str, durations: &[u64]) -> Result<()> {
326 let mut buf = Vec::new();
327 durations.serialize(&mut Serializer::new(&mut buf))?;
328 self.duration_history.insert(node_id, buf)?;
329 Ok(())
330 }
331
332 pub fn load_duration_history(&self, node_id: &str) -> Result<Option<Vec<u64>>> {
334 if let Some(bytes) = self.duration_history.get(node_id)? {
335 let mut deserializer = Deserializer::new(&bytes[..]);
336 let durations: Vec<u64> = Deserialize::deserialize(&mut deserializer)?;
337 Ok(Some(durations))
338 } else {
339 Ok(None)
340 }
341 }
342
343 pub fn get_average_duration(&self, node_id: &str) -> Result<Option<u64>> {
345 if let Some(durations) = self.load_duration_history(node_id)? {
346 if durations.is_empty() {
347 return Ok(None);
348 }
349 let sum: u64 = durations.iter().sum();
350 Ok(Some(sum / durations.len() as u64))
351 } else {
352 Ok(None)
353 }
354 }
355
356 pub fn save_duration_history_batch(&self, histories: &[(&str, &[u64])]) -> Result<()> {
358 if histories.is_empty() {
359 return Ok(());
360 }
361
362 let mut batch = sled::Batch::default();
363 for (node_id, durations) in histories {
364 let mut buf = Vec::new();
365 durations.serialize(&mut Serializer::new(&mut buf))?;
366 batch.insert(node_id.as_bytes(), buf);
367 }
368 self.duration_history.apply_batch(batch)?;
369 Ok(())
370 }
371
372 pub fn save_scheduled_test(&self, test: &ScheduledTest) -> Result<()> {
376 let mut buf = Vec::new();
377 test.serialize(&mut Serializer::new(&mut buf))?;
378 self.scheduler.insert(&test.node_id, buf)?;
379 Ok(())
380 }
381
382 pub fn get_all_scheduled_tests(&self) -> Result<Vec<ScheduledTest>> {
384 let mut tests = Vec::new();
385 for item in self.scheduler.iter() {
386 let (_, bytes) = item?;
387 let mut deserializer = Deserializer::new(&bytes[..]);
388 let test: ScheduledTest = Deserialize::deserialize(&mut deserializer)?;
389 tests.push(test);
390 }
391 Ok(tests)
392 }
393
394 pub fn clear_scheduler(&self) -> Result<()> {
396 self.scheduler.clear()?;
397 Ok(())
398 }
399
400 pub fn save_context(&self, context_id: &str, data: &serde_json::Value) -> Result<()> {
404 let bytes = serde_json::to_vec(data)?;
405 self.contexts.insert(context_id, bytes)?;
406 Ok(())
407 }
408
409 pub fn load_context(&self, context_id: &str) -> Result<Option<serde_json::Value>> {
411 if let Some(bytes) = self.contexts.get(context_id)? {
412 let data: serde_json::Value = serde_json::from_slice(&bytes)?;
413 Ok(Some(data))
414 } else {
415 Ok(None)
416 }
417 }
418
419 pub fn get_all_context_ids(&self) -> Vec<String> {
421 self.contexts
422 .iter()
423 .filter_map(|item| item.ok())
424 .map(|(key, _)| String::from_utf8_lossy(&key).to_string())
425 .collect()
426 }
427
428 pub fn delete_context(&self, context_id: &str) -> Result<()> {
430 self.contexts.remove(context_id)?;
431 Ok(())
432 }
433
434 pub fn save_config(&self, key: &str, value: &serde_json::Value) -> Result<()> {
438 let bytes = serde_json::to_vec(value)?;
439 self.config.insert(key, bytes)?;
440 Ok(())
441 }
442
443 pub fn load_config(&self, key: &str) -> Result<Option<serde_json::Value>> {
445 if let Some(bytes) = self.config.get(key)? {
446 let value: serde_json::Value = serde_json::from_slice(&bytes)?;
447 Ok(Some(value))
448 } else {
449 Ok(None)
450 }
451 }
452
453 pub fn flush(&self) -> Result<()> {
455 self.db.flush()?;
456 Ok(())
457 }
458
459 pub fn evict_old_data(&self, _max_age_days: u32) -> Result<usize> {
464 let mut removed = 0;
466
467 let mut keys_to_remove = Vec::new();
469 for item in self.flakiness.iter() {
470 if let Ok((key, value)) = item {
471 let mut deserializer = Deserializer::new(&value[..]);
472 if let Ok(record) = FlakinessRecord::deserialize(&mut deserializer) {
473 if record.total_runs < 3 {
475 keys_to_remove.push(key);
476 }
477 }
478 }
479 }
480
481 for key in keys_to_remove {
482 self.flakiness.remove(key)?;
483 removed += 1;
484 }
485
486 if removed > 100 {
488 debug!("Evicted {} records, triggering compaction", removed);
489 self.compact_async();
490 }
491
492 Ok(removed)
493 }
494
495 pub fn compact_async(&self) {
497 let db = self.db.clone();
498 std::thread::spawn(move || {
499 if let Err(e) = db.flush() {
500 error!("Background compaction failed: {}", e);
501 }
502 });
503 }
504
505 pub fn db(&self) -> &Db {
507 &self.db
508 }
509}