zombienet_prom_metrics_parser/
lib.rs1use std::{collections::HashMap, num::ParseFloatError};
2
3use pest::Parser;
4use pest_derive::Parser;
5
6#[derive(thiserror::Error, Debug)]
8pub enum ParserError {
9 #[error("error parsing input")]
10 ParseError(Box<pest::error::Error<Rule>>),
11 #[error("root node should be valid: {0}")]
12 ParseRootNodeError(String),
13 #[error("can't cast metric value as f64: {0}")]
14 CastValueError(#[from] ParseFloatError),
15}
16
17const _GRAMMAR: &str = include_str!("grammar.pest");
20
21#[derive(Parser)]
22#[grammar = "grammar.pest"]
23pub struct MetricsParser;
24
25pub type MetricMap = HashMap<String, f64>;
26
27pub fn parse(input: &str) -> Result<MetricMap, ParserError> {
28 let mut metric_map: MetricMap = Default::default();
29 let mut pairs = MetricsParser::parse(Rule::statement, input)
30 .map_err(|e| ParserError::ParseError(Box::new(e)))?;
31
32 let root = pairs
33 .next()
34 .ok_or(ParserError::ParseRootNodeError(pairs.as_str().to_string()))?;
35 for token in root.into_inner() {
36 if token.as_rule() == Rule::block {
37 let inner = token.into_inner();
38 for value in inner {
39 match value.as_rule() {
40 Rule::genericomment | Rule::typexpr | Rule::helpexpr => {
41 continue;
43 },
44 Rule::promstmt => {
45 let mut key: &str = "";
46 let mut labels: Vec<(&str, &str)> = Vec::new();
47 let mut val: f64 = 0_f64;
48 for v in value.clone().into_inner() {
49 match &v.as_rule() {
50 Rule::key => {
51 key = v.as_span().as_str();
52 },
53 Rule::NaN | Rule::posInf | Rule::negInf => {
54 },
56 Rule::number => {
57 val = v.as_span().as_str().parse::<f64>()?;
58 },
59 Rule::labels => {
60 for p in v.into_inner() {
64 let mut inner = p.into_inner();
65 let key = inner.next().unwrap().as_span().as_str();
66 let value = inner
67 .next()
68 .unwrap()
69 .into_inner()
70 .next()
71 .unwrap()
72 .as_span()
73 .as_str();
74
75 labels.push((key, value));
76 }
77 },
78 _ => {
79 todo!("not implemented");
80 },
81 }
82 }
83
84 let key_with_out_prefix =
90 key.split('_').collect::<Vec<&str>>()[1..].join("_");
91 let (labels_without_chain, labels_with_chain) =
92 labels.iter().fold((vec![], vec![]), |mut acc, item| {
93 if item.0.eq("chain") {
94 acc.1.push(format!("{}=\"{}\"", item.0, item.1));
95 } else {
96 acc.0.push(format!("{}=\"{}\"", item.0, item.1));
97 acc.1.push(format!("{}=\"{}\"", item.0, item.1));
98 }
99 acc
100 });
101
102 let labels_with_chain_str = if labels_with_chain.is_empty() {
103 String::from("")
104 } else {
105 format!("{{{}}}", labels_with_chain.join(","))
106 };
107
108 let labels_without_chain_str = if labels_without_chain.is_empty() {
109 String::from("")
110 } else {
111 format!("{{{}}}", labels_without_chain.join(","))
112 };
113
114 metric_map.insert(format!("{key}{labels_without_chain_str}"), val);
115 metric_map.insert(
116 format!("{key_with_out_prefix}{labels_without_chain_str}"),
117 val,
118 );
119 metric_map.insert(format!("{key}{labels_with_chain_str}"), val);
120 metric_map
121 .insert(format!("{key_with_out_prefix}{labels_with_chain_str}"), val);
122 },
123 _ => {},
124 }
125 }
126 }
127 }
128
129 Ok(metric_map)
130}
131
132#[cfg(test)]
133mod tests {
134 use std::fs;
135
136 use super::*;
137
138 #[test]
139 fn parse_metrics_works() {
140 let metrics_raw = fs::read_to_string("./testing/metrics.txt").unwrap();
141 let metrics = parse(&metrics_raw).unwrap();
142
143 assert_eq!(
145 metrics
146 .get("polkadot_node_is_active_validator{chain=\"rococo_local_testnet\"}")
147 .unwrap(),
148 &1_f64
149 );
150 assert_eq!(
152 metrics.get("polkadot_node_is_active_validator").unwrap(),
153 &1_f64
154 );
155 assert_eq!(
157 metrics
158 .get("node_is_active_validator{chain=\"rococo_local_testnet\"}")
159 .unwrap(),
160 &1_f64
161 );
162 assert_eq!(metrics.get("node_is_active_validator").unwrap(), &1_f64);
164 }
165
166 #[test]
167 fn parse_invalid_metrics_str_should_fail() {
168 let metrics_raw = r"
169 # HELP polkadot_node_is_active_validator Tracks if the validator is in the active set. Updates at session boundary.
170 # TYPE polkadot_node_is_active_validator gauge
171 polkadot_node_is_active_validator{chain=} 1
172 ";
173
174 let metrics = parse(metrics_raw);
175 assert!(metrics.is_err());
176 assert!(matches!(metrics, Err(ParserError::ParseError(_))));
177 }
178}