entrenar/dashboard/wasm/
storage.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7use crate::storage::{ExperimentStorage, MetricPoint, Result, RunStatus, StorageError};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub(crate) struct ExperimentData {
12 pub id: String,
13 pub name: String,
14 pub config: Option<serde_json::Value>,
15 pub created_at: DateTime<Utc>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub(crate) struct RunData {
21 pub id: String,
22 pub experiment_id: String,
23 pub status: RunStatus,
24 pub span_id: Option<String>,
25 pub started_at: Option<DateTime<Utc>>,
26 pub completed_at: Option<DateTime<Utc>>,
27}
28
29#[derive(Debug, Default)]
35pub struct IndexedDbStorage {
36 experiments: HashMap<String, ExperimentData>,
38 runs: HashMap<String, RunData>,
40 metrics: HashMap<String, HashMap<String, Vec<MetricPoint>>>,
42 artifacts: HashMap<String, Vec<u8>>,
44 next_exp_id: u64,
46 next_run_id: u64,
48}
49
50impl IndexedDbStorage {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn list_experiments(&self) -> Vec<String> {
58 self.experiments.keys().cloned().collect()
59 }
60
61 pub fn list_runs(&self, experiment_id: &str) -> Vec<String> {
63 self.runs
64 .values()
65 .filter(|r| r.experiment_id == experiment_id)
66 .map(|r| r.id.clone())
67 .collect()
68 }
69
70 pub fn list_metric_keys(&self, run_id: &str) -> Vec<String> {
72 self.metrics.get(run_id).map(|m| m.keys().cloned().collect()).unwrap_or_default()
73 }
74}
75
76impl ExperimentStorage for IndexedDbStorage {
77 fn create_experiment(
78 &mut self,
79 name: &str,
80 config: Option<serde_json::Value>,
81 ) -> Result<String> {
82 let id = format!("exp-{}", self.next_exp_id);
83 self.next_exp_id += 1;
84
85 let experiment = ExperimentData {
86 id: id.clone(),
87 name: name.to_string(),
88 config,
89 created_at: Utc::now(),
90 };
91
92 self.experiments.insert(id.clone(), experiment);
93 Ok(id)
94 }
95
96 fn create_run(&mut self, experiment_id: &str) -> Result<String> {
97 if !self.experiments.contains_key(experiment_id) {
98 return Err(StorageError::ExperimentNotFound(experiment_id.to_string()));
99 }
100
101 let id = format!("run-{}", self.next_run_id);
102 self.next_run_id += 1;
103
104 let run = RunData {
105 id: id.clone(),
106 experiment_id: experiment_id.to_string(),
107 status: RunStatus::Pending,
108 span_id: None,
109 started_at: None,
110 completed_at: None,
111 };
112
113 self.runs.insert(id.clone(), run);
114 self.metrics.insert(id.clone(), HashMap::new());
115 Ok(id)
116 }
117
118 fn start_run(&mut self, run_id: &str) -> Result<()> {
119 let run = self
120 .runs
121 .get_mut(run_id)
122 .ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
123
124 if run.status != RunStatus::Pending {
125 return Err(StorageError::InvalidState(format!(
126 "Run {run_id} is not in Pending state"
127 )));
128 }
129
130 run.status = RunStatus::Running;
131 run.started_at = Some(Utc::now());
132 Ok(())
133 }
134
135 fn complete_run(&mut self, run_id: &str, status: RunStatus) -> Result<()> {
136 let run = self
137 .runs
138 .get_mut(run_id)
139 .ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
140
141 if run.status != RunStatus::Running {
142 return Err(StorageError::InvalidState(format!(
143 "Run {run_id} is not in Running state"
144 )));
145 }
146
147 run.status = status;
148 run.completed_at = Some(Utc::now());
149 Ok(())
150 }
151
152 fn log_metric(&mut self, run_id: &str, key: &str, step: u64, value: f64) -> Result<()> {
153 let metrics = self
154 .metrics
155 .get_mut(run_id)
156 .ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
157
158 let points = metrics.entry(key.to_string()).or_default();
159 points.push(MetricPoint::new(step, value));
160 Ok(())
161 }
162
163 fn log_artifact(&mut self, run_id: &str, key: &str, data: &[u8]) -> Result<String> {
164 if !self.runs.contains_key(run_id) {
165 return Err(StorageError::RunNotFound(run_id.to_string()));
166 }
167
168 use sha2::{Digest, Sha256};
170 let mut hasher = Sha256::new();
171 hasher.update(data);
172 let hash = hex::encode(hasher.finalize());
173
174 let artifact_key = format!("{run_id}/{key}/{hash}");
176 self.artifacts.insert(artifact_key, data.to_vec());
177
178 Ok(hash)
179 }
180
181 fn get_metrics(&self, run_id: &str, key: &str) -> Result<Vec<MetricPoint>> {
182 let metrics = self
183 .metrics
184 .get(run_id)
185 .ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
186
187 Ok(metrics.get(key).cloned().unwrap_or_default())
188 }
189
190 fn get_run_status(&self, run_id: &str) -> Result<RunStatus> {
191 let run =
192 self.runs.get(run_id).ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
193
194 Ok(run.status)
195 }
196
197 fn set_span_id(&mut self, run_id: &str, span_id: &str) -> Result<()> {
198 let run = self
199 .runs
200 .get_mut(run_id)
201 .ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
202
203 run.span_id = Some(span_id.to_string());
204 Ok(())
205 }
206
207 fn get_span_id(&self, run_id: &str) -> Result<Option<String>> {
208 let run =
209 self.runs.get(run_id).ok_or_else(|| StorageError::RunNotFound(run_id.to_string()))?;
210
211 Ok(run.span_id.clone())
212 }
213}