You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-29 23:07:45 +02:00
Add summary support in the OpenCensus bridge (#4668)
* support summaries in the OpenCensus bridge * divide quantiles by 100
This commit is contained in:
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- Add Summary, SummaryDataPoint, and QuantileValue to `go.opentelemetry.io/sdk/metric/metricdata`. (#4622)
|
||||
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` now supports exemplars from OpenCensus. (#4585)
|
||||
- Add support for `WithExplicitBucketBoundaries` in `go.opentelemetry.io/otel/sdk/metric`. (#4605)
|
||||
- Add support for Summary metrics in `go.opentelemetry.io/otel/bridge/opencensus`. (#4668)
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
// implemented, and An error will be sent to the OpenTelemetry ErrorHandler.
|
||||
//
|
||||
// There are known limitations to the metric bridge:
|
||||
// - Summary-typed metrics are dropped
|
||||
// - GaugeDistribution-typed metrics are dropped
|
||||
// - Histogram's SumOfSquaredDeviation field is dropped
|
||||
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
var (
|
||||
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
|
||||
errMismatchedValueTypes = errors.New("wrong value type for data point")
|
||||
errNegativeDistributionCount = errors.New("distribution count is negative")
|
||||
errNegativeCount = errors.New("distribution or summary count is negative")
|
||||
errNegativeBucketCount = errors.New("distribution bucket count is negative")
|
||||
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
|
||||
errInvalidExemplarSpanContext = errors.New("span context exemplar attachment does not contain an OpenCensus SpanContext")
|
||||
@@ -78,7 +78,8 @@ func convertAggregation(metric *ocmetricdata.Metric) (metricdata.Aggregation, er
|
||||
return convertSum[float64](labelKeys, metric.TimeSeries)
|
||||
case ocmetricdata.TypeCumulativeDistribution:
|
||||
return convertHistogram(labelKeys, metric.TimeSeries)
|
||||
// TODO: Support summaries, once it is in the OTel data types.
|
||||
case ocmetricdata.TypeSummary:
|
||||
return convertSummary(labelKeys, metric.TimeSeries)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %q", errAggregationType, metric.Descriptor.Type)
|
||||
}
|
||||
@@ -146,7 +147,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
|
||||
continue
|
||||
}
|
||||
if dist.Count < 0 {
|
||||
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeDistributionCount, dist.Count))
|
||||
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, dist.Count))
|
||||
continue
|
||||
}
|
||||
points = append(points, metricdata.HistogramDataPoint[float64]{
|
||||
@@ -340,6 +341,65 @@ func complexToString[N complex64 | complex128](val N) string {
|
||||
return strconv.FormatComplex(complex128(val), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// convertSummary converts OpenCensus Summary timeseries to an
|
||||
// OpenTelemetry Summary.
|
||||
func convertSummary(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Summary, error) {
|
||||
points := make([]metricdata.SummaryDataPoint, 0, len(ts))
|
||||
var err error
|
||||
for _, t := range ts {
|
||||
attrs, attrErr := convertAttrs(labelKeys, t.LabelValues)
|
||||
if attrErr != nil {
|
||||
err = errors.Join(err, attrErr)
|
||||
continue
|
||||
}
|
||||
for _, p := range t.Points {
|
||||
summary, ok := p.Value.(*ocmetricdata.Summary)
|
||||
if !ok {
|
||||
err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value))
|
||||
continue
|
||||
}
|
||||
if summary.Count < 0 {
|
||||
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, summary.Count))
|
||||
continue
|
||||
}
|
||||
point := metricdata.SummaryDataPoint{
|
||||
Attributes: attrs,
|
||||
StartTime: t.StartTime,
|
||||
Time: p.Time,
|
||||
Count: uint64(summary.Count),
|
||||
QuantileValues: convertQuantiles(summary.Snapshot),
|
||||
Sum: summary.Sum,
|
||||
}
|
||||
points = append(points, point)
|
||||
}
|
||||
}
|
||||
return metricdata.Summary{DataPoints: points}, err
|
||||
}
|
||||
|
||||
// convertQuantiles converts an OpenCensus summary snapshot to
|
||||
// OpenTelemetry quantiles.
|
||||
func convertQuantiles(snapshot ocmetricdata.Snapshot) []metricdata.QuantileValue {
|
||||
quantileValues := make([]metricdata.QuantileValue, 0, len(snapshot.Percentiles))
|
||||
for quantile, value := range snapshot.Percentiles {
|
||||
quantileValues = append(quantileValues, metricdata.QuantileValue{
|
||||
// OpenCensus quantiles are range (0-100.0], but OpenTelemetry
|
||||
// quantiles are range [0.0, 1.0].
|
||||
Quantile: quantile / 100.0,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
sort.Sort(byQuantile(quantileValues))
|
||||
return quantileValues
|
||||
}
|
||||
|
||||
// byQuantile implements sort.Interface for []metricdata.QuantileValue
|
||||
// based on the Quantile field.
|
||||
type byQuantile []metricdata.QuantileValue
|
||||
|
||||
func (a byQuantile) Len() int { return len(a) }
|
||||
func (a byQuantile) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byQuantile) Less(i, j int) bool { return a[i].Quantile < a[j].Quantile }
|
||||
|
||||
// convertAttrs converts from OpenCensus attribute keys and values to an
|
||||
// OpenTelemetry attribute Set.
|
||||
func convertAttrs(keys []ocmetricdata.LabelKey, values []ocmetricdata.LabelValue) (attribute.Set, error) {
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestConvertMetrics(t *testing.T) {
|
||||
expected: []metricdata.Metrics{},
|
||||
},
|
||||
{
|
||||
desc: "normal Histogram, gauges, and sums",
|
||||
desc: "normal Histogram, summary, gauges, and sums",
|
||||
input: []*ocmetricdata.Metric{
|
||||
{
|
||||
Descriptor: ocmetricdata.Descriptor{
|
||||
@@ -285,6 +285,54 @@ func TestConvertMetrics(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Descriptor: ocmetricdata.Descriptor{
|
||||
Name: "foo.com/summary-a",
|
||||
Description: "a testing summary",
|
||||
Unit: ocmetricdata.UnitMilliseconds,
|
||||
Type: ocmetricdata.TypeSummary,
|
||||
LabelKeys: []ocmetricdata.LabelKey{
|
||||
{Key: "g"},
|
||||
{Key: "h"},
|
||||
},
|
||||
},
|
||||
TimeSeries: []*ocmetricdata.TimeSeries{
|
||||
{
|
||||
LabelValues: []ocmetricdata.LabelValue{
|
||||
{
|
||||
Value: "ding",
|
||||
Present: true,
|
||||
}, {
|
||||
Value: "dong",
|
||||
Present: true,
|
||||
},
|
||||
},
|
||||
Points: []ocmetricdata.Point{
|
||||
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
|
||||
Count: 10,
|
||||
Sum: 13.2,
|
||||
HasCountAndSum: true,
|
||||
Snapshot: ocmetricdata.Snapshot{
|
||||
Percentiles: map[float64]float64{
|
||||
50.0: 1.0,
|
||||
0.0: 0.1,
|
||||
100.0: 10.4,
|
||||
},
|
||||
},
|
||||
}),
|
||||
ocmetricdata.NewSummaryPoint(endTime2, &ocmetricdata.Summary{
|
||||
Count: 12,
|
||||
Snapshot: ocmetricdata.Snapshot{
|
||||
Percentiles: map[float64]float64{
|
||||
0.0: 0.2,
|
||||
50.0: 1.1,
|
||||
100.0: 10.5,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []metricdata.Metrics{
|
||||
@@ -489,6 +537,64 @@ func TestConvertMetrics(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "foo.com/summary-a",
|
||||
Description: "a testing summary",
|
||||
Unit: "ms",
|
||||
Data: metricdata.Summary{
|
||||
DataPoints: []metricdata.SummaryDataPoint{
|
||||
{
|
||||
Attributes: attribute.NewSet(attribute.KeyValue{
|
||||
Key: attribute.Key("g"),
|
||||
Value: attribute.StringValue("ding"),
|
||||
}, attribute.KeyValue{
|
||||
Key: attribute.Key("h"),
|
||||
Value: attribute.StringValue("dong"),
|
||||
}),
|
||||
Time: endTime1,
|
||||
Count: 10,
|
||||
Sum: 13.2,
|
||||
QuantileValues: []metricdata.QuantileValue{
|
||||
{
|
||||
Quantile: 0.0,
|
||||
Value: 0.1,
|
||||
},
|
||||
{
|
||||
Quantile: 0.5,
|
||||
Value: 1.0,
|
||||
},
|
||||
{
|
||||
Quantile: 1.0,
|
||||
Value: 10.4,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Attributes: attribute.NewSet(attribute.KeyValue{
|
||||
Key: attribute.Key("g"),
|
||||
Value: attribute.StringValue("ding"),
|
||||
}, attribute.KeyValue{
|
||||
Key: attribute.Key("h"),
|
||||
Value: attribute.StringValue("dong"),
|
||||
}),
|
||||
Time: endTime2,
|
||||
Count: 12,
|
||||
QuantileValues: []metricdata.QuantileValue{
|
||||
{
|
||||
Quantile: 0.0,
|
||||
Value: 0.2,
|
||||
},
|
||||
{
|
||||
Quantile: 0.5,
|
||||
Value: 1.1,
|
||||
},
|
||||
{
|
||||
Quantile: 1.0,
|
||||
Value: 10.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -586,7 +692,7 @@ func TestConvertMetrics(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: errNegativeDistributionCount,
|
||||
expectedErr: errNegativeCount,
|
||||
},
|
||||
{
|
||||
desc: "histogram with negative bucket count",
|
||||
@@ -638,6 +744,82 @@ func TestConvertMetrics(t *testing.T) {
|
||||
},
|
||||
expectedErr: errMismatchedValueTypes,
|
||||
},
|
||||
{
|
||||
desc: "summary with mismatched attributes",
|
||||
input: []*ocmetricdata.Metric{
|
||||
{
|
||||
Descriptor: ocmetricdata.Descriptor{
|
||||
Name: "foo.com/summary-mismatched",
|
||||
Description: "a mismatched summary",
|
||||
Unit: ocmetricdata.UnitMilliseconds,
|
||||
Type: ocmetricdata.TypeSummary,
|
||||
LabelKeys: []ocmetricdata.LabelKey{
|
||||
{Key: "g"},
|
||||
},
|
||||
},
|
||||
TimeSeries: []*ocmetricdata.TimeSeries{
|
||||
{
|
||||
LabelValues: []ocmetricdata.LabelValue{
|
||||
{
|
||||
Value: "ding",
|
||||
Present: true,
|
||||
}, {
|
||||
Value: "dong",
|
||||
Present: true,
|
||||
},
|
||||
},
|
||||
Points: []ocmetricdata.Point{
|
||||
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
|
||||
Count: 10,
|
||||
Sum: 13.2,
|
||||
HasCountAndSum: true,
|
||||
Snapshot: ocmetricdata.Snapshot{
|
||||
Percentiles: map[float64]float64{
|
||||
0.0: 0.1,
|
||||
0.5: 1.0,
|
||||
1.0: 10.4,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: errMismatchedAttributeKeyValues,
|
||||
},
|
||||
{
|
||||
desc: "summary with negative count",
|
||||
input: []*ocmetricdata.Metric{
|
||||
{
|
||||
Descriptor: ocmetricdata.Descriptor{
|
||||
Name: "foo.com/summary-negative",
|
||||
Description: "a negative count summary",
|
||||
Unit: ocmetricdata.UnitMilliseconds,
|
||||
Type: ocmetricdata.TypeSummary,
|
||||
},
|
||||
TimeSeries: []*ocmetricdata.TimeSeries{
|
||||
{
|
||||
Points: []ocmetricdata.Point{
|
||||
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
|
||||
Count: -10,
|
||||
Sum: 13.2,
|
||||
HasCountAndSum: true,
|
||||
Snapshot: ocmetricdata.Snapshot{
|
||||
Percentiles: map[float64]float64{
|
||||
0.0: 0.1,
|
||||
0.5: 1.0,
|
||||
1.0: 10.4,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: errNegativeCount,
|
||||
},
|
||||
{
|
||||
desc: "histogram with invalid span context exemplar",
|
||||
input: []*ocmetricdata.Metric{
|
||||
@@ -722,6 +904,28 @@ func TestConvertMetrics(t *testing.T) {
|
||||
},
|
||||
expectedErr: errMismatchedValueTypes,
|
||||
},
|
||||
{
|
||||
desc: "summary with non-summary datapoint type",
|
||||
input: []*ocmetricdata.Metric{
|
||||
{
|
||||
Descriptor: ocmetricdata.Descriptor{
|
||||
Name: "foo.com/bad-point",
|
||||
Description: "a bad type",
|
||||
Unit: ocmetricdata.UnitDimensionless,
|
||||
Type: ocmetricdata.TypeSummary,
|
||||
},
|
||||
TimeSeries: []*ocmetricdata.TimeSeries{
|
||||
{
|
||||
Points: []ocmetricdata.Point{
|
||||
ocmetricdata.NewDistributionPoint(endTime1, &ocmetricdata.Distribution{}),
|
||||
},
|
||||
StartTime: startTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: errMismatchedValueTypes,
|
||||
},
|
||||
{
|
||||
desc: "unsupported Gauge Distribution type",
|
||||
input: []*ocmetricdata.Metric{
|
||||
@@ -740,7 +944,7 @@ func TestConvertMetrics(t *testing.T) {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
output, err := ConvertMetrics(tc.input)
|
||||
if !errors.Is(err, tc.expectedErr) {
|
||||
t.Errorf("convertAggregation(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr)
|
||||
t.Errorf("ConvertMetrics(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr)
|
||||
}
|
||||
metricdatatest.AssertEqual[metricdata.ScopeMetrics](t,
|
||||
metricdata.ScopeMetrics{Metrics: tc.expected},
|
||||
|
||||
@@ -472,7 +472,7 @@ func equalSummaryDataPoint(a, b metricdata.SummaryDataPoint, cfg config) (reason
|
||||
}
|
||||
r := compareDiff(diffSlices(
|
||||
a.QuantileValues,
|
||||
a.QuantileValues,
|
||||
b.QuantileValues,
|
||||
func(a, b metricdata.QuantileValue) bool {
|
||||
r := equalQuantileValue(a, b, cfg)
|
||||
return len(r) == 0
|
||||
|
||||
Reference in New Issue
Block a user