use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use crate::data::scope::AxisSettings;
use crate::data::trace_look::TraceLook;
use crate::data::traces::TraceRef;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ThresholdKind {
GreaterThan { value: f64 },
LessThan { value: f64 },
InRange { low: f64, high: f64 },
}
impl ThresholdKind {
pub fn excess(&self, v: f64) -> f64 {
match self {
ThresholdKind::GreaterThan { value } => (v - *value).max(0.0),
ThresholdKind::LessThan { value } => (*value - v).max(0.0),
ThresholdKind::InRange { low, high } => {
if v >= *low && v <= *high {
v - *low
} else {
0.0
}
}
}
}
pub fn is_active(&self, v: f64) -> bool {
self.excess(v) > 0.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdDef {
pub name: String,
pub target: TraceRef,
pub kind: ThresholdKind,
#[serde(skip)]
pub look: TraceLook,
#[serde(skip)]
pub start_look: TraceLook,
#[serde(skip)]
pub stop_look: TraceLook,
pub min_duration_s: f64,
pub max_events: usize,
#[serde(skip)]
pub runtime_state: ThresholdRuntimeState,
}
impl Default for ThresholdDef {
fn default() -> Self {
Self {
name: String::new(),
target: TraceRef::default(),
kind: ThresholdKind::GreaterThan { value: 0.0 },
look: TraceLook::default(),
start_look: TraceLook::default(),
stop_look: TraceLook::default(),
min_duration_s: 0.002,
max_events: 100,
runtime_state: ThresholdRuntimeState::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdEvent {
pub threshold: String,
pub trace: TraceRef,
pub start_t: f64,
pub end_t: f64,
pub duration: f64,
pub area: f64,
}
#[derive(Debug, Clone, Default)]
pub struct ThresholdRuntimeState {
active: bool,
pub start_t: f64,
pub last_t: Option<f64>,
pub last_excess: f64,
pub accum_area: f64,
pub prev_in_t: Option<f64>,
pub events: VecDeque<ThresholdEvent>,
}
impl ThresholdRuntimeState {
pub fn push_event_capped(&mut self, evt: ThresholdEvent, cap: usize) {
self.events.push_back(evt);
while self.events.len() > cap {
self.events.pop_front();
}
}
pub fn reset(&mut self) {
self.active = false;
self.start_t = 0.0;
self.last_t = None;
self.last_excess = 0.0;
self.accum_area = 0.0;
self.prev_in_t = None;
self.events.clear();
}
}
impl ThresholdDef {
pub fn get_info(&self, axis_setting: &AxisSettings) -> String {
let dec_pl = 4usize;
match &self.kind {
ThresholdKind::GreaterThan { value } => {
let v_fmt = axis_setting.format_value(*value, dec_pl, value.abs());
format!("{} > {}", self.target.0, v_fmt)
}
ThresholdKind::LessThan { value } => {
let v_fmt = axis_setting.format_value(*value, dec_pl, value.abs());
format!("{} < {}", self.target.0, v_fmt)
}
ThresholdKind::InRange { low, high } => {
let diff = (*high - *low).abs();
let lo = axis_setting.format_value(*low, dec_pl, diff);
let hi = axis_setting.format_value(*high, dec_pl, diff);
format!("{} in [{}, {}]", self.target.0, lo, hi)
}
}
}
pub fn clear_threshold_events(&mut self) {
self.runtime_state.events.clear();
}
pub fn count_threshold_events(&self) -> usize {
self.runtime_state.events.len()
}
pub fn get_last_threshold_event(&self) -> Option<ThresholdEvent> {
self.runtime_state.events.back().cloned()
}
pub fn get_threshold_events(&self) -> Vec<ThresholdEvent> {
self.runtime_state.events.iter().cloned().collect()
}
pub fn get_runtime_state(&self) -> &ThresholdRuntimeState {
&self.runtime_state
}
pub fn process_threshold(&mut self, sources: HashMap<TraceRef, VecDeque<[f64; 2]>>) {
let data = match sources.get(&self.target) {
Some(d) => d,
None => return,
};
let mut start_idx = 0usize;
if let Some(t0) = self.runtime_state.prev_in_t {
start_idx = match data.binary_search_by(|p| p[0].partial_cmp(&t0).unwrap()) {
Ok(mut i) => {
while i < data.len() && data[i][0] <= t0 {
i += 1;
}
i
}
Err(i) => i,
};
}
for p in data.iter().skip(start_idx) {
let t = p[0];
let v = p[1];
let e = self.kind.excess(v);
if let Some(t0) = self.runtime_state.last_t {
let dt = (t - t0).max(0.0);
if self.runtime_state.active || e > 0.0 {
self.runtime_state.accum_area +=
0.5 * (self.runtime_state.last_excess + e) * dt;
}
}
if !self.runtime_state.active && e > 0.0 {
self.runtime_state.active = true;
self.runtime_state.start_t = t;
} else if self.runtime_state.active && e == 0.0 {
let end_t = t;
let dur = end_t - self.runtime_state.start_t;
if dur >= self.min_duration_s {
let evt = ThresholdEvent {
threshold: self.name.clone(),
trace: self.target.clone(),
start_t: self.runtime_state.start_t,
end_t,
duration: dur,
area: self.runtime_state.accum_area,
};
self.runtime_state.push_event_capped(evt, self.max_events);
}
self.runtime_state.active = false;
self.runtime_state.accum_area = 0.0;
}
self.runtime_state.last_t = Some(t);
self.runtime_state.last_excess = e;
self.runtime_state.prev_in_t = Some(t);
}
}
}