Introduce the FmtMetrics and FmtLabels traits (#67)
There are many different types of things that implement `fmt::Display` in the metrics library: lists of key-value labels, individial label keys or values, groups of metrics output, etc. While modifying metrics code, it can be very easy to use something that's not a label in the context of a label.
This commit is contained in:
parent
6dde18cf34
commit
1d1d76aa5b
|
@ -2,7 +2,7 @@ use std::fmt::{self, Display};
|
|||
use std::num::Wrapping;
|
||||
use std::ops;
|
||||
|
||||
use super::FmtMetric;
|
||||
use super::prom::{FmtMetric, FmtLabels};
|
||||
|
||||
/// A Prometheus counter is represented by a `Wrapping` unsigned 64-bit int.
|
||||
///
|
||||
|
@ -77,13 +77,11 @@ impl FmtMetric for Counter {
|
|||
|
||||
fn fmt_metric_labeled<N, L>(&self, f: &mut fmt::Formatter, name: N, labels: L) -> fmt::Result
|
||||
where
|
||||
L: Display,
|
||||
L: FmtLabels,
|
||||
N: Display,
|
||||
{
|
||||
writeln!(f, "{name}{{{labels}}} {value}",
|
||||
name = name,
|
||||
labels = labels,
|
||||
value = self.0,
|
||||
)
|
||||
write!(f, "{}{{", name)?;
|
||||
labels.fmt_labels(f)?;
|
||||
writeln!(f, "}} {}", self.0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use super::FmtMetric;
|
||||
use super::{FmtMetric, FmtLabels};
|
||||
|
||||
/// An instaneous metric value.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
|
@ -47,13 +47,11 @@ impl FmtMetric for Gauge {
|
|||
|
||||
fn fmt_metric_labeled<N, L>(&self, f: &mut fmt::Formatter, name: N, labels: L) -> fmt::Result
|
||||
where
|
||||
L: FmtLabels,
|
||||
N: Display,
|
||||
L: Display,
|
||||
{
|
||||
writeln!(f, "{name}{{{labels}}} {value}",
|
||||
name = name,
|
||||
labels = labels,
|
||||
value = self.0,
|
||||
)
|
||||
write!(f, "{}{{", name)?;
|
||||
labels.fmt_labels(f)?;
|
||||
writeln!(f, "}} {}", self.0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::{cmp, iter, slice};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::{Counter, FmtMetric};
|
||||
use super::{Counter, FmtMetric, FmtLabels};
|
||||
|
||||
/// A series of latency values and counts.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -46,13 +46,10 @@ pub enum Bucket {
|
|||
pub struct Bounds(pub &'static [Bucket]);
|
||||
|
||||
/// Helper that lazily formats metric keys as {0}_{1}.
|
||||
struct Key<A: Display, B: Display>(A, B);
|
||||
|
||||
/// Helper that lazily formats comma-separated labels `A,B`.
|
||||
struct Labels<A: Display, B: Display>(A, B);
|
||||
struct Key<A: fmt::Display, B: fmt::Display>(A, B);
|
||||
|
||||
/// Helper that lazily formats an `{K}="{V}"`" label.
|
||||
struct Label<K: Display, V: Display>(K, V);
|
||||
struct Label<K: fmt::Display, V: fmt::Display>(K, V);
|
||||
|
||||
// ===== impl Histogram =====
|
||||
|
||||
|
@ -195,7 +192,7 @@ impl<'a, V: Into<u64>> IntoIterator for &'a Histogram<V> {
|
|||
impl<V: Into<u64>> FmtMetric for Histogram<V> {
|
||||
const KIND: &'static str = "histogram";
|
||||
|
||||
fn fmt_metric<N: Display>(&self, f: &mut fmt::Formatter, name: N) -> fmt::Result {
|
||||
fn fmt_metric<N: fmt::Display>(&self, f: &mut fmt::Formatter, name: N) -> fmt::Result {
|
||||
let mut total = Counter::default();
|
||||
for (le, count) in self {
|
||||
total += *count;
|
||||
|
@ -209,13 +206,13 @@ impl<V: Into<u64>> FmtMetric for Histogram<V> {
|
|||
|
||||
fn fmt_metric_labeled<N, L>(&self, f: &mut fmt::Formatter, name: N, labels: L) -> fmt::Result
|
||||
where
|
||||
N: Display,
|
||||
L: Display,
|
||||
N: fmt::Display,
|
||||
L: FmtLabels,
|
||||
{
|
||||
let mut total = Counter::default();
|
||||
for (le, count) in self {
|
||||
total += *count;
|
||||
total.fmt_metric_labeled(f, Key(&name, "bucket"), Labels(&labels, Label("le", le)))?;
|
||||
total.fmt_metric_labeled(f, Key(&name, "bucket"), (&labels, Label("le", le)))?;
|
||||
}
|
||||
total.fmt_metric_labeled(f, Key(&name, "count"), &labels)?;
|
||||
self.sum.fmt_metric_labeled(f, Key(&name, "sum"), &labels)?;
|
||||
|
@ -226,7 +223,7 @@ impl<V: Into<u64>> FmtMetric for Histogram<V> {
|
|||
|
||||
// ===== impl Key =====
|
||||
|
||||
impl<A: Display, B: Display> fmt::Display for Key<A, B> {
|
||||
impl<A: fmt::Display, B: fmt::Display> fmt::Display for Key<A, B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}_{}", self.0, self.1)
|
||||
}
|
||||
|
@ -234,20 +231,12 @@ impl<A: Display, B: Display> fmt::Display for Key<A, B> {
|
|||
|
||||
// ===== impl Label =====
|
||||
|
||||
impl<K: Display, V: Display> fmt::Display for Label<K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl<K: fmt::Display, V: fmt::Display> FmtLabels for Label<K, V> {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}=\"{}\"", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Labels =====
|
||||
|
||||
impl<A: Display, B: Display> fmt::Display for Labels<A, B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{},{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Bucket =====
|
||||
|
||||
impl fmt::Display for Bucket {
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
|||
|
||||
use super::{
|
||||
latency,
|
||||
prom::FmtMetrics,
|
||||
Counter,
|
||||
Histogram,
|
||||
RequestLabels,
|
||||
|
@ -34,8 +35,8 @@ impl RequestScopes {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RequestScopes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for RequestScopes {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -72,8 +73,8 @@ impl ResponseScopes {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ResponseScopes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for ResponseScopes {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use http;
|
|||
|
||||
use ctx;
|
||||
use conditional::Conditional;
|
||||
use super::prom::FmtLabels;
|
||||
use transport::tls;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
@ -23,7 +24,7 @@ pub struct RequestLabels {
|
|||
|
||||
/// The value of the `:authority` (HTTP/2) or `Host` (HTTP/1.1) header of
|
||||
/// the request.
|
||||
authority: Option<http::uri::Authority>,
|
||||
authority: Authority,
|
||||
|
||||
/// Whether or not the request was made over TLS.
|
||||
tls_status: TlsStatus,
|
||||
|
@ -35,11 +36,11 @@ pub struct ResponseLabels {
|
|||
request_labels: RequestLabels,
|
||||
|
||||
/// The HTTP status code of the response.
|
||||
status_code: u16,
|
||||
status_code: StatusCode,
|
||||
|
||||
/// The value of the grpc-status trailer. Only applicable to response
|
||||
/// metrics for gRPC responses.
|
||||
grpc_status_code: Option<u32>,
|
||||
grpc_status: Option<GrpcStatus>,
|
||||
|
||||
/// Was the response a success or failure?
|
||||
classification: Classification,
|
||||
|
@ -63,6 +64,15 @@ pub struct DstLabels {
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct TlsStatus(ctx::transport::TlsStatus);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct Authority(Option<http::uri::Authority>);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct StatusCode(u16);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct GrpcStatus(u32);
|
||||
|
||||
// ===== impl RequestLabels =====
|
||||
|
||||
impl RequestLabels {
|
||||
|
@ -78,7 +88,7 @@ impl RequestLabels {
|
|||
RequestLabels {
|
||||
direction,
|
||||
outbound_labels,
|
||||
authority,
|
||||
authority: Authority(authority),
|
||||
tls_status: TlsStatus(req.tls_status()),
|
||||
}
|
||||
}
|
||||
|
@ -89,27 +99,24 @@ impl RequestLabels {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RequestLabels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.authority {
|
||||
Some(ref authority) =>
|
||||
write!(f, "authority=\"{}\",{}", authority, self.direction),
|
||||
None =>
|
||||
write!(f, "authority=\"\",{}", self.direction),
|
||||
}?;
|
||||
impl FmtLabels for RequestLabels {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let dst = (self.outbound_labels.as_ref(), &self.tls_status);
|
||||
|
||||
if let Some(ref outbound) = self.outbound_labels {
|
||||
// leading comma added between the direction label and the
|
||||
// destination labels, if there are destination labels.
|
||||
write!(f, ",{}", outbound)?;
|
||||
}
|
||||
|
||||
write!(f, ",{}", self.tls_status)?;
|
||||
|
||||
Ok(())
|
||||
((&self.authority, &self.direction), dst).fmt_labels(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FmtLabels for Authority {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0 {
|
||||
Some(ref a) => write!(f, "authority=\"{}\"", a),
|
||||
None => write!(f, "authority=\"\""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== impl ResponseLabels =====
|
||||
|
||||
impl ResponseLabels {
|
||||
|
@ -119,8 +126,8 @@ impl ResponseLabels {
|
|||
let classification = Classification::classify(rsp, grpc_status_code);
|
||||
ResponseLabels {
|
||||
request_labels,
|
||||
status_code: rsp.status.as_u16(),
|
||||
grpc_status_code,
|
||||
status_code: StatusCode(rsp.status.as_u16()),
|
||||
grpc_status: grpc_status_code.map(GrpcStatus),
|
||||
classification,
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +139,8 @@ impl ResponseLabels {
|
|||
request_labels,
|
||||
// TODO: is it correct to always treat this as 500?
|
||||
// Alternatively, the status_code field could be made optional...
|
||||
status_code: 500,
|
||||
grpc_status_code: None,
|
||||
status_code: StatusCode(500),
|
||||
grpc_status: None,
|
||||
classification: Classification::Failure,
|
||||
}
|
||||
}
|
||||
|
@ -144,21 +151,23 @@ impl ResponseLabels {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ResponseLabels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{},{},status_code=\"{}\"",
|
||||
self.request_labels,
|
||||
self.classification,
|
||||
self.status_code
|
||||
)?;
|
||||
impl FmtLabels for ResponseLabels {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let status = (&self.status_code, self.grpc_status.as_ref());
|
||||
let class = (&self.classification, status);
|
||||
(&self.request_labels, class).fmt_labels(f)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref status) = self.grpc_status_code {
|
||||
// leading comma added between the status code label and the
|
||||
// gRPC status code labels, if there is a gRPC status code.
|
||||
write!(f, ",grpc_status_code=\"{}\"", status)?;
|
||||
}
|
||||
impl FmtLabels for StatusCode {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "status_code=\"{}\"", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
impl FmtLabels for GrpcStatus {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "grpc_status_code=\"{}\"", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,8 +199,8 @@ impl Classification {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Classification {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for Classification {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&Classification::Success => f.pad("classification=\"success\""),
|
||||
&Classification::Failure => f.pad("classification=\"failure\""),
|
||||
|
@ -207,8 +216,8 @@ impl Direction {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for Direction {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0 {
|
||||
ctx::Proxy::Inbound => f.pad("direction=\"inbound\""),
|
||||
ctx::Proxy::Outbound => f.pad("direction=\"outbound\""),
|
||||
|
@ -268,16 +277,16 @@ impl hash::Hash for DstLabels {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DstLabels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.formatted.fmt(f)
|
||||
impl FmtLabels for DstLabels {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.formatted, f)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl TlsStatus =====
|
||||
|
||||
impl fmt::Display for TlsStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for TlsStatus {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0 {
|
||||
Conditional::None(tls::ReasonForNoTls::NoIdentity(why)) =>
|
||||
write!(f, "tls=\"no_identity\",no_tls_reason=\"{}\"", why),
|
||||
|
@ -292,12 +301,13 @@ impl From<ctx::transport::TlsStatus> for TlsStatus {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
impl Into<ctx::transport::TlsStatus> for TlsStatus {
|
||||
fn into(self) -> ctx::transport::TlsStatus {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for tls::ReasonForNoIdentity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
|
|
|
@ -27,8 +27,7 @@
|
|||
//! 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::default::Default;
|
||||
use std::fmt::{self, Display};
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
|
@ -38,6 +37,7 @@ mod histogram;
|
|||
mod http;
|
||||
mod labels;
|
||||
pub mod latency;
|
||||
pub mod prom;
|
||||
mod record;
|
||||
mod scopes;
|
||||
mod serve;
|
||||
|
@ -51,6 +51,7 @@ use self::labels::{
|
|||
ResponseLabels,
|
||||
};
|
||||
pub use self::labels::DstLabels;
|
||||
pub use self::prom::{FmtMetrics, FmtLabels, FmtMetric};
|
||||
pub use self::record::Record;
|
||||
pub use self::scopes::Scopes;
|
||||
pub use self::serve::Serve;
|
||||
|
@ -58,34 +59,6 @@ pub use self::transport::Transports;
|
|||
use super::process;
|
||||
use super::tls_config_reload;
|
||||
|
||||
/// Writes a metric in prometheus-formatted output.
|
||||
///
|
||||
/// This trait is implemented by `Counter`, `Gauge`, and `Histogram` to account for the
|
||||
/// differences in formatting each type of metric. Specifically, `Histogram` formats a
|
||||
/// counter for each bucket, as well as a count and total sum.
|
||||
pub trait FmtMetric {
|
||||
/// The metric's `TYPE` in help messages.
|
||||
const KIND: &'static str;
|
||||
|
||||
/// Writes a metric with the given name and no labels.
|
||||
fn fmt_metric<N: Display>(&self, f: &mut fmt::Formatter, name: N) -> fmt::Result;
|
||||
|
||||
/// Writes a metric with the given name and labels.
|
||||
fn fmt_metric_labeled<N, L>(&self, f: &mut fmt::Formatter, name: N, labels: L) -> fmt::Result
|
||||
where
|
||||
N: Display,
|
||||
L: Display;
|
||||
}
|
||||
|
||||
/// Describes a metric statically.
|
||||
///
|
||||
/// Formats help messages and metric values for prometheus output.
|
||||
pub struct Metric<'a, M: FmtMetric> {
|
||||
pub name: &'a str,
|
||||
pub help: &'a str,
|
||||
pub _p: PhantomData<M>,
|
||||
}
|
||||
|
||||
/// The root scope for all runtime metrics.
|
||||
#[derive(Debug, Default)]
|
||||
struct Root {
|
||||
|
@ -115,41 +88,6 @@ pub fn new(start_time: SystemTime, idle_retain: Duration, tls: tls_config_reload
|
|||
(Record::new(&metrics), Serve::new(&metrics, idle_retain))
|
||||
}
|
||||
|
||||
// ===== impl Metric =====
|
||||
|
||||
impl<'a, M: FmtMetric> Metric<'a, M> {
|
||||
/// Formats help messages for this metric.
|
||||
pub fn fmt_help(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "# HELP {} {}", self.name, self.help)?;
|
||||
writeln!(f, "# TYPE {} {}", self.name, M::KIND)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Formats a single metric without labels.
|
||||
pub fn fmt_metric(&self, f: &mut fmt::Formatter, metric: M) -> fmt::Result {
|
||||
metric.fmt_metric(f, self.name)
|
||||
}
|
||||
|
||||
/// Formats a single metric across labeled scopes.
|
||||
pub fn fmt_scopes<'s, L, S: 's, I, F>(
|
||||
&self,
|
||||
f: &mut fmt::Formatter,
|
||||
scopes: I,
|
||||
to_metric: F
|
||||
) -> fmt::Result
|
||||
where
|
||||
L: Display,
|
||||
I: IntoIterator<Item = (L, &'s S)>,
|
||||
F: Fn(&S) -> &M
|
||||
{
|
||||
for (labels, scope) in scopes {
|
||||
to_metric(scope).fmt_metric_labeled(f, self.name, labels)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Root =====
|
||||
|
||||
impl Root {
|
||||
|
@ -179,13 +117,13 @@ impl Root {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Root {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.requests.fmt(f)?;
|
||||
self.responses.fmt(f)?;
|
||||
self.transports.fmt(f)?;
|
||||
self.tls_config_reload.fmt(f)?;
|
||||
self.process.fmt(f)?;
|
||||
impl FmtMetrics for Root {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.requests.fmt_metrics(f)?;
|
||||
self.responses.fmt_metrics(f)?;
|
||||
self.transports.fmt_metrics(f)?;
|
||||
self.tls_config_reload.fmt_metrics(f)?;
|
||||
self.process.fmt_metrics(f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
use std::fmt;
|
||||
use std::marker::{PhantomData, Sized};
|
||||
|
||||
/// Writes a block of metrics in prometheus-formatted output.
|
||||
pub trait FmtMetrics {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result;
|
||||
|
||||
fn as_display(&self) -> DisplayMetrics<&Self> where Self: Sized {
|
||||
DisplayMetrics(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapts `FmtMetrics` to `fmt::Display`.
|
||||
pub struct DisplayMetrics<F>(F);
|
||||
|
||||
impl<F: FmtMetrics> fmt::Display for DisplayMetrics<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt_metrics(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a series of key-quoted-val pairs for use as prometheus labels.
|
||||
pub trait FmtLabels {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result;
|
||||
}
|
||||
|
||||
/// Writes a metric in prometheus-formatted output.
|
||||
///
|
||||
/// This trait is implemented by `Counter`, `Gauge`, and `Histogram` to account for the
|
||||
/// differences in formatting each type of metric. Specifically, `Histogram` formats a
|
||||
/// counter for each bucket, as well as a count and total sum.
|
||||
pub trait FmtMetric {
|
||||
/// The metric's `TYPE` in help messages.
|
||||
const KIND: &'static str;
|
||||
|
||||
/// Writes a metric with the given name and no labels.
|
||||
fn fmt_metric<N: fmt::Display>(&self, f: &mut fmt::Formatter, name: N) -> fmt::Result;
|
||||
|
||||
/// Writes a metric with the given name and labels.
|
||||
fn fmt_metric_labeled<N, L>(&self, f: &mut fmt::Formatter, name: N, labels: L) -> fmt::Result
|
||||
where
|
||||
N: fmt::Display,
|
||||
L: FmtLabels;
|
||||
}
|
||||
|
||||
/// Describes a metric statically.
|
||||
///
|
||||
/// Formats help messages and metric values for prometheus output.
|
||||
pub struct Metric<'a, M: FmtMetric> {
|
||||
pub name: &'a str,
|
||||
pub help: &'a str,
|
||||
pub _p: PhantomData<M>,
|
||||
}
|
||||
|
||||
// ===== impl Metric =====
|
||||
|
||||
impl<'a, M: FmtMetric> Metric<'a, M> {
|
||||
/// Formats help messages for this metric.
|
||||
pub fn fmt_help(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "# HELP {} {}", self.name, self.help)?;
|
||||
writeln!(f, "# TYPE {} {}", self.name, M::KIND)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Formats a single metric without labels.
|
||||
pub fn fmt_metric(&self, f: &mut fmt::Formatter, metric: M) -> fmt::Result {
|
||||
metric.fmt_metric(f, self.name)
|
||||
}
|
||||
|
||||
/// Formats a single metric across labeled scopes.
|
||||
pub fn fmt_scopes<'s, L, S: 's, I, F>(
|
||||
&self,
|
||||
f: &mut fmt::Formatter,
|
||||
scopes: I,
|
||||
to_metric: F
|
||||
) -> fmt::Result
|
||||
where
|
||||
L: FmtLabels,
|
||||
I: IntoIterator<Item = (L, &'s S)>,
|
||||
F: Fn(&S) -> &M
|
||||
{
|
||||
for (labels, scope) in scopes {
|
||||
to_metric(scope).fmt_metric_labeled(f, self.name, labels)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FmtLabels =====
|
||||
|
||||
impl<'a, A: FmtLabels + 'a> FmtLabels for &'a A {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
(*self).fmt_labels(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: FmtLabels, B: FmtLabels> FmtLabels for (A, B) {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt_labels(f)?;
|
||||
f.pad(",")?;
|
||||
self.1.fmt_labels(f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: FmtLabels, B: FmtLabels> FmtLabels for (A, Option<B>) {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt_labels(f)?;
|
||||
if let Some(ref b) = self.1 {
|
||||
f.pad(",")?;
|
||||
b.fmt_labels(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: FmtLabels, B: FmtLabels> FmtLabels for (Option<A>, B) {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(ref a) = self.0 {
|
||||
a.fmt_labels(f)?;
|
||||
f.pad(",")?;
|
||||
}
|
||||
self.1.fmt_labels(f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FmtMetrics =====
|
||||
|
||||
impl<'a, A: FmtMetrics + 'a> FmtMetrics for &'a A {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
(*self).fmt_metrics(f)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
use indexmap::IndexMap;
|
||||
use std::{fmt::Display, hash::Hash};
|
||||
use super::prom::FmtLabels;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Holds an `S`-typed scope for each `L`-typed label set.
|
||||
///
|
||||
/// An `S` type typically holds one or more metrics.
|
||||
#[derive(Debug)]
|
||||
pub struct Scopes<L: Display + Hash + Eq, S>(IndexMap<L, S>);
|
||||
pub struct Scopes<L: FmtLabels + Hash + Eq, S>(IndexMap<L, S>);
|
||||
|
||||
impl<L: Display + Hash + Eq, S> Default for Scopes<L, S> {
|
||||
impl<L: FmtLabels + Hash + Eq, S> Default for Scopes<L, S> {
|
||||
fn default() -> Self {
|
||||
Scopes(IndexMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Display + Hash + Eq, S> Scopes<L, S> {
|
||||
impl<L: FmtLabels + Hash + Eq, S> Scopes<L, S> {
|
||||
pub fn get(&self, key: &L) -> Option<&S> {
|
||||
self.0.get(key)
|
||||
}
|
||||
|
@ -34,13 +35,13 @@ impl<L: Display + Hash + Eq, S> Scopes<L, S> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<L: Display + Hash + Eq, S: Default> Scopes<L, S> {
|
||||
impl<L: FmtLabels + Hash + Eq, S: Default> Scopes<L, S> {
|
||||
pub fn get_or_default(&mut self, key: L) -> &mut S {
|
||||
self.0.entry(key).or_insert_with(|| S::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, L: Display + Hash + Eq, S> IntoIterator for &'a Scopes<L, S> {
|
||||
impl<'a, L: FmtLabels + Hash + Eq, S> IntoIterator for &'a Scopes<L, S> {
|
||||
type Item = <&'a IndexMap<L, S> as IntoIterator>::Item;
|
||||
type IntoIter = <&'a IndexMap<L, S> as IntoIterator>::IntoIter;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::sync::{Arc, Mutex};
|
|||
use std::time::{Duration, Instant};
|
||||
use tokio::executor::current_thread::TaskExecutor;
|
||||
|
||||
use super::Root;
|
||||
use super::{prom::FmtMetrics, Root};
|
||||
use task;
|
||||
use transport::BoundPort;
|
||||
|
||||
|
@ -109,7 +109,7 @@ impl Service for Serve {
|
|||
let resp = if Self::is_gzip(&req) {
|
||||
trace!("gzipping metrics");
|
||||
let mut writer = GzEncoder::new(Vec::<u8>::new(), CompressionOptions::fast());
|
||||
write!(&mut writer, "{}", *metrics)
|
||||
write!(&mut writer, "{}", (*metrics).as_display())
|
||||
.and_then(|_| writer.finish())
|
||||
.map_err(ServeError::from)
|
||||
.and_then(|body| {
|
||||
|
@ -121,7 +121,7 @@ impl Service for Serve {
|
|||
})
|
||||
} else {
|
||||
let mut writer = Vec::<u8>::new();
|
||||
write!(&mut writer, "{}", *metrics)
|
||||
write!(&mut writer, "{}", (*metrics).as_display())
|
||||
.map_err(ServeError::from)
|
||||
.and_then(|_| {
|
||||
Response::builder()
|
||||
|
|
|
@ -6,6 +6,7 @@ use ctx;
|
|||
use super::{
|
||||
labels::{Direction, TlsStatus},
|
||||
latency,
|
||||
prom::{FmtLabels, FmtMetrics},
|
||||
Counter,
|
||||
Gauge,
|
||||
Histogram,
|
||||
|
@ -69,8 +70,6 @@ struct EosMetrics {
|
|||
connection_duration: Histogram<latency::Ms>,
|
||||
}
|
||||
|
||||
struct FmtEos<'a>(&'a Key, &'a Eos);
|
||||
|
||||
// ===== impl Transports =====
|
||||
|
||||
impl Transports {
|
||||
|
@ -101,11 +100,11 @@ impl Transports {
|
|||
}
|
||||
|
||||
/// Iterates over all end-of-stream metrics.
|
||||
fn iter_eos(&self) -> impl Iterator<Item = (FmtEos, &EosMetrics)> {
|
||||
fn iter_eos(&self) -> impl Iterator<Item = ((&Key, &Eos), &EosMetrics)> {
|
||||
self.metrics
|
||||
.iter()
|
||||
.flat_map(|(k, t)| {
|
||||
t.by_eos.iter().map(move |(e, m)| (FmtEos(k ,e), m))
|
||||
t.by_eos.iter().map(move |(e, m)| ((k ,e), m))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -150,8 +149,8 @@ impl Transports {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Transports {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for Transports {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.metrics.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -180,9 +179,9 @@ impl fmt::Display for Transports {
|
|||
|
||||
// ===== impl Key =====
|
||||
|
||||
impl fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{},{},{}", self.direction, self.peer, self.tls_status)
|
||||
impl FmtLabels for Key {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
((self.direction, self.peer), self.tls_status).fmt_labels(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,8 +198,8 @@ impl Key {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Peer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for Peer {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Peer::Src => f.pad("peer=\"src\""),
|
||||
Peer::Dst => f.pad("peer=\"dst\""),
|
||||
|
@ -211,8 +210,8 @@ impl fmt::Display for Peer {
|
|||
|
||||
// ===== impl Eos =====
|
||||
|
||||
impl fmt::Display for Eos {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for Eos {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self {
|
||||
Eos::Clean => f.pad("classification=\"success\""),
|
||||
Eos::Error { errno } => {
|
||||
|
@ -225,11 +224,3 @@ impl fmt::Display for Eos {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FmtEos =====
|
||||
|
||||
impl<'a> fmt::Display for FmtEos<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{},{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ macro_rules! metrics {
|
|||
{ $( $name:ident : $kind:ty { $help:expr } ),+ } => {
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $name: ::telemetry::metrics::Metric<'static, $kind> =
|
||||
::telemetry::metrics::Metric {
|
||||
const $name: ::telemetry::metrics::prom::Metric<'static, $kind> =
|
||||
::telemetry::metrics::prom::Metric {
|
||||
name: stringify!($name),
|
||||
help: $help,
|
||||
_p: ::std::marker::PhantomData,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use super::metrics::Gauge;
|
||||
use super::metrics::{Gauge, prom::FmtMetrics};
|
||||
|
||||
use self::system::System;
|
||||
|
||||
|
@ -38,13 +38,13 @@ impl Report {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Report {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for Report {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
process_start_time_seconds.fmt_help(f)?;
|
||||
process_start_time_seconds.fmt_metric(f, self.start_time)?;
|
||||
|
||||
if let Some(ref sys) = self.system {
|
||||
sys.fmt(f)?;
|
||||
sys.fmt_metrics(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -58,7 +58,7 @@ mod system {
|
|||
use std::{io, fs};
|
||||
|
||||
use super::*;
|
||||
use super::super::metrics::{Counter, Gauge};
|
||||
use super::super::metrics::{Counter, Gauge, FmtMetrics};
|
||||
|
||||
metrics! {
|
||||
process_cpu_seconds_total: Counter {
|
||||
|
@ -119,8 +119,8 @@ mod system {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for System {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for System {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// XXX potentially blocking call
|
||||
let stat = match pid::stat_self() {
|
||||
Ok(stat) => stat,
|
||||
|
@ -179,6 +179,8 @@ mod system {
|
|||
mod system {
|
||||
use std::{fmt, io};
|
||||
|
||||
use super::super::metrics::FmtMetrics;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct System {}
|
||||
|
||||
|
@ -191,8 +193,8 @@ mod system {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for System {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for System {
|
||||
fn fmt_metrics(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,15 @@ use std::{
|
|||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use telemetry::{Errno, metrics::{Counter, Gauge, Scopes}};
|
||||
use telemetry::{
|
||||
metrics::{
|
||||
prom::{FmtLabels, FmtMetrics},
|
||||
Counter,
|
||||
Gauge,
|
||||
Scopes,
|
||||
},
|
||||
Errno,
|
||||
};
|
||||
use transport::tls;
|
||||
|
||||
metrics! {
|
||||
|
@ -74,8 +82,8 @@ impl Sensor {
|
|||
|
||||
// ===== impl Report =====
|
||||
|
||||
impl fmt::Display for Report {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtMetrics for Report {
|
||||
fn fmt_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let lock = match self.0.upgrade() {
|
||||
None => return Ok(()),
|
||||
Some(lock) => lock,
|
||||
|
@ -115,8 +123,8 @@ impl From<tls::ConfigError> for Status {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Status {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl FmtLabels for Status {
|
||||
fn fmt_labels(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Status::Reloaded => f.pad("status=\"reloaded\""),
|
||||
Status::Io {
|
||||
|
|
Loading…
Reference in New Issue