Skip to content

Commit 1635737

Browse files
authored
Add summary support in the OpenCensus bridge (#4668)
* support summaries in the OpenCensus bridge * divide quantiles by 100
1 parent 480edcc commit 1635737

File tree

5 files changed

+272
-8
lines changed

5 files changed

+272
-8
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2222
- Add Summary, SummaryDataPoint, and QuantileValue to `go.opentelemetry.io/sdk/metric/metricdata`. (#4622)
2323
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` now supports exemplars from OpenCensus. (#4585)
2424
- Add support for `WithExplicitBucketBoundaries` in `go.opentelemetry.io/otel/sdk/metric`. (#4605)
25+
- Add support for Summary metrics in `go.opentelemetry.io/otel/bridge/opencensus`. (#4668)
2526

2627
### Deprecated
2728

bridge/opencensus/doc.go

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
// implemented, and An error will be sent to the OpenTelemetry ErrorHandler.
5757
//
5858
// There are known limitations to the metric bridge:
59-
// - Summary-typed metrics are dropped
6059
// - GaugeDistribution-typed metrics are dropped
6160
// - Histogram's SumOfSquaredDeviation field is dropped
6261
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"

bridge/opencensus/internal/ocmetric/metric.go

+63-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
var (
3333
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
3434
errMismatchedValueTypes = errors.New("wrong value type for data point")
35-
errNegativeDistributionCount = errors.New("distribution count is negative")
35+
errNegativeCount = errors.New("distribution or summary count is negative")
3636
errNegativeBucketCount = errors.New("distribution bucket count is negative")
3737
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
3838
errInvalidExemplarSpanContext = errors.New("span context exemplar attachment does not contain an OpenCensus SpanContext")
@@ -78,7 +78,8 @@ func convertAggregation(metric *ocmetricdata.Metric) (metricdata.Aggregation, er
7878
return convertSum[float64](labelKeys, metric.TimeSeries)
7979
case ocmetricdata.TypeCumulativeDistribution:
8080
return convertHistogram(labelKeys, metric.TimeSeries)
81-
// TODO: Support summaries, once it is in the OTel data types.
81+
case ocmetricdata.TypeSummary:
82+
return convertSummary(labelKeys, metric.TimeSeries)
8283
}
8384
return nil, fmt.Errorf("%w: %q", errAggregationType, metric.Descriptor.Type)
8485
}
@@ -146,7 +147,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
146147
continue
147148
}
148149
if dist.Count < 0 {
149-
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeDistributionCount, dist.Count))
150+
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, dist.Count))
150151
continue
151152
}
152153
points = append(points, metricdata.HistogramDataPoint[float64]{
@@ -340,6 +341,65 @@ func complexToString[N complex64 | complex128](val N) string {
340341
return strconv.FormatComplex(complex128(val), 'f', -1, 64)
341342
}
342343

344+
// convertSummary converts OpenCensus Summary timeseries to an
345+
// OpenTelemetry Summary.
346+
func convertSummary(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Summary, error) {
347+
points := make([]metricdata.SummaryDataPoint, 0, len(ts))
348+
var err error
349+
for _, t := range ts {
350+
attrs, attrErr := convertAttrs(labelKeys, t.LabelValues)
351+
if attrErr != nil {
352+
err = errors.Join(err, attrErr)
353+
continue
354+
}
355+
for _, p := range t.Points {
356+
summary, ok := p.Value.(*ocmetricdata.Summary)
357+
if !ok {
358+
err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value))
359+
continue
360+
}
361+
if summary.Count < 0 {
362+
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, summary.Count))
363+
continue
364+
}
365+
point := metricdata.SummaryDataPoint{
366+
Attributes: attrs,
367+
StartTime: t.StartTime,
368+
Time: p.Time,
369+
Count: uint64(summary.Count),
370+
QuantileValues: convertQuantiles(summary.Snapshot),
371+
Sum: summary.Sum,
372+
}
373+
points = append(points, point)
374+
}
375+
}
376+
return metricdata.Summary{DataPoints: points}, err
377+
}
378+
379+
// convertQuantiles converts an OpenCensus summary snapshot to
380+
// OpenTelemetry quantiles.
381+
func convertQuantiles(snapshot ocmetricdata.Snapshot) []metricdata.QuantileValue {
382+
quantileValues := make([]metricdata.QuantileValue, 0, len(snapshot.Percentiles))
383+
for quantile, value := range snapshot.Percentiles {
384+
quantileValues = append(quantileValues, metricdata.QuantileValue{
385+
// OpenCensus quantiles are range (0-100.0], but OpenTelemetry
386+
// quantiles are range [0.0, 1.0].
387+
Quantile: quantile / 100.0,
388+
Value: value,
389+
})
390+
}
391+
sort.Sort(byQuantile(quantileValues))
392+
return quantileValues
393+
}
394+
395+
// byQuantile implements sort.Interface for []metricdata.QuantileValue
396+
// based on the Quantile field.
397+
type byQuantile []metricdata.QuantileValue
398+
399+
func (a byQuantile) Len() int { return len(a) }
400+
func (a byQuantile) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
401+
func (a byQuantile) Less(i, j int) bool { return a[i].Quantile < a[j].Quantile }
402+
343403
// convertAttrs converts from OpenCensus attribute keys and values to an
344404
// OpenTelemetry attribute Set.
345405
func convertAttrs(keys []ocmetricdata.LabelKey, values []ocmetricdata.LabelValue) (attribute.Set, error) {

bridge/opencensus/internal/ocmetric/metric_test.go

+207-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestConvertMetrics(t *testing.T) {
4747
expected: []metricdata.Metrics{},
4848
},
4949
{
50-
desc: "normal Histogram, gauges, and sums",
50+
desc: "normal Histogram, summary, gauges, and sums",
5151
input: []*ocmetricdata.Metric{
5252
{
5353
Descriptor: ocmetricdata.Descriptor{
@@ -285,6 +285,54 @@ func TestConvertMetrics(t *testing.T) {
285285
},
286286
},
287287
},
288+
}, {
289+
Descriptor: ocmetricdata.Descriptor{
290+
Name: "foo.com/summary-a",
291+
Description: "a testing summary",
292+
Unit: ocmetricdata.UnitMilliseconds,
293+
Type: ocmetricdata.TypeSummary,
294+
LabelKeys: []ocmetricdata.LabelKey{
295+
{Key: "g"},
296+
{Key: "h"},
297+
},
298+
},
299+
TimeSeries: []*ocmetricdata.TimeSeries{
300+
{
301+
LabelValues: []ocmetricdata.LabelValue{
302+
{
303+
Value: "ding",
304+
Present: true,
305+
}, {
306+
Value: "dong",
307+
Present: true,
308+
},
309+
},
310+
Points: []ocmetricdata.Point{
311+
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
312+
Count: 10,
313+
Sum: 13.2,
314+
HasCountAndSum: true,
315+
Snapshot: ocmetricdata.Snapshot{
316+
Percentiles: map[float64]float64{
317+
50.0: 1.0,
318+
0.0: 0.1,
319+
100.0: 10.4,
320+
},
321+
},
322+
}),
323+
ocmetricdata.NewSummaryPoint(endTime2, &ocmetricdata.Summary{
324+
Count: 12,
325+
Snapshot: ocmetricdata.Snapshot{
326+
Percentiles: map[float64]float64{
327+
0.0: 0.2,
328+
50.0: 1.1,
329+
100.0: 10.5,
330+
},
331+
},
332+
}),
333+
},
334+
},
335+
},
288336
},
289337
},
290338
expected: []metricdata.Metrics{
@@ -489,6 +537,64 @@ func TestConvertMetrics(t *testing.T) {
489537
},
490538
},
491539
},
540+
}, {
541+
Name: "foo.com/summary-a",
542+
Description: "a testing summary",
543+
Unit: "ms",
544+
Data: metricdata.Summary{
545+
DataPoints: []metricdata.SummaryDataPoint{
546+
{
547+
Attributes: attribute.NewSet(attribute.KeyValue{
548+
Key: attribute.Key("g"),
549+
Value: attribute.StringValue("ding"),
550+
}, attribute.KeyValue{
551+
Key: attribute.Key("h"),
552+
Value: attribute.StringValue("dong"),
553+
}),
554+
Time: endTime1,
555+
Count: 10,
556+
Sum: 13.2,
557+
QuantileValues: []metricdata.QuantileValue{
558+
{
559+
Quantile: 0.0,
560+
Value: 0.1,
561+
},
562+
{
563+
Quantile: 0.5,
564+
Value: 1.0,
565+
},
566+
{
567+
Quantile: 1.0,
568+
Value: 10.4,
569+
},
570+
},
571+
}, {
572+
Attributes: attribute.NewSet(attribute.KeyValue{
573+
Key: attribute.Key("g"),
574+
Value: attribute.StringValue("ding"),
575+
}, attribute.KeyValue{
576+
Key: attribute.Key("h"),
577+
Value: attribute.StringValue("dong"),
578+
}),
579+
Time: endTime2,
580+
Count: 12,
581+
QuantileValues: []metricdata.QuantileValue{
582+
{
583+
Quantile: 0.0,
584+
Value: 0.2,
585+
},
586+
{
587+
Quantile: 0.5,
588+
Value: 1.1,
589+
},
590+
{
591+
Quantile: 1.0,
592+
Value: 10.5,
593+
},
594+
},
595+
},
596+
},
597+
},
492598
},
493599
},
494600
},
@@ -586,7 +692,7 @@ func TestConvertMetrics(t *testing.T) {
586692
},
587693
},
588694
},
589-
expectedErr: errNegativeDistributionCount,
695+
expectedErr: errNegativeCount,
590696
},
591697
{
592698
desc: "histogram with negative bucket count",
@@ -638,6 +744,82 @@ func TestConvertMetrics(t *testing.T) {
638744
},
639745
expectedErr: errMismatchedValueTypes,
640746
},
747+
{
748+
desc: "summary with mismatched attributes",
749+
input: []*ocmetricdata.Metric{
750+
{
751+
Descriptor: ocmetricdata.Descriptor{
752+
Name: "foo.com/summary-mismatched",
753+
Description: "a mismatched summary",
754+
Unit: ocmetricdata.UnitMilliseconds,
755+
Type: ocmetricdata.TypeSummary,
756+
LabelKeys: []ocmetricdata.LabelKey{
757+
{Key: "g"},
758+
},
759+
},
760+
TimeSeries: []*ocmetricdata.TimeSeries{
761+
{
762+
LabelValues: []ocmetricdata.LabelValue{
763+
{
764+
Value: "ding",
765+
Present: true,
766+
}, {
767+
Value: "dong",
768+
Present: true,
769+
},
770+
},
771+
Points: []ocmetricdata.Point{
772+
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
773+
Count: 10,
774+
Sum: 13.2,
775+
HasCountAndSum: true,
776+
Snapshot: ocmetricdata.Snapshot{
777+
Percentiles: map[float64]float64{
778+
0.0: 0.1,
779+
0.5: 1.0,
780+
1.0: 10.4,
781+
},
782+
},
783+
}),
784+
},
785+
},
786+
},
787+
},
788+
},
789+
expectedErr: errMismatchedAttributeKeyValues,
790+
},
791+
{
792+
desc: "summary with negative count",
793+
input: []*ocmetricdata.Metric{
794+
{
795+
Descriptor: ocmetricdata.Descriptor{
796+
Name: "foo.com/summary-negative",
797+
Description: "a negative count summary",
798+
Unit: ocmetricdata.UnitMilliseconds,
799+
Type: ocmetricdata.TypeSummary,
800+
},
801+
TimeSeries: []*ocmetricdata.TimeSeries{
802+
{
803+
Points: []ocmetricdata.Point{
804+
ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{
805+
Count: -10,
806+
Sum: 13.2,
807+
HasCountAndSum: true,
808+
Snapshot: ocmetricdata.Snapshot{
809+
Percentiles: map[float64]float64{
810+
0.0: 0.1,
811+
0.5: 1.0,
812+
1.0: 10.4,
813+
},
814+
},
815+
}),
816+
},
817+
},
818+
},
819+
},
820+
},
821+
expectedErr: errNegativeCount,
822+
},
641823
{
642824
desc: "histogram with invalid span context exemplar",
643825
input: []*ocmetricdata.Metric{
@@ -722,6 +904,28 @@ func TestConvertMetrics(t *testing.T) {
722904
},
723905
expectedErr: errMismatchedValueTypes,
724906
},
907+
{
908+
desc: "summary with non-summary datapoint type",
909+
input: []*ocmetricdata.Metric{
910+
{
911+
Descriptor: ocmetricdata.Descriptor{
912+
Name: "foo.com/bad-point",
913+
Description: "a bad type",
914+
Unit: ocmetricdata.UnitDimensionless,
915+
Type: ocmetricdata.TypeSummary,
916+
},
917+
TimeSeries: []*ocmetricdata.TimeSeries{
918+
{
919+
Points: []ocmetricdata.Point{
920+
ocmetricdata.NewDistributionPoint(endTime1, &ocmetricdata.Distribution{}),
921+
},
922+
StartTime: startTime,
923+
},
924+
},
925+
},
926+
},
927+
expectedErr: errMismatchedValueTypes,
928+
},
725929
{
726930
desc: "unsupported Gauge Distribution type",
727931
input: []*ocmetricdata.Metric{
@@ -740,7 +944,7 @@ func TestConvertMetrics(t *testing.T) {
740944
t.Run(tc.desc, func(t *testing.T) {
741945
output, err := ConvertMetrics(tc.input)
742946
if !errors.Is(err, tc.expectedErr) {
743-
t.Errorf("convertAggregation(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr)
947+
t.Errorf("ConvertMetrics(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr)
744948
}
745949
metricdatatest.AssertEqual[metricdata.ScopeMetrics](t,
746950
metricdata.ScopeMetrics{Metrics: tc.expected},

sdk/metric/metricdata/metricdatatest/comparisons.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ func equalSummaryDataPoint(a, b metricdata.SummaryDataPoint, cfg config) (reason
472472
}
473473
r := compareDiff(diffSlices(
474474
a.QuantileValues,
475-
a.QuantileValues,
475+
b.QuantileValues,
476476
func(a, b metricdata.QuantileValue) bool {
477477
r := equalQuantileValue(a, b, cfg)
478478
return len(r) == 0

0 commit comments

Comments
 (0)