vector/sources/host_metrics/
filesystem.rs1use futures::StreamExt;
2use heim::units::information::byte;
3#[cfg(not(windows))]
4use heim::units::ratio::ratio;
5use vector_lib::{configurable::configurable_component, metric_tags};
6
7use super::{FilterList, HostMetrics, default_all_devices, example_devices, filter_result};
8use crate::internal_events::{HostMetricsScrapeDetailError, HostMetricsScrapeFilesystemError};
9
10#[configurable_component]
12#[derive(Clone, Debug, Default)]
13pub struct FilesystemConfig {
14 #[serde(default = "default_all_devices")]
17 #[configurable(metadata(docs::examples = "example_devices()"))]
18 devices: FilterList,
19
20 #[serde(default = "default_all_devices")]
23 #[configurable(metadata(docs::examples = "example_filesystems()"))]
24 filesystems: FilterList,
25
26 #[serde(default = "default_all_devices")]
29 #[configurable(metadata(docs::examples = "example_mountpoints()"))]
30 mountpoints: FilterList,
31}
32
33fn example_filesystems() -> FilterList {
34 FilterList {
35 includes: Some(vec!["ntfs".try_into().unwrap()]),
36 excludes: Some(vec!["ext*".try_into().unwrap()]),
37 }
38}
39
40fn example_mountpoints() -> FilterList {
41 FilterList {
42 includes: Some(vec!["/home".try_into().unwrap()]),
43 excludes: Some(vec!["/raid*".try_into().unwrap()]),
44 }
45}
46
47impl HostMetrics {
48 pub async fn filesystem_metrics(&self, output: &mut super::MetricsBuffer) {
49 output.name = "filesystem";
50 match heim::disk::partitions().await {
51 Ok(partitions) => {
52 for (partition, usage) in partitions
53 .filter_map(|result| {
54 filter_result(result, "Failed to load/parse partition data.")
55 })
56 .map(|partition| {
58 self.config
59 .filesystem
60 .mountpoints
61 .contains_path(Some(partition.mount_point()))
62 .then_some(partition)
63 })
64 .filter_map(|partition| async { partition })
65 .map(|partition| {
67 self.config
68 .filesystem
69 .devices
70 .contains_path(partition.device().map(|d| d.as_ref()))
71 .then_some(partition)
72 })
73 .filter_map(|partition| async { partition })
74 .map(|partition| {
76 self.config
77 .filesystem
78 .filesystems
79 .contains_str(Some(partition.file_system().as_str()))
80 .then_some(partition)
81 })
82 .filter_map(|partition| async { partition })
83 .filter_map(|partition| async {
85 heim::disk::usage(partition.mount_point())
86 .await
87 .map_err(|error| {
88 emit!(HostMetricsScrapeFilesystemError {
89 message: "Failed to load partitions info.",
90 mount_point: partition
91 .mount_point()
92 .to_str()
93 .unwrap_or("unknown")
94 .to_string(),
95 error,
96 })
97 })
98 .map(|usage| (partition, usage))
99 .ok()
100 })
101 .collect::<Vec<_>>()
102 .await
103 {
104 let fs = partition.file_system();
105 let mut tags = metric_tags! {
106 "filesystem" => fs.as_str(),
107 "mountpoint" => partition.mount_point().to_string_lossy()
108 };
109 if let Some(device) = partition.device() {
110 tags.replace("device".into(), device.to_string_lossy().to_string());
111 }
112 output.gauge(
113 "filesystem_free_bytes",
114 usage.free().get::<byte>() as f64,
115 tags.clone(),
116 );
117 output.gauge(
118 "filesystem_total_bytes",
119 usage.total().get::<byte>() as f64,
120 tags.clone(),
121 );
122 output.gauge(
123 "filesystem_used_bytes",
124 usage.used().get::<byte>() as f64,
125 tags.clone(),
126 );
127 #[cfg(not(windows))]
128 output.gauge(
129 "filesystem_used_ratio",
130 usage.ratio().get::<ratio>() as f64,
131 tags,
132 );
133 }
134 }
135 Err(error) => {
136 emit!(HostMetricsScrapeDetailError {
137 message: "Failed to load partitions info.",
138 error,
139 });
140 }
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::{
148 super::{
149 HostMetrics, HostMetricsConfig, MetricsBuffer,
150 tests::{all_gauges, assert_filtered_metrics, count_name, count_tag},
151 },
152 FilesystemConfig,
153 };
154
155 #[cfg(not(windows))]
156 #[tokio::test]
157 async fn generates_filesystem_metrics() {
158 let mut buffer = MetricsBuffer::new(None);
159 HostMetrics::new(HostMetricsConfig::default())
160 .filesystem_metrics(&mut buffer)
161 .await;
162 let metrics = buffer.metrics;
163 assert!(!metrics.is_empty());
164 assert!(metrics.len().is_multiple_of(4));
165 assert!(all_gauges(&metrics));
166
167 for name in &[
169 "filesystem_free_bytes",
170 "filesystem_total_bytes",
171 "filesystem_used_bytes",
172 "filesystem_used_ratio",
173 ] {
174 assert_eq!(count_name(&metrics, name), metrics.len() / 4, "name={name}");
175 }
176
177 assert_eq!(count_tag(&metrics, "filesystem"), metrics.len());
179 assert_eq!(count_tag(&metrics, "mountpoint"), metrics.len());
180 }
181
182 #[cfg(windows)]
183 #[tokio::test]
184 async fn generates_filesystem_metrics() {
185 let mut buffer = MetricsBuffer::new(None);
186 HostMetrics::new(HostMetricsConfig::default())
187 .filesystem_metrics(&mut buffer)
188 .await;
189 let metrics = buffer.metrics;
190 assert!(!metrics.is_empty());
191 assert!(metrics.len() % 3 == 0);
192 assert!(all_gauges(&metrics));
193
194 for name in &[
196 "filesystem_free_bytes",
197 "filesystem_total_bytes",
198 "filesystem_used_bytes",
199 ] {
200 assert_eq!(
201 count_name(&metrics, name),
202 metrics.len() / 3,
203 "name={}",
204 name
205 );
206 }
207
208 assert_eq!(count_tag(&metrics, "filesystem"), metrics.len());
210 assert_eq!(count_tag(&metrics, "mountpoint"), metrics.len());
211 }
212
213 #[tokio::test]
214 async fn filesystem_metrics_filters_on_device() {
215 assert_filtered_metrics("device", |devices| async move {
216 let mut buffer = MetricsBuffer::new(None);
217 HostMetrics::new(HostMetricsConfig {
218 filesystem: FilesystemConfig {
219 devices,
220 ..Default::default()
221 },
222 ..Default::default()
223 })
224 .filesystem_metrics(&mut buffer)
225 .await;
226 buffer.metrics
227 })
228 .await;
229 }
230
231 #[tokio::test]
232 async fn filesystem_metrics_filters_on_filesystem() {
233 assert_filtered_metrics("filesystem", |filesystems| async move {
234 let mut buffer = MetricsBuffer::new(None);
235 HostMetrics::new(HostMetricsConfig {
236 filesystem: FilesystemConfig {
237 filesystems,
238 ..Default::default()
239 },
240 ..Default::default()
241 })
242 .filesystem_metrics(&mut buffer)
243 .await;
244 buffer.metrics
245 })
246 .await;
247 }
248
249 #[tokio::test]
250 async fn filesystem_metrics_filters_on_mountpoint() {
251 assert_filtered_metrics("mountpoint", |mountpoints| async move {
252 let mut buffer = MetricsBuffer::new(None);
253 HostMetrics::new(HostMetricsConfig {
254 filesystem: FilesystemConfig {
255 mountpoints,
256 ..Default::default()
257 },
258 ..Default::default()
259 })
260 .filesystem_metrics(&mut buffer)
261 .await;
262 buffer.metrics
263 })
264 .await;
265 }
266}