v0.34.2
This commit is contained in:
1 parent
34fb534e02
commit
b2b5b45d8a
22 files changed
+2146
-107
No files matched your search
@@ -2,6 +2,7 @@ use serde::Serialize;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
#[derive(Default)]
|
||||
pub struct Metrics {
|
||||
pub summary_rate_sampler: RateSampler,
|
||||
pub admin_requests: AtomicU64,
|
||||
pub admin_response_headers: AtomicU64,
|
||||
pub admin_header_duration_us: AtomicU64,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Bounded pull-driven rate sampling for the JSON summary. No per-client state,
|
||||
// background task, or unbounded history. A cached sample is shared by readers.
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize)]
|
||||
pub struct SummaryRates {
|
||||
pub requests_per_second: Option<f64>,
|
||||
pub errors_per_second: Option<f64>,
|
||||
pub bytes_in_per_second: Option<f64>,
|
||||
pub bytes_out_per_second: Option<f64>,
|
||||
pub sample_interval_seconds: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct SummaryCounters {
|
||||
requests: u64,
|
||||
errors: u64,
|
||||
bytes_in: u64,
|
||||
bytes_out: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RateState {
|
||||
baseline: Option<(Instant, SummaryCounters)>,
|
||||
latest: SummaryRates,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RateSampler(Mutex<RateState>);
|
||||
|
||||
impl RateSampler {
|
||||
fn sample(&self, counters: SummaryCounters, now: Instant) -> SummaryRates {
|
||||
let mut state = self.0.lock().unwrap_or_else(|error| error.into_inner());
|
||||
if let Some((previous_at, previous)) = state.baseline {
|
||||
let elapsed = now.saturating_duration_since(previous_at);
|
||||
// Do not turn rapid parallel polls into zero/spiky rates. Reuse
|
||||
// the last measured rate until a useful sampling interval passes.
|
||||
if elapsed < Duration::from_secs(1) {
|
||||
return state.latest;
|
||||
}
|
||||
let continuous = elapsed <= Duration::from_secs(30)
|
||||
&& counters.requests >= previous.requests
|
||||
&& counters.errors >= previous.errors
|
||||
&& counters.bytes_in >= previous.bytes_in
|
||||
&& counters.bytes_out >= previous.bytes_out;
|
||||
if continuous {
|
||||
let seconds = elapsed.as_secs_f64();
|
||||
state.latest = SummaryRates {
|
||||
requests_per_second: Some((counters.requests - previous.requests) as f64 / seconds),
|
||||
errors_per_second: Some((counters.errors - previous.errors) as f64 / seconds),
|
||||
bytes_in_per_second: Some((counters.bytes_in - previous.bytes_in) as f64 / seconds),
|
||||
bytes_out_per_second: Some((counters.bytes_out - previous.bytes_out) as f64 / seconds),
|
||||
sample_interval_seconds: Some(seconds),
|
||||
};
|
||||
} else {
|
||||
// Long polling gaps or counter resets invalidate the baseline.
|
||||
state.latest = SummaryRates::default();
|
||||
}
|
||||
}
|
||||
state.baseline = Some((now, counters));
|
||||
state.latest
|
||||
}
|
||||
}
|
||||
|
||||
impl Metrics {
|
||||
pub fn summary_rates(&self) -> SummaryRates {
|
||||
self.summary_rate_sampler.sample(SummaryCounters {
|
||||
requests: self.requests.load(Ordering::Relaxed),
|
||||
errors: self.errors.load(Ordering::Relaxed),
|
||||
bytes_in: self.bytes_in.load(Ordering::Relaxed),
|
||||
bytes_out: self.bytes_out.load(Ordering::Relaxed),
|
||||
}, Instant::now())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod summary_rate_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sampled_rates_handle_first_read_short_intervals_long_gaps_and_resets() {
|
||||
let sampler = RateSampler::default();
|
||||
let at = Instant::now();
|
||||
let counts = |requests, errors, bytes_in, bytes_out| SummaryCounters {
|
||||
requests, errors, bytes_in, bytes_out
|
||||
};
|
||||
assert!(sampler.sample(counts(10, 2, 100, 200), at).requests_per_second.is_none());
|
||||
let rate = sampler.sample(counts(20, 4, 300, 500), at + Duration::from_secs(2));
|
||||
assert_eq!(rate.requests_per_second, Some(5.0));
|
||||
assert_eq!(rate.errors_per_second, Some(1.0));
|
||||
assert_eq!(rate.bytes_in_per_second, Some(100.0));
|
||||
assert_eq!(rate.bytes_out_per_second, Some(150.0));
|
||||
assert_eq!(rate.sample_interval_seconds, Some(2.0));
|
||||
assert_eq!(sampler.sample(counts(21, 4, 310, 520), at + Duration::from_millis(2300)).requests_per_second, Some(5.0));
|
||||
assert!(sampler.sample(counts(22, 4, 330, 550), at + Duration::from_secs(40)).requests_per_second.is_none());
|
||||
assert!(sampler.sample(counts(1, 0, 0, 0), at + Duration::from_secs(42)).requests_per_second.is_none());
|
||||
assert_eq!(sampler.sample(counts(5, 0, 200, 300), at + Duration::from_secs(44)).requests_per_second, Some(2.0));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user