Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
3079c0095b | |||
0faaa89c68 | |||
df0a909c00 | |||
6a79d5adf1 | |||
83d8cd2d54 | |||
3cafa3d5ae | |||
d8c59413bd | |||
f7176ef42c | |||
c78e0837a4 | |||
fc9d3b4e45 | |||
2fbac41ed6 | |||
2972cb2476 | |||
854ddced3a | |||
490c2031d2 | |||
a803fdd213 | |||
1c1a31dbec | |||
576f5728a4 | |||
d16e06b253 | |||
58ff42a3c2 | |||
c2988995e1 | |||
a17ee534b2 | |||
0f3dc4ee21 | |||
82f7101f54 | |||
e6210b42c9 | |||
7cbb65afcb | |||
1263be9622 | |||
3b29d072dc | |||
d6900c7aa9 | |||
44251fa2f7 | |||
0448980dd9 | |||
eccfe68530 | |||
a1f772efe2 | |||
b5e72dacb0 | |||
e97e8f7d70 | |||
732733ac9c | |||
15160b197c | |||
0e8f270742 | |||
2c2c5359b6 | |||
c6d5505f2a |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
.idea
|
||||
.idea
|
||||
/complete
|
||||
/man
|
||||
|
|
68
CHANGELOG.md
68
CHANGELOG.md
|
@ -1,2 +1,66 @@
|
|||
### v0.1.0
|
||||
Initial implementation. This works, but still has compiler warnings during compilation.
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.5.1] - 2022-04-03
|
||||
### Fixed
|
||||
- shell completion for config file now completes files
|
||||
|
||||
## [v0.5.0] - 2022-04-02
|
||||
### Added
|
||||
- shell completion generation
|
||||
- man page generation
|
||||
- allow configuring timeout
|
||||
- allow configuring ping bucket sizes
|
||||
### Changed
|
||||
- replace prometheus crate with metrics
|
||||
- replace raw hyper with axum
|
||||
|
||||
## [v0.4.1] - 2021-04-27
|
||||
### Changed
|
||||
- code cleanup
|
||||
- switch over to tokio-ping fork, tokio-icmp-echo
|
||||
|
||||
## [v0.4.0] - 2021-04-23
|
||||
### Changed
|
||||
- update dependencies, including stable tokio this time.
|
||||
- migrate error handling to anyhow
|
||||
|
||||
## [v0.3.0] - 2021-04-19
|
||||
### Added
|
||||
- added healthcheck endpoint
|
||||
|
||||
### Changed
|
||||
- updated dependencies
|
||||
|
||||
## [v0.2.3] - 2020-04-08
|
||||
### Fixed
|
||||
- fix interval timings concurrent pings
|
||||
|
||||
## [v0.2.2] - 2020-04-06
|
||||
### Added
|
||||
- added documentation to the sample config file
|
||||
- improved help message
|
||||
|
||||
## [v0.2.1] - 2020-04-06
|
||||
### Fixed
|
||||
- fix interval timings
|
||||
|
||||
## [v0.2.0] - 2020-04-06
|
||||
### Added
|
||||
- added copyright headers
|
||||
- improved error handling
|
||||
|
||||
### Changed
|
||||
- switched from oping to tokio-ping. This removes the dependency on liboping and
|
||||
a bunch of custom build tooling.
|
||||
- update dependencies
|
||||
- use async/await syntax and updated async ecosystem libraries where possible
|
||||
|
||||
## [v0.1.0] - 2019-02-06
|
||||
Initial implementation. This works, but still has compiler warnings during
|
||||
compilation.
|
||||
|
|
1774
Cargo.lock
generated
1774
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
41
Cargo.toml
41
Cargo.toml
|
@ -1,20 +1,33 @@
|
|||
[package]
|
||||
name = "peshming"
|
||||
version = "0.1.0"
|
||||
version = "0.5.1"
|
||||
license = "AGPL-3.0-only"
|
||||
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]
|
||||
edition = "2018"
|
||||
repository = "https://git.jcg.re/jcgruenhage/peshming"
|
||||
keywords = ["ping", "icmp", "prometheus"]
|
||||
edition = "2021"
|
||||
description = "Pings configured hosts in a configurable intervals and exposes metrics for prometheus."
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
prometheus = "0.5.0"
|
||||
oping = "0.3.3"
|
||||
toml = "0.4.10"
|
||||
hyper = "0.12.23"
|
||||
lazy_static = "1.2.0"
|
||||
futures = "0.1.25"
|
||||
tokio = "0.1.15"
|
||||
clap = "2.32.0"
|
||||
fern = "0.5.7"
|
||||
log = "0.4.6"
|
||||
chrono = "0.4.6"
|
||||
serde = { version = "1.0.87", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
clap = { version = "3", features = ["derive", "wrap_help"] }
|
||||
fern = "0.6"
|
||||
log = { version = "0.4", features = ["serde"] }
|
||||
chrono = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_with = "1"
|
||||
tokio-icmp-echo = "0.4"
|
||||
futures-util = "0.3"
|
||||
anyhow = "1"
|
||||
async-anyhow-logger = "0.1"
|
||||
axum = "0.5"
|
||||
metrics = "0.18"
|
||||
metrics-exporter-prometheus = { version = "0.9", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "3", features = ["derive", "wrap_help"] }
|
||||
clap_mangen = "0.1"
|
||||
clap_complete = "3"
|
||||
|
|
19
README.md
19
README.md
|
@ -9,7 +9,8 @@ someone comes up with something better.
|
|||
|
||||
### Usage:
|
||||
```
|
||||
peshming 0.1.0
|
||||
$ peshming --help
|
||||
peshming 0.2.3
|
||||
Jan Christian Grünhage <jan.christian@gruenhage.xyz>
|
||||
Pings configured hosts in a configurable intervals and exposes metrics for prometheus.
|
||||
|
||||
|
@ -18,11 +19,23 @@ USAGE:
|
|||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-v, --verbose Be verbose (you can add this up to 4 times for more logs)
|
||||
-v, --verbose Be verbose (you can add this up to 4 times for more logs).
|
||||
By default, only errors are logged, so no output is a good thing.
|
||||
-V, --version Prints version information
|
||||
|
||||
ARGS:
|
||||
<config> Set config file
|
||||
|
||||
```
|
||||
For configuration options, see the included sample config file.
|
||||
For configuration options, see the included sample config file.
|
||||
|
||||
### Endpoints:
|
||||
There's two endpoints available:
|
||||
- `/metrics`, which serves the metrics
|
||||
- `/health`, which should always return a 200 status code
|
||||
|
||||
### Packaging Notes
|
||||
If you're packaging peshming, you might be interested in the env vars
|
||||
`PESHMING_MAN_DIR` and `PESHMING_COMPLETIONS_DIR`, which when set, will cause
|
||||
cargo to generate a man page and shell completions to the folder passed in
|
||||
those variables.
|
||||
|
|
60
build.rs
Normal file
60
build.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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 std::path::PathBuf;
|
||||
|
||||
use clap::{ArgEnum, CommandFactory};
|
||||
use clap_complete::{generate_to, Shell};
|
||||
use cli::Cli;
|
||||
|
||||
#[path = "src/cli.rs"] mod cli;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let mut cli = Cli::command();
|
||||
|
||||
if let Some(completions_dir) = std::env::var_os("PESHMING_COMPLETIONS_DIR") {
|
||||
let completions_dir: PathBuf = completions_dir.into();
|
||||
|
||||
std::fs::create_dir_all(&completions_dir)
|
||||
.expect("Could not create shell completions output folder.");
|
||||
|
||||
for shell in Shell::value_variants() {
|
||||
generate_to(*shell, &mut cli, "peshming", &completions_dir).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to generate shell completions for {}: {}.",
|
||||
shell, err
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(man_dir) = std::env::var_os("PESHMING_MAN_DIR") {
|
||||
let man_dir: PathBuf = man_dir.into();
|
||||
|
||||
std::fs::create_dir_all(&man_dir).expect("Could not create man page output folder.");
|
||||
|
||||
let man = clap_mangen::Man::new(cli);
|
||||
let mut buffer: Vec<u8> = Default::default();
|
||||
man.render(&mut buffer)?;
|
||||
|
||||
std::fs::write(man_dir.join("peshming.1"), buffer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,6 +1,21 @@
|
|||
# Defines where the metrics will be served.
|
||||
# Takes the format IPv4:port or [IPv6]:port
|
||||
listener = "[::]:9898"
|
||||
|
||||
[hosts]
|
||||
"1.1.1.1" = 5
|
||||
"1.0.0.1" = 5
|
||||
# Configuration of peshmings pinging behaviour
|
||||
[ping]
|
||||
# The timeout is specified in milliseconds, with a default of 3 seconds.
|
||||
# timeout = 3000
|
||||
|
||||
# Array of hosts to ping. Currently only supports plain IPs, no DNS names.
|
||||
# The format here is `"host" = interval in milliseconds`, so these examples
|
||||
# will ping the primary and secondary IP of cloudflare's 1.1.1.1 DNS service
|
||||
# every 500ms, or twice per second.
|
||||
[ping.hosts]
|
||||
"1.1.1.1" = 500
|
||||
"1.0.0.1" = 500
|
||||
|
||||
# Configure logging is also possible here instead of using the CLI. If both are
|
||||
# specified, the more verbose of the two will be used.
|
||||
# [log]
|
||||
# level = "ERROR"
|
||||
|
|
33
src/cli.rs
Normal file
33
src/cli.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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 clap::ValueHint;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[clap(author, version)]
|
||||
/// Pings configured hosts in a configurable intervals and exposes metrics for prometheus.
|
||||
pub struct Cli {
|
||||
/// Set config file
|
||||
#[clap(value_hint = ValueHint::FilePath)]
|
||||
pub config: std::path::PathBuf,
|
||||
#[clap(short, long, parse(from_occurrences))]
|
||||
/// Be verbose (you can add this up to 4 times for more logs).
|
||||
/// By default, only errors are logged, so no output is a good thing.
|
||||
pub verbose: usize,
|
||||
}
|
157
src/config.rs
157
src/config.rs
|
@ -1,33 +1,88 @@
|
|||
use std::collections::HashMap;
|
||||
use log::{error, warn, info, debug, trace};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use clap::{clap_app, crate_name, crate_version, crate_description, crate_authors};
|
||||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
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) hosts: HashMap<String, u64>,
|
||||
pub(crate) ping: PingConfig,
|
||||
#[serde(default)]
|
||||
pub(crate) log: LogConfig,
|
||||
}
|
||||
|
||||
pub(crate) fn setup_clap() -> (clap::ArgMatches<'static>) {
|
||||
clap_app!(myapp =>
|
||||
(name: crate_name!())
|
||||
(version: crate_version!())
|
||||
(author: crate_authors!())
|
||||
(about: crate_description!())
|
||||
(@arg config: +required "Set config file")
|
||||
(@arg v: -v --verbose ... "Be verbose (you can add this up to 4 times for more logs)")
|
||||
).get_matches()
|
||||
#[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>,
|
||||
}
|
||||
|
||||
pub(crate) fn setup_fern(level: u64) {
|
||||
let level = match level {
|
||||
0 => log::LevelFilter::Error,
|
||||
1 => log::LevelFilter::Warn,
|
||||
2 => log::LevelFilter::Info,
|
||||
3 => log::LevelFilter::Debug,
|
||||
_ => log::LevelFilter::Trace
|
||||
};
|
||||
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!(
|
||||
|
@ -39,7 +94,8 @@ pub(crate) fn setup_fern(level: u64) {
|
|||
})
|
||||
.level(level)
|
||||
.chain(std::io::stdout())
|
||||
.apply() {
|
||||
.apply()
|
||||
{
|
||||
Err(_) => {
|
||||
eprintln!("error setting up logging!");
|
||||
}
|
||||
|
@ -47,27 +103,48 @@ pub(crate) fn setup_fern(level: u64) {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_config(path: &str) -> Result<Config, Error> {
|
||||
let config_file_content = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str(&config_file_content)?)
|
||||
fn read_config(path: &std::path::Path) -> 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) struct Error {}
|
||||
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 })
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for Error {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
Error{}
|
||||
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)
|
||||
}
|
||||
|
||||
impl std::convert::From<toml::de::Error> for Error {
|
||||
fn from(_: toml::de::Error) -> Self {
|
||||
Error{}
|
||||
}
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
||||
impl std::convert::From<oping::PingError> for Error {
|
||||
fn from(_: oping::PingError) -> Self {
|
||||
Error{}
|
||||
}
|
||||
Ord::max(cli_level, config_level)
|
||||
}
|
||||
|
|
59
src/main.rs
59
src/main.rs
|
@ -1,27 +1,46 @@
|
|||
use log::{error, warn, info, debug, trace};
|
||||
use futures::future::lazy;
|
||||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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::Result;
|
||||
use async_anyhow_logger::catch;
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod metrics;
|
||||
mod ping;
|
||||
use crate::config::{Config, read_config, setup_clap, setup_fern};
|
||||
use crate::metrics::start_serving_metrics;
|
||||
use crate::ping::start_pinging_hosts;
|
||||
|
||||
fn main() {
|
||||
let clap = setup_clap();
|
||||
setup_fern(clap.occurrences_of("v"));
|
||||
let config = match read_config(clap.value_of("config").unwrap()) {
|
||||
Ok(config) => config,
|
||||
Err(_) => {
|
||||
error!("Couldn't read config file!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
use crate::{
|
||||
config::{setup_app, App},
|
||||
metrics::start_serving_metrics,
|
||||
ping::start_pinging_hosts,
|
||||
};
|
||||
|
||||
tokio::run(lazy(move || {
|
||||
start_serving_metrics(&config);
|
||||
start_pinging_hosts(&config);
|
||||
Ok(())
|
||||
}));
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let App { config, handle } = setup_app()?;
|
||||
|
||||
let ping_fut = catch(start_pinging_hosts(&config));
|
||||
let serve_fut = catch(start_serving_metrics(&config, handle));
|
||||
|
||||
futures::pin_mut!(ping_fut);
|
||||
futures::pin_mut!(serve_fut);
|
||||
|
||||
futures::future::select(ping_fut, serve_fut).await;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,51 +1,43 @@
|
|||
use crate::config::{Config};
|
||||
use lazy_static::lazy_static;
|
||||
use hyper::{Server, Response, Body, header::CONTENT_TYPE, service::service_fn_ok};
|
||||
use prometheus::{TextEncoder, Counter, Gauge, HistogramVec};
|
||||
use prometheus::*;
|
||||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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 crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use axum::{response::IntoResponse, routing::get, Router, Server};
|
||||
use log::info;
|
||||
use metrics_exporter_prometheus::PrometheusHandle;
|
||||
|
||||
use futures::future::Future;
|
||||
|
||||
lazy_static! {
|
||||
static ref HTTP_COUNTER: Counter = register_counter!(opts!(
|
||||
"http_requests_total",
|
||||
"Total number of HTTP requests made.",
|
||||
labels! {"handler" => "all",}
|
||||
))
|
||||
.unwrap();
|
||||
static ref HTTP_BODY_GAUGE: Gauge = register_gauge!(opts!(
|
||||
"http_response_size_bytes",
|
||||
"The HTTP response sizes in bytes.",
|
||||
labels! {"handler" => "all",}
|
||||
))
|
||||
.unwrap();
|
||||
static ref HTTP_REQ_HISTOGRAM: HistogramVec = register_histogram_vec!(
|
||||
"http_request_duration_seconds",
|
||||
"The HTTP request latencies in seconds.",
|
||||
&["handler"]
|
||||
)
|
||||
.unwrap();
|
||||
async fn metrics(handle: PrometheusHandle) -> impl IntoResponse {
|
||||
handle.render()
|
||||
}
|
||||
|
||||
pub(crate) fn start_serving_metrics(config: &Config) {
|
||||
let serve_metrics = || {
|
||||
service_fn_ok(|_req| {
|
||||
HTTP_COUNTER.inc();
|
||||
let timer = HTTP_REQ_HISTOGRAM.with_label_values(&["all"]).start_timer();
|
||||
let metric_families = prometheus::gather();
|
||||
let mut buffer = vec![];
|
||||
let encoder = TextEncoder::new();
|
||||
encoder.encode(&metric_families, &mut buffer).unwrap();
|
||||
HTTP_BODY_GAUGE.set(buffer.len() as f64);
|
||||
let mut res = Response::new(Body::from(buffer));
|
||||
res.headers_mut().insert(CONTENT_TYPE, encoder.format_type().parse().unwrap());
|
||||
timer.observe_duration();
|
||||
res
|
||||
})
|
||||
};
|
||||
println!("Listening on {}", &config.listener);
|
||||
let server = Server::bind(&config.listener)
|
||||
.serve(serve_metrics)
|
||||
.map_err(|err| eprintln!("server error: {}", err));
|
||||
tokio::spawn(server);
|
||||
}
|
||||
pub(crate) async fn start_serving_metrics(config: &Config, handle: PrometheusHandle) -> Result<()> {
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/metrics",
|
||||
get({
|
||||
let handle = handle.clone();
|
||||
move || metrics(handle)
|
||||
}),
|
||||
)
|
||||
.route("/health", get(|| async { "" }));
|
||||
let serve_future = Server::bind(&config.listener).serve(app.into_make_service());
|
||||
info!("Listening on {}", &config.listener);
|
||||
Ok(serve_future.await?)
|
||||
}
|
||||
|
|
122
src/ping.rs
122
src/ping.rs
|
@ -1,55 +1,75 @@
|
|||
use crate::config::{Config, Error};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::timer::{Interval};
|
||||
use futures::{future::{lazy, Future}, stream::Stream};
|
||||
use oping::{Ping};
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use prometheus::*;
|
||||
use lazy_static::lazy_static;
|
||||
/********************************************************************************
|
||||
* Prometheus exporter for monitoring network connectivity using icmp pings *
|
||||
* *
|
||||
* Copyright (C) 2019-2022 Jan Christian Grünhage *
|
||||
* Copyright (C) 2020-2021 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 crate::config::Config;
|
||||
use anyhow::{Context, Result};
|
||||
use async_anyhow_logger::catch;
|
||||
use log::{info, trace};
|
||||
use metrics::histogram;
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
use tokio_icmp_echo::{PingFuture, Pinger};
|
||||
|
||||
lazy_static! {
|
||||
static ref PING_HISTOGRAM : HistogramVec = register_histogram_vec!(
|
||||
"ping_rtt_milliseconds",
|
||||
"The ping round trip time in milliseconds",
|
||||
&["target"],
|
||||
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]
|
||||
).unwrap();
|
||||
pub(crate) async fn start_pinging_hosts(config: &Config) -> Result<()> {
|
||||
let pinger = Pinger::new().await.context("Couldn't create pinger")?;
|
||||
let mut handles = vec![];
|
||||
for (host, interval) in config.ping.hosts.clone() {
|
||||
info!("Spawn ping task for {}", host);
|
||||
handles.push(tokio::spawn(ping_host(
|
||||
pinger.clone(),
|
||||
host,
|
||||
interval,
|
||||
config.ping.timeout,
|
||||
)));
|
||||
}
|
||||
let (result, _, _) = futures::future::select_all(handles).await;
|
||||
result??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn start_pinging_hosts(config: &Config) {
|
||||
for (host, interval) in config.hosts.clone() {
|
||||
info!("Spawn ping task for {}", host);
|
||||
tokio::spawn(
|
||||
Interval::new(Instant::now(), Duration::from_millis(interval))
|
||||
.for_each(move |_| {
|
||||
let mut ping = Ping::new();
|
||||
ping.set_timeout(2.5);
|
||||
ping.add_host(&host);
|
||||
for response in match ping.send() {
|
||||
Ok(iterator) => iterator,
|
||||
Err(e) => {
|
||||
error!("Something went wrong sending the ping: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
}{
|
||||
if response.dropped > 0 {
|
||||
debug!("No response from host: {}", response.hostname);
|
||||
PING_HISTOGRAM
|
||||
.with_label_values(&[&host])
|
||||
.observe(2500.0)
|
||||
} else {
|
||||
debug!("Response from host {} (address {}): latency {} ms",
|
||||
response.hostname, response.address, response.latency_ms);
|
||||
trace!(" all details: {:?}", response);
|
||||
PING_HISTOGRAM
|
||||
.with_label_values(&[&host])
|
||||
.observe(response.latency_ms);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}).map_err(|_| ())
|
||||
);
|
||||
async fn ping_host(pinger: Pinger, host: IpAddr, interval: u64, timeout: Duration) -> Result<()> {
|
||||
let mut pingchain = pinger.chain(host).timeout(timeout);
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(interval));
|
||||
let host_string = host.to_string();
|
||||
loop {
|
||||
interval.tick().await;
|
||||
tokio::spawn(catch(handle_ping_result(
|
||||
pingchain.send(),
|
||||
host_string.clone(),
|
||||
timeout,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_ping_result(result: PingFuture, host: String, timeout: Duration) -> Result<()> {
|
||||
let pong = result.await.context(format!("Couldn't ping {}", &host))?;
|
||||
match pong {
|
||||
Some(time) => {
|
||||
let ms = time.as_millis();
|
||||
trace!("Received pong from {} after {} ms", &host, &ms);
|
||||
histogram!("ping_rtt_milliseconds", ms as f64, "target" => host);
|
||||
}
|
||||
None => {
|
||||
trace!("Received no response from {} within timeout", &host);
|
||||
histogram!("ping_rtt_milliseconds", timeout.as_millis() as f64, "target" => host);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue