init commit
This commit is contained in:
commit
a965314466
7 changed files with 1795 additions and 0 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1285
Cargo.lock
generated
Normal file
1285
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "koyori"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
arrayvec = "0.7"
|
||||
async-trait = "0.1"
|
||||
axum = "0.5"
|
||||
futures = "0.3"
|
||||
http = "0.2"
|
||||
once_cell = "1"
|
||||
prometheus-client = "0.16"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "process", "tracing"] }
|
||||
hostname = "0.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
console-subscriber = "0.1"
|
198
src/cpu.rs
Normal file
198
src/cpu.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use prometheus_client::encoding::text::Encode;
|
||||
use prometheus_client::metrics::family::Family;
|
||||
use prometheus_client::registry::Registry;
|
||||
use serde::Deserialize;
|
||||
use tokio::process::Command;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{F64Gauge, Metrics, HOSTNAME};
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
|
||||
struct Label {
|
||||
hostname: &'static str,
|
||||
cpu: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Exporter {
|
||||
usr: Family<Label, F64Gauge>,
|
||||
nice: Family<Label, F64Gauge>,
|
||||
sys: Family<Label, F64Gauge>,
|
||||
iowait: Family<Label, F64Gauge>,
|
||||
irq: Family<Label, F64Gauge>,
|
||||
soft: Family<Label, F64Gauge>,
|
||||
steal: Family<Label, F64Gauge>,
|
||||
guest: Family<Label, F64Gauge>,
|
||||
gnice: Family<Label, F64Gauge>,
|
||||
idle: Family<Label, F64Gauge>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Metrics for Exporter {
|
||||
fn prefix(&self) -> &'static str {
|
||||
"cpu"
|
||||
}
|
||||
|
||||
async fn should_collect(&self) -> Result<()> {
|
||||
Command::new("mpstat")
|
||||
.output()
|
||||
.await
|
||||
.context("While checking mpstat")
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
fn register(&self, sub_registry: &mut Registry) -> Result<()> {
|
||||
sub_registry.register("usr", "usr time (mpstat)", Box::new(self.usr.clone()));
|
||||
sub_registry.register("nice", "nice time (mpstat)", Box::new(self.nice.clone()));
|
||||
sub_registry.register("sys", "sys time (mpstat)", Box::new(self.sys.clone()));
|
||||
sub_registry.register(
|
||||
"iowait",
|
||||
"iowait time (mpstat)",
|
||||
Box::new(self.iowait.clone()),
|
||||
);
|
||||
sub_registry.register("irq", "irq time (mpstat)", Box::new(self.irq.clone()));
|
||||
sub_registry.register("soft", "soft time (mpstat)", Box::new(self.soft.clone()));
|
||||
sub_registry.register("steal", "steal time (mpstat)", Box::new(self.steal.clone()));
|
||||
sub_registry.register("guest", "guest time (mpstat)", Box::new(self.guest.clone()));
|
||||
sub_registry.register(
|
||||
"gnice",
|
||||
"guest nice time (mpstat)",
|
||||
Box::new(self.gnice.clone()),
|
||||
);
|
||||
sub_registry.register("idle ", "idle time (mpstat)", Box::new(self.idle.clone()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn collect(&self) -> Result<()> {
|
||||
trace!("Started");
|
||||
|
||||
let json = Command::new("mpstat")
|
||||
.args(["-o", "JSON", "-P", "ALL", "5", "1"])
|
||||
.output()
|
||||
.await
|
||||
.context("While collecting mpstat")?
|
||||
.stdout;
|
||||
let data: MpStatOutput = serde_json::from_slice(&json)?;
|
||||
let statistics = data
|
||||
.sysstat
|
||||
.hosts
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("Getting the first host")?
|
||||
.statistics;
|
||||
|
||||
let cpus = statistics
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("getting first stat measurement")?
|
||||
.cpu_load;
|
||||
|
||||
for cpu in cpus {
|
||||
let label = Label {
|
||||
hostname: &HOSTNAME,
|
||||
cpu: cpu.identifier,
|
||||
};
|
||||
self.usr.get_or_create(&label).set(cpu.usr);
|
||||
self.nice.get_or_create(&label).set(cpu.nice);
|
||||
self.sys.get_or_create(&label).set(cpu.sys);
|
||||
self.iowait.get_or_create(&label).set(cpu.iowait);
|
||||
self.irq.get_or_create(&label).set(cpu.irq);
|
||||
self.soft.get_or_create(&label).set(cpu.soft);
|
||||
self.steal.get_or_create(&label).set(cpu.steal);
|
||||
self.guest.get_or_create(&label).set(cpu.guest);
|
||||
self.gnice.get_or_create(&label).set(cpu.gnice);
|
||||
self.idle.get_or_create(&label).set(cpu.idle);
|
||||
}
|
||||
|
||||
trace!("Done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MpStatOutput {
|
||||
sysstat: SysStat,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SysStat {
|
||||
hosts: Vec<Host>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Host {
|
||||
statistics: Vec<Statistic>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct Statistic {
|
||||
cpu_load: Vec<CpuLoad>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CpuLoad {
|
||||
#[serde(rename = "cpu")]
|
||||
identifier: String,
|
||||
usr: f64,
|
||||
nice: f64,
|
||||
sys: f64,
|
||||
iowait: f64,
|
||||
irq: f64,
|
||||
soft: f64,
|
||||
steal: f64,
|
||||
guest: f64,
|
||||
gnice: f64,
|
||||
idle: f64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mp_stat {
|
||||
use anyhow::Result;
|
||||
use serde_json::json;
|
||||
|
||||
use super::MpStatOutput;
|
||||
|
||||
#[test]
|
||||
fn deserializes() -> Result<(), serde_json::Error> {
|
||||
let raw = json!({
|
||||
"sysstat": {
|
||||
"hosts": [{
|
||||
"nodename": "kurante",
|
||||
"sysname": "Linux",
|
||||
"release": "5.17.7-zen1-1-zen",
|
||||
"machine": "x86_64",
|
||||
"number-of-cpus": 16,
|
||||
"date": "05/27/2022",
|
||||
"statistics": [{
|
||||
"timestamp": "12:46:14 PM",
|
||||
"cpu-load": [
|
||||
{"cpu": "all", "usr": 3.19, "nice": 0.03, "sys": 0.73, "iowait": 0.08, "irq": 0.14, "soft": 0.08, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.77},
|
||||
{"cpu": "0", "usr": 2.41, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.80, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.78},
|
||||
{"cpu": "1", "usr": 1.20, "nice": 0.20, "sys": 0.80, "iowait": 1.00, "irq": 0.00, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.61},
|
||||
{"cpu": "2", "usr": 3.58, "nice": 0.00, "sys": 1.19, "iowait": 0.20, "irq": 0.60, "soft": 0.40, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 94.04},
|
||||
{"cpu": "3", "usr": 3.21, "nice": 0.00, "sys": 1.00, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.59},
|
||||
{"cpu": "4", "usr": 3.62, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.98},
|
||||
{"cpu": "5", "usr": 3.21, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.39},
|
||||
{"cpu": "6", "usr": 4.22, "nice": 0.00, "sys": 0.60, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.18},
|
||||
{"cpu": "7", "usr": 2.81, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.79},
|
||||
{"cpu": "8", "usr": 2.01, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 97.38},
|
||||
{"cpu": "9", "usr": 2.18, "nice": 0.00, "sys": 1.39, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.23},
|
||||
{"cpu": "10", "usr": 2.80, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.20},
|
||||
{"cpu": "11", "usr": 5.18, "nice": 0.00, "sys": 1.00, "iowait": 0.00, "irq": 0.20, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 93.43},
|
||||
{"cpu": "12", "usr": 2.61, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.99},
|
||||
{"cpu": "13", "usr": 4.83, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 94.77},
|
||||
{"cpu": "14", "usr": 1.79, "nice": 0.20, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 97.21},
|
||||
{"cpu": "15", "usr": 5.41, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 93.79}
|
||||
]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
serde_json::from_value::<MpStatOutput>(raw).map(drop)
|
||||
}
|
||||
}
|
178
src/main.rs
Normal file
178
src/main.rs
Normal file
|
@ -0,0 +1,178 @@
|
|||
#![warn(clippy::pedantic, clippy::nursery)]
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use axum::body::Full;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::get;
|
||||
use axum::{Extension, Router, Server};
|
||||
use futures::future::join_all;
|
||||
use futures::stream::iter;
|
||||
use futures::StreamExt;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use once_cell::sync::Lazy;
|
||||
use prometheus_client::encoding::text::encode;
|
||||
use prometheus_client::metrics::gauge::Gauge;
|
||||
use prometheus_client::registry::Registry;
|
||||
use tokio::select;
|
||||
use tokio::sync::Notify;
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, error, info};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
mod cpu;
|
||||
mod zfs;
|
||||
|
||||
pub static HOSTNAME: Lazy<String> = Lazy::new(|| {
|
||||
hostname::get()
|
||||
.expect("to get the hostname")
|
||||
.into_string()
|
||||
.expect("hostname to be valid utf-8")
|
||||
});
|
||||
|
||||
type MetricClient = dyn Metrics + Send + Sync;
|
||||
|
||||
type U64Gauge = Gauge<u64, AtomicU64>;
|
||||
type U32Gauge = Gauge<u32, AtomicU32>;
|
||||
type F64Gauge = Gauge<f64, AtomicU64>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::registry()
|
||||
// .with(console_subscriber::spawn())
|
||||
.with(fmt::layer())
|
||||
.with(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let mut registry = Registry::default();
|
||||
let namespaced_registry = registry.sub_registry_with_prefix("koyori");
|
||||
|
||||
let collectors: Vec<Box<MetricClient>> = iter([
|
||||
Box::new(zfs::Exporter::default()) as Box<MetricClient>,
|
||||
Box::new(cpu::Exporter::default()),
|
||||
])
|
||||
.filter_map(|collector| async {
|
||||
if let Err(e) = collector.should_collect().await {
|
||||
error!("Not collecting {}: {e}", collector.prefix());
|
||||
None
|
||||
} else {
|
||||
info!("{} collector enabled.", collector.prefix());
|
||||
Some(collector)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
info!("Started with {} collectors", collectors.len());
|
||||
|
||||
for collector in &collectors {
|
||||
collector.register(namespaced_registry.sub_registry_with_prefix(collector.prefix()))?;
|
||||
}
|
||||
|
||||
let stop_signal = Arc::new(Notify::new());
|
||||
let stop_signal_listener = Arc::clone(&stop_signal);
|
||||
let stop_signal_bool = Arc::new(AtomicBool::new(false));
|
||||
let exporter_fut = tokio::spawn(periodically_collect(
|
||||
collectors,
|
||||
Arc::clone(&stop_signal),
|
||||
Arc::clone(&stop_signal_bool),
|
||||
));
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
info!("Sending stop signal...");
|
||||
stop_signal.notify_waiters();
|
||||
stop_signal_bool.store(true, Ordering::Release);
|
||||
});
|
||||
|
||||
let registry = Arc::new(registry);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/metrics", get(|registry| async { metrics(registry) }))
|
||||
.layer(Extension(registry));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
info!("Listening on {}", addr);
|
||||
let server_fut = Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.with_graceful_shutdown(async move {
|
||||
stop_signal_listener.notified().await;
|
||||
info!("Stopping server...");
|
||||
});
|
||||
|
||||
if let Err(e) = server_fut.await {
|
||||
error!("An error occurred while starting the server: {e}");
|
||||
}
|
||||
|
||||
match exporter_fut.await {
|
||||
Ok(Ok(_)) => Ok(()),
|
||||
Ok(Err(e)) => {
|
||||
error!("An error occurred while collecting metrics: {e}");
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("The exporter task panicked: {e}");
|
||||
Err(e).context("While running the exporter task")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn periodically_collect(
|
||||
collectors: Vec<Box<MetricClient>>,
|
||||
stop_signal: Arc<Notify>,
|
||||
stop_signal_bool: Arc<AtomicBool>,
|
||||
) -> Result<()> {
|
||||
while !stop_signal_bool.load(Ordering::Acquire) {
|
||||
// Ensure a minimum 5s interval between collections
|
||||
let wait_until = Instant::now() + Duration::from_secs(5);
|
||||
debug!("Running collectors...");
|
||||
|
||||
let collectors_fut = join_all(collectors.iter().map(|c| c.collect()));
|
||||
|
||||
// Wait on the collectors to complete, or stop immediately if a stop
|
||||
// signal is received.
|
||||
let collector_results = select! {
|
||||
_ = stop_signal.notified() => break,
|
||||
res = collectors_fut => res,
|
||||
};
|
||||
|
||||
for res in collector_results {
|
||||
if let Err(e) = res {
|
||||
error!("Error while collecting metrics: {e}");
|
||||
}
|
||||
}
|
||||
tokio::time::sleep_until(wait_until).await;
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
|
||||
fn metrics(Extension(registry): Extension<Arc<Registry>>) -> impl IntoResponse {
|
||||
let mut encoded = Vec::new();
|
||||
encode(&mut encoded, ®istry).unwrap();
|
||||
Response::builder()
|
||||
.header(
|
||||
CONTENT_TYPE,
|
||||
"application/openmetrics-text; version=1.0.0; charset=utf-8",
|
||||
)
|
||||
.body(Full::from(encoded))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
trait Metrics {
|
||||
fn prefix(&self) -> &'static str;
|
||||
|
||||
async fn should_collect(&self) -> Result<()>;
|
||||
|
||||
fn register(&self, sub_registry: &mut Registry) -> Result<()>;
|
||||
|
||||
async fn collect(&self) -> Result<()>;
|
||||
}
|
108
src/zfs.rs
Normal file
108
src/zfs.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use anyhow::{Context, Result};
|
||||
use arrayvec::ArrayVec;
|
||||
use async_trait::async_trait;
|
||||
use prometheus_client::encoding::text::Encode;
|
||||
use prometheus_client::metrics::family::Family;
|
||||
use prometheus_client::registry::Registry;
|
||||
use tokio::process::Command;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{F64Gauge, Metrics, U32Gauge, U64Gauge, HOSTNAME};
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
|
||||
struct Label {
|
||||
hostname: &'static str,
|
||||
zpool: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Exporter {
|
||||
size: Family<Label, U64Gauge>,
|
||||
allocated: Family<Label, U64Gauge>,
|
||||
free: Family<Label, U64Gauge>,
|
||||
fragmentation: Family<Label, U64Gauge>,
|
||||
capacity: Family<Label, U64Gauge>,
|
||||
dedupe: Family<Label, F64Gauge>,
|
||||
health: Family<Label, U32Gauge>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Metrics for Exporter {
|
||||
fn prefix(&self) -> &'static str {
|
||||
"zfs"
|
||||
}
|
||||
|
||||
async fn should_collect(&self) -> Result<()> {
|
||||
Command::new("zpool")
|
||||
.arg("list")
|
||||
.output()
|
||||
.await
|
||||
.context("While checking zpool list")
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
fn register(&self, sub_registry: &mut Registry) -> Result<()> {
|
||||
sub_registry.register("size", "zpool size", Box::new(self.size.clone()));
|
||||
sub_registry.register(
|
||||
"allocated",
|
||||
"zpool allocated",
|
||||
Box::new(self.allocated.clone()),
|
||||
);
|
||||
sub_registry.register("free", "zpool free", Box::new(self.free.clone()));
|
||||
sub_registry.register(
|
||||
"fragmentation",
|
||||
"zpool fragmentation",
|
||||
Box::new(self.fragmentation.clone()),
|
||||
);
|
||||
sub_registry.register(
|
||||
"capacity",
|
||||
"zpool capacity",
|
||||
Box::new(self.capacity.clone()),
|
||||
);
|
||||
sub_registry.register("dedupe", "zpool dedupe", Box::new(self.dedupe.clone()));
|
||||
sub_registry.register("health", "zpool health", Box::new(self.health.clone()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn collect(&self) -> Result<()> {
|
||||
trace!("Started");
|
||||
|
||||
let zpool = Command::new("zpool")
|
||||
.args([
|
||||
"list",
|
||||
"-H",
|
||||
"-p",
|
||||
"-o",
|
||||
"name,size,alloc,free,frag,cap,dedup,health",
|
||||
])
|
||||
.output()
|
||||
.await?;
|
||||
for string_data in String::from_utf8(zpool.stdout)?.lines() {
|
||||
let mut info = string_data.split_whitespace();
|
||||
let zpool_name = info.next().context("getting the zpool name")?;
|
||||
let [size, alloc, free, frag, cap, dedup, health] = info
|
||||
.collect::<ArrayVec<_, 7>>()
|
||||
.into_inner()
|
||||
.map_err(|_| anyhow::anyhow!("parsing zpool info"))?;
|
||||
|
||||
let label = Label {
|
||||
hostname: &*HOSTNAME,
|
||||
zpool: zpool_name.to_string(),
|
||||
};
|
||||
|
||||
self.size.get_or_create(&label).set(size.parse()?);
|
||||
self.allocated.get_or_create(&label).set(alloc.parse()?);
|
||||
self.free.get_or_create(&label).set(free.parse()?);
|
||||
self.fragmentation.get_or_create(&label).set(frag.parse()?);
|
||||
self.capacity.get_or_create(&label).set(cap.parse()?);
|
||||
self.dedupe.get_or_create(&label).set(dedup.parse()?);
|
||||
self.health
|
||||
.get_or_create(&label)
|
||||
.set(if health == "ONLINE" { 0 } else { 1 });
|
||||
}
|
||||
|
||||
trace!("Done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue