Skip to content

Commit 6c2ce12

Browse files
authored
Add Exponential histogram (#1267)
1 parent 08c3d48 commit 6c2ce12

File tree

12 files changed

+2114
-24
lines changed

12 files changed

+2114
-24
lines changed

opentelemetry-proto/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Implement tonic metrics proto transformations (#1184)
88
- Move proto for zPage to tonic [#1214](https://github.com/open-telemetry/opentelemetry-rust/pull/1214)
9+
- Support exponential histograms (#1267)
910

1011
### Changed
1112

opentelemetry-proto/src/transform/metrics.rs

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,28 @@ pub mod tonic {
1010

1111
use opentelemetry::{global, metrics::MetricsError, Key, Value};
1212
use opentelemetry_sdk::metrics::data::{
13-
self, Exemplar as SdkExemplar, Gauge as SdkGauge, Histogram as SdkHistogram,
14-
Metric as SdkMetric, ScopeMetrics as SdkScopeMetrics, Sum as SdkSum, Temporality,
13+
self, Exemplar as SdkExemplar, ExponentialHistogram as SdkExponentialHistogram,
14+
Gauge as SdkGauge, Histogram as SdkHistogram, Metric as SdkMetric,
15+
ScopeMetrics as SdkScopeMetrics, Sum as SdkSum, Temporality,
1516
};
1617
use opentelemetry_sdk::Resource as SdkResource;
1718

1819
use crate::proto::tonic::{
1920
collector::metrics::v1::ExportMetricsServiceRequest,
2021
common::v1::KeyValue,
2122
metrics::v1::{
22-
exemplar, exemplar::Value as TonicExemplarValue, metric::Data as TonicMetricData,
23-
number_data_point, number_data_point::Value as TonicDataPointValue,
23+
exemplar, exemplar::Value as TonicExemplarValue,
24+
exponential_histogram_data_point::Buckets as TonicBuckets,
25+
metric::Data as TonicMetricData, number_data_point,
26+
number_data_point::Value as TonicDataPointValue,
2427
AggregationTemporality as TonicTemporality, AggregationTemporality,
25-
DataPointFlags as TonicDataPointFlags, Exemplar as TonicExemplar, Gauge as TonicGauge,
26-
Histogram as TonicHistogram, HistogramDataPoint as TonicHistogramDataPoint,
27-
Metric as TonicMetric, NumberDataPoint as TonicNumberDataPoint,
28-
ResourceMetrics as TonicResourceMetrics, ScopeMetrics as TonicScopeMetrics,
29-
Sum as TonicSum,
28+
DataPointFlags as TonicDataPointFlags, Exemplar as TonicExemplar,
29+
ExponentialHistogram as TonicExponentialHistogram,
30+
ExponentialHistogramDataPoint as TonicExponentialHistogramDataPoint,
31+
Gauge as TonicGauge, Histogram as TonicHistogram,
32+
HistogramDataPoint as TonicHistogramDataPoint, Metric as TonicMetric,
33+
NumberDataPoint as TonicNumberDataPoint, ResourceMetrics as TonicResourceMetrics,
34+
ScopeMetrics as TonicScopeMetrics, Sum as TonicSum,
3035
},
3136
resource::v1::Resource as TonicResource,
3237
};
@@ -159,6 +164,12 @@ pub mod tonic {
159164
Ok(TonicMetricData::Histogram(hist.into()))
160165
} else if let Some(hist) = data.downcast_ref::<SdkHistogram<f64>>() {
161166
Ok(TonicMetricData::Histogram(hist.into()))
167+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<i64>>() {
168+
Ok(TonicMetricData::ExponentialHistogram(hist.into()))
169+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<u64>>() {
170+
Ok(TonicMetricData::ExponentialHistogram(hist.into()))
171+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<f64>>() {
172+
Ok(TonicMetricData::ExponentialHistogram(hist.into()))
162173
} else if let Some(sum) = data.downcast_ref::<SdkSum<u64>>() {
163174
Ok(TonicMetricData::Sum(sum.into()))
164175
} else if let Some(sum) = data.downcast_ref::<SdkSum<i64>>() {
@@ -229,6 +240,43 @@ pub mod tonic {
229240
}
230241
}
231242

243+
impl<T> From<&SdkExponentialHistogram<T>> for TonicExponentialHistogram
244+
where
245+
T: Numeric,
246+
{
247+
fn from(hist: &SdkExponentialHistogram<T>) -> Self {
248+
TonicExponentialHistogram {
249+
data_points: hist
250+
.data_points
251+
.iter()
252+
.map(|dp| TonicExponentialHistogramDataPoint {
253+
attributes: dp.attributes.iter().map(Into::into).collect(),
254+
start_time_unix_nano: to_nanos(dp.start_time),
255+
time_unix_nano: to_nanos(dp.time),
256+
count: dp.count as u64,
257+
sum: Some(dp.sum.into_f64()),
258+
scale: dp.scale.into(),
259+
zero_count: dp.zero_count,
260+
positive: Some(TonicBuckets {
261+
offset: dp.positive_bucket.offset,
262+
bucket_counts: dp.positive_bucket.counts.clone(),
263+
}),
264+
negative: Some(TonicBuckets {
265+
offset: dp.negative_bucket.offset,
266+
bucket_counts: dp.negative_bucket.counts.clone(),
267+
}),
268+
flags: TonicDataPointFlags::default() as u32,
269+
exemplars: dp.exemplars.iter().map(Into::into).collect(),
270+
min: dp.min.map(Numeric::into_f64),
271+
max: dp.max.map(Numeric::into_f64),
272+
zero_threshold: dp.zero_threshold,
273+
})
274+
.collect(),
275+
aggregation_temporality: TonicTemporality::from(hist.temporality).into(),
276+
}
277+
}
278+
}
279+
232280
impl<T> From<&SdkSum<T>> for TonicSum
233281
where
234282
T: fmt::Debug + Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy,
@@ -302,19 +350,24 @@ pub mod grpcio {
302350

303351
use opentelemetry::{global, metrics::MetricsError, Key, Value};
304352
use opentelemetry_sdk::metrics::data::{
305-
self, Exemplar as SdkExemplar, Gauge as SdkGauge, Histogram as SdkHistogram,
306-
Metric as SdkMetric, ScopeMetrics as SdkScopeMetrics, Sum as SdkSum, Temporality,
353+
self, Exemplar as SdkExemplar, ExponentialHistogram as SdkExponentialHistogram,
354+
Gauge as SdkGauge, Histogram as SdkHistogram, Metric as SdkMetric,
355+
ScopeMetrics as SdkScopeMetrics, Sum as SdkSum, Temporality,
307356
};
308357
use opentelemetry_sdk::Resource as SdkResource;
309358

310359
use crate::proto::grpcio::{
311360
collector::metrics::v1::ExportMetricsServiceRequest,
312361
common::v1::KeyValue,
313362
metrics::v1::{
314-
exemplar, exemplar::Value as GrpcioExemplarValue, metric::Data as GrpcioMetricData,
315-
number_data_point, number_data_point::Value as GrpcioDataPointValue,
363+
exemplar, exemplar::Value as GrpcioExemplarValue,
364+
exponential_histogram_data_point::Buckets as GrpcioBuckets,
365+
metric::Data as GrpcioMetricData, number_data_point,
366+
number_data_point::Value as GrpcioDataPointValue,
316367
AggregationTemporality as GrpcioTemporality, AggregationTemporality,
317368
DataPointFlags as GrpcioDataPointFlags, Exemplar as GrpcioExemplar,
369+
ExponentialHistogram as GrpcioExponentialHistogram,
370+
ExponentialHistogramDataPoint as GrpcioExponentialHistogramDataPoint,
318371
Gauge as GrpcioGauge, Histogram as GrpcioHistogram,
319372
HistogramDataPoint as GrpcioHistogramDataPoint, Metric as GrpcioMetric,
320373
NumberDataPoint as GrpcioNumberDataPoint, ResourceMetrics as GrpcioResourceMetrics,
@@ -451,6 +504,12 @@ pub mod grpcio {
451504
Ok(GrpcioMetricData::Histogram(hist.into()))
452505
} else if let Some(hist) = data.downcast_ref::<SdkHistogram<f64>>() {
453506
Ok(GrpcioMetricData::Histogram(hist.into()))
507+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<i64>>() {
508+
Ok(GrpcioMetricData::ExponentialHistogram(hist.into()))
509+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<u64>>() {
510+
Ok(GrpcioMetricData::ExponentialHistogram(hist.into()))
511+
} else if let Some(hist) = data.downcast_ref::<SdkExponentialHistogram<f64>>() {
512+
Ok(GrpcioMetricData::ExponentialHistogram(hist.into()))
454513
} else if let Some(sum) = data.downcast_ref::<SdkSum<u64>>() {
455514
Ok(GrpcioMetricData::Sum(sum.into()))
456515
} else if let Some(sum) = data.downcast_ref::<SdkSum<i64>>() {
@@ -521,6 +580,43 @@ pub mod grpcio {
521580
}
522581
}
523582

583+
impl<T> From<&SdkExponentialHistogram<T>> for GrpcioExponentialHistogram
584+
where
585+
T: Numeric,
586+
{
587+
fn from(hist: &SdkExponentialHistogram<T>) -> Self {
588+
GrpcioExponentialHistogram {
589+
data_points: hist
590+
.data_points
591+
.iter()
592+
.map(|dp| GrpcioExponentialHistogramDataPoint {
593+
attributes: dp.attributes.iter().map(Into::into).collect(),
594+
start_time_unix_nano: to_nanos(dp.start_time),
595+
time_unix_nano: to_nanos(dp.time),
596+
count: dp.count as u64,
597+
sum: Some(dp.sum.into_f64()),
598+
scale: dp.scale.into(),
599+
zero_count: dp.zero_count,
600+
positive: Some(GrpcioBuckets {
601+
offset: dp.positive_bucket.offset,
602+
bucket_counts: dp.positive_bucket.counts.clone(),
603+
}),
604+
negative: Some(GrpcioBuckets {
605+
offset: dp.negative_bucket.offset,
606+
bucket_counts: dp.negative_bucket.counts.clone(),
607+
}),
608+
flags: GrpcioDataPointFlags::default() as u32,
609+
exemplars: dp.exemplars.iter().map(Into::into).collect(),
610+
min: dp.min.map(Numeric::into_f64),
611+
max: dp.max.map(Numeric::into_f64),
612+
zero_threshold: dp.zero_threshold,
613+
})
614+
.collect(),
615+
aggregation_temporality: GrpcioTemporality::from(hist.temporality).into(),
616+
}
617+
}
618+
}
619+
524620
impl<T> From<&SdkSum<T>> for GrpcioSum
525621
where
526622
T: fmt::Debug + Into<GrpcioExemplarValue> + Into<GrpcioDataPointValue> + Copy,

opentelemetry-sdk/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Log warning if two instruments have the same name with different (#1266)
88
casing
99
- Log warning if view is created with empty criteria (#1266)
10+
- Add exponential histogram support (#1267)
1011

1112
### Changed
1213

opentelemetry-sdk/src/attributes/set.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl Eq for HashKeyValue {}
104104
///
105105
/// This must implement [Hash], [PartialEq], and [Eq] so it may be used as
106106
/// HashMap keys and other de-duplication methods.
107-
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
107+
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
108108
pub struct AttributeSet(BTreeSet<HashKeyValue>);
109109

110110
impl From<&[KeyValue]> for AttributeSet {

opentelemetry-sdk/src/metrics/aggregation.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt;
22

3+
use crate::metrics::internal::{EXPO_MAX_SCALE, EXPO_MIN_SCALE};
34
use opentelemetry::metrics::{MetricsError, Result};
45

56
/// The way recorded measurements are summarized.
@@ -62,6 +63,32 @@ pub enum Aggregation {
6263
/// instances.
6364
record_min_max: bool,
6465
},
66+
67+
/// An aggregation that summarizes a set of measurements as a histogram with
68+
/// bucket widths that grow exponentially.
69+
Base2ExponentialHistogram {
70+
/// The maximum number of buckets to use for the histogram.
71+
max_size: u32,
72+
73+
/// The maximum resolution scale to use for the histogram.
74+
///
75+
/// The maximum value is `20`, in which case the maximum number of buckets
76+
/// that can fit within the range of a signed 32-bit integer index could be
77+
/// used.
78+
///
79+
/// The minimum value is `-10` in which case only two buckets will be used.
80+
max_scale: i8,
81+
82+
/// Indicates whether to not record the min and max of the distribution.
83+
///
84+
/// By default, these values are recorded.
85+
///
86+
/// It is generally not valuable to record min and max for cumulative data
87+
/// as they will represent the entire life of the instrument instead of just
88+
/// the current collection cycle, you can opt out by setting this value to
89+
/// `false`
90+
record_min_max: bool,
91+
},
6592
}
6693

6794
impl fmt::Display for Aggregation {
@@ -73,6 +100,7 @@ impl fmt::Display for Aggregation {
73100
Aggregation::Sum => "Sum",
74101
Aggregation::LastValue => "LastValue",
75102
Aggregation::ExplicitBucketHistogram { .. } => "ExplicitBucketHistogram",
103+
Aggregation::Base2ExponentialHistogram { .. } => "Base2ExponentialHistogram",
76104
};
77105

78106
f.write_str(name)
@@ -97,6 +125,22 @@ impl Aggregation {
97125
}
98126
}
99127

128+
Ok(())
129+
}
130+
Aggregation::Base2ExponentialHistogram { max_scale, .. } => {
131+
if *max_scale > EXPO_MAX_SCALE {
132+
return Err(MetricsError::Config(format!(
133+
"aggregation: exponential histogram: max scale ({}) is greater than 20",
134+
max_scale,
135+
)));
136+
}
137+
if *max_scale < -EXPO_MIN_SCALE {
138+
return Err(MetricsError::Config(format!(
139+
"aggregation: exponential histogram: max scale ({}) is less than -10",
140+
max_scale,
141+
)));
142+
}
143+
100144
Ok(())
101145
}
102146
}

opentelemetry-sdk/src/metrics/data/mod.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,88 @@ impl<T: Copy> Clone for HistogramDataPoint<T> {
186186
}
187187
}
188188

189+
/// The histogram of all measurements of values from an instrument.
190+
#[derive(Debug)]
191+
pub struct ExponentialHistogram<T> {
192+
/// The individual aggregated measurements with unique attributes.
193+
pub data_points: Vec<ExponentialHistogramDataPoint<T>>,
194+
195+
/// Describes if the aggregation is reported as the change from the last report
196+
/// time, or the cumulative changes since a fixed start time.
197+
pub temporality: Temporality,
198+
}
199+
200+
impl<T: fmt::Debug + Send + Sync + 'static> Aggregation for ExponentialHistogram<T> {
201+
fn as_any(&self) -> &dyn any::Any {
202+
self
203+
}
204+
fn as_mut(&mut self) -> &mut dyn any::Any {
205+
self
206+
}
207+
}
208+
209+
/// A single exponential histogram data point in a time series.
210+
#[derive(Debug)]
211+
pub struct ExponentialHistogramDataPoint<T> {
212+
/// The set of key value pairs that uniquely identify the time series.
213+
pub attributes: AttributeSet,
214+
/// When the time series was started.
215+
pub start_time: SystemTime,
216+
/// The time when the time series was recorded.
217+
pub time: SystemTime,
218+
219+
/// The number of updates this histogram has been calculated with.
220+
pub count: usize,
221+
/// The minimum value recorded.
222+
pub min: Option<T>,
223+
/// The maximum value recorded.
224+
pub max: Option<T>,
225+
/// The sum of the values recorded.
226+
pub sum: T,
227+
228+
/// Describes the resolution of the histogram.
229+
///
230+
/// Boundaries are located at powers of the base, where:
231+
///
232+
/// base = 2 ^ (2 ^ -scale)
233+
pub scale: i8,
234+
235+
/// The number of values whose absolute value is less than or equal to
236+
/// `zero_threshold`.
237+
///
238+
/// When `zero_threshold` is `0`, this is the number of values that cannot be
239+
/// expressed using the standard exponential formula as well as values that have
240+
/// been rounded to zero.
241+
pub zero_count: u64,
242+
243+
/// The range of positive value bucket counts.
244+
pub positive_bucket: ExponentialBucket,
245+
/// The range of negative value bucket counts.
246+
pub negative_bucket: ExponentialBucket,
247+
248+
/// The width of the zero region.
249+
///
250+
/// Where the zero region is defined as the closed interval
251+
/// [-zero_threshold, zero_threshold].
252+
pub zero_threshold: f64,
253+
254+
/// The sampled exemplars collected during the time series.
255+
pub exemplars: Vec<Exemplar<T>>,
256+
}
257+
258+
/// A set of bucket counts, encoded in a contiguous array of counts.
259+
#[derive(Debug, PartialEq)]
260+
pub struct ExponentialBucket {
261+
/// The bucket index of the first entry in the `counts` vec.
262+
pub offset: i32,
263+
264+
/// A vec where `counts[i]` carries the count of the bucket at index `offset + i`.
265+
///
266+
/// `counts[i]` is the count of values greater than base^(offset+i) and less than
267+
/// or equal to base^(offset+i+1).
268+
pub counts: Vec<u64>,
269+
}
270+
189271
/// A measurement sampled from a time series providing a typical example.
190272
#[derive(Debug)]
191273
pub struct Exemplar<T> {

opentelemetry-sdk/src/metrics/instrument.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ pub(crate) struct InstrumentId {
226226
pub(crate) description: Cow<'static, str>,
227227
/// Defines the functional group of the instrument.
228228
pub(crate) kind: InstrumentKind,
229-
/// the unit of measurement recorded.
229+
/// The unit of measurement recorded.
230230
pub(crate) unit: Unit,
231231
/// Number is the underlying data type of the instrument.
232232
pub(crate) number: Cow<'static, str>,

0 commit comments

Comments
 (0)