Make telemetry::metrics application-agnostic (#82)

Now that transport details have been separated into modules, the
`metrics::Root` type makes more sense as a `telemetry::Report` type.
With this change, the `telemetry::metrics` module holds only the
abstract structural details of metrics reporting.

To this end:
- `metrics::Root` is now `telemetry::Report`
- `metrics::Serve` is now generic over `FmtMetrics`. It's only an
  implementation detail that the `telemetry::Report` type is used.
- all _Report_ types now implement `Clone` so that the main report
  can be cloned for each connection (i.e. from prometheus).
This commit is contained in:
Oliver Gould 2018-08-22 17:43:17 -07:00 committed by GitHub
parent d6d05905f1
commit 60683aeca3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 105 deletions

View File

@ -1,85 +1,16 @@
//! Records and serves Prometheus metrics.
//!
//! # A note on label formatting
//!
//! Prometheus labels are represented as a comma-separated list of values
//! Since the proxy labels its metrics with a fixed set of labels
//! which we know in advance, we represent these labels using a number of
//! `struct`s, all of which implement `fmt::Display`. Some of the label
//! `struct`s contain other structs which represent a subset of the labels
//! which can be present on metrics in that scope. In this case, the
//! `fmt::Display` impls for those structs call the `fmt::Display` impls for
//! the structs that they own. This has the potential to complicate the
//! insertion of commas to separate label values.
//!
//! In order to ensure that commas are added correctly to separate labels,
//! we expect the `fmt::Display` implementations for label types to behave in
//! a consistent way: A label struct is *never* responsible for printing
//! leading or trailing commas before or after the label values it contains.
//! If it contains multiple labels, it *is* responsible for ensuring any
//! labels it owns are comma-separated. This way, the `fmt::Display` impl for
//! any struct that represents a subset of the labels are position-agnostic;
//! they don't need to know if there are other labels before or after them in
//! the formatted output. The owner is responsible for managing that.
//!
//! If this rule is followed consistently across all structs representing
//! labels, we can add new labels or modify the existing ones without having
//! to worry about missing commas, double commas, or trailing commas at the
//! end of the label set (all of which will make Prometheus angry).
use std::fmt;
//! Utilties for exposing metrics to Prometheus.
mod counter;
mod gauge;
mod histogram;
pub mod latency;
pub mod prom;
mod prom;
mod scopes;
mod serve;
pub use self::counter::Counter;
pub use self::gauge::Gauge;
pub use self::histogram::Histogram;
pub use self::prom::{FmtMetrics, FmtLabels, FmtMetric};
pub use self::prom::{FmtMetrics, FmtLabels, FmtMetric, Metric};
pub use self::scopes::Scopes;
pub use self::serve::Serve;
use super::{http, process, tls_config_reload, transport};
/// The root scope for all runtime metrics.
#[derive(Debug)]
pub struct Root {
http: http::Report,
transports: transport::Report,
tls_config_reload: tls_config_reload::Report,
process: process::Report,
}
// ===== impl Root =====
impl Root {
pub(super) fn new(
http: http::Report,
transports: transport::Report,
tls_config_reload: tls_config_reload::Report,
process: process::Report,
) -> Self {
Self {
http,
transports,
tls_config_reload,
process,
}
}
}
// ===== impl Root =====
impl FmtMetrics for Root {
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.http.fmt_metrics(f)?;
self.transports.fmt_metrics(f)?;
self.tls_config_reload.fmt_metrics(f)?;
self.process.fmt_metrics(f)?;
Ok(())
}
}

View File

