Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add summary support in the OpenCensus bridge #4668

Merged
merged 8 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `Version` function in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4660)
- Add `Version` function in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4660)
- Add Summary, SummaryDataPoint, and QuantileValue to `go.opentelemetry.io/sdk/metric/metricdata`. (#4622)
- 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
Original file line number Diff line number Diff line change
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
// - Exemplars on Histograms are dropped
Expand Down
65 changes: 62 additions & 3 deletions bridge/opencensus/internal/ocmetric/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
import (
"errors"
"fmt"
"sort"

ocmetricdata "go.opencensus.io/metric/metricdata"

Expand All @@ -27,7 +28,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")
)
Expand Down Expand Up @@ -72,7 +73,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 @@ -140,7 +142,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
}
// TODO: handle exemplars
Expand Down Expand Up @@ -170,6 +172,63 @@ func convertBucketCounts(buckets []ocmetricdata.Bucket) ([]uint64, error) {
return bucketCounts, nil
}

// 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{
Quantile: quantile,
dashpole marked this conversation as resolved.
Show resolved Hide resolved
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
208 changes: 206 additions & 2 deletions bridge/opencensus/internal/ocmetric/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,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 @@ -206,6 +206,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{
0.5: 1.0,
0.0: 0.1,
1.0: 10.4,
},
},
}),
ocmetricdata.NewSummaryPoint(endTime2, &ocmetricdata.Summary{
Count: 12,
Snapshot: ocmetricdata.Snapshot{
Percentiles: map[float64]float64{
0.0: 0.2,
0.5: 1.1,
1.0: 10.5,
},
},
}),
},
},
},
},
},
expected: []metricdata.Metrics{
Expand Down Expand Up @@ -367,6 +415,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 @@ -464,7 +570,7 @@ func TestConvertMetrics(t *testing.T) {
},
},
},
expectedErr: errNegativeDistributionCount,
expectedErr: errNegativeCount,
},
{
desc: "histogram with negative bucket count",
Expand Down Expand Up @@ -516,6 +622,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: "sum with non-sum datapoint type",
input: []*ocmetricdata.Metric{
Expand Down Expand Up @@ -560,6 +742,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 Down
Loading