1use std::{collections::HashMap, error, fmt, iter, num, sync::LazyLock};
2
3use chrono::{DateTime, Utc};
4
5use crate::event::metric::{Metric, MetricKind, MetricTags, MetricValue};
6
7static SCOREBOARD: LazyLock<HashMap<char, &'static str>> = LazyLock::new(|| {
8 vec![
9 ('_', "waiting"),
10 ('S', "starting"),
11 ('R', "reading"),
12 ('W', "sending"),
13 ('K', "keepalive"),
14 ('D', "dnslookup"),
15 ('C', "closing"),
16 ('L', "logging"),
17 ('G', "finishing"),
18 ('I', "idle_cleanup"),
19 ('.', "open"),
20 ]
21 .into_iter()
22 .collect()
23});
24
25enum StatusFieldStatistic<'a> {
27 ServerUptimeSeconds(u64),
28 TotalAccesses(u64),
29 TotalKBytes(u64),
30 TotalDuration(u64),
31 CpuUser(f64),
32 CpuSystem(f64),
33 CpuChildrenUser(f64),
34 CpuChildrenSystem(f64),
35 CpuLoad(f64),
36 IdleWorkers(u64),
37 BusyWorkers(u64),
38 ConnsTotal(u64),
39 ConnsAsyncWriting(u64),
40 ConnsAsyncKeepAlive(u64),
41 ConnsAsyncClosing(u64),
42 Scoreboard(&'a str),
43}
44
45impl<'a> StatusFieldStatistic<'a> {
46 fn from_key_value(
47 key: &str,
48 value: &'a str,
49 ) -> Option<Result<StatusFieldStatistic<'a>, ParseError>> {
50 match key {
51 "ServerUptimeSeconds" => {
52 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ServerUptimeSeconds))
53 }
54 "Total Accesses" => {
55 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalAccesses))
56 }
57 "Total kBytes" => {
58 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalKBytes))
59 }
60 "Total Duration" => {
61 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalDuration))
62 }
63 "CPUUser" => Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuUser)),
64 "CPUSystem" => {
65 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuSystem))
66 }
67 "CPUChildrenUser" => {
68 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuChildrenUser))
69 }
70 "CPUChildrenSystem" => {
71 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuChildrenSystem))
72 }
73 "CPULoad" => Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuLoad)),
74 "IdleWorkers" => {
75 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::IdleWorkers))
76 }
77 "BusyWorkers" => {
78 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::BusyWorkers))
79 }
80 "ConnsTotal" => {
81 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsTotal))
82 }
83 "ConnsAsyncWriting" => {
84 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncWriting))
85 }
86 "ConnsAsyncClosing" => {
87 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncClosing))
88 }
89 "ConnsAsyncKeepAlive" => {
90 Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncKeepAlive))
91 }
92 "Scoreboard" => Some(Ok(StatusFieldStatistic::Scoreboard(value))),
93
94 _ => None,
95 }
96 }
97}
98
99pub fn parse(
111 payload: &str,
112 namespace: Option<&str>,
113 now: DateTime<Utc>,
114 tags: Option<&MetricTags>,
115) -> impl Iterator<Item = Result<Metric, ParseError>> + use<> {
116 let parsed = payload
120 .lines()
121 .filter_map(|l| {
122 let mut parts = l.splitn(2, ':');
123 let key = parts.next();
124 let value = parts.next();
125 match (key, value) {
126 (Some(k), Some(v)) => Some((k, v.trim())),
127 _ => None,
128 }
129 })
130 .collect::<HashMap<_, _>>();
131
132 parsed
133 .iter()
134 .filter_map(|(key, value)| line_to_metrics(key, value, namespace, now, tags))
135 .fold(vec![], |mut acc, v| {
136 match v {
137 Ok(metrics) => metrics.for_each(|v| acc.push(Ok(v))),
138 Err(error) => acc.push(Err(error)),
139 };
140 acc
141 })
142 .into_iter()
143}
144
145fn line_to_metrics<'a>(
146 key: &str,
147 value: &str,
148 namespace: Option<&'a str>,
149 now: DateTime<Utc>,
150 tags: Option<&'a MetricTags>,
151) -> Option<Result<Box<dyn Iterator<Item = Metric> + 'a>, ParseError>> {
152 StatusFieldStatistic::from_key_value(key, value).map(move |result| {
153 result.map(move |statistic| match statistic {
154 StatusFieldStatistic::ServerUptimeSeconds(value) => Box::new(iter::once(
155 Metric::new(
156 "uptime_seconds_total",
157 MetricKind::Absolute,
158 MetricValue::Counter {
159 value: value as f64,
160 },
161 )
162 .with_namespace(namespace.map(str::to_string))
163 .with_tags(tags.cloned())
164 .with_timestamp(Some(now)),
165 )),
166 StatusFieldStatistic::TotalAccesses(value) => Box::new(iter::once(
167 Metric::new(
168 "access_total",
169 MetricKind::Absolute,
170 MetricValue::Counter {
171 value: value as f64,
172 },
173 )
174 .with_namespace(namespace.map(str::to_string))
175 .with_tags(tags.cloned())
176 .with_timestamp(Some(now)),
177 )),
178 StatusFieldStatistic::TotalKBytes(value) => Box::new(iter::once(
179 Metric::new(
180 "sent_bytes_total",
181 MetricKind::Absolute,
182 MetricValue::Counter {
183 value: (value * 1024) as f64,
184 },
185 )
186 .with_namespace(namespace.map(str::to_string))
187 .with_tags(tags.cloned())
188 .with_timestamp(Some(now)),
189 )),
190 StatusFieldStatistic::TotalDuration(value) => Box::new(iter::once(
191 Metric::new(
192 "duration_seconds_total",
193 MetricKind::Absolute,
194 MetricValue::Counter {
195 value: value as f64,
196 },
197 )
198 .with_namespace(namespace.map(str::to_string))
199 .with_tags(tags.cloned())
200 .with_timestamp(Some(now)),
201 )),
202 StatusFieldStatistic::CpuUser(value) => Box::new(iter::once(
203 Metric::new(
204 "cpu_seconds_total",
205 MetricKind::Absolute,
206 MetricValue::Gauge { value },
207 )
208 .with_namespace(namespace.map(str::to_string))
209 .with_tags({
210 let mut tags = tags.cloned().unwrap_or_default();
211 tags.replace("type".to_string(), "user".to_string());
212 Some(tags)
213 })
214 .with_timestamp(Some(now)),
215 ))
216 as Box<dyn Iterator<Item = Metric>>,
217 StatusFieldStatistic::CpuSystem(value) => Box::new(iter::once(
218 Metric::new(
219 "cpu_seconds_total",
220 MetricKind::Absolute,
221 MetricValue::Gauge { value },
222 )
223 .with_namespace(namespace.map(str::to_string))
224 .with_tags({
225 let mut tags = tags.cloned().unwrap_or_default();
226 tags.replace("type".to_string(), "system".to_string());
227 Some(tags)
228 })
229 .with_timestamp(Some(now)),
230 ))
231 as Box<dyn Iterator<Item = Metric>>,
232 StatusFieldStatistic::CpuChildrenUser(value) => Box::new(iter::once(
233 Metric::new(
234 "cpu_seconds_total",
235 MetricKind::Absolute,
236 MetricValue::Gauge { value },
237 )
238 .with_namespace(namespace.map(str::to_string))
239 .with_tags({
240 let mut tags = tags.cloned().unwrap_or_default();
241 tags.replace("type".to_string(), "children_user".to_string());
242 Some(tags)
243 })
244 .with_timestamp(Some(now)),
245 ))
246 as Box<dyn Iterator<Item = Metric>>,
247 StatusFieldStatistic::CpuChildrenSystem(value) => Box::new(iter::once(
248 Metric::new(
249 "cpu_seconds_total",
250 MetricKind::Absolute,
251 MetricValue::Gauge { value },
252 )
253 .with_namespace(namespace.map(str::to_string))
254 .with_tags({
255 let mut tags = tags.cloned().unwrap_or_default();
256 tags.replace("type".to_string(), "children_system".to_string());
257 Some(tags)
258 })
259 .with_timestamp(Some(now)),
260 ))
261 as Box<dyn Iterator<Item = Metric>>,
262 StatusFieldStatistic::CpuLoad(value) => Box::new(iter::once(
263 Metric::new(
264 "cpu_load",
265 MetricKind::Absolute,
266 MetricValue::Gauge { value },
267 )
268 .with_namespace(namespace.map(str::to_string))
269 .with_tags(tags.cloned())
270 .with_timestamp(Some(now)),
271 ))
272 as Box<dyn Iterator<Item = Metric>>,
273 StatusFieldStatistic::IdleWorkers(value) => Box::new(iter::once(
274 Metric::new(
275 "workers",
276 MetricKind::Absolute,
277 MetricValue::Gauge {
278 value: value as f64,
279 },
280 )
281 .with_namespace(namespace.map(str::to_string))
282 .with_tags({
283 let mut tags = tags.cloned().unwrap_or_default();
284 tags.replace("state".to_string(), "idle".to_string());
285 Some(tags)
286 })
287 .with_timestamp(Some(now)),
288 ))
289 as Box<dyn Iterator<Item = Metric>>,
290 StatusFieldStatistic::BusyWorkers(value) => Box::new(iter::once(
291 Metric::new(
292 "workers",
293 MetricKind::Absolute,
294 MetricValue::Gauge {
295 value: value as f64,
296 },
297 )
298 .with_namespace(namespace.map(str::to_string))
299 .with_tags({
300 let mut tags = tags.cloned().unwrap_or_default();
301 tags.replace("state".to_string(), "busy".to_string());
302 Some(tags)
303 })
304 .with_timestamp(Some(now)),
305 )),
306 StatusFieldStatistic::ConnsTotal(value) => Box::new(iter::once(
307 Metric::new(
308 "connections",
309 MetricKind::Absolute,
310 MetricValue::Gauge {
311 value: value as f64,
312 },
313 )
314 .with_namespace(namespace.map(str::to_string))
315 .with_tags({
316 let mut tags = tags.cloned().unwrap_or_default();
317 tags.replace("state".to_string(), "total".to_string());
318 Some(tags)
319 })
320 .with_timestamp(Some(now)),
321 )),
322 StatusFieldStatistic::ConnsAsyncWriting(value) => Box::new(iter::once(
323 Metric::new(
324 "connections",
325 MetricKind::Absolute,
326 MetricValue::Gauge {
327 value: value as f64,
328 },
329 )
330 .with_namespace(namespace.map(str::to_string))
331 .with_tags({
332 let mut tags = tags.cloned().unwrap_or_default();
333 tags.replace("state".to_string(), "writing".to_string());
334 Some(tags)
335 })
336 .with_timestamp(Some(now)),
337 )),
338 StatusFieldStatistic::ConnsAsyncClosing(value) => Box::new(iter::once(
339 Metric::new(
340 "connections",
341 MetricKind::Absolute,
342 MetricValue::Gauge {
343 value: value as f64,
344 },
345 )
346 .with_namespace(namespace.map(str::to_string))
347 .with_tags({
348 let mut tags = tags.cloned().unwrap_or_default();
349 tags.replace("state".to_string(), "closing".to_string());
350 Some(tags)
351 })
352 .with_timestamp(Some(now)),
353 )),
354 StatusFieldStatistic::ConnsAsyncKeepAlive(value) => Box::new(iter::once(
355 Metric::new(
356 "connections",
357 MetricKind::Absolute,
358 MetricValue::Gauge {
359 value: value as f64,
360 },
361 )
362 .with_namespace(namespace.map(str::to_string))
363 .with_tags({
364 let mut tags = tags.cloned().unwrap_or_default();
365 tags.replace("state".to_string(), "keepalive".to_string());
366 Some(tags)
367 })
368 .with_timestamp(Some(now)),
369 )),
370 StatusFieldStatistic::Scoreboard(value) => {
371 let scores = value.chars().fold(HashMap::new(), |mut m, c| {
372 *m.entry(c).or_insert(0u32) += 1;
373 m
374 });
375
376 Box::new(SCOREBOARD.iter().map(move |(c, name)| {
377 score_to_metric(
378 namespace,
379 now,
380 tags,
381 name,
382 scores.get(c).copied().unwrap_or_default(),
383 )
384 })) as Box<dyn Iterator<Item = Metric>>
385 }
386 })
387 })
388}
389
390fn parse_numeric_value<T: std::str::FromStr>(key: &str, value: &str) -> Result<T, ParseError>
391where
392 T::Err: Into<ValueParseError> + 'static,
393{
394 value.parse::<T>().map_err(|error| ParseError {
395 key: key.to_string(),
396 error: error.into(),
397 })
398}
399
400fn score_to_metric(
401 namespace: Option<&str>,
402 now: DateTime<Utc>,
403 tags: Option<&MetricTags>,
404 state: &str,
405 count: u32,
406) -> Metric {
407 Metric::new(
408 "scoreboard",
409 MetricKind::Absolute,
410 MetricValue::Gauge {
411 value: count.into(),
412 },
413 )
414 .with_namespace(namespace.map(str::to_string))
415 .with_tags({
416 let mut tags = tags.cloned().unwrap_or_default();
417 tags.replace("state".to_string(), state.to_string());
418 Some(tags)
419 })
420 .with_timestamp(Some(now))
421}
422
423#[derive(Debug)]
424enum ValueParseError {
425 Float(num::ParseFloatError),
426 Int(num::ParseIntError),
427}
428
429impl From<num::ParseFloatError> for ValueParseError {
430 fn from(error: num::ParseFloatError) -> ValueParseError {
431 ValueParseError::Float(error)
432 }
433}
434
435impl From<num::ParseIntError> for ValueParseError {
436 fn from(error: num::ParseIntError) -> ValueParseError {
437 ValueParseError::Int(error)
438 }
439}
440
441impl error::Error for ValueParseError {
442 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
443 match *self {
444 ValueParseError::Float(ref e) => Some(e),
445 ValueParseError::Int(ref e) => Some(e),
446 }
447 }
448}
449
450impl fmt::Display for ValueParseError {
451 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
452 match *self {
453 ValueParseError::Float(ref e) => e.fmt(f),
454 ValueParseError::Int(ref e) => e.fmt(f),
455 }
456 }
457}
458
459#[derive(Debug)]
460pub struct ParseError {
461 key: String,
462 error: ValueParseError,
463}
464
465impl fmt::Display for ParseError {
466 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467 write!(f, "could not parse value for {}: {}", self.key, self.error)
468 }
469}
470
471impl error::Error for ParseError {
472 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
473 Some(&self.error)
474 }
475}
476
477#[cfg(test)]
478mod test {
479 use chrono::{DateTime, Utc};
480 use similar_asserts::assert_eq;
481 use vector_lib::{assert_event_data_eq, metric_tags};
482
483 use super::*;
484 use crate::event::metric::{Metric, MetricKind, MetricValue};
485
486 #[test]
489 fn test_not_extended() {
490 let payload = r"
491localhost
492ServerVersion: Apache/2.4.46 (Unix)
493ServerMPM: event
494Server Built: Aug 5 2020 23:20:17
495CurrentTime: Thursday, 03-Sep-2020 20:48:54 UTC
496RestartTime: Thursday, 03-Sep-2020 20:48:41 UTC
497ParentServerConfigGeneration: 1
498ParentServerMPMGeneration: 0
499ServerUptimeSeconds: 12
500ServerUptime: 12 seconds
501Load1: 0.75
502Load5: 0.59
503Load15: 0.76
504BusyWorkers: 1
505IdleWorkers: 74
506Processes: 3
507Stopping: 0
508BusyWorkers: 1
509IdleWorkers: 74
510ConnsTotal: 1
511ConnsAsyncWriting: 0
512ConnsAsyncKeepAlive: 0
513ConnsAsyncClosing: 0
514Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
515 ";
516
517 let (now, metrics, errors) = parse_sort(payload);
518
519 assert_event_data_eq!(
520 metrics,
521 vec![
522 Metric::new(
523 "connections",
524 MetricKind::Absolute,
525 MetricValue::Gauge { value: 0.0 },
526 )
527 .with_namespace(Some("apache"))
528 .with_tags(Some(metric_tags!("state" => "closing")))
529 .with_timestamp(Some(now)),
530 Metric::new(
531 "connections",
532 MetricKind::Absolute,
533 MetricValue::Gauge { value: 0.0 },
534 )
535 .with_namespace(Some("apache"))
536 .with_tags(Some(metric_tags!("state" => "keepalive")))
537 .with_timestamp(Some(now)),
538 Metric::new(
539 "connections",
540 MetricKind::Absolute,
541 MetricValue::Gauge { value: 1.0 },
542 )
543 .with_namespace(Some("apache"))
544 .with_tags(Some(metric_tags!("state" => "total")))
545 .with_timestamp(Some(now)),
546 Metric::new(
547 "connections",
548 MetricKind::Absolute,
549 MetricValue::Gauge { value: 0.0 },
550 )
551 .with_namespace(Some("apache"))
552 .with_tags(Some(metric_tags!("state" => "writing")))
553 .with_timestamp(Some(now)),
554 Metric::new(
555 "scoreboard",
556 MetricKind::Absolute,
557 MetricValue::Gauge { value: 1.0 },
558 )
559 .with_namespace(Some("apache"))
560 .with_tags(Some(metric_tags!("state" => "closing")))
561 .with_timestamp(Some(now)),
562 Metric::new(
563 "scoreboard",
564 MetricKind::Absolute,
565 MetricValue::Gauge { value: 1.0 },
566 )
567 .with_namespace(Some("apache"))
568 .with_tags(Some(metric_tags!("state" => "dnslookup")))
569 .with_timestamp(Some(now)),
570 Metric::new(
571 "scoreboard",
572 MetricKind::Absolute,
573 MetricValue::Gauge { value: 1.0 },
574 )
575 .with_namespace(Some("apache"))
576 .with_tags(Some(metric_tags!("state" => "finishing")))
577 .with_timestamp(Some(now)),
578 Metric::new(
579 "scoreboard",
580 MetricKind::Absolute,
581 MetricValue::Gauge { value: 2.0 },
582 )
583 .with_namespace(Some("apache"))
584 .with_tags(Some(metric_tags!("state" => "idle_cleanup")))
585 .with_timestamp(Some(now)),
586 Metric::new(
587 "scoreboard",
588 MetricKind::Absolute,
589 MetricValue::Gauge { value: 2.0 },
590 )
591 .with_namespace(Some("apache"))
592 .with_tags(Some(metric_tags!("state" => "keepalive")))
593 .with_timestamp(Some(now)),
594 Metric::new(
595 "scoreboard",
596 MetricKind::Absolute,
597 MetricValue::Gauge { value: 1.0 },
598 )
599 .with_namespace(Some("apache"))
600 .with_tags(Some(metric_tags!("state" => "logging")))
601 .with_timestamp(Some(now)),
602 Metric::new(
603 "scoreboard",
604 MetricKind::Absolute,
605 MetricValue::Gauge { value: 325.0 },
606 )
607 .with_namespace(Some("apache"))
608 .with_tags(Some(metric_tags!("state" => "open")))
609 .with_timestamp(Some(now)),
610 Metric::new(
611 "scoreboard",
612 MetricKind::Absolute,
613 MetricValue::Gauge { value: 1.0 },
614 )
615 .with_namespace(Some("apache"))
616 .with_tags(Some(metric_tags!("state" => "reading")))
617 .with_timestamp(Some(now)),
618 Metric::new(
619 "scoreboard",
620 MetricKind::Absolute,
621 MetricValue::Gauge { value: 1.0 },
622 )
623 .with_namespace(Some("apache"))
624 .with_tags(Some(metric_tags!("state" => "sending")))
625 .with_timestamp(Some(now)),
626 Metric::new(
627 "scoreboard",
628 MetricKind::Absolute,
629 MetricValue::Gauge { value: 1.0 },
630 )
631 .with_namespace(Some("apache"))
632 .with_tags(Some(metric_tags!("state" => "starting")))
633 .with_timestamp(Some(now)),
634 Metric::new(
635 "scoreboard",
636 MetricKind::Absolute,
637 MetricValue::Gauge { value: 64.0 },
638 )
639 .with_namespace(Some("apache"))
640 .with_tags(Some(metric_tags!("state" => "waiting")))
641 .with_timestamp(Some(now)),
642 Metric::new(
643 "uptime_seconds_total",
644 MetricKind::Absolute,
645 MetricValue::Counter { value: 12.0 },
646 )
647 .with_namespace(Some("apache"))
648 .with_timestamp(Some(now)),
649 Metric::new(
650 "workers",
651 MetricKind::Absolute,
652 MetricValue::Gauge { value: 1.0 },
653 )
654 .with_namespace(Some("apache"))
655 .with_tags(Some(metric_tags!("state" => "busy")))
656 .with_timestamp(Some(now)),
657 Metric::new(
658 "workers",
659 MetricKind::Absolute,
660 MetricValue::Gauge { value: 74.0 },
661 )
662 .with_namespace(Some("apache"))
663 .with_tags(Some(metric_tags!("state" => "idle")))
664 .with_timestamp(Some(now)),
665 ]
666 );
667 assert_eq!(errors.len(), 0);
668 }
669
670 #[test]
673 fn test_extended() {
674 let payload = r"
675localhost
676ServerVersion: Apache/2.4.46 (Unix)
677ServerMPM: event
678Server Built: Aug 5 2020 23:20:17
679CurrentTime: Friday, 21-Aug-2020 18:41:34 UTC
680RestartTime: Friday, 21-Aug-2020 18:41:08 UTC
681ParentServerConfigGeneration: 1
682ParentServerMPMGeneration: 0
683ServerUptimeSeconds: 26
684ServerUptime: 26 seconds
685Load1: 0.00
686Load5: 0.03
687Load15: 0.03
688Total Accesses: 30
689Total kBytes: 217
690Total Duration: 11
691CPUUser: .2
692CPUSystem: .02
693CPUChildrenUser: 0
694CPUChildrenSystem: 0
695CPULoad: .846154
696Uptime: 26
697ReqPerSec: 1.15385
698BytesPerSec: 8546.46
699BytesPerReq: 7406.93
700DurationPerReq: .366667
701BusyWorkers: 1
702IdleWorkers: 74
703Processes: 3
704Stopping: 0
705BusyWorkers: 1
706IdleWorkers: 74
707ConnsTotal: 1
708ConnsAsyncWriting: 0
709ConnsAsyncKeepAlive: 0
710ConnsAsyncClosing: 0
711Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
712 ";
713
714 let (now, metrics, errors) = parse_sort(payload);
715
716 assert_event_data_eq!(
717 metrics,
718 vec![
719 Metric::new(
720 "access_total",
721 MetricKind::Absolute,
722 MetricValue::Counter { value: 30.0 },
723 )
724 .with_namespace(Some("apache"))
725 .with_timestamp(Some(now)),
726 Metric::new(
727 "connections",
728 MetricKind::Absolute,
729 MetricValue::Gauge { value: 0.0 },
730 )
731 .with_namespace(Some("apache"))
732 .with_tags(Some(metric_tags!("state" => "closing")))
733 .with_timestamp(Some(now)),
734 Metric::new(
735 "connections",
736 MetricKind::Absolute,
737 MetricValue::Gauge { value: 0.0 },
738 )
739 .with_namespace(Some("apache"))
740 .with_tags(Some(metric_tags!("state" => "keepalive")))
741 .with_timestamp(Some(now)),
742 Metric::new(
743 "connections",
744 MetricKind::Absolute,
745 MetricValue::Gauge { value: 1.0 },
746 )
747 .with_namespace(Some("apache"))
748 .with_tags(Some(metric_tags!("state" => "total")))
749 .with_timestamp(Some(now)),
750 Metric::new(
751 "connections",
752 MetricKind::Absolute,
753 MetricValue::Gauge { value: 0.0 },
754 )
755 .with_namespace(Some("apache"))
756 .with_tags(Some(metric_tags!("state" => "writing")))
757 .with_timestamp(Some(now)),
758 Metric::new(
759 "cpu_load",
760 MetricKind::Absolute,
761 MetricValue::Gauge { value: 0.846154 },
762 )
763 .with_namespace(Some("apache"))
764 .with_timestamp(Some(now)),
765 Metric::new(
766 "cpu_seconds_total",
767 MetricKind::Absolute,
768 MetricValue::Gauge { value: 0.0 },
769 )
770 .with_namespace(Some("apache"))
771 .with_tags(Some(metric_tags!("type" => "children_system")))
772 .with_timestamp(Some(now)),
773 Metric::new(
774 "cpu_seconds_total",
775 MetricKind::Absolute,
776 MetricValue::Gauge { value: 0.0 },
777 )
778 .with_namespace(Some("apache"))
779 .with_tags(Some(metric_tags!("type" => "children_user")))
780 .with_timestamp(Some(now)),
781 Metric::new(
782 "cpu_seconds_total",
783 MetricKind::Absolute,
784 MetricValue::Gauge { value: 0.02 },
785 )
786 .with_namespace(Some("apache"))
787 .with_tags(Some(metric_tags!("type" => "system")))
788 .with_timestamp(Some(now)),
789 Metric::new(
790 "cpu_seconds_total",
791 MetricKind::Absolute,
792 MetricValue::Gauge { value: 0.2 },
793 )
794 .with_namespace(Some("apache"))
795 .with_tags(Some(metric_tags!("type" => "user")))
796 .with_timestamp(Some(now)),
797 Metric::new(
798 "duration_seconds_total",
799 MetricKind::Absolute,
800 MetricValue::Counter { value: 11.0 },
801 )
802 .with_namespace(Some("apache"))
803 .with_timestamp(Some(now)),
804 Metric::new(
805 "scoreboard",
806 MetricKind::Absolute,
807 MetricValue::Gauge { value: 1.0 },
808 )
809 .with_namespace(Some("apache"))
810 .with_tags(Some(metric_tags!("state" => "closing")))
811 .with_timestamp(Some(now)),
812 Metric::new(
813 "scoreboard",
814 MetricKind::Absolute,
815 MetricValue::Gauge { value: 1.0 },
816 )
817 .with_namespace(Some("apache"))
818 .with_tags(Some(metric_tags!("state" => "dnslookup")))
819 .with_timestamp(Some(now)),
820 Metric::new(
821 "scoreboard",
822 MetricKind::Absolute,
823 MetricValue::Gauge { value: 1.0 },
824 )
825 .with_namespace(Some("apache"))
826 .with_tags(Some(metric_tags!("state" => "finishing")))
827 .with_timestamp(Some(now)),
828 Metric::new(
829 "scoreboard",
830 MetricKind::Absolute,
831 MetricValue::Gauge { value: 2.0 },
832 )
833 .with_namespace(Some("apache"))
834 .with_tags(Some(metric_tags!("state" => "idle_cleanup")))
835 .with_timestamp(Some(now)),
836 Metric::new(
837 "scoreboard",
838 MetricKind::Absolute,
839 MetricValue::Gauge { value: 2.0 },
840 )
841 .with_namespace(Some("apache"))
842 .with_tags(Some(metric_tags!("state" => "keepalive")))
843 .with_timestamp(Some(now)),
844 Metric::new(
845 "scoreboard",
846 MetricKind::Absolute,
847 MetricValue::Gauge { value: 1.0 },
848 )
849 .with_namespace(Some("apache"))
850 .with_tags(Some(metric_tags!("state" => "logging")))
851 .with_timestamp(Some(now)),
852 Metric::new(
853 "scoreboard",
854 MetricKind::Absolute,
855 MetricValue::Gauge { value: 325.0 },
856 )
857 .with_namespace(Some("apache"))
858 .with_tags(Some(metric_tags!("state" => "open")))
859 .with_timestamp(Some(now)),
860 Metric::new(
861 "scoreboard",
862 MetricKind::Absolute,
863 MetricValue::Gauge { value: 1.0 },
864 )
865 .with_namespace(Some("apache"))
866 .with_tags(Some(metric_tags!("state" => "reading")))
867 .with_timestamp(Some(now)),
868 Metric::new(
869 "scoreboard",
870 MetricKind::Absolute,
871 MetricValue::Gauge { value: 1.0 },
872 )
873 .with_namespace(Some("apache"))
874 .with_tags(Some(metric_tags!("state" => "sending")))
875 .with_timestamp(Some(now)),
876 Metric::new(
877 "scoreboard",
878 MetricKind::Absolute,
879 MetricValue::Gauge { value: 1.0 },
880 )
881 .with_namespace(Some("apache"))
882 .with_tags(Some(metric_tags!("state" => "starting")))
883 .with_timestamp(Some(now)),
884 Metric::new(
885 "scoreboard",
886 MetricKind::Absolute,
887 MetricValue::Gauge { value: 64.0 },
888 )
889 .with_namespace(Some("apache"))
890 .with_tags(Some(metric_tags!("state" => "waiting")))
891 .with_timestamp(Some(now)),
892 Metric::new(
893 "sent_bytes_total",
894 MetricKind::Absolute,
895 MetricValue::Counter { value: 222208.0 },
896 )
897 .with_namespace(Some("apache"))
898 .with_timestamp(Some(now)),
899 Metric::new(
900 "uptime_seconds_total",
901 MetricKind::Absolute,
902 MetricValue::Counter { value: 26.0 },
903 )
904 .with_namespace(Some("apache"))
905 .with_timestamp(Some(now)),
906 Metric::new(
907 "workers",
908 MetricKind::Absolute,
909 MetricValue::Gauge { value: 1.0 },
910 )
911 .with_namespace(Some("apache"))
912 .with_tags(Some(metric_tags!("state" => "busy")))
913 .with_timestamp(Some(now)),
914 Metric::new(
915 "workers",
916 MetricKind::Absolute,
917 MetricValue::Gauge { value: 74.0 },
918 )
919 .with_namespace(Some("apache"))
920 .with_tags(Some(metric_tags!("state" => "idle")))
921 .with_timestamp(Some(now)),
922 ]
923 );
924 assert_eq!(errors.len(), 0);
925 }
926
927 #[test]
928 fn test_parse_failure() {
929 let payload = r"
930ServerUptimeSeconds: not a number
931ConnsTotal: 1
932 ";
933
934 let (now, metrics, errors) = parse_sort(payload);
935
936 assert_event_data_eq!(
937 metrics,
938 vec![
939 Metric::new(
940 "connections",
941 MetricKind::Absolute,
942 MetricValue::Gauge { value: 1.0 },
943 )
944 .with_namespace(Some("apache"))
945 .with_tags(Some(metric_tags!("state" => "total")))
946 .with_timestamp(Some(now)),
947 ]
948 );
949 assert_eq!(errors.len(), 1);
950 }
951
952 fn parse_sort(payload: &str) -> (DateTime<Utc>, Vec<Metric>, Vec<ParseError>) {
953 let now: DateTime<Utc> = Utc::now();
954 let (mut metrics, errors) = parse(payload, Some("apache"), now, None).fold(
955 (vec![], vec![]),
956 |(mut metrics, mut errors), v| {
957 match v {
958 Ok(m) => metrics.push(m),
959 Err(e) => errors.push(e),
960 }
961 (metrics, errors)
962 },
963 );
964
965 metrics.sort_by_key(|metric| metric.series().to_string());
966
967 (now, metrics, errors)
968 }
969}