Hastic standalone
https://hastic.io
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
5.0 KiB
187 lines
5.0 KiB
use crate::services::{ |
|
analytic_service::types::{HSR}, |
|
metric_service::MetricService, |
|
segments_service::SegmentsService, |
|
}; |
|
|
|
use super::types::{AnalyticUnit, AnalyticUnitConfig, AnomalyConfig, LearningResult}; |
|
|
|
use async_trait::async_trait; |
|
use subbeat::metric::MetricResult; |
|
|
|
|
|
struct SARIMA { |
|
pub ts: Vec<f64>, |
|
pub seasonality: u64 |
|
} |
|
|
|
impl SARIMA { |
|
|
|
pub fn new(seasonality: u64) -> SARIMA { |
|
return SARIMA { |
|
ts: Vec::new(), |
|
seasonality |
|
} |
|
} |
|
|
|
pub fn learn(ts: Vec<(u64, f64)>) { |
|
// TODO: compute avg based on seasonality |
|
} |
|
pub fn predict(timestamp: u64, value: f64) -> (f64, f64, f64) { |
|
// TODO: implement |
|
return (0.0, 0.0, 0.0); |
|
} |
|
|
|
pub fn push_point() { |
|
// TODO: inmplement |
|
} |
|
|
|
// TODO: don't count NaNs in model |
|
} |
|
|
|
|
|
|
|
// TODO: move to config |
|
const DETECTION_STEP: u64 = 10; |
|
|
|
// offset from intex in timrange in seconds |
|
fn get_value_with_offset(ts: &Vec<(u64, f64)>, index: usize, offset: u64) -> anyhow::Result<f64> { |
|
// TODO: implement |
|
if index == 0 { |
|
return Err(anyhow::format_err!("index should be > 0")); |
|
} |
|
return Ok(0.0); |
|
// let step = |
|
// let index_candidate = |
|
// let intex_candidate = |
|
} |
|
|
|
pub struct AnomalyAnalyticUnit { |
|
config: AnomalyConfig, |
|
sarima: Option<SARIMA> |
|
} |
|
|
|
impl AnomalyAnalyticUnit { |
|
pub fn new(config: AnomalyConfig) -> AnomalyAnalyticUnit { |
|
AnomalyAnalyticUnit { config, sarima: None } |
|
} |
|
|
|
fn get_hsr_from_metric_result(&self, mr: &MetricResult) -> anyhow::Result<HSR> { |
|
// TODO: get it from model |
|
if mr.data.keys().len() == 0 { |
|
return Ok(HSR::ConfidenceTimeSerie(Vec::new())); |
|
} |
|
|
|
let k = mr.data.keys().nth(0).unwrap(); |
|
let ts = mr.data[k].clone(); |
|
|
|
if ts.len() == 0 { |
|
return Ok(HSR::ConfidenceTimeSerie(Vec::new())); |
|
} |
|
|
|
let mut sts = Vec::new(); |
|
sts.push(( |
|
ts[0].0, |
|
ts[0].1, |
|
(( |
|
ts[0].1 + self.config.confidence, |
|
ts[0].1 - self.config.confidence, |
|
)), |
|
)); |
|
|
|
for t in 1..ts.len() { |
|
let alpha = self.config.alpha; |
|
let stv = alpha * ts[t].1 + (1.0 - alpha) * sts[t - 1].1; |
|
sts.push(( |
|
ts[t].0, |
|
stv, |
|
(stv + self.config.confidence, stv - self.config.confidence), |
|
)); |
|
} |
|
|
|
Ok(HSR::ConfidenceTimeSerie(sts)) |
|
} |
|
} |
|
|
|
#[async_trait] |
|
impl AnalyticUnit for AnomalyAnalyticUnit { |
|
fn set_config(&mut self, config: AnalyticUnitConfig) { |
|
if let AnalyticUnitConfig::Anomaly(cfg) = config { |
|
self.config = cfg; |
|
} else { |
|
panic!("Bad config!"); |
|
} |
|
} |
|
async fn learn(&mut self, _ms: MetricService, _ss: SegmentsService) -> LearningResult { |
|
|
|
let sarima = SARIMA::new(self.config.seasonality); |
|
// TODO: ensue that learning runs on seasonaliy change |
|
// TODO: load data to learning |
|
|
|
// TODO: update model to work online |
|
return LearningResult::Finished; |
|
} |
|
async fn detect( |
|
&self, |
|
ms: MetricService, |
|
from: u64, |
|
to: u64, |
|
) -> anyhow::Result<Vec<(u64, u64)>> { |
|
if self.sarima.is_none() { |
|
return Err(anyhow::format_err!("Learning model is not ready")); |
|
} |
|
let mr = ms |
|
.query(from - self.config.seasonality * 5, to, DETECTION_STEP) |
|
.await |
|
.unwrap(); |
|
|
|
if mr.data.keys().len() == 0 { |
|
return Ok(Vec::new()); |
|
} |
|
|
|
let k = mr.data.keys().nth(0).unwrap(); |
|
let ts = mr.data[k].clone(); |
|
|
|
if ts.len() == 0 { |
|
return Ok(Vec::new()); |
|
} |
|
|
|
let mut result = Vec::new(); |
|
|
|
let confidence_time_serie = self.get_hsr_from_metric_result(&mr)?; |
|
|
|
if let HSR::ConfidenceTimeSerie(hsr) = confidence_time_serie { |
|
let mut from = None; |
|
|
|
for ((t, _, (u, l)), (t1, rv)) in hsr.iter().zip(ts.iter()) { |
|
if *t != *t1 { |
|
return Err(anyhow::format_err!("incompatible hsr/ts")); |
|
} |
|
if rv > u || rv < l { |
|
if from.is_none() { |
|
from = Some(*t); |
|
} |
|
} else { |
|
if from.is_some() { |
|
result.push((from.unwrap(), *t)); |
|
from = None; |
|
} |
|
} |
|
} |
|
|
|
if from.is_some() { |
|
result.push((from.unwrap(), ts.last().unwrap().0)); |
|
} |
|
|
|
return Ok(result); |
|
} else { |
|
return Err(anyhow::format_err!("bad hsr")); |
|
} |
|
} |
|
|
|
// TODO: use hsr for learning and detections |
|
async fn get_hsr(&self, ms: MetricService, from: u64, to: u64) -> anyhow::Result<HSR> { |
|
let mr = ms.query(from, to, DETECTION_STEP).await.unwrap(); |
|
return self.get_hsr_from_metric_result(&mr); |
|
} |
|
}
|
|
|