Skip to content

Commit

Permalink
Add summary support in the OpenCensus bridge (#4668)
Browse files Browse the repository at this point in the history
* support summaries in the OpenCensus bridge

* divide quantiles by 100
  • Loading branch information
dashpole committed Nov 2, 2023
1 parent 480edcc commit 1635737
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion bridge/opencensus/doc.go
Expand Up @@ -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"
66 changes: 63 additions & 3 deletions bridge/opencensus/internal/ocmetric/metric.go
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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]{
Expand Down Expand Up @@ -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) {
Expand Down
210 changes: 207 additions & 3 deletions bridge/opencensus/internal/ocmetric/metric_test.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
},
},
},
},
},
},
},
},
Expand Down Expand Up @@ -586,7 +692,7 @@ func TestConvertMetrics(t *testing.T) {
},
},
},
expectedErr: errNegativeDistributionCount,
expectedErr: errNegativeCount,
},
{
desc: "histogram with negative bucket count",
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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{
Expand All @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion sdk/metric/metricdata/metricdatatest/comparisons.go
Expand Up @@ -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
Expand Down

0 comments on commit 1635737

Please sign in to comment.