vector/sources/apache_metrics/
parser.rs

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
25/// enum of mod_status fields we care about
26enum 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
99/// Parses the text output from Apache's mod_status and returns:
100///
101/// - A list of metrics generated from the output
102/// - A list of parse errors that were encountered
103///
104/// # Arguments
105///
106/// - `payload` - the mod_status output
107/// - `namespace` - the namespace to put the generated metrics in
108/// - `now` - the time the payload was fetched
109/// - `tags` - any base tags to apply to the metrics
110pub 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    // We use a HashMap rather than a Vector as mod_status has
117    // BusyWorkers/IdleWorkers repeated
118    // https://bz.apache.org/bugzilla/show_bug.cgi?id=63300
119    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 ExtendedStatus: Off
488    // https://httpd.apache.org/docs/2.4/mod/core.html#extendedstatus
489    #[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 ExtendedStatus: On
672    // https://httpd.apache.org/docs/2.4/mod/core.html#extendedstatus
673    #[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}