Skip to content

Commit

Permalink
feat: add ping_resolve_error metric
Browse files Browse the repository at this point in the history
  • Loading branch information
knsd committed Nov 12, 2018
1 parent 461dd2e commit b619af0
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 221 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.

## 0.2.0 - 2018-11-12

- Add `ping_resolve_error` metric


## 0.1.0 - 2018-06-15

- Initial release
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ping-exporter"
version = "0.1.0"
version = "0.2.0"
authors = ["Fedor Gogolev <[email protected]>"]
publish = false

Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ Visiting [http://localhost:9346/ping?target=google.com](http://localhost:9346/pi

### `/ping` endpoint

| Metric name | Type | Description |
| -------------------- | --------- | ------------------------------------------------------ |
| ping_resolve_time | gauge | Time it take to resolve domain to an IP address |
| ping_packets_total | gauge | Total number of sent pings |
| ping_packets_success | gauge | Total number of success pings |
| ping_packets_failed | gauge | Total number of failed pings |
| ping_packets_loss | gauge | A percentage of failed pings from the total pings |
| ping_times | histogram | A histogram of round-trip times |
| Metric name | Type | Description |
| -------------------- | --------- | ---------------------------------------------------------------------------------------------- |
| ping_resolve_error | gauge | Boolean metric if there's an error during the resolve (error message will be in "error" label) |
| ping_resolve_time | gauge | Time it take to resolve domain to an IP address |
| ping_packets_total | gauge | Total number of sent pings |
| ping_packets_success | gauge | Total number of success pings |
| ping_packets_failed | gauge | Total number of failed pings |
| ping_packets_loss | gauge | A percentage of failed pings from the total pings |
| ping_times | histogram | A histogram of round-trip times |

### `/metrics` endpoint

Expand Down
182 changes: 106 additions & 76 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::net::IpAddr;

use futures::{Future, future};
use hyper::{Server, Request, Response, Body, Method, StatusCode};
use hyper::service::{Service, NewService};
use futures::{future, Future};
use hyper::service::{NewService, Service};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use serde_urlencoded;
use tacho;

use metrics::{METRICS, REPORTER};
use pinger::{Pinger, Error as PingerError};
use pinger::{Pinger, Report};
use settings::Settings;
use utils::{Protocol, NameOrIpAddr, boxed};
use utils::{boxed, NameOrIpAddr, Protocol};

lazy_static! {
static ref HTTP_PING: tacho::Counter = METRICS.counter("http_ping", "Number of /ping requests");
Expand Down Expand Up @@ -64,7 +64,7 @@ impl Service for App {
type ReqBody = Body;
type ResBody = Body;
type Error = Box<::std::error::Error + Send + Sync>;
type Future = Box<Future<Item=Response<Self::ResBody>, Error=Self::Error> + Send>;
type Future = Box<Future<Item = Response<Self::ResBody>, Error = Self::Error> + Send>;

fn call(&mut self, req: Request<<Self as Service>::ReqBody>) -> <Self as Service>::Future {
let st = ::time::precise_time_ns();
Expand All @@ -83,19 +83,20 @@ impl Service for App {
};

let future = match request_type {
RequestType::Unknown => {
boxed(future::err((StatusCode::NOT_FOUND, Body::from("Not Found"))))
}
RequestType::Metrics => {
boxed(get_metrics())
}
RequestType::Unknown => boxed(future::err((
StatusCode::NOT_FOUND,
Body::from("Not Found"),
))),
RequestType::Metrics => boxed(get_metrics()),
RequestType::Ping => {
let query = req.uri().query().unwrap_or("");

let mb_req = serde_urlencoded::from_str::<PingRequest>(query)
.map_err(|err| {
(StatusCode::BAD_REQUEST, Body::from(format!("Bad Request: {}", err)))
});
let mb_req = serde_urlencoded::from_str::<PingRequest>(query).map_err(|err| {
(
StatusCode::BAD_REQUEST,
Body::from(format!("Bad Request: {}", err)),
)
});

let future = future::result(mb_req);

Expand All @@ -106,17 +107,15 @@ impl Service for App {
}
};

let future = future.then(|request| {
match request {
Err((status_code, body)) => {
let mut response = Response::new(body);
*response.status_mut() = status_code;
future::ok(response)
}
Ok(body) => {
let mut response = Response::new(body);
future::ok(response)
}
let future = future.then(|request| match request {
Err((status_code, body)) => {
let mut response = Response::new(body);
*response.status_mut() = status_code;
future::ok(response)
}
Ok(body) => {
let mut response = Response::new(body);
future::ok(response)
}
});

Expand All @@ -133,11 +132,15 @@ impl Service for App {
}
}

fn get_metrics() -> impl Future<Item=Body, Error=((StatusCode, Body))> {
fn get_metrics() -> impl Future<Item = Body, Error = ((StatusCode, Body))> {
future::result(format_metrics(&REPORTER.peek()))
}

fn ping(request: PingRequest, settings: Settings, pinger: Pinger) -> impl Future<Item=Body, Error=((StatusCode, Body))> {
fn ping(
request: PingRequest,
settings: Settings,
pinger: Pinger,
) -> impl Future<Item = Body, Error = ((StatusCode, Body))> {
let count = request.count.unwrap_or(settings.count);
let ping_timeout = request.ping_timeout.unwrap_or(settings.ping_timeout);
let resolve_timeout = request.resolve_timeout.unwrap_or(settings.resolve_timeout);
Expand All @@ -161,93 +164,120 @@ fn ping(request: PingRequest, settings: Settings, pinger: Pinger) -> impl Future
};

if let Some(body) = bad_request_body {
return boxed(future::err((StatusCode::BAD_REQUEST, Body::from(body))))
return boxed(future::err((StatusCode::BAD_REQUEST, Body::from(body))));
}

let mut protocol = request.protocol.unwrap_or(settings.protocol);
match &request.target {
&NameOrIpAddr::IpAddr(IpAddr::V4(_)) => protocol = Protocol::V4,
&NameOrIpAddr::IpAddr(IpAddr::V6(_)) => protocol = Protocol::V6,
_ => ()
_ => (),
}

let name = request.target;

let future = pinger.ping(name.clone(), protocol, count, resolve_timeout, ping_timeout);
let future = future.map_err(|error| {
let body = Body::from(match error {
PingerError::ResolveError {..} => "Unable to resolve",
PingerError::ResolveTimeoutError {..} => "Resolve timeout",
PingerError::PingError {..} => "Internal error",
});
let future = future.map_err(|_| {
let body = Body::from("Internal error");
(StatusCode::INTERNAL_SERVER_ERROR, body)

});

let future = future.and_then(move |(resolve_time_ns, ip, pings)| {
let future = future.and_then(move |report| {
let (metrics, reporter) = tacho::new();
let metrics = metrics
.labeled("target", name)
.labeled("protocol", protocol)
.labeled("count", count)
.labeled("ping_timeout", ping_timeout)
.labeled("resolve_timeout", resolve_timeout)
.labeled("ip", ip);
.labeled("resolve_timeout", resolve_timeout);

set_metrics(&metrics, resolve_time_ns, pings);
set_metrics(metrics, report);

format_metrics(&reporter.peek())
});

boxed(future)
}

pub fn set_metrics(metrics: &tacho::Scope, resolve_time_ns: u64, pings: Vec<Option<f64>>) {
metrics.gauge("ping_resolve_time",
"Time it take to resolve domain to an IP address")
.set((resolve_time_ns / 1000000) as usize);

let times = metrics.stat("ping_times", "A histogram of round-trip times");

let mut failures = 0;
let mut successful = 0;
let total = pings.len();

for reply_time in pings {
match reply_time {
Some(reply_time) => {
times.add((reply_time * 1000.0) as u64);
successful += 1;
},
None => {
failures += 1;
pub fn set_metrics(mut metrics: tacho::Scope, report: Report) {
let resolve_error: Option<&'static str>;

match report {
Report::ResolveTimedOut => resolve_error = Some("timed out"),
Report::ResolveNotFound => resolve_error = Some("not found"),
Report::ResolveOtherError => resolve_error = Some("internal error"),
Report::Success {
resolve_time_ns,
addr,
pings,
} => {
resolve_error = None;
metrics = metrics.labeled("ip", addr);

metrics
.gauge(
"ping_resolve_time",
"Time it take to resolve domain to an IP address",
).set((resolve_time_ns / 1000000) as usize);

let times = metrics.stat("ping_times", "A histogram of round-trip times");

let mut failures = 0;
let mut successful = 0;
let total = pings.len();

for reply_time in pings {
match reply_time {
Some(reply_time) => {
times.add((reply_time * 1000.0) as u64);
successful += 1;
}
None => {
failures += 1;
}
}
}

metrics
.gauge("ping_packets_total", "Total number of sent pings")
.set(total);
metrics
.gauge("ping_packets_success", "Total number of success pings")
.set(successful);
metrics
.gauge("ping_packets_failed", "Total number of failed pings")
.set(failures);

if total > 0 {
let loss = failures as f64 / total as f64 * 100.0;
metrics
.gauge(
"ping_packets_loss",
"A percentage of failed pings from the total pings",
).set(loss as usize);
}
}
}
};

metrics.gauge("ping_packets_total", "Total number of sent pings")
.set(total);
metrics.gauge("ping_packets_success", "Total number of success pings")
.set(successful);
metrics.gauge("ping_packets_failed", "Total number of failed pings")
.set(failures);

if total > 0 {
let loss = failures as f64 / total as f64 * 100.0;
metrics.gauge("ping_packets_loss",
"A percentage of failed pings from the total pings")
.set(loss as usize);
if let Some(error) = resolve_error {
metrics = metrics.labeled("error", error);
}

metrics.gauge("ping_resolve_error", "Boolean metric if there's an error during the resolve (error message will be in \"error\" label)")
.set(resolve_error.map(|_| 1).unwrap_or(0));
}

fn format_metrics(report: &tacho::Report) -> Result<Body, (StatusCode, Body)> {
match tacho::prometheus::string(report) {
Ok(s) => Ok(Body::from(s)),
Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, Body::from("Internal Error"))),
Err(_) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Body::from("Internal Error"),
)),
}
}

pub fn server(settings: Settings, pinger: Pinger) -> impl Future<Item=(), Error=()> {
pub fn server(settings: Settings, pinger: Pinger) -> impl Future<Item = (), Error = ()> {
let builder = Server::try_bind(&settings.listen);
let future = future::result(builder).and_then(move |builder| {
info!("Listening on {}", &settings.listen);
Expand Down
Loading

0 comments on commit b619af0

Please sign in to comment.