use super::Clock;
use opentelemetry::KeyValue;
use opentelemetry::metrics::Histogram;
use quanta::Instant;
#[must_use]
pub struct RecordDurationGuard {
histogram: Histogram<f64>,
clock: Clock,
start: Instant,
attributes: Vec<KeyValue>,
cancelled: bool,
}
impl RecordDurationGuard {
pub fn new(histogram: Histogram<f64>, attributes: impl Into<Vec<KeyValue>>) -> Self {
Self::with_clock(histogram, Clock::new(), attributes)
}
pub fn with_clock(
histogram: Histogram<f64>,
clock: Clock,
attributes: impl Into<Vec<KeyValue>>,
) -> Self {
let start = clock.now();
Self {
histogram,
clock,
start,
attributes: attributes.into(),
cancelled: false,
}
}
pub fn set(&mut self, kv: KeyValue) {
if let Some(existing) = self.attributes.iter_mut().find(|a| a.key == kv.key) {
*existing = kv;
} else {
self.attributes.push(kv);
}
}
pub fn cancel(&mut self) {
self.cancelled = true;
}
}
impl Drop for RecordDurationGuard {
fn drop(&mut self) {
if !self.cancelled {
let elapsed = (self.clock.now() - self.start).as_secs_f64();
self.histogram.record(elapsed, &self.attributes);
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use apollo_opentelemetry_test::{TelemetryContext, assert_metrics_snapshot};
use opentelemetry::KeyValue;
use opentelemetry::global;
use quanta::Clock;
use super::RecordDurationGuard;
#[test]
fn records_on_drop() {
let ctx = TelemetryContext::new();
let histogram = global::meter_provider()
.meter("test")
.f64_histogram("test.duration")
.build();
let (clock, mock) = Clock::mock();
{
let _timer = RecordDurationGuard::with_clock(histogram, clock, []);
mock.increment(Duration::from_millis(1500));
}
assert_metrics_snapshot!(ctx, @r"
- name: test.duration
data:
type: Histogram
data_points:
- count: 1
sum: 1.5
min: 1.5
max: 1.5
bounds:
- 0
- 5
- 10
- 25
- 50
- 75
- 100
- 250
- 500
- 750
- 1000
- 2500
- 5000
- 7500
- 10000
bucket_counts:
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
temporality: Cumulative
");
}
#[test]
fn cancelled_does_not_record() {
let ctx = TelemetryContext::new();
let histogram = global::meter_provider()
.meter("test")
.f64_histogram("test.duration")
.build();
let (clock, mock) = Clock::mock();
{
let mut timer = RecordDurationGuard::with_clock(histogram, clock, []);
mock.increment(Duration::from_millis(1500));
timer.cancel();
}
assert_metrics_snapshot!(ctx, @"[]");
}
#[test]
fn set_deduplicates_attribute_by_key() {
let ctx = TelemetryContext::new();
let histogram = global::meter_provider()
.meter("test")
.f64_histogram("test.duration")
.build();
let (clock, mock) = Clock::mock();
{
let mut timer = RecordDurationGuard::with_clock(histogram, clock, []);
mock.increment(Duration::from_millis(1500));
timer.set(KeyValue::new("http.method", "GET"));
timer.set(KeyValue::new("http.method", "POST"));
}
assert_metrics_snapshot!(ctx, @r"
- name: test.duration
data:
type: Histogram
data_points:
- attributes:
http.method: POST
count: 1
sum: 1.5
min: 1.5
max: 1.5
bounds:
- 0
- 5
- 10
- 25
- 50
- 75
- 100
- 250
- 500
- 750
- 1000
- 2500
- 5000
- 7500
- 10000
bucket_counts:
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
temporality: Cumulative
");
}
}