From 876ae02af1da140fc24924b2d82b5e8a40674f8d Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 30 Nov 2020 15:38:43 -0800 Subject: [PATCH] metrics: Expose process_cpu_seconds_total as a float (#754) Prometheus handles all values as `f64`, but we only expose values as whole integers. This means that the `process_cpu_seconds_total` metric only exposes whole second values, while Linux exposes process time in 10ms increments. This change modifies the `Counter` metric type to store an additional marker that provides a strategy for converting the stored `u64` value to `f64` for export. This strategy is employed so that we can continue to use `AtomicU64` to back counters and only use floats at export-time. By default the unit type is used to convert counters as before, but an alternate `MillisAsSeconds` strategy is used to expose fractional seconds from a millisecond counter. This necessitates changing the histogram buckets to floats as well. While this change doesn't modify the bucket values, this sets up future changes to latency metrics. --- linkerd/app/core/src/telemetry/process.rs | 41 +++-- linkerd/metrics/src/counter.rs | 106 ++++++++---- linkerd/metrics/src/gauge.rs | 4 +- linkerd/metrics/src/histogram.rs | 186 +++++++++++----------- linkerd/metrics/src/latency.rs | 50 +++--- linkerd/metrics/src/lib.rs | 25 +++ linkerd/metrics/src/prom.rs | 7 - 7 files changed, 240 insertions(+), 179 deletions(-) diff --git a/linkerd/app/core/src/telemetry/process.rs b/linkerd/app/core/src/telemetry/process.rs index f5a1a03699..13d767a8fc 100644 --- a/linkerd/app/core/src/telemetry/process.rs +++ b/linkerd/app/core/src/telemetry/process.rs @@ -54,14 +54,14 @@ impl FmtMetrics for Report { #[cfg(target_os = "linux")] mod system { use libc::{self, pid_t}; - use linkerd2_metrics::{metrics, Counter, FmtMetrics, Gauge}; + use linkerd2_metrics::{metrics, Counter, FmtMetrics, Gauge, MillisAsSeconds}; use procinfo::pid; use std::fmt; use std::{fs, io}; use tracing::{error, warn}; metrics! { - process_cpu_seconds_total: Counter { + process_cpu_seconds_total: Counter { "Total user and system CPU time spent in seconds." }, process_open_fds: Gauge { "Number of open file descriptors." }, @@ -77,16 +77,28 @@ mod system { #[derive(Clone, Debug)] pub(super) struct System { page_size: u64, - clock_ticks_per_sec: u64, + ms_per_tick: u64, } impl System { pub fn new() -> io::Result { let page_size = Self::sysconf(libc::_SC_PAGESIZE, "page size")?; + + // On Linux, CLK_TCK is ~always `100`, so pure integer division + // works. This is probably not suitable if we encounter other + // values. let clock_ticks_per_sec = Self::sysconf(libc::_SC_CLK_TCK, "clock ticks per second")?; + let ms_per_tick = 1_000 / clock_ticks_per_sec; + if clock_ticks_per_sec != 100 { + warn!( + clock_ticks_per_sec, + ms_per_tick, "Unexpected value; process_cpu_seconds_total may be inaccurate." + ); + } + Ok(Self { page_size, - clock_ticks_per_sec, + ms_per_tick, }) } @@ -130,9 +142,16 @@ mod system { }; let clock_ticks = stat.utime as u64 + stat.stime as u64; - let cpu = Counter::from(clock_ticks / self.clock_ticks_per_sec); + let cpu_ms = clock_ticks * self.ms_per_tick; process_cpu_seconds_total.fmt_help(f)?; - process_cpu_seconds_total.fmt_metric(f, &cpu)?; + process_cpu_seconds_total.fmt_metric(f, &Counter::from(cpu_ms))?; + + process_virtual_memory_bytes.fmt_help(f)?; + process_virtual_memory_bytes.fmt_metric(f, &Gauge::from(stat.vsize as u64))?; + + process_resident_memory_bytes.fmt_help(f)?; + process_resident_memory_bytes + .fmt_metric(f, &Gauge::from(stat.rss as u64 * self.page_size))?; match Self::open_fds(stat.pid) { Ok(open_fds) => { @@ -141,7 +160,6 @@ mod system { } Err(err) => { warn!("could not determine process_open_fds: {}", err); - return Ok(()); } } @@ -153,17 +171,10 @@ mod system { } Err(err) => { warn!("could not determine process_max_fds: {}", err); - return Ok(()); } } - process_virtual_memory_bytes.fmt_help(f)?; - let vsz = Gauge::from(stat.vsize as u64); - process_virtual_memory_bytes.fmt_metric(f, &vsz)?; - - process_resident_memory_bytes.fmt_help(f)?; - let rss = Gauge::from(stat.rss as u64 * self.page_size); - process_resident_memory_bytes.fmt_metric(f, &rss) + Ok(()) } } } diff --git a/linkerd/metrics/src/counter.rs b/linkerd/metrics/src/counter.rs index 8d5ce34308..a5b3874290 100644 --- a/linkerd/metrics/src/counter.rs +++ b/linkerd/metrics/src/counter.rs @@ -1,4 +1,7 @@ -use super::prom::{FmtLabels, FmtMetric, MAX_PRECISE_VALUE}; +use super::{ + prom::{FmtLabels, FmtMetric}, + Factor, +}; use std::fmt::{self, Display}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -14,12 +17,22 @@ use std::sync::atomic::{AtomicU64, Ordering}; /// [`rate()`]: https://prometheus.io/docs/prometheus/latest/querying/functions/#rate() /// [`irate()`]: https://prometheus.io/docs/prometheus/latest/querying/functions/#irate() /// [`resets()`]: https://prometheus.io/docs/prometheus/latest/querying/functions/#resets -#[derive(Debug, Default)] -pub struct Counter(AtomicU64); +#[derive(Debug)] +pub struct Counter(AtomicU64, std::marker::PhantomData); // ===== impl Counter ===== -impl Counter { +impl Default for Counter { + fn default() -> Self { + Self(AtomicU64::default(), std::marker::PhantomData) + } +} + +impl Counter { + pub fn new() -> Self { + Self::default() + } + pub fn incr(&self) { self.add(1) } @@ -27,28 +40,35 @@ impl Counter { pub fn add(&self, n: u64) { self.0.fetch_add(n, Ordering::Release); } +} +impl Counter { /// Return current counter value, wrapped to be safe for use with Prometheus. - pub fn value(&self) -> u64 { - self.0 - .load(Ordering::Acquire) - .wrapping_rem(MAX_PRECISE_VALUE + 1) + pub fn value(&self) -> f64 { + let n = self.0.load(Ordering::Acquire); + F::factor(n) } } -impl Into for Counter { - fn into(self) -> u64 { +impl Into for &Counter { + fn into(self) -> f64 { self.value() } } -impl From for Counter { +impl Into for &Counter { + fn into(self) -> u64 { + self.0.load(Ordering::Acquire) + } +} + +impl From for Counter { fn from(value: u64) -> Self { - Counter(value.into()) + Counter(value.into(), std::marker::PhantomData) } } -impl FmtMetric for Counter { +impl FmtMetric for Counter { const KIND: &'static str = "counter"; fn fmt_metric(&self, f: &mut fmt::Formatter<'_>, name: N) -> fmt::Result { @@ -74,34 +94,50 @@ impl FmtMetric for Counter { #[cfg(test)] mod tests { use super::*; + use crate::{MillisAsSeconds, MAX_PRECISE_UINT64}; #[test] fn count_simple() { - let cnt = Counter::from(0); - assert_eq!(cnt.value(), 0); - cnt.incr(); - assert_eq!(cnt.value(), 1); - cnt.add(41); - assert_eq!(cnt.value(), 42); - cnt.add(0); - assert_eq!(cnt.value(), 42); + let c = Counter::<()>::default(); + assert_eq!(c.value(), 0.0); + c.incr(); + assert_eq!(c.value(), 1.0); + c.add(41); + assert_eq!(c.value(), 42.0); + c.add(0); + assert_eq!(c.value(), 42.0); } #[test] fn count_wrapping() { - let cnt = Counter::from(MAX_PRECISE_VALUE - 1); - assert_eq!(cnt.value(), MAX_PRECISE_VALUE - 1); - cnt.incr(); - assert_eq!(cnt.value(), MAX_PRECISE_VALUE); - cnt.incr(); - assert_eq!(cnt.value(), 0); - cnt.incr(); - assert_eq!(cnt.value(), 1); - - let max = Counter::from(MAX_PRECISE_VALUE); - assert_eq!(max.value(), MAX_PRECISE_VALUE); - - let over = Counter::from(MAX_PRECISE_VALUE + 1); - assert_eq!(over.value(), 0); + let c = Counter::<()>::from(MAX_PRECISE_UINT64 - 1); + assert_eq!(c.value(), (MAX_PRECISE_UINT64 - 1) as f64); + c.incr(); + assert_eq!(c.value(), MAX_PRECISE_UINT64 as f64); + c.incr(); + assert_eq!(c.value(), 0.0); + c.incr(); + assert_eq!(c.value(), 1.0); + + let max = Counter::<()>::from(MAX_PRECISE_UINT64); + assert_eq!(max.value(), MAX_PRECISE_UINT64 as f64); + } + + #[test] + fn millis_as_seconds() { + let c = Counter::::from(1); + assert_eq!(c.value(), 0.001); + + let c = Counter::::from((MAX_PRECISE_UINT64 - 1) * 1000); + assert_eq!(c.value(), (MAX_PRECISE_UINT64 - 1) as f64); + c.add(1000); + assert_eq!(c.value(), MAX_PRECISE_UINT64 as f64); + c.add(1000); + assert_eq!(c.value(), 0.0); + c.add(1000); + assert_eq!(c.value(), 1.0); + + let max = Counter::::from(MAX_PRECISE_UINT64 * 1000); + assert_eq!(max.value(), MAX_PRECISE_UINT64 as f64); } } diff --git a/linkerd/metrics/src/gauge.rs b/linkerd/metrics/src/gauge.rs index 9dc7b86a41..a6d1a8077a 100644 --- a/linkerd/metrics/src/gauge.rs +++ b/linkerd/metrics/src/gauge.rs @@ -1,4 +1,4 @@ -use super::prom::{FmtLabels, FmtMetric, MAX_PRECISE_VALUE}; +use super::prom::{FmtLabels, FmtMetric}; use std::fmt::{self, Display}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -20,7 +20,7 @@ impl Gauge { pub fn value(&self) -> u64 { self.0 .load(Ordering::Acquire) - .wrapping_rem(MAX_PRECISE_VALUE + 1) + .wrapping_rem(crate::MAX_PRECISE_UINT64 + 1) } } diff --git a/linkerd/metrics/src/histogram.rs b/linkerd/metrics/src/histogram.rs index 18611cd9ac..6f3dfdfedf 100644 --- a/linkerd/metrics/src/histogram.rs +++ b/linkerd/metrics/src/histogram.rs @@ -2,13 +2,13 @@ use std::fmt; use std::marker::PhantomData; use std::{cmp, iter, slice}; -use super::{Counter, FmtLabels, FmtMetric}; +use super::{Counter, Factor, FmtLabels, FmtMetric}; /// A series of latency values and counts. #[derive(Debug)] -pub struct Histogram> { +pub struct Histogram, F = ()> { bounds: &'static Bounds, - buckets: Box<[Counter]>, + buckets: Box<[Counter]>, /// The total sum of all observed latency values. /// @@ -35,9 +35,9 @@ pub struct Histogram> { _p: PhantomData, } -#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[derive(Debug, PartialEq, Copy, Clone)] pub enum Bucket { - Le(u64), + Le(f64), Inf, } @@ -53,13 +53,13 @@ struct Label(K, V); // ===== impl Histogram ===== -impl> Histogram { +impl, F: Factor> Histogram { pub fn new(bounds: &'static Bounds) -> Self { let mut buckets = Vec::with_capacity(bounds.0.len()); - let mut prior = &Bucket::Le(0); + let mut prior = &Bucket::Le(0.0); for bound in bounds.0.iter() { assert!(prior < bound); - buckets.push(Counter::default()); + buckets.push(Counter::new()); prior = bound; } @@ -80,7 +80,7 @@ impl> Histogram { .0 .iter() .position(|b| match *b { - Bucket::Le(ceiling) => value <= ceiling, + Bucket::Le(ceiling) => F::factor(value) <= ceiling, Bucket::Inf => true, }) .expect("all values must fit into a bucket"); @@ -91,9 +91,9 @@ impl> Histogram { } #[cfg(any(test, feature = "test_util"))] -impl> Histogram { +impl, F: Factor + std::fmt::Debug> Histogram { /// Assert the bucket containing `le` has a count of at least `at_least`. - pub fn assert_bucket_at_least(&self, le: u64, at_least: u64) { + pub fn assert_bucket_at_least(&self, le: f64, at_least: f64) { for (&bucket, ref count) in self { if bucket >= le { let count = count.value(); @@ -104,7 +104,7 @@ impl> Histogram { } /// Assert the bucket containing `le` has a count of exactly `exactly`. - pub fn assert_bucket_exactly(&self, le: u64, exactly: u64) -> &Self { + pub fn assert_bucket_exactly(&self, le: f64, exactly: f64) -> &Self { for (&bucket, ref count) in self { if bucket >= le { let count = count.value(); @@ -121,7 +121,7 @@ impl> Histogram { /// Assert all buckets less than the one containing `value` have /// counts of exactly `exactly`. - pub fn assert_lt_exactly(&self, value: u64, exactly: u64) -> &Self { + pub fn assert_lt_exactly(&self, value: f64, exactly: f64) -> &Self { for (i, &bucket) in self.bounds.0.iter().enumerate() { let ceiling = match bucket { Bucket::Le(c) => c, @@ -137,7 +137,7 @@ impl> Histogram { break; } - let count: u64 = self.buckets[i].value(); + let count: f64 = self.buckets[i].value(); assert_eq!(count, exactly, "bucket={:?}; value={:?};", bucket, value,); } self @@ -145,7 +145,7 @@ impl> Histogram { /// Assert all buckets greater than the one containing `value` have /// counts of exactly `exactly`. - pub fn assert_gt_exactly(&self, value: u64, exactly: u64) -> &Self { + pub fn assert_gt_exactly(&self, value: f64, exactly: f64) -> &Self { // We set this to true after we've iterated past the first bucket // whose upper bound is >= `value`. let mut past_le = false; @@ -173,22 +173,22 @@ impl> Histogram { } } -impl<'a, V: Into> IntoIterator for &'a Histogram { - type Item = (&'a Bucket, &'a Counter); - type IntoIter = iter::Zip, slice::Iter<'a, Counter>>; +impl<'a, V: Into, F> IntoIterator for &'a Histogram { + type Item = (&'a Bucket, &'a Counter); + type IntoIter = iter::Zip, slice::Iter<'a, Counter>>; fn into_iter(self) -> Self::IntoIter { self.bounds.0.iter().zip(self.buckets.iter()) } } -impl> FmtMetric for Histogram { +impl, F: Factor> FmtMetric for Histogram { const KIND: &'static str = "histogram"; fn fmt_metric(&self, f: &mut fmt::Formatter<'_>, name: N) -> fmt::Result { - let total = Counter::default(); + let total = Counter::::new(); for (le, count) in self { - total.add(count.value()); + total.add(count.into()); total.fmt_metric_labeled(f, Key(&name, "bucket"), Label("le", le))?; } total.fmt_metric(f, Key(&name, "count"))?; @@ -207,9 +207,9 @@ impl> FmtMetric for Histogram { N: fmt::Display, L: FmtLabels, { - let total = Counter::default(); + let total = Counter::::new(); for (le, count) in self { - total.add(count.value()); + total.add(count.into()); total.fmt_metric_labeled(f, Key(&name, "bucket"), (&labels, Label("le", le)))?; } total.fmt_metric_labeled(f, Key(&name, "count"), &labels)?; @@ -248,12 +248,19 @@ impl fmt::Display for Bucket { impl cmp::PartialOrd for Bucket { fn partial_cmp(&self, rhs: &Bucket) -> Option { - Some(self.cmp(rhs)) + match (self, rhs) { + (Bucket::Inf, Bucket::Inf) => None, + (Bucket::Le(s), _) if *s == std::f64::NAN => None, + (_, Bucket::Le(r)) if *r == std::f64::NAN => None, + (Bucket::Le(_), Bucket::Inf) => Some(cmp::Ordering::Less), + (Bucket::Inf, Bucket::Le(_)) => Some(cmp::Ordering::Greater), + (Bucket::Le(s), Bucket::Le(r)) => s.partial_cmp(r), + } } } -impl cmp::PartialEq for Bucket { - fn eq(&self, rhs: &u64) -> bool { +impl cmp::PartialEq for Bucket { + fn eq(&self, rhs: &f64) -> bool { if let Bucket::Le(ref ceiling) = *self { ceiling == rhs } else { @@ -263,8 +270,8 @@ impl cmp::PartialEq for Bucket { } } -impl cmp::PartialOrd for Bucket { - fn partial_cmp(&self, rhs: &u64) -> Option { +impl cmp::PartialOrd for Bucket { + fn partial_cmp(&self, rhs: &f64) -> Option { if let Bucket::Le(ref ceiling) = *self { ceiling.partial_cmp(rhs) } else { @@ -274,17 +281,6 @@ impl cmp::PartialOrd for Bucket { } } -impl cmp::Ord for Bucket { - fn cmp(&self, rhs: &Bucket) -> cmp::Ordering { - match (*self, *rhs) { - (Bucket::Le(s), Bucket::Le(r)) => s.cmp(&r), - (Bucket::Le(_), Bucket::Inf) => cmp::Ordering::Less, - (Bucket::Inf, Bucket::Le(_)) => cmp::Ordering::Greater, - (Bucket::Inf, Bucket::Inf) => cmp::Ordering::Equal, - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -294,52 +290,52 @@ mod tests { use std::u64; static BOUNDS: &'static Bounds = &Bounds(&[ - Bucket::Le(10), - Bucket::Le(20), - Bucket::Le(30), - Bucket::Le(40), - Bucket::Le(50), - Bucket::Le(60), - Bucket::Le(70), - Bucket::Le(80), - Bucket::Le(90), - Bucket::Le(100), - Bucket::Le(200), - Bucket::Le(300), - Bucket::Le(400), - Bucket::Le(500), - Bucket::Le(600), - Bucket::Le(700), - Bucket::Le(800), - Bucket::Le(900), - Bucket::Le(1_000), - Bucket::Le(2_000), - Bucket::Le(3_000), - Bucket::Le(4_000), - Bucket::Le(5_000), - Bucket::Le(6_000), - Bucket::Le(7_000), - Bucket::Le(8_000), - Bucket::Le(9_000), - Bucket::Le(10_000), - Bucket::Le(20_000), - Bucket::Le(30_000), - Bucket::Le(40_000), - Bucket::Le(50_000), - Bucket::Le(60_000), - Bucket::Le(70_000), - Bucket::Le(80_000), - Bucket::Le(90_000), - Bucket::Le(100_000), - Bucket::Le(200_000), - Bucket::Le(300_000), - Bucket::Le(400_000), - Bucket::Le(500_000), - Bucket::Le(600_000), - Bucket::Le(700_000), - Bucket::Le(800_000), - Bucket::Le(900_000), - Bucket::Le(1_000_000), + Bucket::Le(0.010), + Bucket::Le(0.020), + Bucket::Le(0.030), + Bucket::Le(0.040), + Bucket::Le(0.050), + Bucket::Le(0.060), + Bucket::Le(0.070), + Bucket::Le(0.080), + Bucket::Le(0.090), + Bucket::Le(0.100), + Bucket::Le(0.200), + Bucket::Le(0.300), + Bucket::Le(0.400), + Bucket::Le(0.500), + Bucket::Le(0.600), + Bucket::Le(0.700), + Bucket::Le(0.800), + Bucket::Le(0.900), + Bucket::Le(1.000), + Bucket::Le(2.000), + Bucket::Le(3.000), + Bucket::Le(4.000), + Bucket::Le(5.000), + Bucket::Le(6.000), + Bucket::Le(7.000), + Bucket::Le(8.000), + Bucket::Le(9.000), + Bucket::Le(10.000), + Bucket::Le(20.000), + Bucket::Le(30.000), + Bucket::Le(40.000), + Bucket::Le(50.000), + Bucket::Le(60.000), + Bucket::Le(70.000), + Bucket::Le(80.000), + Bucket::Le(90.000), + Bucket::Le(100.000), + Bucket::Le(200.000), + Bucket::Le(300.000), + Bucket::Le(400.000), + Bucket::Le(500.000), + Bucket::Le(600.000), + Bucket::Le(700.000), + Bucket::Le(800.000), + Bucket::Le(900.000), + Bucket::Le(1_000.000), Bucket::Inf, ]); @@ -348,20 +344,20 @@ mod tests { let hist = Histogram::::new(&BOUNDS); hist.add(obs); // The bucket containing `obs` must have count 1. - hist.assert_bucket_exactly(obs, 1) + hist.assert_bucket_exactly(obs as f64, 1.0) // All buckets less than the one containing `obs` must have // counts of exactly 0. - .assert_lt_exactly(obs, 0) + .assert_lt_exactly(obs as f64, 0.0) // All buckets greater than the one containing `obs` must // have counts of exactly 0. - .assert_gt_exactly(obs, 0); + .assert_gt_exactly(obs as f64, 0.0); true } fn sum_equals_total_of_observations(observations: Vec) -> bool { let hist = Histogram::::new(&BOUNDS); - let expected_sum = Counter::default(); + let expected_sum = Counter::<()>::default(); for obs in observations { expected_sum.add(obs); hist.add(obs); @@ -377,31 +373,31 @@ mod tests { hist.add(*obs); } - let count = hist.buckets.iter().map(|ref c| c.value()).sum::() as usize; - count == observations.len() + let count = hist.buckets.iter().map(|ref c| c.value()).sum::(); + count == observations.len() as f64 } fn multiple_observations_increment_buckets(observations: Vec) -> bool { - let mut buckets_and_counts: HashMap = HashMap::new(); + let mut buckets_and_counts: HashMap = HashMap::new(); let hist = Histogram::::new(&BOUNDS); for obs in observations { let incremented_bucket = &BOUNDS.0.iter() .position(|bucket| match *bucket { - Bucket::Le(ceiling) => obs <= ceiling, + Bucket::Le(ceiling) => obs as f64 <= ceiling, Bucket::Inf => true, }) .unwrap(); *buckets_and_counts .entry(*incremented_bucket) - .or_insert(0) += 1; + .or_insert(0.0) += 1.0; hist.add(obs); } for (i, count) in hist.buckets.iter().enumerate() { let count = count.value(); - assert_eq!(buckets_and_counts.get(&i).unwrap_or(&0), &count); + assert_eq!(buckets_and_counts.get(&i).unwrap_or(&0.0), &count); } true } diff --git a/linkerd/metrics/src/latency.rs b/linkerd/metrics/src/latency.rs index 50733884c3..6c469c3a5f 100644 --- a/linkerd/metrics/src/latency.rs +++ b/linkerd/metrics/src/latency.rs @@ -5,31 +5,31 @@ use super::histogram::{Bounds, Bucket, Histogram}; /// The maximum value (inclusive) for each latency bucket in /// milliseconds. pub const BOUNDS: &Bounds = &Bounds(&[ - Bucket::Le(1), - Bucket::Le(2), - Bucket::Le(3), - Bucket::Le(4), - Bucket::Le(5), - Bucket::Le(10), - Bucket::Le(20), - Bucket::Le(30), - Bucket::Le(40), - Bucket::Le(50), - Bucket::Le(100), - Bucket::Le(200), - Bucket::Le(300), - Bucket::Le(400), - Bucket::Le(500), - Bucket::Le(1_000), - Bucket::Le(2_000), - Bucket::Le(3_000), - Bucket::Le(4_000), - Bucket::Le(5_000), - Bucket::Le(10_000), - Bucket::Le(20_000), - Bucket::Le(30_000), - Bucket::Le(40_000), - Bucket::Le(50_000), + Bucket::Le(1.0), + Bucket::Le(2.0), + Bucket::Le(3.0), + Bucket::Le(4.0), + Bucket::Le(5.0), + Bucket::Le(10.0), + Bucket::Le(20.0), + Bucket::Le(30.0), + Bucket::Le(40.0), + Bucket::Le(50.0), + Bucket::Le(100.0), + Bucket::Le(200.0), + Bucket::Le(300.0), + Bucket::Le(400.0), + Bucket::Le(500.0), + Bucket::Le(1_000.0), + Bucket::Le(2_000.0), + Bucket::Le(3_000.0), + Bucket::Le(4_000.0), + Bucket::Le(5_000.0), + Bucket::Le(10_000.0), + Bucket::Le(20_000.0), + Bucket::Le(30_000.0), + Bucket::Le(40_000.0), + Bucket::Le(50_000.0), // A final upper bound. Bucket::Inf, ]); diff --git a/linkerd/metrics/src/lib.rs b/linkerd/metrics/src/lib.rs index 214d8155d7..afbba44ad7 100644 --- a/linkerd/metrics/src/lib.rs +++ b/linkerd/metrics/src/lib.rs @@ -31,3 +31,28 @@ macro_rules! metrics { )+ } } + +pub trait Factor { + fn factor(n: u64) -> f64; +} + +pub struct MillisAsSeconds; + +/// Largest `u64` that can fit without loss of precision in `f64` (2^53). +/// +/// Wrapping is based on the fact that Prometheus models values as f64 (52-bits +/// mantissa), thus integer values over 2^53 are not guaranteed to be correctly +/// exposed. +const MAX_PRECISE_UINT64: u64 = 0x20_0000_0000_0000; + +impl Factor for () { + fn factor(n: u64) -> f64 { + n.wrapping_rem(MAX_PRECISE_UINT64 + 1) as f64 + } +} + +impl Factor for MillisAsSeconds { + fn factor(n: u64) -> f64 { + n.wrapping_rem((MAX_PRECISE_UINT64 + 1) * 1000) as f64 * 0.001 + } +} diff --git a/linkerd/metrics/src/prom.rs b/linkerd/metrics/src/prom.rs index fdfd6ff46d..2c889cb6ec 100644 --- a/linkerd/metrics/src/prom.rs +++ b/linkerd/metrics/src/prom.rs @@ -1,13 +1,6 @@ use std::fmt; use std::marker::{PhantomData, Sized}; -/// Largest `u64` that can fit without loss of precision in `f64` (2^53). -/// -/// Wrapping is based on the fact that Prometheus models values as f64 (52-bits -/// mantissa), thus integer values over 2^53 are not guaranteed to be correctly -/// exposed. -pub(crate) const MAX_PRECISE_VALUE: u64 = 0x20_0000_0000_0000; - /// Writes a block of metrics in prometheus-formatted output. pub trait FmtMetrics { fn fmt_metrics(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;