Skip to content

Commit 7ffb360

Browse files
committed
WIP add exemplar support to OpenCensus bridge
1 parent c7f53cc commit 7ffb360

File tree

3 files changed

+88
-16
lines changed

3 files changed

+88
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1111
### Added
1212

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

1516
### Deprecated
1617

bridge/opencensus/doc.go

-1
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,4 @@
5959
// - Summary-typed metrics are dropped
6060
// - GaugeDistribution-typed metrics are dropped
6161
// - Histogram's SumOfSquaredDeviation field is dropped
62-
// - Exemplars on Histograms are dropped
6362
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"

bridge/opencensus/internal/ocmetric/metric.go

+87-15
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,26 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
1717
import (
1818
"errors"
1919
"fmt"
20+
"reflect"
2021

2122
ocmetricdata "go.opencensus.io/metric/metricdata"
23+
octrace "go.opencensus.io/trace"
2224

2325
"go.opentelemetry.io/otel/attribute"
2426
"go.opentelemetry.io/otel/sdk/metric/metricdata"
2527
)
2628

2729
var (
28-
errConversion = errors.New("converting from OpenCensus to OpenTelemetry")
29-
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
30-
errMismatchedValueTypes = errors.New("wrong value type for data point")
31-
errNumberDataPoint = errors.New("converting a number data point")
32-
errHistogramDataPoint = errors.New("converting a histogram data point")
33-
errNegativeDistributionCount = errors.New("distribution count is negative")
34-
errNegativeBucketCount = errors.New("distribution bucket count is negative")
35-
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
30+
errConversion = errors.New("converting from OpenCensus to OpenTelemetry")
31+
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
32+
errMismatchedValueTypes = errors.New("wrong value type for data point")
33+
errNumberDataPoint = errors.New("converting a number data point")
34+
errHistogramDataPoint = errors.New("converting a histogram data point")
35+
errNegativeDistributionCount = errors.New("distribution count is negative")
36+
errNegativeBucketCount = errors.New("distribution bucket count is negative")
37+
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
38+
errInvalidExemplarSpanContext = errors.New("SpanContext exemplar attachment did not contain an OpenCensus SpanContext")
39+
errInvalidExemplarAttachmentValue = errors.New("exemplar attachment is not a supported OpenTelemetry attribute type")
3640
)
3741

3842
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
@@ -142,7 +146,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
142146
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errMismatchedValueTypes, p.Value))
143147
continue
144148
}
145-
bucketCounts, err := convertBucketCounts(dist.Buckets)
149+
bucketCounts, exemplars, err := convertBuckets(dist.Buckets)
146150
if err != nil {
147151
errInfo = append(errInfo, err.Error())
148152
continue
@@ -151,7 +155,6 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
151155
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errNegativeDistributionCount, dist.Count))
152156
continue
153157
}
154-
// TODO: handle exemplars
155158
points = append(points, metricdata.HistogramDataPoint[float64]{
156159
Attributes: attrs,
157160
StartTime: t.StartTime,
@@ -160,6 +163,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
160163
Sum: dist.Sum,
161164
Bounds: dist.BucketOptions.Bounds,
162165
BucketCounts: bucketCounts,
166+
Exemplars: exemplars,
163167
})
164168
}
165169
}
@@ -170,16 +174,84 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
170174
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, aggregatedError
171175
}
172176

173-
// convertBucketCounts converts from OpenCensus bucket counts to slice of uint64.
174-
func convertBucketCounts(buckets []ocmetricdata.Bucket) ([]uint64, error) {
177+
// convertBuckets converts from OpenCensus bucket counts to slice of uint64,
178+
// and converts OpenCensus exemplars to OpenTelemetry exemplars.
179+
func convertBuckets(buckets []ocmetricdata.Bucket) ([]uint64, []metricdata.Exemplar[float64], error) {
175180
bucketCounts := make([]uint64, len(buckets))
181+
exemplars := []metricdata.Exemplar[float64]{}
182+
var err error
176183
for i, bucket := range buckets {
177184
if bucket.Count < 0 {
178-
return nil, fmt.Errorf("%w: %q", errNegativeBucketCount, bucket.Count)
185+
err = errors.Join(err, fmt.Errorf("%w: %q", errNegativeBucketCount, bucket.Count))
186+
} else {
187+
bucketCounts[i] = uint64(bucket.Count)
179188
}
180-
bucketCounts[i] = uint64(bucket.Count)
189+
if bucket.Exemplar != nil {
190+
exemplar, exemplarErr := convertExemplar(bucket.Exemplar)
191+
if exemplarErr != nil {
192+
err = errors.Join(err, exemplarErr)
193+
} else {
194+
exemplars = append(exemplars, exemplar)
195+
}
196+
}
197+
}
198+
return bucketCounts, exemplars, err
199+
}
200+
201+
// convertExemplar converts an OpenCensus exemplar to an OpenTelemetry exemplar
202+
func convertExemplar(ocExemplar *ocmetricdata.Exemplar) (metricdata.Exemplar[float64], error) {
203+
exemplar := metricdata.Exemplar[float64]{
204+
Value: ocExemplar.Value,
205+
Time: ocExemplar.Timestamp,
206+
}
207+
if ocExemplar.Attachments == nil {
208+
return exemplar, nil
209+
}
210+
var err error
211+
for k, v := range ocExemplar.Attachments {
212+
if k == ocmetricdata.AttachmentKeySpanContext {
213+
if sc, ok := v.(octrace.SpanContext); ok {
214+
exemplar.SpanID = sc.SpanID[:]
215+
exemplar.TraceID = sc.TraceID[:]
216+
} else {
217+
err = errors.Join(err, fmt.Errorf("%w: %v", errInvalidExemplarSpanContext, reflect.TypeOf(v)))
218+
}
219+
} else if kv := convertKV(k, v); kv.Valid() {
220+
exemplar.FilteredAttributes = append(exemplar.FilteredAttributes, kv)
221+
} else {
222+
err = errors.Join(err, fmt.Errorf("%w: %v", errInvalidExemplarAttachmentValue, reflect.TypeOf(v)))
223+
}
224+
}
225+
return exemplar, err
226+
}
227+
228+
// convertKV converts an OpenCensus Attachment to an OpenTelemetry KeyValue
229+
func convertKV(key string, value any) attribute.KeyValue {
230+
switch typedVal := value.(type) {
231+
case bool:
232+
return attribute.Bool(key, typedVal)
233+
case []bool:
234+
return attribute.BoolSlice(key, typedVal)
235+
case int:
236+
return attribute.Int(key, typedVal)
237+
case []int:
238+
return attribute.IntSlice(key, typedVal)
239+
case int64:
240+
return attribute.Int64(key, typedVal)
241+
case []int64:
242+
return attribute.Int64Slice(key, typedVal)
243+
case float64:
244+
return attribute.Float64(key, typedVal)
245+
case []float64:
246+
return attribute.Float64Slice(key, typedVal)
247+
case string:
248+
return attribute.String(key, typedVal)
249+
case []string:
250+
return attribute.StringSlice(key, typedVal)
251+
case fmt.Stringer:
252+
return attribute.Stringer(key, typedVal)
181253
}
182-
return bucketCounts, nil
254+
return attribute.KeyValue{}
183255
}
184256

185257
// convertAttrs converts from OpenCensus attribute keys and values to an

0 commit comments

Comments
 (0)