@ -11,17 +11,16 @@ use hyper::{
use std::error::Error;
use std::fmt;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use tokio::executor::current_thread::TaskExecutor;
use super::{prom::FmtMetrics, Root};
use super::FmtMetrics;
use task;
use transport::BoundPort;
/// Serve Prometheues metrics.
#[derive(Debug, Clone)]
pub struct Serve {
metrics: Arc<Mutex<Root>>,
pub struct Serve<M: FmtMetrics> {
metrics: M,
}
#[derive(Debug)]
@ -32,9 +31,11 @@ enum ServeError {
// ===== impl Serve =====
impl Serve {
pub fn new(metrics: &Arc<Mutex<Root>>) -> Self {
Self { metrics: metrics.clone() }
impl<M: FmtMetrics> Serve<M> {
pub fn new(metrics: M) -> Self {
Self {
metrics,
}
}
fn is_gzip<B>(req: &Request<B>) -> bool {
@ -46,7 +47,9 @@ impl Serve {
.unwrap_or(false)
})
}
}
impl<M: FmtMetrics + Clone + Send + 'static> Serve<M> {
pub fn serve(self, bound_port: BoundPort) -> impl Future<Item = (), Error = ()> {
use hyper;
@ -80,7 +83,7 @@ impl Serve {
}
}
impl Service for Serve {
impl<M: FmtMetrics> Service for Serve<M> {
type ReqBody = Body;
type ResBody = Body;
type Error = io::Error;
@ -95,13 +98,10 @@ impl Service for Serve {
return future::ok(rsp);
}
let metrics = self.metrics.lock()
.expect("metrics lock poisoned");
let resp = if Self::is_gzip(&req) {
trace!("gzipping metrics");
let mut writer = GzEncoder::new(Vec::<u8>::new(), CompressionOptions::fast());
write!(&mut writer, "{}", (*metrics).as_display())
write!(&mut writer, "{}", self.metrics.as_display())
.and_then(|_| writer.finish())
.map_err(ServeError::from)
.and_then(|body| {
@ -113,7 +113,7 @@ impl Service for Serve {
})
} else {
let mut writer = Vec::<u8>::new();
write!(&mut writer, "{}", (*metrics).as_display())
write!(&mut writer, "{}", self.metrics.as_display())
.map_err(ServeError::from)
.and_then(|_| {
Response::builder()

View File

@ -5,8 +5,8 @@ macro_rules! metrics {
{ $( $name:ident : $kind:ty { $help:expr } ),+ } => {
$(
#[allow(non_upper_case_globals)]
const $name: ::telemetry::metrics::prom::Metric<'static, $kind> =
::telemetry::metrics::prom::Metric {
const $name: ::telemetry::metrics::Metric<'static, $kind> =
::telemetry::metrics::Metric {
name: stringify!($name),
help: $help,
_p: ::std::marker::PhantomData,
@ -19,15 +19,18 @@ mod errno;
pub mod http;
mod metrics;
mod process;
mod report;
pub mod tap;
pub mod tls_config_reload;
pub mod transport;
use self::errno::Errno;
pub use self::http::event::Event;
pub use self::metrics::{Serve as ServeMetrics};
pub use self::report::Report;
pub use self::http::Sensors;
pub type ServeMetrics = metrics::Serve<Report>;
pub fn new(
start_time: SystemTime,
metrics_retain_idle: Duration,
@ -36,14 +39,8 @@ pub fn new(
let process = process::Report::new(start_time);
let (http_sensors, http_report) = http::new(metrics_retain_idle, taps);
let (transport_registry, transport_report) = transport::new();
let (tls_config_sensor, tls_config_fmt) = tls_config_reload::new();
let (tls_config_sensor, tls_config_report) = tls_config_reload::new();
let report = Arc::new(Mutex::new(metrics::Root::new(
http_report,
transport_report,
tls_config_fmt,
process,
)));
let serve = ServeMetrics::new(&report);
(http_sensors, transport_registry, tls_config_sensor, serve)
let report = Report::new(http_report, transport_report, tls_config_report, process);
(http_sensors, transport_registry, tls_config_sensor, ServeMetrics::new(report))
}

View File

@ -1,7 +1,7 @@
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
use super::metrics::{Gauge, prom::FmtMetrics};
use super::metrics::{FmtMetrics, Gauge};
use self::system::System;
@ -11,7 +11,7 @@ metrics! {
}
}
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Report {
start_time: Gauge,
system: Option<System>,
@ -74,7 +74,7 @@ mod system {
}
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub(super) struct System {
page_size: u64,
clock_ticks_per_sec: u64,
@ -181,7 +181,7 @@ mod system {
use super::super::metrics::FmtMetrics;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub(super) struct System {}
impl System {

44
src/telemetry/report.rs Normal file
View File

@ -0,0 +1,44 @@
use std::fmt;
use super::{http, process, tls_config_reload, transport};
use super::metrics::FmtMetrics;
/// Implements `FmtMetrics` to report runtime metrics.
#[derive(Clone, Debug)]
pub struct Report {
http: http::Report,
transports: transport::Report,
tls_config_reload: tls_config_reload::Report,
process: process::Report,
}
// ===== impl Report =====
impl Report {
pub(super) fn new(
http: http::Report,
transports: transport::Report,
tls_config_reload: tls_config_reload::Report,
process: process::Report,
) -> Self {
Self {
http,
transports,
tls_config_reload,
process,
}
}
}
// ===== impl Report =====
impl FmtMetrics for Report {
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.http.fmt_metrics(f)?;
self.transports.fmt_metrics(f)?;
self.tls_config_reload.fmt_metrics(f)?;
self.process.fmt_metrics(f)?;
Ok(())
}
}

View File

@ -7,8 +7,9 @@ use std::{
use telemetry::{
metrics::{
prom::{FmtLabels, FmtMetrics},
Counter,
FmtLabels,
FmtMetrics,
Gauge,
Scopes,
},
@ -40,7 +41,7 @@ pub fn new() -> (Sensor, Report) {
pub struct Sensor(Arc<Mutex<Inner>>);
/// Formats metrics for Prometheus for a corresonding `Sensor`.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Report(Weak<Mutex<Inner>>);
#[derive(Debug, Default)]

View File

@ -9,10 +9,13 @@ use ctx;
use telemetry::Errno;
use telemetry::metrics::{
latency,
prom::{FmtLabels, FmtMetric, FmtMetrics, Metric},
Counter,
FmtLabels,
FmtMetric,
FmtMetrics,
Gauge,
Histogram,
Metric,
};
use transport::Connection;
@ -36,7 +39,7 @@ pub fn new() -> (Registry, Report) {
}
/// Implements `FmtMetrics` to render prometheus-formatted metrics for all transports.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Report(Arc<Mutex<Inner>>);
/// Instruments transports to record telemetry.