You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Add summary support in the OpenCensus bridge (#4668)
* support summaries in the OpenCensus bridge * divide quantiles by 100
This commit is contained in:
		| @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm | ||||
| - Add Summary, SummaryDataPoint, and QuantileValue to `go.opentelemetry.io/sdk/metric/metricdata`. (#4622) | ||||
| - `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` now supports exemplars from OpenCensus. (#4585) | ||||
| - Add support for `WithExplicitBucketBoundaries` in `go.opentelemetry.io/otel/sdk/metric`. (#4605) | ||||
| - Add support for Summary metrics in `go.opentelemetry.io/otel/bridge/opencensus`. (#4668) | ||||
|  | ||||
| ### Deprecated | ||||
|  | ||||
|   | ||||
| @@ -56,7 +56,6 @@ | ||||
| //     implemented, and An error will be sent to the OpenTelemetry ErrorHandler. | ||||
| // | ||||
| // There are known limitations to the metric bridge: | ||||
| //   - Summary-typed metrics are dropped | ||||
| //   - GaugeDistribution-typed metrics are dropped | ||||
| //   - Histogram's SumOfSquaredDeviation field is dropped | ||||
| package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus" | ||||
|   | ||||
| @@ -32,7 +32,7 @@ import ( | ||||
| var ( | ||||
| 	errAggregationType              = errors.New("unsupported OpenCensus aggregation type") | ||||
| 	errMismatchedValueTypes         = errors.New("wrong value type for data point") | ||||
| 	errNegativeDistributionCount    = errors.New("distribution count is negative") | ||||
| 	errNegativeCount                = errors.New("distribution or summary count is negative") | ||||
| 	errNegativeBucketCount          = errors.New("distribution bucket count is negative") | ||||
| 	errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values") | ||||
| 	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 | ||||
| 		return convertSum[float64](labelKeys, metric.TimeSeries) | ||||
| 	case ocmetricdata.TypeCumulativeDistribution: | ||||
| 		return convertHistogram(labelKeys, metric.TimeSeries) | ||||
| 		// TODO: Support summaries, once it is in the OTel data types. | ||||
| 	case ocmetricdata.TypeSummary: | ||||
| 		return convertSummary(labelKeys, metric.TimeSeries) | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("%w: %q", errAggregationType, metric.Descriptor.Type) | ||||
| } | ||||
| @@ -146,7 +147,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time | ||||
| 				continue | ||||
| 			} | ||||
| 			if dist.Count < 0 { | ||||
| 				err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeDistributionCount, dist.Count)) | ||||
| 				err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, dist.Count)) | ||||
| 				continue | ||||
| 			} | ||||
| 			points = append(points, metricdata.HistogramDataPoint[float64]{ | ||||
| @@ -340,6 +341,65 @@ func complexToString[N complex64 | complex128](val N) string { | ||||
| 	return strconv.FormatComplex(complex128(val), 'f', -1, 64) | ||||
| } | ||||
|  | ||||
| // convertSummary converts OpenCensus Summary timeseries to an | ||||
| // OpenTelemetry Summary. | ||||
| func convertSummary(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Summary, error) { | ||||
| 	points := make([]metricdata.SummaryDataPoint, 0, len(ts)) | ||||
| 	var err error | ||||
| 	for _, t := range ts { | ||||
| 		attrs, attrErr := convertAttrs(labelKeys, t.LabelValues) | ||||
| 		if attrErr != nil { | ||||
| 			err = errors.Join(err, attrErr) | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, p := range t.Points { | ||||
| 			summary, ok := p.Value.(*ocmetricdata.Summary) | ||||
| 			if !ok { | ||||
| 				err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value)) | ||||
| 				continue | ||||
| 			} | ||||
| 			if summary.Count < 0 { | ||||
| 				err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, summary.Count)) | ||||
| 				continue | ||||
| 			} | ||||
| 			point := metricdata.SummaryDataPoint{ | ||||
| 				Attributes:     attrs, | ||||
| 				StartTime:      t.StartTime, | ||||
| 				Time:           p.Time, | ||||
| 				Count:          uint64(summary.Count), | ||||
| 				QuantileValues: convertQuantiles(summary.Snapshot), | ||||
| 				Sum:            summary.Sum, | ||||
| 			} | ||||
| 			points = append(points, point) | ||||
| 		} | ||||
| 	} | ||||
| 	return metricdata.Summary{DataPoints: points}, err | ||||
| } | ||||
|  | ||||
| // convertQuantiles converts an OpenCensus summary snapshot to | ||||
| // OpenTelemetry quantiles. | ||||
| func convertQuantiles(snapshot ocmetricdata.Snapshot) []metricdata.QuantileValue { | ||||
| 	quantileValues := make([]metricdata.QuantileValue, 0, len(snapshot.Percentiles)) | ||||
| 	for quantile, value := range snapshot.Percentiles { | ||||
| 		quantileValues = append(quantileValues, metricdata.QuantileValue{ | ||||
| 			// OpenCensus quantiles are range (0-100.0], but OpenTelemetry | ||||
| 			// quantiles are range [0.0, 1.0]. | ||||
| 			Quantile: quantile / 100.0, | ||||
| 			Value:    value, | ||||
| 		}) | ||||
| 	} | ||||
| 	sort.Sort(byQuantile(quantileValues)) | ||||
| 	return quantileValues | ||||
| } | ||||
|  | ||||
| // byQuantile implements sort.Interface for []metricdata.QuantileValue | ||||
| // based on the Quantile field. | ||||
| type byQuantile []metricdata.QuantileValue | ||||
|  | ||||
| func (a byQuantile) Len() int           { return len(a) } | ||||
| func (a byQuantile) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||
| func (a byQuantile) Less(i, j int) bool { return a[i].Quantile < a[j].Quantile } | ||||
|  | ||||
| // convertAttrs converts from OpenCensus attribute keys and values to an | ||||
| // OpenTelemetry attribute Set. | ||||
| func convertAttrs(keys []ocmetricdata.LabelKey, values []ocmetricdata.LabelValue) (attribute.Set, error) { | ||||
|   | ||||
| @@ -47,7 +47,7 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 			expected: []metricdata.Metrics{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "normal Histogram, gauges, and sums", | ||||
| 			desc: "normal Histogram, summary, gauges, and sums", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| 				{ | ||||
| 					Descriptor: ocmetricdata.Descriptor{ | ||||
| @@ -285,6 +285,54 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, { | ||||
| 					Descriptor: ocmetricdata.Descriptor{ | ||||
| 						Name:        "foo.com/summary-a", | ||||
| 						Description: "a testing summary", | ||||
| 						Unit:        ocmetricdata.UnitMilliseconds, | ||||
| 						Type:        ocmetricdata.TypeSummary, | ||||
| 						LabelKeys: []ocmetricdata.LabelKey{ | ||||
| 							{Key: "g"}, | ||||
| 							{Key: "h"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					TimeSeries: []*ocmetricdata.TimeSeries{ | ||||
| 						{ | ||||
| 							LabelValues: []ocmetricdata.LabelValue{ | ||||
| 								{ | ||||
| 									Value:   "ding", | ||||
| 									Present: true, | ||||
| 								}, { | ||||
| 									Value:   "dong", | ||||
| 									Present: true, | ||||
| 								}, | ||||
| 							}, | ||||
| 							Points: []ocmetricdata.Point{ | ||||
| 								ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{ | ||||
| 									Count:          10, | ||||
| 									Sum:            13.2, | ||||
| 									HasCountAndSum: true, | ||||
| 									Snapshot: ocmetricdata.Snapshot{ | ||||
| 										Percentiles: map[float64]float64{ | ||||
| 											50.0:  1.0, | ||||
| 											0.0:   0.1, | ||||
| 											100.0: 10.4, | ||||
| 										}, | ||||
| 									}, | ||||
| 								}), | ||||
| 								ocmetricdata.NewSummaryPoint(endTime2, &ocmetricdata.Summary{ | ||||
| 									Count: 12, | ||||
| 									Snapshot: ocmetricdata.Snapshot{ | ||||
| 										Percentiles: map[float64]float64{ | ||||
| 											0.0:   0.2, | ||||
| 											50.0:  1.1, | ||||
| 											100.0: 10.5, | ||||
| 										}, | ||||
| 									}, | ||||
| 								}), | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expected: []metricdata.Metrics{ | ||||
| @@ -489,6 +537,64 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, { | ||||
| 					Name:        "foo.com/summary-a", | ||||
| 					Description: "a testing summary", | ||||
| 					Unit:        "ms", | ||||
| 					Data: metricdata.Summary{ | ||||
| 						DataPoints: []metricdata.SummaryDataPoint{ | ||||
| 							{ | ||||
| 								Attributes: attribute.NewSet(attribute.KeyValue{ | ||||
| 									Key:   attribute.Key("g"), | ||||
| 									Value: attribute.StringValue("ding"), | ||||
| 								}, attribute.KeyValue{ | ||||
| 									Key:   attribute.Key("h"), | ||||
| 									Value: attribute.StringValue("dong"), | ||||
| 								}), | ||||
| 								Time:  endTime1, | ||||
| 								Count: 10, | ||||
| 								Sum:   13.2, | ||||
| 								QuantileValues: []metricdata.QuantileValue{ | ||||
| 									{ | ||||
| 										Quantile: 0.0, | ||||
| 										Value:    0.1, | ||||
| 									}, | ||||
| 									{ | ||||
| 										Quantile: 0.5, | ||||
| 										Value:    1.0, | ||||
| 									}, | ||||
| 									{ | ||||
| 										Quantile: 1.0, | ||||
| 										Value:    10.4, | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, { | ||||
| 								Attributes: attribute.NewSet(attribute.KeyValue{ | ||||
| 									Key:   attribute.Key("g"), | ||||
| 									Value: attribute.StringValue("ding"), | ||||
| 								}, attribute.KeyValue{ | ||||
| 									Key:   attribute.Key("h"), | ||||
| 									Value: attribute.StringValue("dong"), | ||||
| 								}), | ||||
| 								Time:  endTime2, | ||||
| 								Count: 12, | ||||
| 								QuantileValues: []metricdata.QuantileValue{ | ||||
| 									{ | ||||
| 										Quantile: 0.0, | ||||
| 										Value:    0.2, | ||||
| 									}, | ||||
| 									{ | ||||
| 										Quantile: 0.5, | ||||
| 										Value:    1.1, | ||||
| 									}, | ||||
| 									{ | ||||
| 										Quantile: 1.0, | ||||
| 										Value:    10.5, | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @@ -586,7 +692,7 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: errNegativeDistributionCount, | ||||
| 			expectedErr: errNegativeCount, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "histogram with negative bucket count", | ||||
| @@ -638,6 +744,82 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 			}, | ||||
| 			expectedErr: errMismatchedValueTypes, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "summary with mismatched attributes", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| 				{ | ||||
| 					Descriptor: ocmetricdata.Descriptor{ | ||||
| 						Name:        "foo.com/summary-mismatched", | ||||
| 						Description: "a mismatched summary", | ||||
| 						Unit:        ocmetricdata.UnitMilliseconds, | ||||
| 						Type:        ocmetricdata.TypeSummary, | ||||
| 						LabelKeys: []ocmetricdata.LabelKey{ | ||||
| 							{Key: "g"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					TimeSeries: []*ocmetricdata.TimeSeries{ | ||||
| 						{ | ||||
| 							LabelValues: []ocmetricdata.LabelValue{ | ||||
| 								{ | ||||
| 									Value:   "ding", | ||||
| 									Present: true, | ||||
| 								}, { | ||||
| 									Value:   "dong", | ||||
| 									Present: true, | ||||
| 								}, | ||||
| 							}, | ||||
| 							Points: []ocmetricdata.Point{ | ||||
| 								ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{ | ||||
| 									Count:          10, | ||||
| 									Sum:            13.2, | ||||
| 									HasCountAndSum: true, | ||||
| 									Snapshot: ocmetricdata.Snapshot{ | ||||
| 										Percentiles: map[float64]float64{ | ||||
| 											0.0: 0.1, | ||||
| 											0.5: 1.0, | ||||
| 											1.0: 10.4, | ||||
| 										}, | ||||
| 									}, | ||||
| 								}), | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: errMismatchedAttributeKeyValues, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "summary with negative count", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| 				{ | ||||
| 					Descriptor: ocmetricdata.Descriptor{ | ||||
| 						Name:        "foo.com/summary-negative", | ||||
| 						Description: "a negative count summary", | ||||
| 						Unit:        ocmetricdata.UnitMilliseconds, | ||||
| 						Type:        ocmetricdata.TypeSummary, | ||||
| 					}, | ||||
| 					TimeSeries: []*ocmetricdata.TimeSeries{ | ||||
| 						{ | ||||
| 							Points: []ocmetricdata.Point{ | ||||
| 								ocmetricdata.NewSummaryPoint(endTime1, &ocmetricdata.Summary{ | ||||
| 									Count:          -10, | ||||
| 									Sum:            13.2, | ||||
| 									HasCountAndSum: true, | ||||
| 									Snapshot: ocmetricdata.Snapshot{ | ||||
| 										Percentiles: map[float64]float64{ | ||||
| 											0.0: 0.1, | ||||
| 											0.5: 1.0, | ||||
| 											1.0: 10.4, | ||||
| 										}, | ||||
| 									}, | ||||
| 								}), | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: errNegativeCount, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "histogram with invalid span context exemplar", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| @@ -722,6 +904,28 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 			}, | ||||
| 			expectedErr: errMismatchedValueTypes, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "summary with non-summary datapoint type", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| 				{ | ||||
| 					Descriptor: ocmetricdata.Descriptor{ | ||||
| 						Name:        "foo.com/bad-point", | ||||
| 						Description: "a bad type", | ||||
| 						Unit:        ocmetricdata.UnitDimensionless, | ||||
| 						Type:        ocmetricdata.TypeSummary, | ||||
| 					}, | ||||
| 					TimeSeries: []*ocmetricdata.TimeSeries{ | ||||
| 						{ | ||||
| 							Points: []ocmetricdata.Point{ | ||||
| 								ocmetricdata.NewDistributionPoint(endTime1, &ocmetricdata.Distribution{}), | ||||
| 							}, | ||||
| 							StartTime: startTime, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedErr: errMismatchedValueTypes, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "unsupported Gauge Distribution type", | ||||
| 			input: []*ocmetricdata.Metric{ | ||||
| @@ -740,7 +944,7 @@ func TestConvertMetrics(t *testing.T) { | ||||
| 		t.Run(tc.desc, func(t *testing.T) { | ||||
| 			output, err := ConvertMetrics(tc.input) | ||||
| 			if !errors.Is(err, tc.expectedErr) { | ||||
| 				t.Errorf("convertAggregation(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr) | ||||
| 				t.Errorf("ConvertMetrics(%+v) = err(%v), want err(%v)", tc.input, err, tc.expectedErr) | ||||
| 			} | ||||
| 			metricdatatest.AssertEqual[metricdata.ScopeMetrics](t, | ||||
| 				metricdata.ScopeMetrics{Metrics: tc.expected}, | ||||
|   | ||||
| @@ -472,7 +472,7 @@ func equalSummaryDataPoint(a, b metricdata.SummaryDataPoint, cfg config) (reason | ||||
| 		} | ||||
| 		r := compareDiff(diffSlices( | ||||
| 			a.QuantileValues, | ||||
| 			a.QuantileValues, | ||||
| 			b.QuantileValues, | ||||
| 			func(a, b metricdata.QuantileValue) bool { | ||||
| 				r := equalQuantileValue(a, b, cfg) | ||||
| 				return len(r) == 0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user