Skip to content

Commit 3933f4a

Browse files
omercierscresto31
andauthored
enh(rust-plugins): add collections+tests+enhancements (#6254)
Co-authored-by: Sylvain Cresto <scresto@centreon.com> Refs: CTOR-2299
1 parent 75a926f commit 3933f4a

25 files changed

Lines changed: 862 additions & 151 deletions

rust-plugins/examples/tcpcon.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"collect": {
3+
"snmp": [
4+
{
5+
"name": "tcpConnectionState",
6+
"oid": "1.3.6.1.2.1.6.19.1.7",
7+
"query": "Walk"
8+
},
9+
{
10+
"name": "tcpListenerProcess",
11+
"oid": "1.3.6.1.2.1.6.20.1.4",
12+
"query": "Walk"
13+
},
14+
{
15+
"name": "tcpConnState",
16+
"oid": "1.3.6.1.2.1.6.13.1.1",
17+
"query": "Walk"
18+
}
19+
]
20+
},
21+
"compute": {
22+
"metrics": [
23+
{
24+
"name": "tcp.connections",
25+
"value": "Average({tcpConnectionState})",
26+
"uom": "",
27+
"min": 0,
28+
"threshold-suffix": "tcp-connections"
29+
}
30+
]
31+
},
32+
"output": {
33+
"ok": "TCP connections are OK",
34+
"detail_ok": false,
35+
"warning": "TCP connections WARNING: ",
36+
"detail_warning": true,
37+
"critical": "TCP connections CRITICAL: ",
38+
"detail_critical": true,
39+
"instance_separator": " - ",
40+
"metric_separator": ", "
41+
}
42+
}

rust-plugins/examples/udpcon.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"collect": {
3+
"snmp": [
4+
{
5+
"name": "udpLocalAddress",
6+
"oid": "1.3.6.1.2.1.7.7.1.8",
7+
"query": "Walk"
8+
}
9+
]
10+
},
11+
"compute": {
12+
"metrics": [
13+
{
14+
"name": "udp.connections.ipv4",
15+
"value": "({udpLocalAddress})",
16+
"uom": "",
17+
"min": 0
18+
}
19+
]
20+
},
21+
"output": {
22+
"ok": "UDP connections are OK",
23+
"detail_ok": false,
24+
"warning": "UDP connections WARNING: ",
25+
"detail_warning": true,
26+
"critical": "UDP connections CRITICAL: ",
27+
"detail_critical": true,
28+
"instance_separator": " - ",
29+
"metric_separator": ", "
30+
}
31+
}

rust-plugins/src/compute/ast.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Abstract syntax tree and expression evaluation.
22
33
use crate::snmp::SnmpResult;
4-
use log::{info, warn};
4+
use log::{info, trace, warn};
55
use std::str;
66

