1use crate::common::token_parser;
2#[cfg(test)]
3use assert_approx_eq::assert_approx_eq;
4use nom::branch::alt;
5use nom::bytes::complete::{is_not, tag};
6use nom::character::complete::{char, line_ending, none_of, space0, space1};
7use nom::combinator::{map, map_opt, map_res, opt, value};
8#[cfg(test)]
9use nom::error::ErrorKind;
10use nom::multi::{fold_many0, separated_list};
11use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
12#[cfg(test)]
13use nom::Err::Error;
14use nom::IResult;
15use std::collections::HashMap;
16
17#[derive(Debug, PartialEq)]
18pub struct SampleEntry<'a> {
19 pub name: &'a str,
20 pub labels: HashMap<&'a str, String>,
21 pub value: f64,
22 pub timestamp_ms: Option<i64>,
23}
24
25fn timestamp_parser(i: &str) -> IResult<&str, i64> {
26 map_opt(is_not("\n "), |x: &str| x.parse::<i64>().ok())(i)
27}
28
29fn value_parser(i: &str) -> IResult<&str, f64> {
32 alt((
33 value(std::f64::NAN, tag("NaN")),
34 value(std::f64::INFINITY, tag("+Inf")),
35 value(std::f64::NEG_INFINITY, tag("-Inf")),
36 map_res(is_not("\n "), |x: &str| x.parse::<f64>()),
37 ))(i)
38}
39
40fn tag_value_parser(i: &str) -> IResult<&str, String> {
41 delimited(
42 char('\"'),
43 fold_many0(
44 alt((
45 preceded(
46 char('\\'),
47 alt((
48 value('\n', char('n')),
49 value('\"', char('\"')),
50 value('\\', char('\\')),
51 )),
52 ),
53 none_of("\n\"\\"),
54 )),
55 String::new(),
56 |mut acc, item| {
57 acc.push(item);
58 acc
59 },
60 ),
61 char('\"'),
62 )(i)
63}
64
65fn labels_parser(i: &str) -> IResult<&str, HashMap<&str, String>> {
66 let list_parser = terminated(
67 separated_list(
68 char(','),
69 separated_pair(token_parser, char('='), tag_value_parser),
70 ),
71 opt(char(',')),
72 );
73 let list_parser = map(
74 list_parser,
75 |l: Vec<(&str, String)>| -> HashMap<&str, String> { l.into_iter().collect() },
76 );
77
78 map(
79 opt(delimited(
80 preceded(space0, char('{')),
81 list_parser,
82 char('}'),
83 )),
84 |v| v.unwrap_or(HashMap::new()),
85 )(i)
86}
87
88pub fn parse_sample(i: &str) -> IResult<&str, SampleEntry> {
95 let (input, (name, labels, value, timestamp_ms)) = terminated(
96 tuple((
97 token_parser,
98 labels_parser,
99 preceded(space1, value_parser),
100 opt(preceded(space1, timestamp_parser)),
101 )),
102 line_ending,
103 )(i)?;
104
105 Ok((
106 input,
107 SampleEntry {
108 name,
109 labels,
110 value,
111 timestamp_ms,
112 },
113 ))
114}
115
116#[test]
117fn test_timestamp_parser() {
118 assert_eq!(timestamp_parser(""), Err(Error(("", ErrorKind::IsNot))));
119 assert_eq!(
120 timestamp_parser("foobar"),
121 Err(Error(("foobar", ErrorKind::MapOpt)))
122 );
123 assert_eq!(timestamp_parser("1234"), Ok(("", 1234)));
124 assert_eq!(timestamp_parser("1234 foo"), Ok((" foo", 1234)));
125 assert_eq!(timestamp_parser("-1234 foo"), Ok((" foo", -1234)));
126}
127
128#[test]
129fn test_tag_value_parser() {
130 assert_eq!(tag_value_parser("\"\""), Ok(("", "".to_string())));
132 assert_eq!(tag_value_parser("\"abc\""), Ok(("", "abc".to_string())));
134 assert_eq!(tag_value_parser("\"abc\"aa"), Ok(("aa", "abc".to_string())));
136 assert_eq!(tag_value_parser("\"\\\"\""), Ok(("", "\"".to_string())));
138 assert_eq!(tag_value_parser("\"\\n\""), Ok(("", "\n".to_string())));
140 assert_eq!(tag_value_parser("\"\\\\\""), Ok(("", "\\".to_string())));
142 assert_eq!(
144 tag_value_parser("\"\n\""),
145 Err(Error(("\n\"", ErrorKind::Char)))
146 );
147 assert_eq!(
149 tag_value_parser("\"C:\\\\DIR\\\\FILE.TXT\""),
150 Ok(("", "C:\\DIR\\FILE.TXT".to_string()))
151 );
152 assert_eq!(
154 tag_value_parser("\"Cannot find file:\\n\\\"FILE.TXT\\\"\""),
155 Ok(("", "Cannot find file:\n\"FILE.TXT\"".to_string()))
156 );
157}
158
159#[cfg(test)]
160fn vec_to_hashmap<'a>(vec: Vec<(&'a str, &'a str)>) -> HashMap<&'a str, String> {
161 vec.into_iter().map(|(a, b)| (a, b.to_string())).collect()
162}
163
164#[test]
165fn test_labels_parser() {
166 let assert_labels = |s, vec: Vec<(&str, &str)>| {
167 assert_eq!(labels_parser(s), Ok(("", vec_to_hashmap(vec))));
168 };
169 assert_eq!(labels_parser(" "), Ok((" ", HashMap::new())));
171
172 assert_eq!(labels_parser(" {}"), Ok(("", HashMap::new())));
174 assert_eq!(labels_parser("{}"), Ok(("", HashMap::new())));
176 assert_eq!(labels_parser(""), Ok(("", HashMap::new())));
178 assert_eq!(labels_parser("d{}"), Ok(("d{}", HashMap::new())));
180 assert_eq!(labels_parser("{he=e}"), Ok(("{he=e}", HashMap::new())));
182 assert_labels("{hello=\"how are you?\"}", vec![("hello", "how are you?")]);
184 assert_labels("{a=\"b\",c=\"d\"}", vec![("a", "b"), ("c", "d")]);
186 assert_labels("{a=\"b\",c=\"d\",}", vec![("a", "b"), ("c", "d")]);
188}
189
190#[test]
191fn test_value_parser() {
192 assert_eq!(value_parser("1027"), Ok(("", 1027f64)));
193 assert_eq!(value_parser("1027 ee"), Ok((" ee", 1027f64)));
194 assert_eq!(value_parser("1027\nee"), Ok(("\nee", 1027f64)));
195 assert_eq!(value_parser("ee"), Err(Error(("ee", ErrorKind::MapRes))));
196 assert_eq!(value_parser("+Inf"), Ok(("", std::f64::INFINITY)));
197 assert_eq!(value_parser("-Inf"), Ok(("", std::f64::NEG_INFINITY)));
198 assert!(value_parser("NaN").unwrap().1.is_nan());
199 assert_approx_eq!(value_parser("2.00").unwrap().1, 2f64);
200 assert_approx_eq!(value_parser("1e-3").unwrap().1, 0.001);
201 assert_approx_eq!(value_parser("123.3412312312").unwrap().1, 123.3412312312);
202 assert_approx_eq!(value_parser("1.458255915e9").unwrap().1, 1.458255915e9);
203}
204
205#[cfg(test)]
206fn assert_sample(
207 res: SampleEntry,
208 name: &str,
209 labels: Vec<(&str, &str)>,
210 value: f64,
211 timestamp: Option<i64>,
212) {
213 assert_eq!(res.name, name, "sample name is different {:?}", res);
214 assert_eq!(
215 res.labels,
216 vec_to_hashmap(labels),
217 "labels are different {:?}",
218 res
219 );
220
221 assert!(
223 res.value.is_sign_positive() == value.is_sign_positive()
224 && res.value.is_infinite() == value.is_infinite()
225 && res.value.is_nan() == res.value.is_nan(),
226 "float non similar actual:{} expected:{}",
227 res.value,
228 value
229 );
230 if value.is_finite() {
231 assert_approx_eq!(res.value, value);
232 }
233 assert_eq!(res.timestamp_ms, timestamp, "Timestamps differ {:?}", res);
234}
235
236#[cfg(test)]
237fn assert_sample_parser(
238 s: &str,
239 left: &str,
240 name: &str,
241 labels: Vec<(&str, &str)>,
242 value: f64,
243 timestamp: Option<i64>,
244) {
245 let res = parse_sample(s);
246 assert!(res.is_ok(), "input: {} res: {:?}", s, res);
247 let res = res.unwrap();
248 assert_eq!(res.0, left, "Not the same left string");
249 assert_sample(res.1, name, labels, value, timestamp);
250}
251
252#[test]
253fn test_parse_sample_parser() {
254 assert_sample_parser(
256 "http_requests_total{method=\"post\",code=\"200\"} 1027 1395066363000\n",
257 "",
258 "http_requests_total",
259 vec![("method", "post"), ("code", "200")],
260 1027f64,
261 Option::Some(1395066363000i64),
262 );
263 assert_sample_parser(
264 "http_requests_total{method=\"post\",code=\"400\"} 3 1395066363000\n",
265 "",
266 "http_requests_total",
267 vec![("method", "post"), ("code", "400")],
268 3f64,
269 Option::Some(1395066363000i64),
270 );
271 assert_sample_parser("msdos_file_access_time_seconds{path=\"C:\\\\DIR\\\\FILE.TXT\",error=\"Cannot find file:\\n\\\"FILE.TXT\\\"\"} 1.458255915e9\n", "",
272 "msdos_file_access_time_seconds", vec![("path", "C:\\DIR\\FILE.TXT"), ("error", "Cannot find file:\n\"FILE.TXT\"")], 1.458255915e9, None);
273 assert_sample_parser(
274 "metric_without_timestamp_and_labels 12.47\n",
275 "",
276 "metric_without_timestamp_and_labels",
277 vec![],
278 12.47,
279 None,
280 );
281 assert_sample_parser(
282 "something_weird{problem=\"division by zero\"} +Inf -3982045\n",
283 "",
284 "something_weird",
285 vec![("problem", "division by zero")],
286 std::f64::INFINITY,
287 Some(-3982045),
288 );
289 assert_sample_parser(
290 "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\n",
291 "",
292 "http_request_duration_seconds_bucket",
293 vec![("le", "0.05")],
294 24054f64,
295 None,
296 );
297 assert_sample_parser(
298 "http_request_duration_seconds_bucket{le=\"0.1\"} 33444\n",
299 "",
300 "http_request_duration_seconds_bucket",
301 vec![("le", "0.1")],
302 33444f64,
303 None,
304 );
305 assert_sample_parser(
306 "http_request_duration_seconds_bucket{le=\"0.2\"} 100392\n",
307 "",
308 "http_request_duration_seconds_bucket",
309 vec![("le", "0.2")],
310 100392f64,
311 None,
312 );
313 assert_sample_parser(
314 "http_request_duration_seconds_bucket{le=\"0.5\"} 129389\n",
315 "",
316 "http_request_duration_seconds_bucket",
317 vec![("le", "0.5")],
318 129389f64,
319 None,
320 );
321 assert_sample_parser(
322 "http_request_duration_seconds_bucket{le=\"1\"} 133988\n",
323 "",
324 "http_request_duration_seconds_bucket",
325 vec![("le", "1")],
326 133988f64,
327 None,
328 );
329 assert_sample_parser(
330 "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320\n",
331 "",
332 "http_request_duration_seconds_bucket",
333 vec![("le", "+Inf")],
334 144320f64,
335 None,
336 );
337 assert_sample_parser(
338 "http_request_duration_seconds_sum 53423\n",
339 "",
340 "http_request_duration_seconds_sum",
341 vec![],
342 53423f64,
343 None,
344 );
345 assert_sample_parser(
346 "http_request_duration_seconds_count 144320\n",
347 "",
348 "http_request_duration_seconds_count",
349 vec![],
350 144320f64,
351 None,
352 );
353 assert_sample_parser(
354 "rpc_duration_seconds{quantile=\"0.01\"} 3102\n",
355 "",
356 "rpc_duration_seconds",
357 vec![("quantile", "0.01")],
358 3102f64,
359 None,
360 );
361 assert_sample_parser(
362 "rpc_duration_seconds{quantile=\"0.05\"} 3272\n",
363 "",
364 "rpc_duration_seconds",
365 vec![("quantile", "0.05")],
366 3272f64,
367 None,
368 );
369 assert_sample_parser(
370 "rpc_duration_seconds{quantile=\"0.5\"} 4773\n",
371 "",
372 "rpc_duration_seconds",
373 vec![("quantile", "0.5")],
374 4773f64,
375 None,
376 );
377 assert_sample_parser(
378 "rpc_duration_seconds{quantile=\"0.9\"} 9001\n",
379 "",
380 "rpc_duration_seconds",
381 vec![("quantile", "0.9")],
382 9001f64,
383 None,
384 );
385 assert_sample_parser(
386 "rpc_duration_seconds{quantile=\"0.99\"} 76656\n",
387 "",
388 "rpc_duration_seconds",
389 vec![("quantile", "0.99")],
390 76656f64,
391 None,
392 );
393 assert_sample_parser(
394 "rpc_duration_seconds_sum 1.7560473e+07\n",
395 "",
396 "rpc_duration_seconds_sum",
397 vec![],
398 1.7560473e+07,
399 None,
400 );
401 assert_sample_parser(
402 "rpc_duration_seconds_count 2693\n",
403 "",
404 "rpc_duration_seconds_count",
405 vec![],
406 2693f64,
407 None,
408 );
409
410 assert_sample_parser(
412 "rpc_duration_seconds_count 2693\nfoo",
413 "foo",
414 "rpc_duration_seconds_count",
415 vec![],
416 2693f64,
417 None,
418 );
419
420 assert_sample_parser(
422 "test {a=\"b\"} 0\n",
423 "",
424 "test",
425 vec![("a", "b")],
426 0f64,
427 None,
428 );
429
430 assert_eq!(
432 parse_sample("metric_without_timestamp_and_labels\n"),
433 Err(Error(("\n", ErrorKind::Space)))
434 );
435 assert_eq!(
437 parse_sample("metric_without_timestamp_and_labels1234\n"),
438 Err(Error(("\n", ErrorKind::Space)))
439 );
440 assert_eq!(
442 parse_sample("metric_without_timestamp_and_labels 1234"),
443 Err(Error(("", ErrorKind::CrLf)))
444 );
445}