vector/sources/host_metrics/
cpu.rs1use 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 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 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 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 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 panic!("unrecognized metric name");
119 }
120 }
121
122 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}