use std::{borrow::Cow, collections::HashMap, fmt::Debug};
use chrono::{NaiveDateTime, TimeZone};
use prometheus_client::{
collector::Collector,
encoding::DescriptorEncoder,
metrics::gauge::ConstGauge,
registry::Registry,
};
use serde_with::serde_as;
use super::deserialize_bool;
use crate::{
prometheus_ext::{encode_extra, AsMetrics, EncodeExt},
with_timezone::WithTimezone,
MikrotikClient,
};
impl MikrotikClient {
#[tracing::instrument(level = "debug", err)]
pub async fn get_interfaces(&self) -> anyhow::Result<Vec<Interface>> {
self.get_all("/rest/interface").await
}
}
#[serde_as]
#[derive(serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Interface {
#[serde(rename = ".id")]
pub id: String,
pub name: String,
pub mac_address: Option<eui48::MacAddress>,
#[serde(deserialize_with = "deserialize_bool")]
pub disabled: bool,
#[serde_as(deserialize_as = "Option<MikrotikDatetimeDeserialize>")]
pub last_link_up_time: Option<NaiveDateTime>,
#[serde(flatten)]
pub extra: HashMap<String, String>,
}
impl<T> AsMetrics for WithTimezone<Interface, T>
where
T: TimeZone + Debug + Sync + Send + 'static + Copy,
{
fn register_as_metrics(self: Box<Self>, registry: &mut Registry) {
registry
.sub_registry_with_prefix("interface")
.sub_registry_with_label(("name".into(), Cow::from(self.0.name.clone())))
.register_collector(self);
}
}
impl<T> Collector for WithTimezone<Interface, T>
where
T: TimeZone + Debug + Sync + Send + 'static + Copy,
{
fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
const FIELD_BLACKLIST: [&str; 1] = ["test"];
let WithTimezone(interface, tz) = self;
let Interface {
id: _,
name: _,
mac_address,
disabled,
last_link_up_time,
extra,
} = interface;
ConstGauge::new(i64::from(!(*disabled))).encode_e(&mut encoder, "enabled", "")?;
if let Some(datetime) =
last_link_up_time.and_then(|up_time| up_time.and_local_timezone(*tz).single())
{
ConstGauge::new(datetime.timestamp()).encode_e(
&mut encoder,
"last_link_up_time",
"Last time the link went up",
)?;
};
let mut info_fields = Vec::with_capacity(extra.len());
if let Some(mac) = mac_address {
info_fields.push(("mac_address".to_owned(), mac.to_canonical()));
}
encode_extra(&mut encoder, extra, info_fields, &FIELD_BLACKLIST)?;
Ok(())
}
}
struct MikrotikDatetimeDeserialize;
impl<'de> serde_with::DeserializeAs<'de, NaiveDateTime> for MikrotikDatetimeDeserialize {
fn deserialize_as<D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s: &str = serde::de::Deserialize::deserialize(deserializer)?;
NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S").map_err(serde::de::Error::custom)
}
}