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