Skip to main content

miden_node_store/state/
disk_monitor.rs

1use std::path::Path;
2use std::time::Duration;
3
4use miden_node_utils::spawn::spawn_blocking_in_span;
5use miden_node_utils::tracing::OpenTelemetrySpanExt;
6use tracing::info_span;
7
8use crate::COMPONENT;
9use crate::state::State;
10
11impl State {
12    /// Spawns a background task that periodically records the on-disk size of every store data path
13    /// as `OTel` span attributes.
14    pub fn spawn_disk_monitor(&self) -> tokio::task::JoinHandle<()> {
15        let data_directory = self.data_directory.clone();
16
17        tokio::spawn(async move {
18            let mut interval = tokio::time::interval(Duration::from_mins(5));
19            loop {
20                interval.tick().await;
21                let dir = data_directory.clone();
22                let span = info_span!(target: COMPONENT, "measure_disk_space_usage");
23                let result =
24                    spawn_blocking_in_span(move || measure_disk_usage_bytes(&dir), span.clone())
25                        .await;
26                match result {
27                    Ok(usage) => {
28                        span.set_attribute("db.sqlite.size", usage.sqlite_db);
29                        span.set_attribute("db.sqlite.wal.size", usage.sqlite_wal);
30                        span.set_attribute("db.block_store.size", usage.block_store);
31                        #[cfg(feature = "rocksdb")]
32                        {
33                            span.set_attribute("db.account_tree.size", usage.account_tree);
34                            span.set_attribute("db.nullifier_tree.size", usage.nullifier_tree);
35                            span.set_attribute(
36                                "db.account_state_forest.size",
37                                usage.account_state_forest,
38                            );
39                        }
40                    },
41                    Err(err) => span.set_error(&err),
42                }
43            }
44        })
45    }
46}
47
48/// Byte counts for each on-disk storage component.
49struct DiskUsage {
50    sqlite_db: u64,
51    sqlite_wal: u64,
52    block_store: u64,
53    #[cfg(feature = "rocksdb")]
54    account_tree: u64,
55    #[cfg(feature = "rocksdb")]
56    nullifier_tree: u64,
57    #[cfg(feature = "rocksdb")]
58    account_state_forest: u64,
59}
60
61/// Collects on-disk byte sizes for every store data path under `data_dir`.
62fn measure_disk_usage_bytes(data_dir: &Path) -> DiskUsage {
63    DiskUsage {
64        sqlite_db: path_size_bytes(&data_dir.join("miden-store.sqlite3")),
65        sqlite_wal: path_size_bytes(&data_dir.join("miden-store.sqlite3-wal")),
66        block_store: dir_size_bytes(&data_dir.join("blocks")),
67        #[cfg(feature = "rocksdb")]
68        account_tree: dir_size_bytes(&data_dir.join("accounttree")),
69        #[cfg(feature = "rocksdb")]
70        nullifier_tree: dir_size_bytes(&data_dir.join("nullifiertree")),
71        #[cfg(feature = "rocksdb")]
72        account_state_forest: dir_size_bytes(&data_dir.join("accountstateforest")),
73    }
74}
75
76/// Returns the byte length of the file at `path`, or `0` if it does not exist.
77fn path_size_bytes(path: &Path) -> u64 {
78    fs_err::metadata(path).map(|m| m.len()).unwrap_or(0)
79}
80
81/// Returns the total byte length of all files in `path` iteratively, or `0` on any error.
82fn dir_size_bytes(path: &Path) -> u64 {
83    let mut to_process = vec![path.to_path_buf()];
84    let mut total = 0u64;
85    while let Some(dir) = to_process.pop() {
86        let Ok(entries) = fs_err::read_dir(&dir) else {
87            continue;
88        };
89        for entry in entries.flatten() {
90            if let Ok(meta) = entry.metadata() {
91                if meta.is_dir() {
92                    to_process.push(entry.path());
93                } else {
94                    total += meta.len();
95                }
96            }
97        }
98    }
99    total
100}