@@ -17,19 +17,24 @@ package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/
17
17
import (
18
18
"errors"
19
19
"fmt"
20
+ "reflect"
21
+ "sort"
20
22
21
23
ocmetricdata "go.opencensus.io/metric/metricdata"
24
+ octrace "go.opencensus.io/trace"
22
25
23
26
"go.opentelemetry.io/otel/attribute"
24
27
"go.opentelemetry.io/otel/sdk/metric/metricdata"
25
28
)
26
29
27
30
var (
28
- errAggregationType = errors .New ("unsupported OpenCensus aggregation type" )
29
- errMismatchedValueTypes = errors .New ("wrong value type for data point" )
30
- errNegativeDistributionCount = errors .New ("distribution count is negative" )
31
- errNegativeBucketCount = errors .New ("distribution bucket count is negative" )
32
- 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" )
33
38
)
34
39
35
40
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
@@ -134,7 +139,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
134
139
err = errors .Join (err , fmt .Errorf ("%w: %d" , errMismatchedValueTypes , p .Value ))
135
140
continue
136
141
}
137
- bucketCounts , bucketErr := convertBucketCounts (dist .Buckets )
142
+ bucketCounts , exemplars , bucketErr := convertBuckets (dist .Buckets )
138
143
if bucketErr != nil {
139
144
err = errors .Join (err , bucketErr )
140
145
continue
@@ -143,7 +148,6 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
143
148
err = errors .Join (err , fmt .Errorf ("%w: %d" , errNegativeDistributionCount , dist .Count ))
144
149
continue
145
150
}
146
- // TODO: handle exemplars
147
151
points = append (points , metricdata.HistogramDataPoint [float64 ]{
148
152
Attributes : attrs ,
149
153
StartTime : t .StartTime ,
@@ -152,22 +156,93 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
152
156
Sum : dist .Sum ,
153
157
Bounds : dist .BucketOptions .Bounds ,
154
158
BucketCounts : bucketCounts ,
159
+ Exemplars : exemplars ,
155
160
})
156
161
}
157
162
}
158
163
return metricdata.Histogram [float64 ]{DataPoints : points , Temporality : metricdata .CumulativeTemporality }, err
159
164
}
160
165
161
- // convertBucketCounts converts from OpenCensus bucket counts to slice of uint64.
162
- 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 ) {
163
169
bucketCounts := make ([]uint64 , len (buckets ))
170
+ exemplars := []metricdata.Exemplar [float64 ]{}
171
+ var err error
164
172
for i , bucket := range buckets {
165
173
if bucket .Count < 0 {
166
- 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 )))
167
212
}
168
- bucketCounts [i ] = uint64 (bucket .Count )
169
213
}
170
- 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 {}
171
246
}
172
247
173
248
// convertAttrs converts from OpenCensus attribute keys and values to an
0 commit comments