Skip to content

Commit

Permalink
support summaries in the OpenCensus bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed Oct 25, 2023
1 parent cdd9353 commit 9366a91
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 6 deletions.
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

Check warning on line 184 in bridge/opencensus/internal/ocmetric/metric.go

View check run for this annotation

Codecov / codecov/patch

bridge/opencensus/internal/ocmetric/metric.go#L183-L184

Added lines #L183 - L184 were not covered by tests
}
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

Check warning on line 190 in bridge/opencensus/internal/ocmetric/metric.go

View check run for this annotation

Codecov / codecov/patch

bridge/opencensus/internal/ocmetric/metric.go#L189-L190

Added lines #L189 - L190 were not covered by tests
}
if summary.Count < 0 {
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, summary.Count))
continue

Check warning on line 194 in bridge/opencensus/internal/ocmetric/metric.go

View check run for this annotation

Codecov / codecov/patch

bridge/opencensus/internal/ocmetric/metric.go#L193-L194

Added lines #L193 - L194 were not covered by tests
}
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,
Value: value,
})
}
sort.Sort(byQuantile(quantileValues))
return quantileValues
}

// ByAge 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
110 changes: 108 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.0: 0.1,
0.5: 1.0,
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

0 comments on commit 9366a91

Please sign in to comment.