use crate::task::{CrackleTask, TaskMetadata, TaskOutput, Timestamp};
use crate::patterns::{
ClusteringPattern, ConservationPattern, CorrelationPattern, CracklePattern, PhaseTransitionPattern,
};
use crate::profile::ThermalProfile;
#[derive(Debug, Clone)]
pub struct TaskEntry {
pub label: String,
pub metrics: Vec<(String, f64)>,
pub cooled_metrics: Vec<(String, f64)>,
pub metadata: TaskMetadata,
}
impl TaskEntry {
pub fn all_metrics(&self) -> Vec<(String, f64)> {
let mut result = self.cooled_metrics.clone();
for (name, val) in &self.metrics {
if !result.iter().any(|(n, _)| n == name) {
result.push((name.clone(), *val));
}
}
result
}
}
pub struct Kiln {
profile: ThermalProfile,
entries: Vec<TaskEntry>,
cooled: bool,
}
impl Kiln {
pub fn new(profile: ThermalProfile) -> Self {
Kiln {
profile,
entries: Vec::new(),
cooled: false,
}
}
pub fn default_profile() -> Self {
Kiln::new(ThermalProfile::default())
}
pub fn fire_task<T: CrackleTask>(&self, task: T) -> TaskOutput<T::Output> {
assert!(!self.cooled, "cannot fire tasks after cooling");
task.fire()
}
pub fn fire_and_record<T: CrackleTask>(&mut self, task: T) -> TaskOutput<T::Output> {
assert!(!self.cooled, "cannot fire tasks after cooling");
let label = task.label();
let fired_at = Timestamp::now();
let start = std::time::Instant::now();
let output = task.fire();
let fire_duration = start.elapsed();
let metadata = TaskMetadata {
fired_at,
cooled_at: None,
fire_duration,
label: label.clone(),
};
let entry = TaskEntry {
label,
metrics: output.metrics.clone(),
cooled_metrics: vec![],
metadata,
};
self.entries.push(entry);
output
}
pub fn fire_all<T: CrackleTask>(&mut self, tasks: Vec<T>) -> Vec<TaskOutput<T::Output>> {
tasks
.into_iter()
.map(|task| self.fire_and_record(task))
.collect()
}
pub fn add_entry(&mut self, label: impl Into<String>, metrics: Vec<(String, f64)>) {
let label = label.into();
let metadata = TaskMetadata::new(&label);
self.entries.push(TaskEntry {
label,
metrics,
cooled_metrics: vec![],
metadata,
});
}
pub fn task_count(&self) -> usize {
self.entries.len()
}
pub fn entries(&self) -> &[TaskEntry] {
&self.entries
}
pub fn cool(&mut self) -> Vec<CracklePattern> {
self.cooled = true;
let mut patterns = Vec::new();
if self.entries.len() < self.profile.rate.min_tasks_for_detection() {
return patterns;
}
let labels: Vec<String> = self.entries.iter().map(|e| e.label.clone()).collect();
let metrics: Vec<Vec<(String, f64)>> = self.entries.iter().map(|e| e.all_metrics()).collect();
let cooled_ts = Timestamp::now();
for entry in &mut self.entries {
entry.metadata.cooled_at = Some(cooled_ts);
}
if self.profile.detect_clustering {
let p = ClusteringPattern::detect(
&labels,
&metrics,
self.profile.rate.cluster_threshold(),
);
patterns.extend(p);
}
if self.profile.detect_phase_transitions {
let p = PhaseTransitionPattern::detect(
&labels,
&metrics,
self.profile.rate.phase_transition_sensitivity(),
);
patterns.extend(p);
}
if self.profile.detect_conservation {
let p = ConservationPattern::detect(
&labels,
&metrics,
self.profile.rate.conservation_tolerance(),
);
patterns.extend(p);
}
if self.profile.detect_correlations {
let p = CorrelationPattern::detect(
&labels,
&metrics,
self.profile.rate.correlation_threshold(),
);
patterns.extend(p);
}
patterns.sort_by(|a, b| b.confidence().partial_cmp(&a.confidence()).unwrap_or(std::cmp::Ordering::Equal));
patterns
}
pub fn is_cooled(&self) -> bool {
self.cooled
}
pub fn profile(&self) -> &ThermalProfile {
&self.profile
}
pub fn reset(&mut self) {
self.entries.clear();
self.cooled = false;
}
}