Skip to content

Commit

Permalink
WIP add exemplar support to OpenCensus bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed Oct 6, 2023
1 parent c7f53cc commit cfba9b2
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Add `go.opentelemetry.io/otel/bridge/opencensus.InstallTraceBridge`, which installs the OpenCensus trace bridge, and replaces `opencensus.NewTracer`. (#4567)
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` now supports exemplars from OpenCensus. (#4585)

### 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 @@ -59,5 +59,4 @@
// - Summary-typed metrics are dropped
// - GaugeDistribution-typed metrics are dropped
// - Histogram's SumOfSquaredDeviation field is dropped
// - Exemplars on Histograms are dropped
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
102 changes: 87 additions & 15 deletions bridge/opencensus/internal/ocmetric/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,26 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
import (
"errors"
"fmt"
"reflect"

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

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)

var (
errConversion = errors.New("converting from OpenCensus to OpenTelemetry")
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
errMismatchedValueTypes = errors.New("wrong value type for data point")
errNumberDataPoint = errors.New("converting a number data point")
errHistogramDataPoint = errors.New("converting a histogram data point")
errNegativeDistributionCount = errors.New("distribution count is negative")
errNegativeBucketCount = errors.New("distribution bucket count is negative")
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
errConversion = errors.New("converting from OpenCensus to OpenTelemetry")
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
errMismatchedValueTypes = errors.New("wrong value type for data point")
errNumberDataPoint = errors.New("converting a number data point")
errHistogramDataPoint = errors.New("converting a histogram data point")
errNegativeDistributionCount = errors.New("distribution count is negative")
errNegativeBucketCount = errors.New("distribution bucket count is negative")
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
errInvalidExemplarSpanContext = errors.New("SpanContext exemplar attachment did not contain an OpenCensus SpanContext")
errInvalidExemplarAttachmentValue = errors.New("exemplar attachment is not a supported OpenTelemetry attribute type")
)

// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
Expand Down Expand Up @@ -142,7 +146,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errMismatchedValueTypes, p.Value))
continue
}
bucketCounts, err := convertBucketCounts(dist.Buckets)
bucketCounts, exemplars, err := convertBuckets(dist.Buckets)
if err != nil {
errInfo = append(errInfo, err.Error())
continue
Expand All @@ -151,7 +155,6 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errNegativeDistributionCount, dist.Count))
continue
}
// TODO: handle exemplars
points = append(points, metricdata.HistogramDataPoint[float64]{
Attributes: attrs,
StartTime: t.StartTime,
Expand All @@ -160,6 +163,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
Sum: dist.Sum,
Bounds: dist.BucketOptions.Bounds,
BucketCounts: bucketCounts,
Exemplars: exemplars,
})
}
}
Expand All @@ -170,16 +174,84 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, aggregatedError
}

// convertBucketCounts converts from OpenCensus bucket counts to slice of uint64.
func convertBucketCounts(buckets []ocmetricdata.Bucket) ([]uint64, error) {
// convertBuckets converts from OpenCensus bucket counts to slice of uint64,
// and converts OpenCensus exemplars to OpenTelemetry exemplars.
func convertBuckets(buckets []ocmetricdata.Bucket) ([]uint64, []metricdata.Exemplar[float64], error) {
bucketCounts := make([]uint64, len(buckets))
exemplars := []metricdata.Exemplar[float64]{}
var err error
for i, bucket := range buckets {
if bucket.Count < 0 {
return nil, fmt.Errorf("%w: %q", errNegativeBucketCount, bucket.Count)
err = errors.Join(err, fmt.Errorf("%w: %q", errNegativeBucketCount, bucket.Count))
} else {
bucketCounts[i] = uint64(bucket.Count)
}
bucketCounts[i] = uint64(bucket.Count)
if bucket.Exemplar != nil {
exemplar, exemplarErr := convertExemplar(bucket.Exemplar)
if exemplarErr != nil {
err = errors.Join(err, exemplarErr)
} else {
exemplars = append(exemplars, exemplar)
}
}
}
return bucketCounts, exemplars, err
}

// convertExemplar converts an OpenCensus exemplar to an OpenTelemetry exemplar
func convertExemplar(ocExemplar *ocmetricdata.Exemplar) (metricdata.Exemplar[float64], error) {
exemplar := metricdata.Exemplar[float64]{
Value: ocExemplar.Value,
Time: ocExemplar.Timestamp,
}
if ocExemplar.Attachments == nil {
return exemplar, nil
}
var err error
for k, v := range ocExemplar.Attachments {
if k == ocmetricdata.AttachmentKeySpanContext {
if sc, ok := v.(octrace.SpanContext); ok {
exemplar.SpanID = sc.SpanID[:]
exemplar.TraceID = sc.TraceID[:]
} else {
err = errors.Join(err, fmt.Errorf("%w: %v", errInvalidExemplarSpanContext, reflect.TypeOf(v)))
}
} else if kv := convertKV(k, v); kv.Valid() {
exemplar.FilteredAttributes = append(exemplar.FilteredAttributes, kv)
} else {
err = errors.Join(err, fmt.Errorf("%w: %v", errInvalidExemplarAttachmentValue, reflect.TypeOf(v)))
}
}
return exemplar, err
}

// convertKV converts an OpenCensus Attachment to an OpenTelemetry KeyValue
func convertKV(key string, value any) attribute.KeyValue {
switch typedVal := value.(type) {
case bool:
return attribute.Bool(key, typedVal)
case []bool:
return attribute.BoolSlice(key, typedVal)
case int:
return attribute.Int(key, typedVal)
case []int:
return attribute.IntSlice(key, typedVal)
case int64:
return attribute.Int64(key, typedVal)
case []int64:
return attribute.Int64Slice(key, typedVal)
case float64:
return attribute.Float64(key, typedVal)
case []float64:
return attribute.Float64Slice(key, typedVal)
case string:
return attribute.String(key, typedVal)
case []string:
return attribute.StringSlice(key, typedVal)
case fmt.Stringer:
return attribute.Stringer(key, typedVal)
}
return bucketCounts, nil
return attribute.KeyValue{}
}

// convertAttrs converts from OpenCensus attribute keys and values to an
Expand Down
181 changes: 175 additions & 6 deletions bridge/opencensus/internal/ocmetric/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"time"

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

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
Expand All @@ -28,6 +29,7 @@ import (

func TestConvertMetrics(t *testing.T) {
endTime1 := time.Now()
exemplarTime := endTime1.Add(-10 * time.Second)
endTime2 := endTime1.Add(-time.Millisecond)
startTime := endTime2.Add(-time.Minute)
for _, tc := range []struct {
Expand Down Expand Up @@ -74,9 +76,54 @@ func TestConvertMetrics(t *testing.T) {
Bounds: []float64{1.0, 2.0, 3.0},
},
Buckets: []ocmetricdata.Bucket{
{Count: 1},
{Count: 2},
{Count: 5},
{
Count: 1,
Exemplar: &ocmetricdata.Exemplar{
Value: 0.8,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{1}),
SpanID: octrace.SpanID([8]byte{2}),
},
"string": "string",
"stringslice": []string{"string", "slice"},
"bool": true,
"boolslice": []bool{true, false},
"int": 10,
"intslice": []int{10, 20},
"int64": int64(10),
"int64slice": []int64{10, 20},
// TODO finish the list
},
},
},
{
Count: 2,
Exemplar: &ocmetricdata.Exemplar{
Value: 1.5,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{3}),
SpanID: octrace.SpanID([8]byte{4}),
},
},
},
},
{
Count: 5,
Exemplar: &ocmetricdata.Exemplar{
Value: 2.6,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{5}),
SpanID: octrace.SpanID([8]byte{6}),
},
},
},
},
},
}),
ocmetricdata.NewDistributionPoint(endTime2, &ocmetricdata.Distribution{
Expand All @@ -86,9 +133,45 @@ func TestConvertMetrics(t *testing.T) {
Bounds: []float64{1.0, 2.0, 3.0},
},
Buckets: []ocmetricdata.Bucket{
{Count: 1},
{Count: 4},
{Count: 5},
{
Count: 1,
Exemplar: &ocmetricdata.Exemplar{
Value: 0.9,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{7}),
SpanID: octrace.SpanID([8]byte{8}),
},
},
},
},
{
Count: 4,
Exemplar: &ocmetricdata.Exemplar{
Value: 1.1,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{9}),
SpanID: octrace.SpanID([8]byte{10}),
},
},
},
},
{
Count: 5,
Exemplar: &ocmetricdata.Exemplar{
Value: 2.7,
Timestamp: exemplarTime,
Attachments: map[string]interface{}{
ocmetricdata.AttachmentKeySpanContext: octrace.SpanContext{
TraceID: octrace.TraceID([16]byte{11}),
SpanID: octrace.SpanID([8]byte{12}),
},
},
},
},
},
}),
},
Expand Down Expand Up @@ -230,6 +313,26 @@ func TestConvertMetrics(t *testing.T) {
Sum: 100.0,
Bounds: []float64{1.0, 2.0, 3.0},
BucketCounts: []uint64{1, 2, 5},
Exemplars: []metricdata.Exemplar[float64]{
{
Time: exemplarTime,
Value: 0.8,
TraceID: []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{2, 0, 0, 0, 0, 0, 0, 0},
},
{
Time: exemplarTime,
Value: 1.5,
TraceID: []byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{4, 0, 0, 0, 0, 0, 0, 0},
},
{
Time: exemplarTime,
Value: 2.6,
TraceID: []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{6, 0, 0, 0, 0, 0, 0, 0},
},
},
}, {
Attributes: attribute.NewSet(attribute.KeyValue{
Key: attribute.Key("a"),
Expand All @@ -244,6 +347,26 @@ func TestConvertMetrics(t *testing.T) {
Sum: 110.0,
Bounds: []float64{1.0, 2.0, 3.0},
BucketCounts: []uint64{1, 4, 5},
Exemplars: []metricdata.Exemplar[float64]{
{
Time: exemplarTime,
Value: 0.9,
TraceID: []byte{7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{8, 0, 0, 0, 0, 0, 0, 0},
},
{
Time: exemplarTime,
Value: 1.1,
TraceID: []byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{10, 0, 0, 0, 0, 0, 0, 0},
},
{
Time: exemplarTime,
Value: 2.7,
TraceID: []byte{11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
SpanID: []byte{12, 0, 0, 0, 0, 0, 0, 0},
},
},
},
},
Temporality: metricdata.CumulativeTemporality,
Expand Down Expand Up @@ -511,6 +634,52 @@ func TestConvertMetrics(t *testing.T) {
},
expectedErr: errConversion,
}, {
// desc: "histogram with invalid span context exemplar",
// input: []*ocmetricdata.Metric{
// {
// Descriptor: ocmetricdata.Descriptor{
// Name: "foo.com/histogram-a",
// Description: "a testing histogram",
// Unit: ocmetricdata.UnitDimensionless,
// Type: ocmetricdata.TypeCumulativeDistribution,
// },
// TimeSeries: []*ocmetricdata.TimeSeries{
// {
// Points: []ocmetricdata.Point{
// ocmetricdata.NewDistributionPoint(endTime1, &ocmetricdata.Distribution{
// Count: 1,
// }),
// },
// StartTime: startTime,
// },
// },
// },
// },
// expectedErr: errInvalidExemplarSpanContext,
// }, {
// desc: "histogram with invalid exemplar attachment",
// input: []*ocmetricdata.Metric{
// {
// Descriptor: ocmetricdata.Descriptor{
// Name: "foo.com/histogram-a",
// Description: "a testing histogram",
// Unit: ocmetricdata.UnitDimensionless,
// Type: ocmetricdata.TypeCumulativeDistribution,
// },
// TimeSeries: []*ocmetricdata.TimeSeries{
// {
// Points: []ocmetricdata.Point{
// ocmetricdata.NewDistributionPoint(endTime1, &ocmetricdata.Distribution{
// Count: 1,
// }),
// },
// StartTime: startTime,
// },
// },
// },
// },
// expectedErr: errInvalidExemplarAttachmentValue,
// }, {
desc: "sum with non-sum datapoint type",
input: []*ocmetricdata.Metric{
{
Expand Down

0 comments on commit cfba9b2

Please sign in to comment.