Skip to content
Merged
42 changes: 42 additions & 0 deletions rust-plugins/examples/tcpcon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"collect": {
"snmp": [
{
"name": "tcpConnectionState",
"oid": "1.3.6.1.2.1.6.19.1.7",
"query": "Walk"
},
{
"name": "tcpListenerProcess",
"oid": "1.3.6.1.2.1.6.20.1.4",
"query": "Walk"
},
{
"name": "tcpConnState",
"oid": "1.3.6.1.2.1.6.13.1.1",
"query": "Walk"
}
]
},
"compute": {
"metrics": [
{
"name": "tcp.connections",
"value": "Average({tcpConnectionState})",
"uom": "",
"min": 0,
"threshold-suffix": "tcp-connections"
}
]
},
"output": {
"ok": "TCP connections are OK",
"detail_ok": false,
"warning": "TCP connections WARNING: ",
"detail_warning": true,
"critical": "TCP connections CRITICAL: ",
"detail_critical": true,
"instance_separator": " - ",
"metric_separator": ", "
}
}
31 changes: 31 additions & 0 deletions rust-plugins/examples/udpcon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"collect": {
"snmp": [
{
"name": "udpLocalAddress",
"oid": "1.3.6.1.2.1.7.7.1.8",
"query": "Walk"
}
]
},
"compute": {
"metrics": [
{
"name": "udp.connections.ipv4",
"value": "({udpLocalAddress})",
"uom": "",
"min": 0
}
]
},
"output": {
"ok": "UDP connections are OK",
"detail_ok": false,
"warning": "UDP connections WARNING: ",
"detail_warning": true,
"critical": "UDP connections CRITICAL: ",
"detail_critical": true,
"instance_separator": " - ",
"metric_separator": ", "
}
}
16 changes: 11 additions & 5 deletions rust-plugins/src/compute/ast.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Abstract syntax tree and expression evaluation.

use crate::snmp::SnmpResult;
use log::{info, warn};
use log::{info, trace, warn};
use std::str;

