Skip to content

Commit 9693b0f

Browse files
committed
exporter: prometheus: add exponential histogram support
1 parent e15c058 commit 9693b0f

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

exporters/prometheus/exporter.go

+36
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
233233
addHistogramMetric(ch, v, m, name, kv)
234234
case metricdata.Histogram[float64]:
235235
addHistogramMetric(ch, v, m, name, kv)
236+
case metricdata.ExponentialHistogram[int64]:
237+
addExponentialHistogramMetric(ch, v, m, name, kv)
238+
case metricdata.ExponentialHistogram[float64]:
239+
addExponentialHistogramMetric(ch, v, m, name, kv)
236240
case metricdata.Sum[int64]:
237241
addSumMetric(ch, v, m, name, kv)
238242
case metricdata.Sum[float64]:
@@ -246,6 +250,36 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
246250
}
247251
}
248252

253+
func addExponentialHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.ExponentialHistogram[N], m metricdata.Metrics, name string, kv keyVals) {
254+
for _, dp := range histogram.DataPoints {
255+
keys, values := getAttrs(dp.Attributes)
256+
keys = append(keys, kv.keys...)
257+
values = append(values, kv.vals...)
258+
259+
desc := prometheus.NewDesc(name, m.Description, keys, nil)
260+
261+
// From spec: note that Prometheus Native Histograms buckets are indexed by upper boundary while Exponential Histograms are indexed by lower boundary, the result being that the Offset fields are different-by-one.
262+
positiveBuckets := make(map[int]int64)
263+
for i, c := range dp.PositiveBucket.Counts {
264+
positiveBuckets[int(dp.PositiveBucket.Offset)+i+1] = int64(c)
265+
}
266+
267+
negativeBuckets := make(map[int]int64)
268+
for i, c := range dp.NegativeBucket.Counts {
269+
negativeBuckets[int(dp.NegativeBucket.Offset)+i+1] = int64(c)
270+
}
271+
272+
m, err := prometheus.NewConstNativeHistogram(desc, dp.Count, float64(dp.Sum), positiveBuckets, negativeBuckets, dp.ZeroCount, dp.Scale, dp.ZeroThreshold, dp.StartTime, values...)
273+
if err != nil {
274+
otel.Handle(err)
275+
continue
276+
}
277+
278+
// NOTE(GiedriusS): add exemplars here after https://github.com/prometheus/client_golang/pull/1654#pullrequestreview-2434669425 is done.
279+
ch <- m
280+
}
281+
}
282+
249283
func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, name string, kv keyVals) {
250284
for _, dp := range histogram.DataPoints {
251285
keys, values := getAttrs(dp.Attributes)
@@ -441,6 +475,8 @@ func convertsToUnderscore(b rune) bool {
441475

442476
func (c *collector) metricType(m metricdata.Metrics) *dto.MetricType {
443477
switch v := m.Data.(type) {
478+
case metricdata.ExponentialHistogram[int64], metricdata.ExponentialHistogram[float64]:
479+
return dto.MetricType_HISTOGRAM.Enum()
444480
case metricdata.Histogram[int64], metricdata.Histogram[float64]:
445481
return dto.MetricType_HISTOGRAM.Enum()
446482
case metricdata.Sum[float64]:

exporters/prometheus/exporter_test.go

+66-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestPrometheusExporter(t *testing.T) {
3636
options []Option
3737
expectedFile string
3838
disableUTF8 bool
39+
checkMetricFamilies func(t testing.TB, dtos []*dto.MetricFamily)
3940
}{
4041
{
4142
name: "counter",
@@ -172,6 +173,51 @@ func TestPrometheusExporter(t *testing.T) {
172173
gauge.Add(ctx, -.25, opt)
173174
},
174175
},
176+
{
177+
name: "expontential histogram",
178+
expectedFile: "testdata/exponential_histogram.txt",
179+
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
180+
var hist *dto.MetricFamily
181+
182+
for _, mf := range mfs {
183+
if *mf.Name == `exponential_histogram_baz_bytes` {
184+
hist = mf
185+
break
186+
}
187+
}
188+
189+
if hist == nil {
190+
t.Fatal("expected to find histogram")
191+
}
192+
193+
m := hist.GetMetric()[0].Histogram
194+
195+
require.Equal(t, 236.0, *m.SampleSum)
196+
require.Equal(t, uint64(4), *m.SampleCount)
197+
require.Equal(t, []int64{1, -1, 1, -1, 2}, m.PositiveDelta)
198+
require.Equal(t, uint32(5), *m.PositiveSpan[0].Length)
199+
require.Equal(t, int32(3), *m.PositiveSpan[0].Offset)
200+
201+
},
202+
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
203+
// NOTE(GiedriusS): there is no text format for exponential (native)
204+
// histograms so we don't expect any output.
205+
opt := otelmetric.WithAttributes(
206+
attribute.Key("A").String("B"),
207+
attribute.Key("C").String("D"),
208+
)
209+
histogram, err := meter.Float64Histogram(
210+
"exponential_histogram_baz",
211+
otelmetric.WithDescription("a very nice histogram"),
212+
otelmetric.WithUnit("By"),
213+
)
214+
require.NoError(t, err)
215+
histogram.Record(ctx, 23, opt)
216+
histogram.Record(ctx, 7, opt)
217+
histogram.Record(ctx, 101, opt)
218+
histogram.Record(ctx, 105, opt)
219+
},
220+
},
175221
{
176222
name: "histogram",
177223
expectedFile: "testdata/histogram.txt",
@@ -470,6 +516,9 @@ func TestPrometheusExporter(t *testing.T) {
470516
}
471517

472518
for _, tc := range testCases {
519+
if tc.name != `expontential histogram` {
520+
continue
521+
}
473522
t.Run(tc.name, func(t *testing.T) {
474523
if tc.disableUTF8 {
475524
model.NameValidationScheme = model.LegacyValidation
@@ -508,7 +557,14 @@ func TestPrometheusExporter(t *testing.T) {
508557
metric.Stream{Aggregation: metric.AggregationExplicitBucketHistogram{
509558
Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 1000},
510559
}},
511-
)),
560+
),
561+
metric.NewView(
562+
metric.Instrument{Name: "exponential_histogram_*"},
563+
metric.Stream{Aggregation: metric.AggregationBase2ExponentialHistogram{
564+
MaxSize: 10,
565+
}},
566+
),
567+
),
512568
)
513569
meter := provider.Meter(
514570
"testmeter",
@@ -524,6 +580,15 @@ func TestPrometheusExporter(t *testing.T) {
524580

525581
err = testutil.GatherAndCompare(registry, file)
526582
require.NoError(t, err)
583+
584+
if tc.checkMetricFamilies == nil {
585+
return
586+
}
587+
588+
mfs, err := registry.Gather()
589+
require.NoError(t, err)
590+
591+
tc.checkMetricFamilies(t, mfs)
527592
})
528593
}
529594
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# HELP exponential_histogram_baz_bytes a very nice histogram
2+
# TYPE exponential_histogram_baz_bytes histogram
3+
exponential_histogram_baz_bytes_bucket{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0",le="+Inf"} 4
4+
exponential_histogram_baz_bytes_sum{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 236
5+
exponential_histogram_baz_bytes_count{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4
6+
# HELP otel_scope_info Instrumentation Scope metadata
7+
# TYPE otel_scope_info gauge
8+
otel_scope_info{fizz="buzz",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1
9+
# HELP target_info Target metadata
10+
# TYPE target_info gauge
11+
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1

0 commit comments

Comments
 (0)