Skip to content

Commit b85a5da

Browse files
committed
add exemplar support to OpenCensus bridge
1 parent 68241af commit b85a5da

File tree

4 files changed

+344
-59
lines changed

4 files changed

+344
-59
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

+109-46
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,37 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
1717
import (
1818
"errors"
1919
"fmt"
20+
"reflect"
21+
"sort"
2022

2123
ocmetricdata "go.opencensus.io/metric/metricdata"
24+
octrace "go.opencensus.io/trace"
2225

2326
"go.opentelemetry.io/otel/attribute"
2427
"go.opentelemetry.io/otel/sdk/metric/metricdata"
2528
)
2629

2730
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")
31+
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
32+
errMismatchedValueTypes = errors.New("wrong value type for 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")
36+
errInvalidExemplarSpanContext = errors.New("span context exemplar attachment did not contain an OpenCensus SpanContext")
37+
errInvalidExemplarAttachmentValue = errors.New("exemplar attachment is not a supported OpenTelemetry attribute type")
3638
)
3739

3840
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
3941
func ConvertMetrics(ocmetrics []*ocmetricdata.Metric) ([]metricdata.Metrics, error) {
4042
otelMetrics := make([]metricdata.Metrics, 0, len(ocmetrics))
41-
var errInfo []string
43+
var err error
4244
for _, ocm := range ocmetrics {
4345
if ocm == nil {
4446
continue
4547
}
46-
agg, err := convertAggregation(ocm)
47-
if err != nil {
48-
errInfo = append(errInfo, err.Error())
48+
agg, aggregationErr := convertAggregation(ocm)
49+
if aggregationErr != nil {
50+
err = errors.Join(err, fmt.Errorf("error converting metric %v: %w", ocm.Descriptor.Name, aggregationErr))
4951
continue
5052
}
5153
otelMetrics = append(otelMetrics, metricdata.Metrics{
@@ -55,11 +57,10 @@ func ConvertMetrics(ocmetrics []*ocmetricdata.Metric) ([]metricdata.Metrics, err
5557
Data: agg,
5658
})
5759
}
58-
var aggregatedError error
59-
if len(errInfo) > 0 {
60-
aggregatedError = fmt.Errorf("%w: %q", errConversion, errInfo)
60+
if err != nil {
61+
return otelMetrics, fmt.Errorf("error converting from OpenCensus to OpenTelemetry: %w", err)
6162
}
62-
return otelMetrics, aggregatedError
63+
return otelMetrics, nil
6364
}
6465

6566
// convertAggregation produces an aggregation based on the OpenCensus Metric.
@@ -97,17 +98,17 @@ func convertSum[N int64 | float64](labelKeys []ocmetricdata.LabelKey, ts []*ocme
9798
// convertNumberDataPoints converts OpenCensus TimeSeries to OpenTelemetry DataPoints.
9899
func convertNumberDataPoints[N int64 | float64](labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) ([]metricdata.DataPoint[N], error) {
99100
var points []metricdata.DataPoint[N]
100-
var errInfo []string
101+
var err error
101102
for _, t := range ts {
102-
attrs, err := convertAttrs(labelKeys, t.LabelValues)
103-
if err != nil {
104-
errInfo = append(errInfo, err.Error())
103+
attrs, attrsErr := convertAttrs(labelKeys, t.LabelValues)
104+
if attrsErr != nil {
105+
err = errors.Join(err, attrsErr)
105106
continue
106107
}
107108
for _, p := range t.Points {
108109
v, ok := p.Value.(N)
109110
if !ok {
110-
errInfo = append(errInfo, fmt.Sprintf("%v: %q", errMismatchedValueTypes, p.Value))
111+
err = errors.Join(err, fmt.Errorf("%w: %q", errMismatchedValueTypes, p.Value))
111112
continue
112113
}
113114
points = append(points, metricdata.DataPoint[N]{
@@ -118,40 +119,35 @@ func convertNumberDataPoints[N int64 | float64](labelKeys []ocmetricdata.LabelKe
118119
})
119120
}
120121
}
121-
var aggregatedError error
122-
if len(errInfo) > 0 {
123-
aggregatedError = fmt.Errorf("%w: %v", errNumberDataPoint, errInfo)
124-
}
125-
return points, aggregatedError
122+
return points, err
126123
}
127124

128125
// convertHistogram converts OpenCensus Distribution timeseries to an
129126
// OpenTelemetry Histogram aggregation.
130127
func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Histogram[float64], error) {
131128
points := make([]metricdata.HistogramDataPoint[float64], 0, len(ts))
132-
var errInfo []string
129+
var err error
133130
for _, t := range ts {
134-
attrs, err := convertAttrs(labelKeys, t.LabelValues)
135-
if err != nil {
136-
errInfo = append(errInfo, err.Error())
131+
attrs, attrsErr := convertAttrs(labelKeys, t.LabelValues)
132+
if attrsErr != nil {
133+
err = errors.Join(err, attrsErr)
137134
continue
138135
}
139136
for _, p := range t.Points {
140137
dist, ok := p.Value.(*ocmetricdata.Distribution)
141138
if !ok {
142-
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errMismatchedValueTypes, p.Value))
139+
err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value))
143140
continue
144141
}
145-
bucketCounts, err := convertBucketCounts(dist.Buckets)
146-
if err != nil {
147-
errInfo = append(errInfo, err.Error())
142+
bucketCounts, exemplars, bucketErr := convertBuckets(dist.Buckets)
143+
if bucketErr != nil {
144+
err = errors.Join(err, bucketErr)
148145
continue
149146
}
150147
if dist.Count < 0 {
151-
errInfo = append(errInfo, fmt.Sprintf("%v: %d", errNegativeDistributionCount, dist.Count))
148+
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeDistributionCount, dist.Count))
152149
continue
153150
}
154-
// TODO: handle exemplars
155151
points = append(points, metricdata.HistogramDataPoint[float64]{
156152
Attributes: attrs,
157153
StartTime: t.StartTime,
@@ -160,26 +156,93 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
160156
Sum: dist.Sum,
161157
Bounds: dist.BucketOptions.Bounds,
162158
BucketCounts: bucketCounts,
159+
Exemplars: exemplars,
163160
})
164161
}
165162
}
166-
var aggregatedError error
167-
if len(errInfo) > 0 {
168-
aggregatedError = fmt.Errorf("%w: %v", errHistogramDataPoint, errInfo)
169-
}
170-
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, aggregatedError
163+
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, err
171164
}
172165

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

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

0 commit comments

Comments
 (0)