/src/main.rs
use std::collections::BTreeMap;
use std::thread;
use std::time;
use prometheus_client::encoding::text::encode;
use prometheus_client::encoding::EncodeLabelSet;
use prometheus_client::metrics::family::Family;
use prometheus_client::metrics::gauge::Gauge;
use prometheus_client::registry::Registry;
#[macro_use]
extern crate rocket;
use libzetta::zfs::{Properties, ZfsEngine, ZfsOpen3};
use libzetta::zpool::{open3::StatusOptions, ZpoolEngine, ZpoolOpen3};
use rocket::State;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
struct Labels {
pool: String,
dataset: String,
}
type FsMetric = Family<Labels, Gauge>;
struct Data {
used: FsMetric,
available: FsMetric,
}
#[derive(Responder)]
#[response(
status = 200,
content_type = "application/openmetrics-text; version=1.0.0; charset=utf-8"
)]
struct OpenMetrics(String);
#[get("/metrics")]
fn metrics(registry: &State<Registry>) -> OpenMetrics {
let mut encoded = String::new();
encode(&mut encoded, registry).expect("encoded");
OpenMetrics(encoded)
}
fn init_registry(registry: &mut Registry) -> BTreeMap<String, Data> {
let mut fsdata = BTreeMap::new();
let zpool = ZpoolOpen3::default();
let zfs = ZfsOpen3::new();
let pools = zpool
.status_all(StatusOptions::default())
.expect("zpool status_all");
for p in pools {
let pool = p.name();
let filesystems = zfs.list_filesystems(pool).expect("zfs");
for fs in filesystems {
let dataset = fs.display().to_string();
let used = Family::<Labels, Gauge>::default();
registry.register(
"zfs_dataset_space_used",
"space used by the dataset",
used.clone(),
);
let available = Family::<Labels, Gauge>::default();
registry.register(
"zfs_dataset_space_available",
"space available to the dataset",
available.clone(),
);
fsdata.insert(dataset, Data { used, available });
}
}
fsdata
}
fn collect_data(fsdata: &BTreeMap<String, Data>) {
let zpool = ZpoolOpen3::default();
let zfs = ZfsOpen3::new();
let pools = zpool
.status_all(StatusOptions::default())
.expect("zpool status_all");
for p in pools {
let pool = p.name();
let filesystems = zfs.list_filesystems(pool).expect("zfs");
for fs in filesystems {
let dataset = fs.display().to_string();
let properties = zfs.read_properties(fs).expect("zfs properties");
let filesystem_properties = match properties {
Properties::Filesystem(fsp) => fsp,
_ => unreachable!(),
};
let data = fsdata.get(&dataset).expect("fsdata get");
let labels = Labels { pool: pool.to_owned(), dataset };
data.used
.get_or_create(&labels)
.set(*filesystem_properties.used_by_dataset() as i64);
data.available
.get_or_create(&labels)
.set(*filesystem_properties.available());
}
}
}
#[launch]
fn rocket() -> _ {
let mut registry = <Registry>::default();
let fsdata = init_registry(&mut registry);
thread::spawn(move || {
loop {
collect_data(&fsdata);
thread::sleep(time::Duration::from_secs(60));
}
});
rocket::build()
.manage(registry)
.mount("/", routes![metrics])
}