Skip to content

Commit 88608c3

Browse files
feat: observable histogram
1 parent 353bbb0 commit 88608c3

File tree

7 files changed

+310
-20
lines changed

7 files changed

+310
-20
lines changed

opentelemetry-sdk/src/metrics/instrument.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub enum InstrumentKind {
2626
/// A group of instruments that record a distribution of values synchronously with
2727
/// the code path they are measuring.
2828
Histogram,
29+
/// A group of instruments that record a distribution of values in an asynchronously callback.
30+
ObservableHistogram,
2931
/// A group of instruments that record increasing values in an asynchronous
3032
/// callback.
3133
ObservableCounter,
@@ -51,6 +53,7 @@ impl InstrumentKind {
5153
Temporality::Delta => match self {
5254
Self::Counter
5355
| Self::Histogram
56+
| Self::ObservableHistogram
5457
| Self::ObservableCounter
5558
| Self::Gauge
5659
| Self::ObservableGauge => Temporality::Delta,
@@ -60,7 +63,8 @@ impl InstrumentKind {
6063
},
6164
Temporality::LowMemory => match self {
6265
Self::Counter | InstrumentKind::Histogram => Temporality::Delta,
63-
Self::ObservableCounter
66+
Self::ObservableHistogram
67+
| Self::ObservableCounter
6468
| Self::Gauge
6569
| Self::ObservableGauge
6670
| Self::UpDownCounter

opentelemetry-sdk/src/metrics/meter.rs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::{borrow::Cow, sync::Arc};
44

55
use opentelemetry::{
66
metrics::{
7-
AsyncInstrumentBuilder, Counter, Gauge, Histogram, HistogramBuilder, InstrumentBuilder,
8-
InstrumentProvider, ObservableCounter, ObservableGauge, ObservableUpDownCounter,
9-
UpDownCounter,
7+
AsyncHistogramBuilder, AsyncInstrumentBuilder, Counter, Gauge, Histogram, HistogramBuilder,
8+
InstrumentBuilder, InstrumentProvider, ObservableCounter, ObservableGauge,
9+
ObservableHistogram, ObservableUpDownCounter, UpDownCounter,
1010
},
1111
otel_error, InstrumentationScope,
1212
};
@@ -124,7 +124,7 @@ impl SdkMeter {
124124
let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
125125
if let Err(err) = validation_result {
126126
otel_error!(
127-
name: "InstrumentCreationFailed",
127+
name: "InstrumentCreationFailed",
128128
meter_name = self.scope.name(),
129129
instrument_name = builder.name.as_ref(),
130130
message = "Callbacks for this ObservableCounter will not be invoked.",
@@ -183,7 +183,7 @@ impl SdkMeter {
183183
let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
184184
if let Err(err) = validation_result {
185185
otel_error!(
186-
name: "InstrumentCreationFailed",
186+
name: "InstrumentCreationFailed",
187187
meter_name = self.scope.name(),
188188
instrument_name = builder.name.as_ref(),
189189
message = "Callbacks for this ObservableUpDownCounter will not be invoked.",
@@ -242,7 +242,7 @@ impl SdkMeter {
242242
let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
243243
if let Err(err) = validation_result {
244244
otel_error!(
245-
name: "InstrumentCreationFailed",
245+
name: "InstrumentCreationFailed",
246246
meter_name = self.scope.name(),
247247
instrument_name = builder.name.as_ref(),
248248
message = "Callbacks for this ObservableGauge will not be invoked.",
@@ -290,6 +290,65 @@ impl SdkMeter {
290290
}
291291
}
292292

293+
fn create_observable_histogram<T>(
294+
&self,
295+
builder: AsyncHistogramBuilder<'_, ObservableHistogram<T>, T>,
296+
resolver: &InstrumentResolver<'_, T>,
297+
) -> ObservableHistogram<T>
298+
where
299+
T: Number,
300+
{
301+
let validation_result = validate_instrument_config(builder.name.as_ref(), &builder.unit);
302+
if let Err(err) = validation_result {
303+
otel_error!(
304+
name: "InstrumentCreationFailed",
305+
meter_name = self.scope.name(),
306+
instrument_name = builder.name.as_ref(),
307+
message = "Callbacks for this ObservableHistogram will not be invoked.",
308+
reason = format!("{}", err));
309+
return ObservableHistogram::new();
310+
}
311+
312+
match resolver.measures(
313+
InstrumentKind::ObservableHistogram,
314+
builder.name.clone(),
315+
builder.description,
316+
builder.unit,
317+
None,
318+
) {
319+
Ok(ms) => {
320+
if ms.is_empty() {
321+
otel_error!(
322+
name: "InstrumentCreationFailed",
323+
meter_name = self.scope.name(),
324+
instrument_name = builder.name.as_ref(),
325+
message = "Callbacks for this ObservableHistogram will not be invoked. Check View Configuration."
326+
);
327+
return ObservableHistogram::new();
328+
}
329+
330+
let observable = Arc::new(Observable::new(ms));
331+
332+
for callback in builder.callbacks {
333+
let cb_inst = Arc::clone(&observable);
334+
self.pipes
335+
.register_callback(move || callback(cb_inst.as_ref()));
336+
}
337+
338+
ObservableHistogram::new()
339+
}
340+
Err(err) => {
341+
otel_error!(
342+
name: "InstrumentCreationFailed",
343+
meter_name = self.scope.name(),
344+
instrument_name = builder.name.as_ref(),
345+
message = "Callbacks for this ObservableHistogram will not be invoked.",
346+
reason = format!("{}", err));
347+
ObservableHistogram::new()
348+
}
349+
}
350+
}
351+
293352
fn create_updown_counter<T>(
294353
&self,
295354
builder: InstrumentBuilder<'_, UpDownCounter<T>>,
@@ -548,6 +607,22 @@ impl InstrumentProvider for SdkMeter {
548607
let resolver = InstrumentResolver::new(self, &self.u64_resolver);
549608
self.create_histogram(builder, &resolver)
550609
}
610+
611+
fn f64_observable_histogram(
612+
&self,
613+
builder: AsyncHistogramBuilder<'_, ObservableHistogram<f64>, f64>,
614+
) -> ObservableHistogram<f64> {
615+
let resolver = InstrumentResolver::new(self, &self.f64_resolver);
616+
self.create_observable_histogram(builder, &resolver)
617+
}
618+
619+
fn u64_observable_histogram(
620+
&self,
621+
builder: AsyncHistogramBuilder<'_, ObservableHistogram<u64>, u64>,
622+
) -> ObservableHistogram<u64> {
623+
let resolver = InstrumentResolver::new(self, &self.u64_resolver);
624+
self.create_observable_histogram(builder, &resolver)
625+
}
551626
}
552627

553628
fn validate_instrument_config(name: &str, unit: &Option<Cow<'static, str>>) -> MetricResult<()> {

opentelemetry-sdk/src/metrics/pipeline.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ where
482482
/// * Gauge ⇨ LastValue
483483
/// * Observable Gauge ⇨ LastValue
484484
/// * Histogram ⇨ ExplicitBucketHistogram
485+
/// * Observable Histogram ⇨ ExplicitBucketHistogram
485486
///
486487
/// [the spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.19.0/specification/metrics/sdk.md#default-aggregation
487488
fn default_aggregation_selector(kind: InstrumentKind) -> Aggregation {
@@ -492,13 +493,15 @@ fn default_aggregation_selector(kind: InstrumentKind) -> Aggregation {
492493
| InstrumentKind::ObservableUpDownCounter => Aggregation::Sum,
493494
InstrumentKind::Gauge => Aggregation::LastValue,
494495
InstrumentKind::ObservableGauge => Aggregation::LastValue,
495-
InstrumentKind::Histogram => Aggregation::ExplicitBucketHistogram {
496-
boundaries: vec![
497-
0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0,
498-
5000.0, 7500.0, 10000.0,
499-
],
500-
record_min_max: true,
501-
},
496+
InstrumentKind::Histogram | InstrumentKind::ObservableHistogram => {
497+
Aggregation::ExplicitBucketHistogram {
498+
boundaries: vec![
499+
0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0,
500+
5000.0, 7500.0, 10000.0,
501+
],
502+
record_min_max: true,
503+
}
504+
}
502505
}
503506
}
504507

@@ -529,6 +532,7 @@ fn aggregate_fn<T: Number>(
529532
// MetricReader.Collect MUST only receive data points with measurements recorded since the previous collection
530533
InstrumentKind::ObservableCounter => b.precomputed_sum(true),
531534
InstrumentKind::ObservableUpDownCounter => b.precomputed_sum(false),
535+
InstrumentKind::ObservableHistogram => b.precomputed_sum(false),
532536
InstrumentKind::Counter | InstrumentKind::Histogram => b.sum(true),
533537
_ => b.sum(false),
534538
};
@@ -583,6 +587,7 @@ fn aggregate_fn<T: Number>(
583587
/// | Counter | ✓ | | ✓ | ✓ | ✓ |
584588
/// | UpDownCounter | ✓ | | ✓ | ✓ | ✓ |
585589
/// | Histogram | ✓ | | ✓ | ✓ | ✓ |
590+
/// | Observable Histogram | ✓ | | ✓ | ✓ | ✓ |
586591
/// | Observable Counter | ✓ | | ✓ | ✓ | ✓ |
587592
/// | Observable UpDownCounter | ✓ | | ✓ | ✓ | ✓ |
588593
/// | Gauge | ✓ | ✓ | | ✓ | ✓ |
@@ -601,6 +606,7 @@ fn is_aggregator_compatible(
601606
| InstrumentKind::UpDownCounter
602607
| InstrumentKind::Gauge
603608
| InstrumentKind::Histogram
609+
| InstrumentKind::ObservableHistogram
604610
| InstrumentKind::ObservableCounter
605611
| InstrumentKind::ObservableUpDownCounter
606612
| InstrumentKind::ObservableGauge
@@ -615,6 +621,7 @@ fn is_aggregator_compatible(
615621
| InstrumentKind::ObservableUpDownCounter
616622
| InstrumentKind::Counter
617623
| InstrumentKind::Histogram
624+
| InstrumentKind::ObservableHistogram
618625
| InstrumentKind::UpDownCounter => Ok(()),
619626
_ => {
620627
// TODO: review need for aggregation check after

opentelemetry/src/metrics/instruments/histogram.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,32 @@ impl<T> Histogram<T> {
3333
self.0.measure(value, attributes)
3434
}
3535
}
36+
37+
/// An async instrument that records a distribution of values.
38+
#[derive(Clone)]
39+
#[non_exhaustive]
40+
pub struct ObservableHistogram<T> {
41+
_marker: std::marker::PhantomData<T>,
42+
}
43+
44+
impl<T> fmt::Debug for ObservableHistogram<T>
45+
where
46+
T: fmt::Debug,
47+
{
48+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49+
f.write_fmt(format_args!(
50+
"ObservableHistogram<{}>",
51+
std::any::type_name::<T>()
52+
))
53+
}
54+
}
55+
56+
impl<T> ObservableHistogram<T> {
57+
/// Create a new gauge
58+
#[allow(clippy::new_without_default)]
59+
pub fn new() -> Self {
60+
ObservableHistogram {
61+
_marker: std::marker::PhantomData,
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)