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