@@ -17,22 +17,26 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
17
17
import (
18
18
"errors"
19
19
"fmt"
20
+ "reflect"
20
21
21
22
ocmetricdata "go.opencensus.io/metric/metricdata"
23
+ octrace "go.opencensus.io/trace"
22
24
23
25
"go.opentelemetry.io/otel/attribute"
24
26
"go.opentelemetry.io/otel/sdk/metric/metricdata"
25
27
)
26
28
27
29
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" )
36
40
)
37
41
38
42
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
@@ -142,7 +146,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
142
146
errInfo = append (errInfo , fmt .Sprintf ("%v: %d" , errMismatchedValueTypes , p .Value ))
143
147
continue
144
148
}
145
- bucketCounts , err := convertBucketCounts (dist .Buckets )
149
+ bucketCounts , exemplars , err := convertBuckets (dist .Buckets )
146
150
if err != nil {
147
151
errInfo = append (errInfo , err .Error ())
148
152
continue
@@ -151,7 +155,6 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
151
155
errInfo = append (errInfo , fmt .Sprintf ("%v: %d" , errNegativeDistributionCount , dist .Count ))
152
156
continue
153
157
}
154
- // TODO: handle exemplars
155
158
points = append (points , metricdata.HistogramDataPoint [float64 ]{
156
159
Attributes : attrs ,
157
160
StartTime : t .StartTime ,
@@ -160,6 +163,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
160
163
Sum : dist .Sum ,
161
164
Bounds : dist .BucketOptions .Bounds ,
162
165
BucketCounts : bucketCounts ,
166
+ Exemplars : exemplars ,
163
167
})
164
168
}
165
169
}
@@ -170,16 +174,84 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
170
174
return metricdata.Histogram [float64 ]{DataPoints : points , Temporality : metricdata .CumulativeTemporality }, aggregatedError
171
175
}
172
176
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 ) {
175
180
bucketCounts := make ([]uint64 , len (buckets ))
181
+ exemplars := []metricdata.Exemplar [float64 ]{}
182
+ var err error
176
183
for i , bucket := range buckets {
177
184
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 )
179
188
}
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 )
181
253
}
182
- return bucketCounts , nil
254
+ return attribute. KeyValue {}
183
255
}
184
256
185
257
// convertAttrs converts from OpenCensus attribute keys and values to an
0 commit comments