Skip to content

Commit

Permalink
Add support for summary metrics in the prometheus bridge (#5089)
Browse files Browse the repository at this point in the history
* add support for summary metrics in the prometheus bridge

* Update CHANGELOG.md

* only get timestamp once
  • Loading branch information
dashpole committed Feb 15, 2024
1 parent dabfd13 commit c78da11
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@ The next release will require at least [Go 1.21].

- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)
- Support [Go 1.22]. (#5082)
- Add support for Summary metrics to `go.opentelemetry.io/contrib/bridges/prometheus`. (#5089)

### Removed

Expand Down
1 change: 0 additions & 1 deletion bridges/prometheus/doc.go
Expand Up @@ -19,7 +19,6 @@
// to be used with OpenTelemetry exporters, including OTLP.
//
// Limitations:
// - Summary metrics are dropped by the bridge.
// - Prometheus histograms are translated to OpenTelemetry fixed-bucket
// histograms, rather than exponential histograms.
//
Expand Down
41 changes: 40 additions & 1 deletion bridges/prometheus/producer.go
Expand Up @@ -101,8 +101,10 @@ func convertPrometheusMetricsInto(promMetrics []*dto.MetricFamily, now time.Time
newMetric.Data = convertCounter(pm.GetMetric(), now)
case dto.MetricType_HISTOGRAM:
newMetric.Data = convertHistogram(pm.GetMetric(), now)
case dto.MetricType_SUMMARY:
newMetric.Data = convertSummary(pm.GetMetric(), now)
default:
// MetricType_GAUGE_HISTOGRAM, MetricType_SUMMARY, MetricType_UNTYPED
// MetricType_GAUGE_HISTOGRAM, MetricType_UNTYPED
errs = append(errs, fmt.Errorf("%w: %v for metric %v", errUnsupportedType, pm.GetType(), pm.GetName()))
continue
}
Expand Down Expand Up @@ -204,6 +206,43 @@ func convertBuckets(buckets []*dto.Bucket) ([]float64, []uint64, []metricdata.Ex
return bounds, bucketCounts, exemplars
}

func convertSummary(metrics []*dto.Metric, now time.Time) metricdata.Summary {
otelSummary := metricdata.Summary{
DataPoints: make([]metricdata.SummaryDataPoint, len(metrics)),
}
for i, m := range metrics {
dp := metricdata.SummaryDataPoint{
Attributes: convertLabels(m.GetLabel()),
StartTime: processStartTime,
Time: now,
Count: m.GetSummary().GetSampleCount(),
Sum: m.GetSummary().GetSampleSum(),
QuantileValues: convertQuantiles(m.GetSummary().GetQuantile()),
}
createdTs := m.GetSummary().GetCreatedTimestamp()
if createdTs.IsValid() {
dp.StartTime = createdTs.AsTime()
}
if t := m.GetTimestampMs(); t != 0 {
dp.Time = time.UnixMilli(t)
}
otelSummary.DataPoints[i] = dp
}
return otelSummary
}

func convertQuantiles(quantiles []*dto.Quantile) []metricdata.QuantileValue {
otelQuantiles := make([]metricdata.QuantileValue, len(quantiles))
for i, quantile := range quantiles {
dp := metricdata.QuantileValue{
Quantile: quantile.GetQuantile(),
Value: quantile.GetValue(),
}
otelQuantiles[i] = dp
}
return otelQuantiles
}

func convertLabels(labels []*dto.LabelPair) attribute.Set {
kvs := make([]attribute.KeyValue, len(labels))
for i, l := range labels {
Expand Down
63 changes: 55 additions & 8 deletions bridges/prometheus/producer_test.go
Expand Up @@ -128,19 +128,44 @@ func TestProduce(t *testing.T) {
}},
},
{
name: "summary dropped",
name: "summary",
testFn: func(reg *prometheus.Registry) {
metric := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "test_summary_metric",
Help: "A summary metric for testing",
Name: "test_summary_metric",
Help: "A summary metric for testing",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
ConstLabels: prometheus.Labels(map[string]string{
"foo": "bar",
}),
})
reg.MustRegister(metric)
metric.Observe(15.0)
},
wantErr: errUnsupportedType,
expected: []metricdata.ScopeMetrics{{
Scope: instrumentation.Scope{
Name: scopeName,
},
Metrics: []metricdata.Metrics{
{
Name: "test_summary_metric",
Description: "A summary metric for testing",
Data: metricdata.Summary{
DataPoints: []metricdata.SummaryDataPoint{
{
Count: 1,
Sum: 15.0,
QuantileValues: []metricdata.QuantileValue{
{Quantile: 0.5, Value: 15},
{Quantile: 0.9, Value: 15},
{Quantile: 0.99, Value: 15},
},
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
},
},
},
},
},
}},
},
{
name: "histogram",
Expand Down Expand Up @@ -207,12 +232,13 @@ func TestProduce(t *testing.T) {
})
reg.MustRegister(metric)
metric.Set(123.4)
unsupportedMetric := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "test_summary_metric",
Help: "A summary metric for testing",
unsupportedMetric := prometheus.NewUntypedFunc(prometheus.UntypedOpts{
Name: "test_untyped_metric",
Help: "An untyped metric for testing",
}, func() float64 {
return 135.8
})
reg.MustRegister(unsupportedMetric)
unsupportedMetric.Observe(15.0)
},
expected: []metricdata.ScopeMetrics{{
Scope: instrumentation.Scope{
Expand Down Expand Up @@ -315,6 +341,27 @@ func TestProduceForStartTime(t *testing.T) {
return sts
},
},
{
name: "summary",
testFn: func(reg *prometheus.Registry) {
metric := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "test_summary_metric",
Help: "A summary metric for testing",
ConstLabels: prometheus.Labels(map[string]string{
"foo": "bar",
}),
})
reg.MustRegister(metric)
},
startTimeFn: func(aggr metricdata.Aggregation) []time.Time {
dps := aggr.(metricdata.Summary).DataPoints
sts := make([]time.Time, len(dps))
for i, dp := range dps {
sts[i] = dp.StartTime
}
return sts
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit c78da11

Please sign in to comment.