vector/sources/host_metrics/
cpu.rs

1use crate::internal_events::{HostMetricsScrapeDetailError, HostMetricsScrapeError};
2use futures::StreamExt;
3#[cfg(target_os = "linux")]
4use heim::cpu::os::linux::CpuTimeExt;
5use heim::units::time::second;
6use vector_lib::{event::MetricTags, metric_tags};
7
8use super::{filter_result, HostMetrics};
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::super::{HostMetrics, HostMetricsConfig, MetricsBuffer};
82    use super::{CPU_SECS_TOTAL, LOGICAL_CPUS, MODE, PHYSICAL_CPUS};
83
84    #[tokio::test]
85    async fn generates_cpu_metrics() {
86        let mut buffer = MetricsBuffer::new(None);
87        HostMetrics::new(HostMetricsConfig::default())
88            .cpu_metrics(&mut buffer)
89            .await;
90        let metrics = buffer.metrics;
91
92        assert!(!metrics.is_empty());
93
94        let mut n_physical_cpus = 0;
95        let mut n_logical_cpus = 0;
96
97        for metric in metrics {
98            // the cpu_seconds_total metrics must have mode
99            if metric.name() == CPU_SECS_TOTAL {
100                let tags = metric.tags();
101                assert!(
102                    tags.is_some(),
103                    "Metric cpu_seconds_total must have a mode tag"
104                );
105                let tags = tags.unwrap();
106                assert!(
107                    tags.contains_key(MODE),
108                    "Metric cpu_seconds_total must have a mode tag"
109                );
110            } else if metric.name() == PHYSICAL_CPUS {
111                n_physical_cpus += 1;
112            } else if metric.name() == LOGICAL_CPUS {
113                n_logical_cpus += 1;
114            } else {
115                // catch any bogey
116                panic!("unrecognized metric name");
117            }
118        }
119
120        // cpu count metrics should each be present once
121        assert_eq!(
122            n_logical_cpus, 1,
123            "There can only be one! (logical_cpus metric)"
124        );
125        assert_eq!(
126            n_physical_cpus, 1,
127            "There can only be one! (physical_cpus metric)"
128        );
129    }
130}