151 lines
5.1 KiB
Rust
151 lines
5.1 KiB
Rust
/********************************************************************************
|
|
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
|
* *
|
|
* Copyright (C) 2019-2020 Jan Christian Grünhage *
|
|
* Copyright (C) 2020 Famedly GmbH *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Affero General Public License as *
|
|
* published by the Free Software Foundation, either version 3 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Affero General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Affero General Public License *
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
|
********************************************************************************/
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use log::info;
|
|
use metrics::{describe_histogram, register_histogram, Unit};
|
|
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle};
|
|
use serde::Deserialize;
|
|
use serde_with::{serde_as, DurationMilliSeconds};
|
|
|
|
use std::{collections::HashMap, time::Duration};
|
|
|
|
use crate::cli::Cli;
|
|
|
|
pub(crate) struct App {
|
|
pub(crate) config: Config,
|
|
pub(crate) handle: PrometheusHandle,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone)]
|
|
pub(crate) struct Config {
|
|
pub(crate) listener: std::net::SocketAddr,
|
|
pub(crate) ping: PingConfig,
|
|
#[serde(default)]
|
|
pub(crate) log: LogConfig,
|
|
}
|
|
|
|
#[serde_as]
|
|
#[derive(Deserialize, Clone)]
|
|
pub(crate) struct PingConfig {
|
|
#[serde_as(as = "DurationMilliSeconds<f64>")]
|
|
#[serde(default = "default_timeout")]
|
|
pub(crate) timeout: Duration,
|
|
#[serde(default = "default_buckets")]
|
|
pub(crate) bucket_sizes: Vec<f64>,
|
|
pub(crate) hosts: HashMap<std::net::IpAddr, u64>,
|
|
}
|
|
|
|
fn default_timeout() -> Duration {
|
|
Duration::from_secs(3)
|
|
}
|
|
|
|
fn default_buckets() -> Vec<f64> {
|
|
vec![
|
|
0.5, 1.0, 5.0, 10.0, 15.0, 20.0, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0,
|
|
350.0, 400.0, 450.0, 500.0, 550.0, 600.0, 650.0, 700.0, 750.0, 800.0, 900.0, 1000.0,
|
|
1250.0, 1500.0, 1750.0, 2000.0,
|
|
]
|
|
}
|
|
|
|
#[derive(Deserialize, Clone)]
|
|
pub(crate) struct LogConfig {
|
|
pub(crate) level: log::LevelFilter,
|
|
}
|
|
|
|
impl Default for LogConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
level: log::LevelFilter::Error,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn setup_clap() -> Cli {
|
|
Cli::parse()
|
|
}
|
|
|
|
fn setup_fern(level: log::LevelFilter) {
|
|
match fern::Dispatch::new()
|
|
.format(|out, message, record| {
|
|
out.finish(format_args!(
|
|
"[{}][{}] {}",
|
|
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
|
record.level(),
|
|
message
|
|
))
|
|
})
|
|
.level(level)
|
|
.chain(std::io::stdout())
|
|
.apply()
|
|
{
|
|
Err(_) => {
|
|
eprintln!("error setting up logging!");
|
|
}
|
|
_ => info!("logging set up properly"),
|
|
}
|
|
}
|
|
|
|
fn read_config(path: &str) -> Result<Config> {
|
|
let config_file_content = std::fs::read_to_string(path).context("Couldn't read config file")?;
|
|
toml::from_str(&config_file_content).context("Couldn't parse config file")
|
|
}
|
|
|
|
pub(crate) fn setup_app() -> Result<App> {
|
|
let cli = setup_clap();
|
|
let config = read_config(&cli.config).context("Couldn't read config file!")?;
|
|
setup_fern(determine_level(cli.verbose, config.log.level));
|
|
let handle = setup_prometheus(&config)?;
|
|
Ok(App { config, handle })
|
|
}
|
|
|
|
pub(crate) fn setup_prometheus(config: &Config) -> Result<PrometheusHandle> {
|
|
let handle = PrometheusBuilder::new()
|
|
.set_buckets_for_metric(
|
|
Matcher::Full("ping_rtt_milliseconds".into()),
|
|
&config.ping.bucket_sizes,
|
|
)?
|
|
.install_recorder()?;
|
|
|
|
for target in config.ping.hosts.keys() {
|
|
register_histogram!("ping_rtt_milliseconds", "target" => target.to_string());
|
|
}
|
|
|
|
describe_histogram!(
|
|
"ping_rtt_milliseconds",
|
|
Unit::Milliseconds,
|
|
"The ping round trip time in milliseconds"
|
|
);
|
|
|
|
Ok(handle)
|
|
}
|
|
|
|
fn determine_level(verbose_occurrences: usize, config_level: log::LevelFilter) -> log::LevelFilter {
|
|
let cli_level = match verbose_occurrences {
|
|
0 => log::LevelFilter::Error,
|
|
1 => log::LevelFilter::Warn,
|
|
2 => log::LevelFilter::Info,
|
|
3 => log::LevelFilter::Debug,
|
|
_ => log::LevelFilter::Trace,
|
|
};
|
|
|
|
Ord::max(cli_level, config_level)
|
|
}
|