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