From 9ff58afa425fbcbbd188c5cf3fc6061dc6799696 Mon Sep 17 00:00:00 2001 From: David Boucher Date: Tue, 29 Apr 2025 14:36:18 +0200 Subject: [PATCH] wip(generic-snmp) --- experimental/Cargo.toml | 13 +++-- experimental/src/compute/ast.rs | 26 +++++---- experimental/src/compute/lexer.rs | 19 +++++-- experimental/src/compute/mod.rs | 93 +++++++++++++++++++++++++++++++ experimental/src/generic/mod.rs | 28 +++++++--- experimental/src/main.rs | 17 +++--- 6 files changed, 157 insertions(+), 39 deletions(-) diff --git a/experimental/Cargo.toml b/experimental/Cargo.toml index f2f5eb08d..759e3cd49 100644 --- a/experimental/Cargo.toml +++ b/experimental/Cargo.toml @@ -6,12 +6,13 @@ version = "1.0.0" lalrpop = "0.22.1" [dependencies] -regex = "1.11.1" -rasn = "0.26.2" -rasn-snmp = "0.26.2" -rasn-smi = "0.26.2" -log = "0.4.27" +env_logger = "0.11.8" +lalrpop-util = { version = "0.22.1", features = ["lexer"] } lexopt = "0.3.1" +log = "0.4.27" +rasn = "0.26.2" +rasn-smi = "0.26.2" +rasn-snmp = "0.26.2" +regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" -lalrpop-util = { version = "0.22.1", features = ["lexer"] } diff --git a/experimental/src/compute/ast.rs b/experimental/src/compute/ast.rs index 9340c71e2..7b3045ce3 100644 --- a/experimental/src/compute/ast.rs +++ b/experimental/src/compute/ast.rs @@ -1,3 +1,4 @@ +use log::{debug, info, trace}; use snmp::SnmpResult; use std::str; @@ -232,20 +233,21 @@ impl<'input> Expr<'input> { Expr::Number(n) => ExprResult::Scalar(*n), Expr::Id(key) => { let k = str::from_utf8(key).unwrap(); - println!("Evaluation of Id '{}'", k); for result in collect { - let item = &result.items[k]; - match item { - ExprResult::Vector(n) => { - if n.len() == 1 { - println!("value {}", n[0]); - return ExprResult::Scalar(n[0]); - } else { - println!("value {:?}", n); - return ExprResult::Vector(n.clone()); + match result.items.get(k) { + Some(item) => match item { + ExprResult::Vector(n) => { + if n.len() == 1 { + info!("ID '{}' has value {}", k, n[0]); + return ExprResult::Scalar(n[0]); + } else { + info!("ID '{}' has value {:?}", k, n); + return ExprResult::Vector(n.clone()); + } } - } - _ => panic!("Should be a number"), + _ => panic!("Should be a number"), + }, + None => continue, } } ExprResult::Scalar(0.0) diff --git a/experimental/src/compute/lexer.rs b/experimental/src/compute/lexer.rs index b2d20574e..6e6876c8f 100644 --- a/experimental/src/compute/lexer.rs +++ b/experimental/src/compute/lexer.rs @@ -1,4 +1,4 @@ -use log::debug; +use log::{debug, error, trace}; use std::str; pub type Spanned = Result<(Loc, Tok, Loc), Error>; @@ -54,7 +54,7 @@ impl<'input> Lexer<'input> { end = chars.len(); } - debug!( + trace!( "Token Number from {} to {} with value '{}'", start, end, @@ -83,7 +83,7 @@ impl<'input> Lexer<'input> { if !done { end = chars.len(); } - debug!( + trace!( "Token Identifier from {} to {} with value '{}'", start, end, @@ -119,12 +119,12 @@ impl<'input> Iterator for Lexer<'input> { } b'(' => { self.offset = i + 1; - debug!("Token LParen at {}", i); + trace!("Token LParen at {}", i); return Some(Ok((i, Tok::LParen, i + 1))); } b')' => { self.offset = i + 1; - debug!("Token RParen at {}", i); + trace!("Token RParen at {}", i); return Some(Ok((i, Tok::RParen, i + 1))); } b'{' => { @@ -144,7 +144,7 @@ impl<'input> Iterator for Lexer<'input> { } _ => { // Unknown character - debug!("Unknown character at {}: '{}'", i, *c as char); + error!("Unknown character at {}: '{}'", i, *c as char); self.offset = i + 1; return Some(Err(LexicalError::NotPossible)); } @@ -158,8 +158,13 @@ impl<'input> Iterator for Lexer<'input> { mod Test { use super::*; + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + #[test] fn test_lexer_num_id_num() { + init(); let input = "123 abc 456"; let mut lexer = Lexer::new(input); assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(123_f64), 3)))); @@ -170,6 +175,7 @@ mod Test { #[test] fn test_lexer_id_num_id() { + init(); let input = "abc 123 def"; let mut lexer = Lexer::new(input); assert_eq!(lexer.next(), Some(Ok((0, Tok::Id(b"abc"), 3)))); @@ -179,6 +185,7 @@ mod Test { #[test] fn test_lexer_num_op() { + init(); let input = "1+2*3"; let mut lexer = Lexer::new(input); assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(1_f64), 1)))); diff --git a/experimental/src/compute/mod.rs b/experimental/src/compute/mod.rs index 761790589..c8814a03f 100644 --- a/experimental/src/compute/mod.rs +++ b/experimental/src/compute/mod.rs @@ -4,6 +4,8 @@ pub mod lexer; use self::ast::ExprResult; use self::lexer::{LexicalError, Tok}; use lalrpop_util::{lalrpop_mod, ParseError}; +use log::{trace, debug}; +use regex::Regex; use serde::Deserialize; use snmp::SnmpResult; @@ -44,6 +46,7 @@ impl<'a> Parser<'a> { &self, expr: &'a str, ) -> Result, LexicalError>> { + debug!("Parsing expression: {}", expr); let lexer = lexer::Lexer::new(expr); let res = self.parser.parse(lexer); match res { @@ -54,14 +57,68 @@ impl<'a> Parser<'a> { Err(e) => Err(e), } } + + pub fn eval_str( + &self, + expr: &'a str, + ) -> Result, LexicalError>> { + let re = Regex::new(r"\{[a-zA-Z_][a-zA-Z0-9_.]*\}").unwrap(); + let mut suffix = expr; + let mut result: ExprResult = ExprResult::StrVector(vec![]); + loop { + let found = re.find(suffix); + if let Some(m) = found { + let start = m.start(); + let end = m.end(); + debug!( + "Identifier '{}' found in expr '{}'", + &expr[start + 1..end - 1], + expr + ); + let prefix = &expr[0..start]; + suffix = &expr[end..]; + let mut result = vec![]; + for snmp_result in self.collect { + if let Some(v) = snmp_result.items.get(&expr[start + 1..end - 1]) { + result = join_str_expr(prefix, v); + break; + } + } + trace!("Result string {:?}", result); + } else { + break; + } + } + Ok(result) + } +} + +fn join_str_expr(prefix: &str, v: &ExprResult) -> Vec { + match v { + ExprResult::StrVector(v) => { + let mut result = vec![]; + for item in v { + result.push(format!("{}{}", prefix, item)); + } + result + } + _ => panic!("Expected a string vector"), + } } mod Test { use super::*; + use log::info; use std::collections::HashMap; + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + #[test] fn term() { + init(); + info!("test term"); let lexer = lexer::Lexer::new("123"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -80,6 +137,7 @@ mod Test { #[test] fn sum() { + init(); let lexer = lexer::Lexer::new("1 + 2"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -129,6 +187,7 @@ mod Test { #[test] fn product() { + init(); let lexer = lexer::Lexer::new("2 * 3"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -180,6 +239,7 @@ mod Test { #[test] fn sum_product() { + init(); let lexer = lexer::Lexer::new("1 + (3 + 2 * 3) / 3"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -193,6 +253,7 @@ mod Test { #[test] fn identifier() { + init(); let lexer = lexer::Lexer::new("{abc} + 1"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -212,6 +273,7 @@ mod Test { #[test] fn two_identifiers() { + init(); let lexer = lexer::Lexer::new("100 * (1 - {free}/{total})"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -229,6 +291,7 @@ mod Test { #[test] fn function() { + init(); let lexer = lexer::Lexer::new("Average({abc})"); let res = grammar::ExprParser::new().parse(lexer); assert!(res.is_ok()); @@ -243,4 +306,34 @@ mod Test { _ => panic!("Expected a scalar value"), } } + + #[test] + fn identifier_str() { + init(); + let items = HashMap::from([ + ( + "free".to_string(), + ExprResult::StrVector(vec!["free-one".to_string(), "free-two".to_string()]), + ), + ( + "total".to_string(), + ExprResult::StrVector(vec!["total-one".to_string(), "total-two".to_string()]), + ), + ]); + let collect = vec![SnmpResult::new(items)]; + let parser = Parser::new(&collect); + let res = parser.eval_str("{free}foo{total}bar"); + assert!(res.is_ok()); + let res = res.unwrap(); + match res { + ExprResult::StrVector(v) => { + assert_eq!(v.len(), 4); + assert_eq!(v[0], "free-onefoo"); + assert_eq!(v[1], "free-twofoo"); + assert_eq!(v[2], "total-onebar"); + assert_eq!(v[3], "total-twobar"); + } + _ => panic!("Expected a string vector"), + } + } } diff --git a/experimental/src/generic/mod.rs b/experimental/src/generic/mod.rs index 7d651cbc3..613054f75 100644 --- a/experimental/src/generic/mod.rs +++ b/experimental/src/generic/mod.rs @@ -2,9 +2,12 @@ extern crate serde; extern crate serde_json; use compute::{ast::ExprResult, Compute, Parser}; +use log::{debug, trace}; use serde::Deserialize; use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels}; -use std::collections::HashMap; +use std::{collections::HashMap, ops::IndexMut}; + +use crate::snmp::SnmpResult; #[derive(Debug)] struct Perfdata { @@ -142,22 +145,23 @@ impl Command { let r = snmp_bulk_get(target, version, community, 1, 1, &to_get, &get_name); collect.push(r); } - println!("{:#?}", collect); let mut idx: u32 = 0; let mut metrics = vec![]; + let mut my_res = SnmpResult::new(HashMap::new()); for metric in self.compute.metrics.iter() { let value = &metric.value; let min = metric.min; let max = metric.max; let parser = Parser::new(&collect); let value = parser.eval(value).unwrap(); - println!("ExprResult: {:?}", value); - match value { + + match &value { ExprResult::Vector(v) => { for item in v { let name = match &metric.prefix { Some(prefix) => { + let name_resolved = parser.eval_str(prefix).unwrap(); format!("{:?}#{}", prefix, metric.name) } None => { @@ -168,10 +172,11 @@ impl Command { }; let m = Perfdata { name, - value: item, + value: *item, min, max, }; + trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); } } @@ -188,16 +193,20 @@ impl Command { }; let m = Perfdata { name, - value: s, + value: *s, min, max, }; + trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); } _ => panic!("Aggregation must be applied to a vector"), } - println!("perfdata: {:?}", metrics); + let key = format!("metrics.{}", metric.name); + debug!("New ID '{}' with content: {:?}", key, value); + my_res.items.insert(key, value); } + collect.push(my_res); if let Some(aggregations) = self.compute.aggregations.as_ref() { for metric in aggregations { let value = &metric.value; @@ -252,6 +261,7 @@ impl Command { min, max, }; + trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); } } @@ -263,14 +273,16 @@ impl Command { min, max, }; + trace!("New metric '{}' with value {:?}", m.name, m.value); metrics.push(m); } _ => panic!("Aggregation must be applied to a vector"), } - println!("perfdata: {:?}", metrics); } } + trace!("collect: {:#?}", collect); + println!("metrics: {:#?}", metrics); CmdResult { status: Status::Unknown, output: "No result".to_string(), diff --git a/experimental/src/main.rs b/experimental/src/main.rs index c5494af1b..208ecb424 100644 --- a/experimental/src/main.rs +++ b/experimental/src/main.rs @@ -1,3 +1,4 @@ +extern crate env_logger; extern crate lalrpop_util; extern crate lexopt; extern crate log; @@ -14,8 +15,8 @@ mod snmp; use generic::Command; use lalrpop_util::lalrpop_mod; +use log::{debug, info, trace}; use serde_json::Result; -//use snmp::snmp_get; use std::fs; lalrpop_mod!(grammar); @@ -67,6 +68,8 @@ fn json_to_command(file_name: &str) -> Result { } fn main() { + env_logger::init(); + use lexopt::prelude::*; let mut parser = lexopt::Parser::from_env(); let mut hostname = "localhost".to_string(); @@ -78,30 +81,30 @@ fn main() { let arg = parser.next(); match arg { Ok(arg) => { - println!("{:?} ok", arg); match arg { Some(arg) => match arg { Short('H') | Long("hostname") => { hostname = parser.value().unwrap().into_string().unwrap(); + trace!("hostname: {:}", hostname); } Short('p') | Long("port") => { port = parser.value().unwrap().parse::().unwrap(); - println!("port: {}", port); + trace!("port: {}", port); } Short('j') | Long("json") => { json = Some(parser.value().unwrap().into_string().unwrap()); - println!("json: {:?}", json); + trace!("json: {:?}", json); } Short('v') | Long("snmp-version") => { snmp_version = parser.value().unwrap().into_string().unwrap(); - println!("snmp_version: {}", snmp_version); + trace!("snmp_version: {}", snmp_version); } Short('c') | Long("snmp-community") => { snmp_community = parser.value().unwrap().into_string().unwrap(); - println!("snmp_community: {}", snmp_community); + trace!("snmp_community: {}", snmp_community); } _ => { - println!("other"); + debug!("other unknown argument"); } }, None => {