77
/// An expression node in the AST.
@@ -280,6 +280,7 @@ impl ExprResult {
280280
/// the numbers to strings before concatenation, padding to match lengths
281281
/// where necessary.
282282
pub fn join(&mut self, other: &ExprResult) {
283+
trace!("[join] self: {:?} - other: {:?}", &self, &other);
283284
match self {
284285
ExprResult::Empty => match other {
285286
ExprResult::StrVector(vv) => {
@@ -289,7 +290,9 @@ impl ExprResult {
289290
*self = ExprResult::Str(s.clone());
290291
}
291292
ExprResult::Vector(vv) => {
292-
*self = ExprResult::StrVector(vv.iter().map(|n| crate::output::float_string(n)).collect());
293+
*self = ExprResult::StrVector(
294+
vv.iter().map(|n| crate::output::float_string(n)).collect(),
295+
);
293296
}
294297
_ => panic!("Unable to join objects others than strings"),
295298
},
@@ -344,7 +347,8 @@ impl ExprResult {
344347
*s = format!("{}{}", s, ss);
345348
}
346349
ExprResult::Number(n) => {
347-
*s = format!("{}{:.2}", s, crate::output::float_string(n));
350+
trace!("[join] n: {:?}", &n);
351+
*s = format!("{}{}", s, crate::output::float_string(n));
348352
}
349353
_ => panic!("Unable to join objects others than strings"),
350354
},
@@ -369,8 +373,10 @@ impl<'input> Expr<'input> {
369373
Err(format!("Undefined macro in expression: {{{}}}", k))
370374
}
371375
Expr::Number(_) => Ok(()),
372-
Expr::OpPlus(left, right) | Expr::OpMinus(left, right) |
373-
Expr::OpStar(left, right) | Expr::OpSlash(left, right) => {
376+
Expr::OpPlus(left, right)
377+
| Expr::OpMinus(left, right)
378+
| Expr::OpStar(left, right)
379+
| Expr::OpSlash(left, right) => {
374380
left.validate_macros(collect)?;
375381
right.validate_macros(collect)?;
376382
Ok(())

rust-plugins/src/compute/mod.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use self::ast::ExprResult;
1212
use self::lexer::{LexicalError, Tok};
1313
use crate::snmp::SnmpResult;
1414
use lalrpop_util::{ParseError, lalrpop_mod};
15-
use log::debug;
15+
use log::{debug, trace};
1616
use regex::Regex;
1717
use serde::Deserialize;
1818

@@ -71,7 +71,7 @@ pub struct Compute {
7171
pub struct Parser<'a> {
7272
collect: &'a Vec<SnmpResult>,
7373
parser: grammar::ExprParser,
74-
check_format: bool
74+
check_format: bool,
7575
}
7676

7777
impl<'a> Parser<'a> {
@@ -80,18 +80,15 @@ impl<'a> Parser<'a> {
8080
Parser {
8181
collect,
8282
parser: grammar::ExprParser::new(),
83-
check_format
83+
check_format,
8484
}
8585
}
8686

8787
/// Evaluates a mathematical expression and returns the result.
8888
///
8989
/// Supports arithmetic operations, identifiers in braces (e.g., `{metric_name}`),
9090
/// and functions like `Average()`, `Min()`, `Max()`.
91-
pub fn eval(
92-
&self,
93-
expr: &'a str,
94-
) -> Result<ExprResult, String> {
91+
pub fn eval(&self, expr: &'a str) -> Result<ExprResult, String> {
9592
debug!("Parsing expression: {}", expr);
9693
let lexer = lexer::Lexer::new(expr);
9794
let res = self.parser.parse(lexer);
@@ -110,13 +107,11 @@ impl<'a> Parser<'a> {
110107
///
111108
/// Replaces `{identifier}` with values from SNMP results, handling both
112109
/// scalar and vector values appropriately.
113-
pub fn eval_str(
114-
&self,
115-
expr: &'a str,
116-
) -> Result<ExprResult, String> {
110+
pub fn eval_str(&self, expr: &'a str) -> Result<ExprResult, String> {
117111
let re = Regex::new(r"\{[a-zA-Z_][a-zA-Z0-9_.]*\}").unwrap();
118112
let mut suffix = expr;
119113
let mut result: ExprResult = ExprResult::Empty;
114+
trace!("[eval_str] suffix: {:?} - re: {:?}", &suffix, &re);
120115
loop {
121116
let found = re.find(suffix);
122117
if let Some(m) = found {

rust-plugins/src/generic/mod.rs

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,20 @@ impl Command {
234234
target: &str,
235235
version: &str,
236236
community: &str,
237-
check_format: bool
237+
check_format: bool,
238238
) -> Vec<SnmpResult> {
239239
let mut collect: Vec<SnmpResult> = Vec::new();
240240

241241
if check_format {
242-
// In check-format mode, don't make SNMP requests and initialize with dummy values
242+
// In check-format mode, don't make SNMP requests and initialize with dummy values.
243+
// Get queries return a single value; Walk queries return multiple values.
243244
for s in self.collect.snmp.iter() {
244245
let mut items = HashMap::new();
245-
items.insert(s.name.clone(), ExprResult::Vector(vec![0.0, 0.0]));
246+
let dummy = match s.query {
247+
QueryType::Get => ExprResult::Vector(vec![0.0]),
248+
QueryType::Walk => ExprResult::Vector(vec![0.0, 0.0]),
249+
};
250+
items.insert(s.name.clone(), dummy);
246251
if let Some(lab) = &s.labels {
247252
for label_val in lab.values() {
248253
let key = format!("{}.{}", s.name, label_val);
@@ -302,7 +307,7 @@ impl Command {
302307
community: &str,
303308
filter_in: &Vec<String>,
304309
filter_out: &Vec<String>,
305-
check_format: bool
310+
check_format: bool,
306311
) -> Result<CmdResult> {
307312
let mut collect = self.execute_snmp_collect(target, version, community, check_format);
308313

@@ -328,21 +333,25 @@ impl Command {
328333
let value = &metric.value;
329334
let parser = Parser::new(&collect, check_format);
330335
let value = parser.eval(value).map_err(|e| error::Error::InvalidJSON {
331-
message: format!("Metric \"{}\", field \"value\": {}", metric.name, e)
336+
message: format!("Metric \"{}\", field \"value\": {}", metric.name, e),
332337
})?;
333338
let min = if let Some(min_expr) = metric.min_expr.as_ref() {
334-
parser.eval(&min_expr).map_err(|e| error::Error::InvalidJSON {
335-
message: format!("Metric \"{}\", field \"min_expr\": {}", metric.name, e)
336-
})?
339+
parser
340+
.eval(&min_expr)
341+
.map_err(|e| error::Error::InvalidJSON {
342+
message: format!("Metric \"{}\", field \"min_expr\": {}", metric.name, e),
343+
})?
337344
} else if let Some(min_value) = metric.min {
338345
ExprResult::Number(min_value)
339346
} else {
340347
ExprResult::Empty
341348
};
342349
let max = if let Some(max_expr) = metric.max_expr.as_ref() {
343-
parser.eval(&max_expr).map_err(|e| error::Error::InvalidJSON {
344-
message: format!("Metric \"{}\", field \"max_expr\": {}", metric.name, e)
345-
})?
350+
parser
351+
.eval(&max_expr)
352+
.map_err(|e| error::Error::InvalidJSON {
353+
message: format!("Metric \"{}\", field \"max_expr\": {}", metric.name, e),
354+
})?
346355
} else if let Some(max_value) = metric.max {
347356
ExprResult::Number(max_value)
348357
} else {
@@ -357,38 +366,44 @@ impl Command {
357366
match &value {
358367
ExprResult::Vector(v) => {
359368
let prefix_str = match &metric.prefix {
360-
Some(prefix) => parser.eval_str(prefix).map_err(|e| error::Error::InvalidJSON {
361-
message: format!("Metric \"{}\", field \"prefix\": {}", metric.name, e)
362-
})?,
369+
Some(prefix) => {
370+
parser
371+
.eval_str(prefix)
372+
.map_err(|e| error::Error::InvalidJSON {
373+
message: format!(
374+
"Metric \"{}\", field \"prefix\": {}",
375+
metric.name, e
376+
),
377+
})?
378+
}
363379
None => ExprResult::Empty,
364380
};
365381
for (i, item) in v.iter().enumerate() {
366-
let name = match &prefix_str {
367-
ExprResult::StrVector(v) => {
368-
format!("{:?}#{}", v[i], metric.name)
369-
}
370-
ExprResult::Str(s) => {
371-
format!("{}#{}", s, metric.name)
372-
}
382+
// first, compose the instance name
383+
let instance_name = match &prefix_str {
384+
ExprResult::StrVector(v) => v[i].to_string(),
385+
ExprResult::Str(s) => s.to_string(),
373386
ExprResult::Empty => {
374-
let res = format!("{}#{}", idx, metric.name);
387+
let res = idx.to_string();
375388
idx += 1;
376389
res
377390
}
378391
_ => {
379392
panic!("A label must be a string");
380393
}
381394
};
382-
if !re_in.is_empty() {
383-
if !re_in.iter().any(|re| re.is_match(&name)) {
384-
continue;
385-
}
395+
// then apply filters exclusion and inclusion filters
396+
if !re_out.is_empty() && re_out.iter().any(|re| re.is_match(&instance_name))
397+
{
398+
continue;
386399
}
387-
if !re_out.is_empty() {
388-
if re_out.iter().any(|re| re.is_match(&name)) {
389-
continue;
390-
}
400+
if (!re_in.is_empty()
401+
&& !re_in.iter().any(|re| re.is_match(&instance_name)))
402+
{
403+
continue;
391404
}
405+
// and now concatenate to form the full perfdata
406+
let name = format!("'{}#{}'", instance_name, metric.name);
392407
let current_status =
393408
compute_status(item, &metric.warning, &metric.critical)?;
394409
status = worst(status, current_status);
@@ -417,7 +432,7 @@ impl Command {
417432
ExprResult::Number(s) => {
418433
let name = match &metric.prefix {
419434
Some(prefix) => {
420-
format!("{:?}#{}", prefix, metric.name)
435+
format!("{}#{}", prefix, metric.name)
421436
}
422437
None => {
423438
let res = format!("{}#{}", idx, metric.name);
@@ -472,9 +487,14 @@ impl Command {
472487
let value = &metric.value;
473488
let parser = Parser::new(&collect, check_format);
474489
let max = if let Some(max_expr) = metric.max_expr.as_ref() {
475-
let res = parser.eval(&max_expr).map_err(|e| error::Error::InvalidJSON {
476-
message: format!("Aggregation \"{}\", field \"max_expr\": {}", metric.name, e)
477-
})?;
490+
let res = parser
491+
.eval(&max_expr)
492+
.map_err(|e| error::Error::InvalidJSON {
493+
message: format!(
494+
"Aggregation \"{}\", field \"max_expr\": {}",
495+
metric.name, e
496+
),
497+
})?;
478498
Some(match res {
479499
ExprResult::Number(v) => v,
480500
ExprResult::Vector(v) => {
@@ -489,9 +509,14 @@ impl Command {
489509
None
490510
};
491511
let min = if let Some(min_expr) = metric.min_expr.as_ref() {
492-
let res = parser.eval(&min_expr).map_err(|e| error::Error::InvalidJSON {
493-
message: format!("Aggregation \"{}\", field \"min_expr\": {}", metric.name, e)
494-
})?;
512+
let res = parser
513+
.eval(&min_expr)
514+
.map_err(|e| error::Error::InvalidJSON {
515+
message: format!(
516+
"Aggregation \"{}\", field \"min_expr\": {}",
517+
metric.name, e
518+
),
519+
})?;
495520
Some(match res {
496521
ExprResult::Number(v) => v,
497522
ExprResult::Vector(v) => {
@@ -506,7 +531,7 @@ impl Command {
506531
None
507532
};
508533
let value = parser.eval(value).map_err(|e| error::Error::InvalidJSON {
509-
message: format!("Aggregation \"{}\", field \"value\": {}", metric.name, e)
534+
message: format!("Aggregation \"{}\", field \"value\": {}", metric.name, e),
510535
})?;
511536
match &value {
512537
ExprResult::Vector(v) => {

rust-plugins/src/output/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct Output {
4545
}
4646

4747
fn default_ok() -> String {
48-
"OK: Everything is ok".to_string()
48+
"OK: Everything is ok ".to_string()
4949
}
5050
fn default_warning() -> String {
5151
"WARNING: ".to_string()
@@ -208,7 +208,12 @@ impl<'a> OutputFormatter<'a> {
208208
for m in self.metrics.iter() {
209209
if let Some(status) = m.status {
210210
if status.is_worse_than(self.status) {
211-
v.push(std::format!("{} is {}{}", m.name, float_string(&m.value), m.uom));
211+
v.push(std::format!(
212+
"{} is {}{}",
213+
m.name,
214+
float_string(&m.value),
215+
m.uom
216+
));
212217
}
213218
}
214219
}

rust-plugins/src/snmp/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,9 @@ impl SnmpResult {
620620
match value {
621621
rasn_smi::v2::ApplicationSyntax::Address(ip_address) => todo!(),
622622
rasn_smi::v2::ApplicationSyntax::Counter(counter) => todo!(),
623-
rasn_smi::v2::ApplicationSyntax::Ticks(time_ticks) => todo!(),
623+
rasn_smi::v2::ApplicationSyntax::Ticks(time_ticks) => {
624+
typ = ValueType::Integer(time_ticks.0.into());
625+
}
624626
rasn_smi::v2::ApplicationSyntax::Arbitrary(opaque) => todo!(),
625627
rasn_smi::v2::ApplicationSyntax::BigCounter(counter64) => {
626628
typ = ValueType::Counter64(counter64.0);

0 commit comments

Comments
 (0)