referrerpolicy=no-referrer-when-downgrade

sc_storage_monitor/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use clap::Args;
20use sp_core::traits::SpawnEssentialNamed;
21use std::{
22	io,
23	path::{Path, PathBuf},
24	time::Duration,
25};
26
27const LOG_TARGET: &str = "storage-monitor";
28
29/// Result type used in this crate.
30pub type Result<T> = std::result::Result<T, Error>;
31
32/// Error type used in this crate.
33#[derive(Debug, thiserror::Error)]
34pub enum Error {
35	#[error("IO Error")]
36	IOError(#[from] io::Error),
37	#[error("Out of storage space: available {0}MiB, required {1}MiB")]
38	StorageOutOfSpace(u64, u64),
39}
40
41/// Parameters used to create the storage monitor.
42#[derive(Default, Debug, Clone, Args)]
43pub struct StorageMonitorParams {
44	/// Required available space on database storage.
45	///
46	/// If available space for DB storage drops below the given threshold, node will
47	/// be gracefully terminated.
48	///
49	/// If `0` is given monitoring will be disabled.
50	#[arg(long = "db-storage-threshold", value_name = "MiB", default_value_t = 1024)]
51	pub threshold: u64,
52
53	/// How often available space is polled.
54	#[arg(long = "db-storage-polling-period", value_name = "SECONDS", default_value_t = 5, value_parser = clap::value_parser!(u32).range(1..))]
55	pub polling_period: u32,
56}
57
58/// Storage monitor service: checks the available space for the filesystem for given path.
59pub struct StorageMonitorService {
60	/// watched path
61	path: PathBuf,
62	/// number of megabytes that shall be free on the filesystem for watched path
63	threshold: u64,
64	/// storage space polling period
65	polling_period: Duration,
66}
67
68impl StorageMonitorService {
69	/// Creates new StorageMonitorService for given client config
70	pub fn try_spawn(
71		parameters: StorageMonitorParams,
72		path: PathBuf,
73		spawner: &impl SpawnEssentialNamed,
74	) -> Result<()> {
75		if parameters.threshold == 0 {
76			log::info!(
77				target: LOG_TARGET,
78				"StorageMonitorService: threshold `0` given, storage monitoring disabled",
79			);
80		} else {
81			log::debug!(
82				target: LOG_TARGET,
83				"Initializing StorageMonitorService for db path: {}",
84				path.display()
85			);
86
87			Self::check_free_space(&path, parameters.threshold)?;
88
89			let storage_monitor_service = StorageMonitorService {
90				path,
91				threshold: parameters.threshold,
92				polling_period: Duration::from_secs(parameters.polling_period.into()),
93			};
94
95			spawner.spawn_essential(
96				"storage-monitor",
97				None,
98				Box::pin(storage_monitor_service.run()),
99			);
100		}
101
102		Ok(())
103	}
104
105	/// Main monitoring loop, intended to be spawned as essential task. Quits if free space drop
106	/// below threshold.
107	async fn run(self) {
108		loop {
109			tokio::time::sleep(self.polling_period).await;
110			if Self::check_free_space(&self.path, self.threshold).is_err() {
111				break
112			};
113		}
114	}
115
116	/// Returns free space in MiB, or error if statvfs failed.
117	fn free_space(path: &Path) -> Result<u64> {
118		Ok(fs4::available_space(path).map(|s| s / 1024 / 1024)?)
119	}
120
121	/// Checks if the amount of free space for given `path` is above given `threshold` in MiB.
122	/// If it dropped below, error is returned.
123	/// System errors are silently ignored.
124	fn check_free_space(path: &Path, threshold: u64) -> Result<()> {
125		match StorageMonitorService::free_space(path) {
126			Ok(available_space) => {
127				log::trace!(
128					target: LOG_TARGET,
129					"free: {available_space} , threshold: {threshold}.",
130				);
131
132				if available_space < threshold {
133					log::error!(target: LOG_TARGET, "Available space {available_space}MiB for path `{}` dropped below threshold: {threshold}MiB , terminating...", path.display());
134					Err(Error::StorageOutOfSpace(available_space, threshold))
135				} else {
136					Ok(())
137				}
138			},
139			Err(e) => {
140				log::error!(target: LOG_TARGET, "Could not read available space: {e:?}.");
141				Err(e)
142			},
143		}
144	}
145}