@@ -17,35 +17,37 @@ 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
- 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" )
36
38
)
37
39
38
40
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
39
41
func ConvertMetrics (ocmetrics []* ocmetricdata.Metric ) ([]metricdata.Metrics , error ) {
40
42
otelMetrics := make ([]metricdata.Metrics , 0 , len (ocmetrics ))
41
- var errInfo [] string
43
+ var err error
42
44
for _ , ocm := range ocmetrics {
43
45
if ocm == nil {
44
46
continue
45
47
}
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 ))
49
51
continue
50
52
}
51
53
otelMetrics = append (otelMetrics , metricdata.Metrics {
@@ -55,11 +57,10 @@ func ConvertMetrics(ocmetrics []*ocmetricdata.Metric) ([]metricdata.Metrics, err
55
57
Data : agg ,
56
58
})
57
59
}
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 )
61
62
}
62
- return otelMetrics , aggregatedError
63
+ return otelMetrics , nil
63
64
}
64
65
65
66
// convertAggregation produces an aggregation based on the OpenCensus Metric.
@@ -97,17 +98,17 @@ func convertSum[N int64 | float64](labelKeys []ocmetricdata.LabelKey, ts []*ocme
97
98
// convertNumberDataPoints converts OpenCensus TimeSeries to OpenTelemetry DataPoints.
98
99
func convertNumberDataPoints [N int64 | float64 ](labelKeys []ocmetricdata.LabelKey , ts []* ocmetricdata.TimeSeries ) ([]metricdata.DataPoint [N ], error ) {
99
100
var points []metricdata.DataPoint [N ]
100
- var errInfo [] string
101
+ var err error
101
102
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 )
105
106
continue
106
107
}
107
108
for _ , p := range t .Points {
108
109
v , ok := p .Value .(N )
109
110
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 ))
111
112
continue
112
113
}
113
114
points = append (points , metricdata.DataPoint [N ]{
@@ -118,40 +119,35 @@ func convertNumberDataPoints[N int64 | float64](labelKeys []ocmetricdata.LabelKe
118
119
})
119
120
}
120
121
}
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
126
123
}
127
124
128
125
// convertHistogram converts OpenCensus Distribution timeseries to an
129
126
// OpenTelemetry Histogram aggregation.
130
127
func convertHistogram (labelKeys []ocmetricdata.LabelKey , ts []* ocmetricdata.TimeSeries ) (metricdata.Histogram [float64 ], error ) {
131
128
points := make ([]metricdata.HistogramDataPoint [float64 ], 0 , len (ts ))
132
- var errInfo [] string
129
+ var err error
133
130
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 )
137
134
continue
138
135
}
139
136
for _ , p := range t .Points {
140
137
dist , ok := p .Value .(* ocmetricdata.Distribution )
141
138
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 ))
143
140
continue
144
141
}
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 )
148
145
continue
149
146
}
150
147
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 ))
152
149
continue
153
150
}
154
- // TODO: handle exemplars
155
151
points = append (points , metricdata.HistogramDataPoint [float64 ]{
156
152
Attributes : attrs ,
157
153
StartTime : t .StartTime ,
@@ -160,26 +156,93 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
160
156
Sum : dist .Sum ,
161
157
Bounds : dist .BucketOptions .Bounds ,
162
158
BucketCounts : bucketCounts ,
159
+ Exemplars : exemplars ,
163
160
})
164
161
}
165
162
}
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
171
164
}
172
165
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 ) {
175
169
bucketCounts := make ([]uint64 , len (buckets ))
170
+ exemplars := []metricdata.Exemplar [float64 ]{}
171
+ var err error
176
172
for i , bucket := range buckets {
177
173
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 )))
179
212
}
180
- bucketCounts [i ] = uint64 (bucket .Count )
181
213
}
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 {}
183
246
}
184
247
185
248
// convertAttrs converts from OpenCensus attribute keys and values to an
0 commit comments