vector/sources/host_metrics/
cpu.rs

1use futures::StreamExt;
2#[cfg(target_os = "linux")]
3use heim::cpu::os::linux::CpuTimeExt;
4use heim::units::time::second;
5use vector_lib::{event::MetricTags, metric_tags};
6
7use super::{HostMetrics, filter_result};
8use crate::internal_events::{HostMetricsScrapeDetailError, HostMetricsScrapeError};
9
10const MODE: &str = "mode";
11const CPU_SECS_TOTAL: &str = "cpu_seconds_total";
12const LOGICAL_CPUS: &str = "logical_cpus";
13const PHYSICAL_CPUS: &str = "physical_cpus";
14
15impl HostMetrics {
16    pub async fn cpu_metrics(&self, output: &mut super::MetricsBuffer) {
17        // adds the metrics from cpu time for each cpu
18        match heim::cpu::times().await {
19            Ok(times) => {
20                let times: Vec<_> = times
21                    .filter_map(|result| filter_result(result, "Failed to load/parse CPU time."))
22                    .collect()
23                    .await;
24                output.name = "cpu";
25                for (index, times) in times.into_iter().enumerate() {
26                    let tags = |name: &str| metric_tags!(MODE => name, "cpu" => index.to_string());
27                    output.counter(CPU_SECS_TOTAL, times.idle().get::<second>(), tags("idle"));
28                    #[cfg(target_os = "linux")]
29                    output.counter(
30                        CPU_SECS_TOTAL,
31                        times.io_wait().get::<second>(),
32                        tags("io_wait"),
33                    );
34                    #[cfg(target_os = "linux")]
35                    output.counter(CPU_SECS_TOTAL, times.nice().get::<second>(), tags("nice"));
36                    output.counter(
37                        CPU_SECS_TOTAL,
38                        times.system().get::<second>(),
39                        tags("system"),
40                    );
41                    output.counter(CPU_SECS_TOTAL, times.user().get::<second>(), tags("user"));
42                }
43            }
44            Err(error) => {
45                emit!(HostMetricsScrapeDetailError {
46                    message: "Failed to load CPU times.",
47                    error,
48                });
49            }
50        }
51        // adds the logical cpu count gauge
52        match heim::cpu::logical_count().await {
53            Ok(count) => output.gauge(LOGICAL_CPUS, count as f64, MetricTags::default()),
54            Err(error) => {
55                emit!(HostMetricsScrapeDetailError {
56                    message: "Failed to load logical CPU count.",
57                    error,
58                });
59            }
60        }
61        // adds the physical cpu count gauge
62        match heim::cpu::physical_count().await {
63            Ok(Some(count)) => output.gauge(PHYSICAL_CPUS, count as f64, MetricTags::default()),
64            Ok(None) => {
65                emit!(HostMetricsScrapeError {
66                    message: "Unable to determine physical CPU count.",
67                });
68            }
69            Err(error) => {
70                emit!(HostMetricsScrapeDetailError {
71                    message: "Failed to load physical CPU count.",
72                    error,
73                });
74            }
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::{
82        super::{HostMetrics, HostMetricsConfig, MetricsBuffer},
83        CPU_SECS_TOTAL, LOGICAL_CPUS, MODE, PHYSICAL_CPUS,
84    };
85
86    #[tokio::test]
87    async fn generates_cpu_metrics() {
88        let mut buffer = MetricsBuffer::new(None);
89        HostMetrics::new(HostMetricsConfig::default())
90            .cpu_metrics(&mut buffer)
91            .await;
92        let metrics = buffer.metrics;
93
94        assert!(!metrics.is_empty());
95
96        let mut n_physical_cpus = 0;
97        let mut n_logical_cpus = 0;
98
99        for metric in metrics {
100            // the cpu_seconds_total metrics must have mode
101            if metric.name() == CPU_SECS_TOTAL {
102                let tags = metric.tags();
103                assert!(
104                    tags.is_some(),
105                    "Metric cpu_seconds_total must have a mode tag"
106                );
107                let tags = tags.unwrap();
108                assert!(
109                    tags.contains_key(MODE),
110                    "Metric cpu_seconds_total must have a mode tag"
111                );
112            } else if metric.name() == PHYSICAL_CPUS {
113                n_physical_cpus += 1;
114            } else if metric.name() == LOGICAL_CPUS {
115                n_logical_cpus += 1;
116            } else {
117                // catch any bogey
118                panic!("unrecognized metric name");
119            }
120        }
121
122        // cpu count metrics should each be present once
123        assert_eq!(
124            n_logical_cpus, 1,
125            "There can only be one! (logical_cpus metric)"
126        );
127        assert_eq!(
128            n_physical_cpus, 1,
129            "There can only be one! (physical_cpus metric)"
130        );
131    }
132}