/// An expression node in the AST.
Expand Down Expand Up @@ -280,6 +280,7 @@ impl ExprResult {
/// the numbers to strings before concatenation, padding to match lengths
/// where necessary.
pub fn join(&mut self, other: &ExprResult) {
trace!("[join] self: {:?} - other: {:?}", &self, &other);
match self {
ExprResult::Empty => match other {
ExprResult::StrVector(vv) => {
Expand All @@ -289,7 +290,9 @@ impl ExprResult {
*self = ExprResult::Str(s.clone());
}
ExprResult::Vector(vv) => {
*self = ExprResult::StrVector(vv.iter().map(|n| crate::output::float_string(n)).collect());
*self = ExprResult::StrVector(
vv.iter().map(|n| crate::output::float_string(n)).collect(),
);
}
_ => panic!("Unable to join objects others than strings"),
},
Expand Down Expand Up @@ -344,7 +347,8 @@ impl ExprResult {
*s = format!("{}{}", s, ss);
}
ExprResult::Number(n) => {
*s = format!("{}{:.2}", s, crate::output::float_string(n));
trace!("[join] n: {:?}", &n);
*s = format!("{}{}", s, crate::output::float_string(n));
}
_ => panic!("Unable to join objects others than strings"),
},
Expand All @@ -369,8 +373,10 @@ impl<'input> Expr<'input> {
Err(format!("Undefined macro in expression: {{{}}}", k))
}
Expr::Number(_) => Ok(()),
Expr::OpPlus(left, right) | Expr::OpMinus(left, right) |
Expr::OpStar(left, right) | Expr::OpSlash(left, right) => {
Expr::OpPlus(left, right)
| Expr::OpMinus(left, right)
| Expr::OpStar(left, right)
| Expr::OpSlash(left, right) => {
left.validate_macros(collect)?;
right.validate_macros(collect)?;
Ok(())
Expand Down
17 changes: 6 additions & 11 deletions rust-plugins/src/compute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use self::ast::ExprResult;
use self::lexer::{LexicalError, Tok};
use crate::snmp::SnmpResult;
use lalrpop_util::{ParseError, lalrpop_mod};
use log::debug;
use log::{debug, trace};
use regex::Regex;
use serde::Deserialize;

Expand Down Expand Up @@ -71,7 +71,7 @@ pub struct Compute {
pub struct Parser<'a> {
collect: &'a Vec<SnmpResult>,
parser: grammar::ExprParser,
check_format: bool
check_format: bool,
}

impl<'a> Parser<'a> {
Expand All @@ -80,18 +80,15 @@ impl<'a> Parser<'a> {
Parser {
collect,
parser: grammar::ExprParser::new(),
check_format
check_format,
}
}

/// Evaluates a mathematical expression and returns the result.
///
/// Supports arithmetic operations, identifiers in braces (e.g., `{metric_name}`),
/// and functions like `Average()`, `Min()`, `Max()`.
pub fn eval(
&self,
expr: &'a str,
) -> Result<ExprResult, String> {
pub fn eval(&self, expr: &'a str) -> Result<ExprResult, String> {
debug!("Parsing expression: {}", expr);
let lexer = lexer::Lexer::new(expr);
let res = self.parser.parse(lexer);
Expand All @@ -110,13 +107,11 @@ impl<'a> Parser<'a> {
///
/// Replaces `{identifier}` with values from SNMP results, handling both
/// scalar and vector values appropriately.
pub fn eval_str(
&self,
expr: &'a str,
) -> Result<ExprResult, String> {
pub fn eval_str(&self, expr: &'a str) -> Result<ExprResult, String> {
let re = Regex::new(r"\{[a-zA-Z_][a-zA-Z0-9_.]*\}").unwrap();
let mut suffix = expr;
let mut result: ExprResult = ExprResult::Empty;
trace!("[eval_str] suffix: {:?} - re: {:?}", &suffix, &re);
loop {
let found = re.find(suffix);
if let Some(m) = found {
Expand Down
101 changes: 63 additions & 38 deletions rust-plugins/src/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,20 @@ impl Command {
target: &str,
version: &str,
community: &str,
check_format: bool
check_format: bool,
) -> Vec<SnmpResult> {
let mut collect: Vec<SnmpResult> = Vec::new();

if check_format {
// In check-format mode, don't make SNMP requests and initialize with dummy values
// In check-format mode, don't make SNMP requests and initialize with dummy values.
// Get queries return a single value; Walk queries return multiple values.
for s in self.collect.snmp.iter() {
let mut items = HashMap::new();
items.insert(s.name.clone(), ExprResult::Vector(vec![0.0, 0.0]));
let dummy = match s.query {
QueryType::Get => ExprResult::Vector(vec![0.0]),
QueryType::Walk => ExprResult::Vector(vec![0.0, 0.0]),
};
items.insert(s.name.clone(), dummy);
if let Some(lab) = &s.labels {
for label_val in lab.values() {
let key = format!("{}.{}", s.name, label_val);
Expand Down Expand Up @@ -302,7 +307,7 @@ impl Command {
community: &str,
filter_in: &Vec<String>,
filter_out: &Vec<String>,
check_format: bool
check_format: bool,
) -> Result<CmdResult> {
let mut collect = self.execute_snmp_collect(target, version, community, check_format);

Expand All @@ -328,21 +333,25 @@ impl Command {
let value = &metric.value;
let parser = Parser::new(&collect, check_format);
let value = parser.eval(value).map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"value\": {}", metric.name, e)
message: format!("Metric \"{}\", field \"value\": {}", metric.name, e),
})?;
let min = if let Some(min_expr) = metric.min_expr.as_ref() {
parser.eval(&min_expr).map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"min_expr\": {}", metric.name, e)
})?
parser
.eval(&min_expr)
.map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"min_expr\": {}", metric.name, e),
})?
} else if let Some(min_value) = metric.min {
ExprResult::Number(min_value)
} else {
ExprResult::Empty
};
let max = if let Some(max_expr) = metric.max_expr.as_ref() {
parser.eval(&max_expr).map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"max_expr\": {}", metric.name, e)
})?
parser
.eval(&max_expr)
.map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"max_expr\": {}", metric.name, e),
})?
} else if let Some(max_value) = metric.max {
ExprResult::Number(max_value)
} else {
Expand All @@ -357,38 +366,44 @@ impl Command {
match &value {
ExprResult::Vector(v) => {
let prefix_str = match &metric.prefix {
Some(prefix) => parser.eval_str(prefix).map_err(|e| error::Error::InvalidJSON {
message: format!("Metric \"{}\", field \"prefix\": {}", metric.name, e)
})?,
Some(prefix) => {
parser
.eval_str(prefix)
.map_err(|e| error::Error::InvalidJSON {
message: format!(
"Metric \"{}\", field \"prefix\": {}",
metric.name, e
),
})?
}
None => ExprResult::Empty,
};
for (i, item) in v.iter().enumerate() {
let name = match &prefix_str {
ExprResult::StrVector(v) => {
format!("{:?}#{}", v[i], metric.name)
}
ExprResult::Str(s) => {
format!("{}#{}", s, metric.name)
}
// first, compose the instance name
let instance_name = match &prefix_str {
ExprResult::StrVector(v) => v[i].to_string(),
ExprResult::Str(s) => s.to_string(),
ExprResult::Empty => {
let res = format!("{}#{}", idx, metric.name);
let res = idx.to_string();
idx += 1;
res
}
_ => {
panic!("A label must be a string");
}
};
if !re_in.is_empty() {
if !re_in.iter().any(|re| re.is_match(&name)) {
continue;
}
// then apply filters exclusion and inclusion filters
if !re_out.is_empty() && re_out.iter().any(|re| re.is_match(&instance_name))
{
continue;
}
if !re_out.is_empty() {
if re_out.iter().any(|re| re.is_match(&name)) {
continue;
}
if (!re_in.is_empty()
&& !re_in.iter().any(|re| re.is_match(&instance_name)))
{
continue;
}
// and now concatenate to form the full perfdata
let name = format!("'{}#{}'", instance_name, metric.name);
let current_status =
compute_status(item, &metric.warning, &metric.critical)?;
status = worst(status, current_status);
Expand Down Expand Up @@ -417,7 +432,7 @@ impl Command {
ExprResult::Number(s) => {
let name = match &metric.prefix {
Some(prefix) => {
format!("{:?}#{}", prefix, metric.name)
format!("{}#{}", prefix, metric.name)
}
None => {
let res = format!("{}#{}", idx, metric.name);
Expand Down Expand Up @@ -472,9 +487,14 @@ impl Command {
let value = &metric.value;
let parser = Parser::new(&collect, check_format);
let max = if let Some(max_expr) = metric.max_expr.as_ref() {
let res = parser.eval(&max_expr).map_err(|e| error::Error::InvalidJSON {
message: format!("Aggregation \"{}\", field \"max_expr\": {}", metric.name, e)
})?;
let res = parser
.eval(&max_expr)
.map_err(|e| error::Error::InvalidJSON {
message: format!(
"Aggregation \"{}\", field \"max_expr\": {}",
metric.name, e
),
})?;
Some(match res {
ExprResult::Number(v) => v,
ExprResult::Vector(v) => {
Expand All @@ -489,9 +509,14 @@ impl Command {
None
};
let min = if let Some(min_expr) = metric.min_expr.as_ref() {
let res = parser.eval(&min_expr).map_err(|e| error::Error::InvalidJSON {
message: format!("Aggregation \"{}\", field \"min_expr\": {}", metric.name, e)
})?;
let res = parser
.eval(&min_expr)
.map_err(|e| error::Error::InvalidJSON {
message: format!(
"Aggregation \"{}\", field \"min_expr\": {}",
metric.name, e
),
})?;
Some(match res {
ExprResult::Number(v) => v,
ExprResult::Vector(v) => {
Expand All @@ -506,7 +531,7 @@ impl Command {
None
};
let value = parser.eval(value).map_err(|e| error::Error::InvalidJSON {
message: format!("Aggregation \"{}\", field \"value\": {}", metric.name, e)
message: format!("Aggregation \"{}\", field \"value\": {}", metric.name, e),
})?;
match &value {
ExprResult::Vector(v) => {
Expand Down
9 changes: 7 additions & 2 deletions rust-plugins/src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub struct Output {
}

fn default_ok() -> String {
"OK: Everything is ok".to_string()
"OK: Everything is ok ".to_string()
}
fn default_warning() -> String {
"WARNING: ".to_string()
Expand Down Expand Up @@ -208,7 +208,12 @@ impl<'a> OutputFormatter<'a> {
for m in self.metrics.iter() {
if let Some(status) = m.status {
if status.is_worse_than(self.status) {
v.push(std::format!("{} is {}{}", m.name, float_string(&m.value), m.uom));
v.push(std::format!(
"{} is {}{}",
m.name,
float_string(&m.value),
m.uom
));
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion rust-plugins/src/snmp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,9 @@ impl SnmpResult {
match value {
rasn_smi::v2::ApplicationSyntax::Address(ip_address) => todo!(),
rasn_smi::v2::ApplicationSyntax::Counter(counter) => todo!(),
rasn_smi::v2::ApplicationSyntax::Ticks(time_ticks) => todo!(),
rasn_smi::v2::ApplicationSyntax::Ticks(time_ticks) => {
typ = ValueType::Integer(time_ticks.0.into());
}
rasn_smi::v2::ApplicationSyntax::Arbitrary(opaque) => todo!(),
rasn_smi::v2::ApplicationSyntax::BigCounter(counter64) => {
typ = ValueType::Counter64(counter64.0);
Expand Down
Loading
Loading