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;
482 use vector_lib::metric_tags;
483
484 use super::*;
485 use crate::event::metric::{Metric, MetricKind, MetricValue};
486
487 #[test]
490 fn test_not_extended() {
491 let payload = r"
492localhost
493ServerVersion: Apache/2.4.46 (Unix)
494ServerMPM: event
495Server Built: Aug 5 2020 23:20:17
496CurrentTime: Thursday, 03-Sep-2020 20:48:54 UTC
497RestartTime: Thursday, 03-Sep-2020 20:48:41 UTC
498ParentServerConfigGeneration: 1
499ParentServerMPMGeneration: 0
500ServerUptimeSeconds: 12
501ServerUptime: 12 seconds
502Load1: 0.75
503Load5: 0.59
504Load15: 0.76
505BusyWorkers: 1
506IdleWorkers: 74
507Processes: 3
508Stopping: 0
509BusyWorkers: 1
510IdleWorkers: 74
511ConnsTotal: 1
512ConnsAsyncWriting: 0
513ConnsAsyncKeepAlive: 0
514ConnsAsyncClosing: 0
515Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
516 ";
517
518 let (now, metrics, errors) = parse_sort(payload);
519
520 assert_event_data_eq!(
521 metrics,
522 vec![
523 Metric::new(
524 "connections",
525 MetricKind::Absolute,
526 MetricValue::Gauge { value: 0.0 },
527 )
528 .with_namespace(Some("apache"))
529 .with_tags(Some(metric_tags!("state" => "closing")))
530 .with_timestamp(Some(now)),
531 Metric::new(
532 "connections",
533 MetricKind::Absolute,
534 MetricValue::Gauge { value: 0.0 },
535 )
536 .with_namespace(Some("apache"))
537 .with_tags(Some(metric_tags!("state" => "keepalive")))
538 .with_timestamp(Some(now)),
539 Metric::new(
540 "connections",
541 MetricKind::Absolute,
542 MetricValue::Gauge { value: 1.0 },
543 )
544 .with_namespace(Some("apache"))
545 .with_tags(Some(metric_tags!("state" => "total")))
546 .with_timestamp(Some(now)),
547 Metric::new(
548 "connections",
549 MetricKind::Absolute,
550 MetricValue::Gauge { value: 0.0 },
551 )
552 .with_namespace(Some("apache"))
553 .with_tags(Some(metric_tags!("state" => "writing")))
554 .with_timestamp(Some(now)),
555 Metric::new(
556 "scoreboard",
557 MetricKind::Absolute,
558 MetricValue::Gauge { value: 1.0 },
559 )
560 .with_namespace(Some("apache"))
561 .with_tags(Some(metric_tags!("state" => "closing")))
562 .with_timestamp(Some(now)),
563 Metric::new(
564 "scoreboard",
565 MetricKind::Absolute,
566 MetricValue::Gauge { value: 1.0 },
567 )
568 .with_namespace(Some("apache"))
569 .with_tags(Some(metric_tags!("state" => "dnslookup")))
570 .with_timestamp(Some(now)),
571 Metric::new(
572 "scoreboard",
573 MetricKind::Absolute,
574 MetricValue::Gauge { value: 1.0 },
575 )
576 .with_namespace(Some("apache"))
577 .with_tags(Some(metric_tags!("state" => "finishing")))
578 .with_timestamp(Some(now)),
579 Metric::new(
580 "scoreboard",
581 MetricKind::Absolute,
582 MetricValue::Gauge { value: 2.0 },
583 )
584 .with_namespace(Some("apache"))
585 .with_tags(Some(metric_tags!("state" => "idle_cleanup")))
586 .with_timestamp(Some(now)),
587 Metric::new(
588 "scoreboard",
589 MetricKind::Absolute,
590 MetricValue::Gauge { value: 2.0 },
591 )
592 .with_namespace(Some("apache"))
593 .with_tags(Some(metric_tags!("state" => "keepalive")))
594 .with_timestamp(Some(now)),
595 Metric::new(
596 "scoreboard",
597 MetricKind::Absolute,
598 MetricValue::Gauge { value: 1.0 },
599 )
600 .with_namespace(Some("apache"))
601 .with_tags(Some(metric_tags!("state" => "logging")))
602 .with_timestamp(Some(now)),
603 Metric::new(
604 "scoreboard",
605 MetricKind::Absolute,
606 MetricValue::Gauge { value: 325.0 },
607 )
608 .with_namespace(Some("apache"))
609 .with_tags(Some(metric_tags!("state" => "open")))
610 .with_timestamp(Some(now)),
611 Metric::new(
612 "scoreboard",
613 MetricKind::Absolute,
614 MetricValue::Gauge { value: 1.0 },
615 )
616 .with_namespace(Some("apache"))
617 .with_tags(Some(metric_tags!("state" => "reading")))
618 .with_timestamp(Some(now)),
619 Metric::new(
620 "scoreboard",
621 MetricKind::Absolute,
622 MetricValue::Gauge { value: 1.0 },
623 )
624 .with_namespace(Some("apache"))
625 .with_tags(Some(metric_tags!("state" => "sending")))
626 .with_timestamp(Some(now)),
627 Metric::new(
628 "scoreboard",
629 MetricKind::Absolute,
630 MetricValue::Gauge { value: 1.0 },
631 )
632 .with_namespace(Some("apache"))
633 .with_tags(Some(metric_tags!("state" => "starting")))
634 .with_timestamp(Some(now)),
635 Metric::new(
636 "scoreboard",
637 MetricKind::Absolute,
638 MetricValue::Gauge { value: 64.0 },
639 )
640 .with_namespace(Some("apache"))
641 .with_tags(Some(metric_tags!("state" => "waiting")))
642 .with_timestamp(Some(now)),
643 Metric::new(
644 "uptime_seconds_total",
645 MetricKind::Absolute,
646 MetricValue::Counter { value: 12.0 },
647 )
648 .with_namespace(Some("apache"))
649 .with_timestamp(Some(now)),
650 Metric::new(
651 "workers",
652 MetricKind::Absolute,
653 MetricValue::Gauge { value: 1.0 },
654 )
655 .with_namespace(Some("apache"))
656 .with_tags(Some(metric_tags!("state" => "busy")))
657 .with_timestamp(Some(now)),
658 Metric::new(
659 "workers",
660 MetricKind::Absolute,
661 MetricValue::Gauge { value: 74.0 },
662 )
663 .with_namespace(Some("apache"))
664 .with_tags(Some(metric_tags!("state" => "idle")))
665 .with_timestamp(Some(now)),
666 ]
667 );
668 assert_eq!(errors.len(), 0);
669 }
670
671 #[test]
674 fn test_extended() {
675 let payload = r"
676localhost
677ServerVersion: Apache/2.4.46 (Unix)
678ServerMPM: event
679Server Built: Aug 5 2020 23:20:17
680CurrentTime: Friday, 21-Aug-2020 18:41:34 UTC
681RestartTime: Friday, 21-Aug-2020 18:41:08 UTC
682ParentServerConfigGeneration: 1
683ParentServerMPMGeneration: 0
684ServerUptimeSeconds: 26
685ServerUptime: 26 seconds
686Load1: 0.00
687Load5: 0.03
688Load15: 0.03
689Total Accesses: 30
690Total kBytes: 217
691Total Duration: 11
692CPUUser: .2
693CPUSystem: .02
694CPUChildrenUser: 0
695CPUChildrenSystem: 0
696CPULoad: .846154
697Uptime: 26
698ReqPerSec: 1.15385
699BytesPerSec: 8546.46
700BytesPerReq: 7406.93
701DurationPerReq: .366667
702BusyWorkers: 1
703IdleWorkers: 74
704Processes: 3
705Stopping: 0
706BusyWorkers: 1
707IdleWorkers: 74
708ConnsTotal: 1
709ConnsAsyncWriting: 0
710ConnsAsyncKeepAlive: 0
711ConnsAsyncClosing: 0
712Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
713 ";
714
715 let (now, metrics, errors) = parse_sort(payload);
716
717 assert_event_data_eq!(
718 metrics,
719 vec![
720 Metric::new(
721 "access_total",
722 MetricKind::Absolute,
723 MetricValue::Counter { value: 30.0 },
724 )
725 .with_namespace(Some("apache"))
726 .with_timestamp(Some(now)),
727 Metric::new(
728 "connections",
729 MetricKind::Absolute,
730 MetricValue::Gauge { value: 0.0 },
731 )
732 .with_namespace(Some("apache"))
733 .with_tags(Some(metric_tags!("state" => "closing")))
734 .with_timestamp(Some(now)),
735 Metric::new(
736 "connections",
737 MetricKind::Absolute,
738 MetricValue::Gauge { value: 0.0 },
739 )
740 .with_namespace(Some("apache"))
741 .with_tags(Some(metric_tags!("state" => "keepalive")))
742 .with_timestamp(Some(now)),
743 Metric::new(
744 "connections",
745 MetricKind::Absolute,
746 MetricValue::Gauge { value: 1.0 },
747 )
748 .with_namespace(Some("apache"))
749 .with_tags(Some(metric_tags!("state" => "total")))
750 .with_timestamp(Some(now)),
751 Metric::new(
752 "connections",
753 MetricKind::Absolute,
754 MetricValue::Gauge { value: 0.0 },
755 )
756 .with_namespace(Some("apache"))
757 .with_tags(Some(metric_tags!("state" => "writing")))
758 .with_timestamp(Some(now)),
759 Metric::new(
760 "cpu_load",
761 MetricKind::Absolute,
762 MetricValue::Gauge { value: 0.846154 },
763 )
764 .with_namespace(Some("apache"))
765 .with_timestamp(Some(now)),
766 Metric::new(
767 "cpu_seconds_total",
768 MetricKind::Absolute,
769 MetricValue::Gauge { value: 0.0 },
770 )
771 .with_namespace(Some("apache"))
772 .with_tags(Some(metric_tags!("type" => "children_system")))
773 .with_timestamp(Some(now)),
774 Metric::new(
775 "cpu_seconds_total",
776 MetricKind::Absolute,
777 MetricValue::Gauge { value: 0.0 },
778 )
779 .with_namespace(Some("apache"))
780 .with_tags(Some(metric_tags!("type" => "children_user")))
781 .with_timestamp(Some(now)),
782 Metric::new(
783 "cpu_seconds_total",
784 MetricKind::Absolute,
785 MetricValue::Gauge { value: 0.02 },
786 )
787 .with_namespace(Some("apache"))
788 .with_tags(Some(metric_tags!("type" => "system")))
789 .with_timestamp(Some(now)),
790 Metric::new(
791 "cpu_seconds_total",
792 MetricKind::Absolute,
793 MetricValue::Gauge { value: 0.2 },
794 )
795 .with_namespace(Some("apache"))
796 .with_tags(Some(metric_tags!("type" => "user")))
797 .with_timestamp(Some(now)),
798 Metric::new(
799 "duration_seconds_total",
800 MetricKind::Absolute,
801 MetricValue::Counter { value: 11.0 },
802 )
803 .with_namespace(Some("apache"))
804 .with_timestamp(Some(now)),
805 Metric::new(
806 "scoreboard",
807 MetricKind::Absolute,
808 MetricValue::Gauge { value: 1.0 },
809 )
810 .with_namespace(Some("apache"))
811 .with_tags(Some(metric_tags!("state" => "closing")))
812 .with_timestamp(Some(now)),
813 Metric::new(
814 "scoreboard",
815 MetricKind::Absolute,
816 MetricValue::Gauge { value: 1.0 },
817 )
818 .with_namespace(Some("apache"))
819 .with_tags(Some(metric_tags!("state" => "dnslookup")))
820 .with_timestamp(Some(now)),
821 Metric::new(
822 "scoreboard",
823 MetricKind::Absolute,
824 MetricValue::Gauge { value: 1.0 },
825 )
826 .with_namespace(Some("apache"))
827 .with_tags(Some(metric_tags!("state" => "finishing")))
828 .with_timestamp(Some(now)),
829 Metric::new(
830 "scoreboard",
831 MetricKind::Absolute,
832 MetricValue::Gauge { value: 2.0 },
833 )
834 .with_namespace(Some("apache"))
835 .with_tags(Some(metric_tags!("state" => "idle_cleanup")))
836 .with_timestamp(Some(now)),
837 Metric::new(
838 "scoreboard",
839 MetricKind::Absolute,
840 MetricValue::Gauge { value: 2.0 },
841 )
842 .with_namespace(Some("apache"))
843 .with_tags(Some(metric_tags!("state" => "keepalive")))
844 .with_timestamp(Some(now)),
845 Metric::new(
846 "scoreboard",
847 MetricKind::Absolute,
848 MetricValue::Gauge { value: 1.0 },
849 )
850 .with_namespace(Some("apache"))
851 .with_tags(Some(metric_tags!("state" => "logging")))
852 .with_timestamp(Some(now)),
853 Metric::new(
854 "scoreboard",
855 MetricKind::Absolute,
856 MetricValue::Gauge { value: 325.0 },
857 )
858 .with_namespace(Some("apache"))
859 .with_tags(Some(metric_tags!("state" => "open")))
860 .with_timestamp(Some(now)),
861 Metric::new(
862 "scoreboard",
863 MetricKind::Absolute,
864 MetricValue::Gauge { value: 1.0 },
865 )
866 .with_namespace(Some("apache"))
867 .with_tags(Some(metric_tags!("state" => "reading")))
868 .with_timestamp(Some(now)),
869 Metric::new(
870 "scoreboard",
871 MetricKind::Absolute,
872 MetricValue::Gauge { value: 1.0 },
873 )
874 .with_namespace(Some("apache"))
875 .with_tags(Some(metric_tags!("state" => "sending")))
876 .with_timestamp(Some(now)),
877 Metric::new(
878 "scoreboard",
879 MetricKind::Absolute,
880 MetricValue::Gauge { value: 1.0 },
881 )
882 .with_namespace(Some("apache"))
883 .with_tags(Some(metric_tags!("state" => "starting")))
884 .with_timestamp(Some(now)),
885 Metric::new(
886 "scoreboard",
887 MetricKind::Absolute,
888 MetricValue::Gauge { value: 64.0 },
889 )
890 .with_namespace(Some("apache"))
891 .with_tags(Some(metric_tags!("state" => "waiting")))
892 .with_timestamp(Some(now)),
893 Metric::new(
894 "sent_bytes_total",
895 MetricKind::Absolute,
896 MetricValue::Counter { value: 222208.0 },
897 )
898 .with_namespace(Some("apache"))
899 .with_timestamp(Some(now)),
900 Metric::new(
901 "uptime_seconds_total",
902 MetricKind::Absolute,
903 MetricValue::Counter { value: 26.0 },
904 )
905 .with_namespace(Some("apache"))
906 .with_timestamp(Some(now)),
907 Metric::new(
908 "workers",
909 MetricKind::Absolute,
910 MetricValue::Gauge { value: 1.0 },
911 )
912 .with_namespace(Some("apache"))
913 .with_tags(Some(metric_tags!("state" => "busy")))
914 .with_timestamp(Some(now)),
915 Metric::new(
916 "workers",
917 MetricKind::Absolute,
918 MetricValue::Gauge { value: 74.0 },
919 )
920 .with_namespace(Some("apache"))
921 .with_tags(Some(metric_tags!("state" => "idle")))
922 .with_timestamp(Some(now)),
923 ]
924 );
925 assert_eq!(errors.len(), 0);
926 }
927
928 #[test]
929 fn test_parse_failure() {
930 let payload = r"
931ServerUptimeSeconds: not a number
932ConnsTotal: 1
933 ";
934
935 let (now, metrics, errors) = parse_sort(payload);
936
937 assert_event_data_eq!(
938 metrics,
939 vec![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 assert_eq!(errors.len(), 1);
949 }
950
951 fn parse_sort(payload: &str) -> (DateTime<Utc>, Vec<Metric>, Vec<ParseError>) {
952 let now: DateTime<Utc> = Utc::now();
953 let (mut metrics, errors) = parse(payload, Some("apache"), now, None).fold(
954 (vec![], vec![]),
955 |(mut metrics, mut errors), v| {
956 match v {
957 Ok(m) => metrics.push(m),
958 Err(e) => errors.push(e),
959 }
960 (metrics, errors)
961 },
962 );
963
964 metrics.sort_by_key(|metric| metric.series().to_string());
965
966 (now, metrics, errors)
967 }
968}