diff --git a/experimental/src/compute/mod.rs b/experimental/src/compute/mod.rs index b5f3b7d27..e0958e5e1 100644 --- a/experimental/src/compute/mod.rs +++ b/experimental/src/compute/mod.rs @@ -1,5 +1,6 @@ pub mod ast; pub mod lexer; +pub mod threshold; use self::ast::ExprResult; use self::lexer::{LexicalError, Tok}; diff --git a/experimental/src/compute/threshold.rs b/experimental/src/compute/threshold.rs new file mode 100644 index 000000000..2d55822aa --- /dev/null +++ b/experimental/src/compute/threshold.rs @@ -0,0 +1,105 @@ +use std::f64::INFINITY; + +use log::{debug, error, info, trace, warn}; + +pub struct Threshold { + start: f64, + end: f64, +} + +impl Threshold { + pub fn parse(expr: &str) -> Threshold { + // https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT + let mut start: usize = 0; + let mut in_number = false; + let mut start_value = -INFINITY; + let mut end_value = INFINITY; + let mut in_range = false; + for (idx, c) in expr.char_indices() { + if in_number { + match c { + '0'..='9' => continue, + '.' | '-' | '+' | 'e' | 'E' => continue, + _ => { + in_number = false; + start_value = match expr[start..idx].parse() { + Ok(x) => x, + Err(err) => { + error!("parse error: {}", err); + std::process::exit(3); + } + } + } + } + } + /* No else here, because we can continue the previous match */ + if !in_number { + match c { + ' ' => continue, + '0'..='9' => { + in_number = true; + start = idx; + } + ':' => { + in_range = true; + } + _ => break, + } + } + } + if in_number { + start_value = match expr[start..].parse() { + Ok(x) => x, + Err(err) => { + error!("parse error: {}", err); + std::process::exit(3); + } + } + } + + /* We have noticed a ':' character, so the threshold is a range */ + if in_range { + return Threshold { + start: start_value, + end: INFINITY, + }; + } else { + return Threshold { + start: 0_f64, + end: start_value, + }; + } + } + + fn in_alert(&self, value: f64) -> bool { + if value < self.start || value > self.end { + return true; + } + false + } +} + +mod Test { + use super::*; + + #[test] + fn test_parse_value() { + let expr = "1.2"; + let threshold = Threshold::parse(expr); + assert_eq!(threshold.start, 0_f64); + assert_eq!(threshold.end, 1.2_f64); + assert!(threshold.in_alert(2_f64)); + assert!(threshold.in_alert(-1_f64)); + } + + #[test] + fn test_parse_val_colon() { + let expr = "10:"; + let threshold = Threshold::parse(expr); + assert_eq!(threshold.start, 10_f64); + assert_eq!(threshold.end, INFINITY); + assert!(!threshold.in_alert(10_f64)); + assert!(!threshold.in_alert(11_f64)); + assert!(threshold.in_alert(9_f64)); + } +} diff --git a/experimental/src/generic/mod.rs b/experimental/src/generic/mod.rs index e542d11df..63147ae22 100644 --- a/experimental/src/generic/mod.rs +++ b/experimental/src/generic/mod.rs @@ -1,7 +1,7 @@ extern crate serde; extern crate serde_json; -use compute::{ast::ExprResult, Compute, Parser}; +use compute::{ast::ExprResult, threshold::Threshold, Compute, Parser}; use log::{debug, trace}; use serde::Deserialize; use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels}; @@ -10,11 +10,13 @@ use std::{collections::HashMap, ops::IndexMut}; use crate::snmp::SnmpResult; #[derive(Debug)] -struct Perfdata { +struct Perfdata<'p> { name: String, value: f64, min: Option, max: Option, + warning: Option<&'p str>, + critical: Option<&'p str>, } #[derive(Copy, Clone, PartialEq)] @@ -219,11 +221,22 @@ impl Command { panic!("A label must be a string"); } }; + let status = compute_status(*item, &metric.warning, &metric.critical); + let w = match metric.warning { + Some(ref w) => Some(w.as_str()), + None => None, + }; + let c = match metric.critical { + Some(ref c) => Some(c.as_str()), + None => None, + }; let m = Perfdata { name, value: *item, min: compute_threshold(i, &min), max: compute_threshold(i, &max), + warning: w, + critical: c, }; trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); @@ -240,11 +253,22 @@ impl Command { res } }; + let status = compute_status(*s, &metric.warning, &metric.critical); + let w = match metric.warning { + Some(ref w) => Some(w.as_str()), + None => None, + }; + let c = match metric.critical { + Some(ref c) => Some(c.as_str()), + None => None, + }; let m = Perfdata { name, value: *s, min: compute_threshold(0, &min), max: compute_threshold(0, &max), + warning: w, + critical: c, }; trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); @@ -304,11 +328,22 @@ impl Command { res } }; + let status = compute_status(item, &metric.warning, &metric.critical); + let w = match metric.warning { + Some(ref w) => Some(w.as_str()), + None => None, + }; + let c = match metric.critical { + Some(ref c) => Some(c.as_str()), + None => None, + }; let m = Perfdata { name, value: item, min, max, + warning: w, + critical: c, }; trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); @@ -316,11 +351,22 @@ impl Command { } ExprResult::Number(s) => { let name = &metric.name; + let status = compute_status(s, &metric.warning, &metric.critical); + let w = match metric.warning { + Some(ref w) => Some(w.as_str()), + None => None, + }; + let c = match metric.critical { + Some(ref c) => Some(c.as_str()), + None => None, + }; let m = Perfdata { name: name.to_string(), value: s, min, max, + warning: w, + critical: c, }; trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m);