1use crate::task::{CrackleTask, TaskMetadata, TaskOutput, Timestamp};
2use crate::patterns::{
3 ClusteringPattern, ConservationPattern, CorrelationPattern, CracklePattern, PhaseTransitionPattern,
4};
5use crate::profile::ThermalProfile;
6
7#[derive(Debug, Clone)]
9pub struct TaskEntry {
10 pub label: String,
12 pub metrics: Vec<(String, f64)>,
14 pub cooled_metrics: Vec<(String, f64)>,
16 pub metadata: TaskMetadata,
18}
19
20impl TaskEntry {
21 pub fn all_metrics(&self) -> Vec<(String, f64)> {
23 let mut result = self.cooled_metrics.clone();
24 for (name, val) in &self.metrics {
25 if !result.iter().any(|(n, _)| n == name) {
26 result.push((name.clone(), *val));
27 }
28 }
29 result
30 }
31}
32
33pub struct Kiln {
64 profile: ThermalProfile,
65 entries: Vec<TaskEntry>,
66 cooled: bool,
67}
68
69impl Kiln {
70 pub fn new(profile: ThermalProfile) -> Self {
72 Kiln {
73 profile,
74 entries: Vec::new(),
75 cooled: false,
76 }
77 }
78
79 pub fn default_profile() -> Self {
81 Kiln::new(ThermalProfile::default())
82 }
83
84 pub fn fire_task<T: CrackleTask>(&self, task: T) -> TaskOutput<T::Output> {
92 assert!(!self.cooled, "cannot fire tasks after cooling");
93
94 task.fire()
95 }
96
97 pub fn fire_and_record<T: CrackleTask>(&mut self, task: T) -> TaskOutput<T::Output> {
102 assert!(!self.cooled, "cannot fire tasks after cooling");
103
104 let label = task.label();
105 let fired_at = Timestamp::now();
106 let start = std::time::Instant::now();
107
108 let output = task.fire();
109
110 let fire_duration = start.elapsed();
111 let metadata = TaskMetadata {
112 fired_at,
113 cooled_at: None,
114 fire_duration,
115 label: label.clone(),
116 };
117
118 let entry = TaskEntry {
119 label,
120 metrics: output.metrics.clone(),
121 cooled_metrics: vec![],
122 metadata,
123 };
124
125 self.entries.push(entry);
126 output
127 }
128
129 pub fn fire_all<T: CrackleTask>(&mut self, tasks: Vec<T>) -> Vec<TaskOutput<T::Output>> {
131 tasks
132 .into_iter()
133 .map(|task| self.fire_and_record(task))
134 .collect()
135 }
136
137 pub fn add_entry(&mut self, label: impl Into<String>, metrics: Vec<(String, f64)>) {
139 let label = label.into();
140 let metadata = TaskMetadata::new(&label);
141 self.entries.push(TaskEntry {
142 label,
143 metrics,
144 cooled_metrics: vec![],
145 metadata,
146 });
147 }
148
149 pub fn task_count(&self) -> usize {
151 self.entries.len()
152 }
153
154 pub fn entries(&self) -> &[TaskEntry] {
156 &self.entries
157 }
158
159 pub fn cool(&mut self) -> Vec<CracklePattern> {
167 self.cooled = true;
168 let mut patterns = Vec::new();
169
170 if self.entries.len() < self.profile.rate.min_tasks_for_detection() {
171 return patterns;
172 }
173
174 let labels: Vec<String> = self.entries.iter().map(|e| e.label.clone()).collect();
175 let metrics: Vec<Vec<(String, f64)>> = self.entries.iter().map(|e| e.all_metrics()).collect();
176
177 let cooled_ts = Timestamp::now();
179 for entry in &mut self.entries {
180 entry.metadata.cooled_at = Some(cooled_ts);
181 }
182
183 if self.profile.detect_clustering {
184 let p = ClusteringPattern::detect(
185 &labels,
186 &metrics,
187 self.profile.rate.cluster_threshold(),
188 );
189 patterns.extend(p);
190 }
191
192 if self.profile.detect_phase_transitions {
193 let p = PhaseTransitionPattern::detect(
194 &labels,
195 &metrics,
196 self.profile.rate.phase_transition_sensitivity(),
197 );
198 patterns.extend(p);
199 }
200
201 if self.profile.detect_conservation {
202 let p = ConservationPattern::detect(
203 &labels,
204 &metrics,
205 self.profile.rate.conservation_tolerance(),
206 );
207 patterns.extend(p);
208 }
209
210 if self.profile.detect_correlations {
211 let p = CorrelationPattern::detect(
212 &labels,
213 &metrics,
214 self.profile.rate.correlation_threshold(),
215 );
216 patterns.extend(p);
217 }
218
219 patterns.sort_by(|a, b| b.confidence().partial_cmp(&a.confidence()).unwrap_or(std::cmp::Ordering::Equal));
221
222 patterns
223 }
224
225 pub fn is_cooled(&self) -> bool {
227 self.cooled
228 }
229
230 pub fn profile(&self) -> &ThermalProfile {
232 &self.profile
233 }
234
235 pub fn reset(&mut self) {
237 self.entries.clear();
238 self.cooled = false;
239 }
240}