1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-25 22:41:46 +02:00

Add Exemplar to metricdata package (#3849)

* Add Exemplar to metricdata pkg

* Update histogram Aggregator

* Update opencensus bridge

* Update prometheus exporter

* Update OTLP exporter

* Update stdoutmetric exporter

* Add changes to changelog

* Update fail tests

* Add tests for IgnoreExemplars

* Fix merge
This commit is contained in:
Tyler Yahn
2023-03-14 07:56:18 -07:00
committed by GitHub
parent b62eb2ca88
commit 01b8f15a72
16 changed files with 550 additions and 143 deletions

View File

@@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
- The `WithoutTimestamps` option to `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` to sets all timestamps to zero. (#3828)
- The new `Exemplar` type is added to `go.opentelemetry.io/otel/sdk/metric/metricdata`.
Both the `DataPoint` and `HistogramDataPoint` types from that package have a new field of `Exemplars` containing the sampled exemplars for their timeseries. (#3849)
### Changed
@@ -18,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Optimize memory allocation when creation new metric instruments in `go.opentelemetry.io/otel/sdk/metric`. (#3832)
- Avoid creating new objects on all calls to `WithDeferredSetup` and `SkipContextSetup` in OpenTracing bridge. (#3833)
- The `New` and `Detect` functions from `go.opentelemetry.io/otel/sdk/resource` return errors that wrap underlying errors instead of just containing the underlying error strings. (#3844)
- Both the `Histogram` and `HistogramDataPoint` are redefined with a generic argument of `[N int64 | float64]` in `go.opentelemetry.io/otel/sdk/metric/metricdata`. (#3849)
### Removed

View File

@@ -127,8 +127,8 @@ func convertNumberDataPoints[N int64 | float64](labelKeys []ocmetricdata.LabelKe
// convertHistogram converts OpenCensus Distribution timeseries to an
// OpenTelemetry Histogram aggregation.
func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Histogram, error) {
points := make([]metricdata.HistogramDataPoint, 0, len(ts))
func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Histogram[float64], error) {
points := make([]metricdata.HistogramDataPoint[float64], 0, len(ts))
var errInfo []string
for _, t := range ts {
attrs, err := convertAttrs(labelKeys, t.LabelValues)
@@ -152,7 +152,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
continue
}
// TODO: handle exemplars
points = append(points, metricdata.HistogramDataPoint{
points = append(points, metricdata.HistogramDataPoint[float64]{
Attributes: attrs,
StartTime: t.StartTime,
Time: p.Time,
@@ -167,7 +167,7 @@ func convertHistogram(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.Time
if len(errInfo) > 0 {
aggregatedError = fmt.Errorf("%w: %v", errHistogramDataPoint, errInfo)
}
return metricdata.Histogram{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, aggregatedError
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, aggregatedError
}
// convertBucketCounts converts from OpenCensus bucket counts to slice of uint64.

View File

@@ -214,8 +214,8 @@ func TestConvertMetrics(t *testing.T) {
Name: "foo.com/histogram-a",
Description: "a testing histogram",
Unit: "1",
Data: metricdata.Histogram{
DataPoints: []metricdata.HistogramDataPoint{
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.KeyValue{
Key: attribute.Key("a"),
@@ -387,9 +387,9 @@ func TestConvertMetrics(t *testing.T) {
Name: "foo.com/histogram-a",
Description: "a testing histogram",
Unit: "1",
Data: metricdata.Histogram{
Data: metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{},
DataPoints: []metricdata.HistogramDataPoint[float64]{},
},
},
},

View File

@@ -95,7 +95,9 @@ func metric(m metricdata.Metrics) (*mpb.Metric, error) {
out.Data, err = Sum[int64](a)
case metricdata.Sum[float64]:
out.Data, err = Sum[float64](a)
case metricdata.Histogram:
case metricdata.Histogram[int64]:
out.Data, err = Histogram(a)
case metricdata.Histogram[float64]:
out.Data, err = Histogram(a)
default:
return out, fmt.Errorf("%w: %T", errUnknownAggregation, a)
@@ -155,7 +157,7 @@ func DataPoints[N int64 | float64](dPts []metricdata.DataPoint[N]) []*mpb.Number
// Histogram returns an OTLP Metric_Histogram generated from h. An error is
// returned with a partial Metric_Histogram if the temporality of h is
// unknown.
func Histogram(h metricdata.Histogram) (*mpb.Metric_Histogram, error) {
func Histogram[N int64 | float64](h metricdata.Histogram[N]) (*mpb.Metric_Histogram, error) {
t, err := Temporality(h.Temporality)
if err != nil {
return nil, err
@@ -170,7 +172,7 @@ func Histogram(h metricdata.Histogram) (*mpb.Metric_Histogram, error) {
// HistogramDataPoints returns a slice of OTLP HistogramDataPoint generated
// from dPts.
func HistogramDataPoints(dPts []metricdata.HistogramDataPoint) []*mpb.HistogramDataPoint {
func HistogramDataPoints[N int64 | float64](dPts []metricdata.HistogramDataPoint[N]) []*mpb.HistogramDataPoint {
out := make([]*mpb.HistogramDataPoint, 0, len(dPts))
for _, dPt := range dPts {
sum := dPt.Sum

View File

@@ -52,7 +52,28 @@ var (
minA, maxA, sumA = 2.0, 4.0, 90.0
minB, maxB, sumB = 4.0, 150.0, 234.0
otelHDP = []metricdata.HistogramDataPoint{{
otelHDPInt64 = []metricdata.HistogramDataPoint[int64]{{
Attributes: alice,
StartTime: start,
Time: end,
Count: 30,
Bounds: []float64{1, 5},
BucketCounts: []uint64{0, 30, 0},
Min: metricdata.NewExtrema(minA),
Max: metricdata.NewExtrema(maxA),
Sum: sumA,
}, {
Attributes: bob,
StartTime: start,
Time: end,
Count: 3,
Bounds: []float64{1, 5},
BucketCounts: []uint64{0, 1, 2},
Min: metricdata.NewExtrema(minB),
Max: metricdata.NewExtrema(maxB),
Sum: sumB,
}}
otelHDPFloat64 = []metricdata.HistogramDataPoint[float64]{{
Attributes: alice,
StartTime: start,
Time: end,
@@ -96,14 +117,18 @@ var (
Max: &maxB,
}}
otelHist = metricdata.Histogram{
otelHistInt64 = metricdata.Histogram[int64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: otelHDP,
DataPoints: otelHDPInt64,
}
otelHistFloat64 = metricdata.Histogram[float64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: otelHDPFloat64,
}
invalidTemporality metricdata.Temporality
otelHistInvalid = metricdata.Histogram{
otelHistInvalid = metricdata.Histogram[int64]{
Temporality: invalidTemporality,
DataPoints: otelHDP,
DataPoints: otelHDPInt64,
}
pbHist = &mpb.Histogram{
@@ -215,10 +240,16 @@ var (
Data: otelSumInvalid,
},
{
Name: "histogram",
Name: "int64-histogram",
Description: "Histogram",
Unit: "1",
Data: otelHist,
Data: otelHistInt64,
},
{
Name: "float64-histogram",
Description: "Histogram",
Unit: "1",
Data: otelHistFloat64,
},
{
Name: "invalid-histogram",
@@ -260,7 +291,13 @@ var (
Data: &mpb.Metric_Sum{Sum: pbSumFloat64},
},
{
Name: "histogram",
Name: "int64-histogram",
Description: "Histogram",
Unit: "1",
Data: &mpb.Metric_Histogram{Histogram: pbHist},
},
{
Name: "float64-histogram",
Description: "Histogram",
Unit: "1",
Data: &mpb.Metric_Histogram{Histogram: pbHist},
@@ -327,12 +364,16 @@ func TestTransformations(t *testing.T) {
// errors deep inside the structs).
// DataPoint types.
assert.Equal(t, pbHDP, HistogramDataPoints(otelHDP))
assert.Equal(t, pbHDP, HistogramDataPoints(otelHDPInt64))
assert.Equal(t, pbHDP, HistogramDataPoints(otelHDPFloat64))
assert.Equal(t, pbDPtsInt64, DataPoints[int64](otelDPtsInt64))
require.Equal(t, pbDPtsFloat64, DataPoints[float64](otelDPtsFloat64))
// Aggregations.
h, err := Histogram(otelHist)
h, err := Histogram(otelHistInt64)
assert.NoError(t, err)
assert.Equal(t, &mpb.Metric_Histogram{Histogram: pbHist}, h)
h, err = Histogram(otelHistFloat64)
assert.NoError(t, err)
assert.Equal(t, &mpb.Metric_Histogram{Histogram: pbHist}, h)
h, err = Histogram(otelHistInvalid)

View File

@@ -155,7 +155,9 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
for _, m := range scopeMetrics.Metrics {
switch v := m.Data.(type) {
case metricdata.Histogram:
case metricdata.Histogram[int64]:
addHistogramMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Histogram[float64]:
addHistogramMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Sum[int64]:
addSumMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
@@ -170,7 +172,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
}
}
func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, ks, vs [2]string, name string, mfs map[string]*dto.MetricFamily) {
func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, ks, vs [2]string, name string, mfs map[string]*dto.MetricFamily) {
// TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars
drop, help := validateMetrics(name, m.Description, dto.MetricType_HISTOGRAM.Enum(), mfs)
if drop {

View File

@@ -81,9 +81,9 @@ var (
Name: "latency",
Description: "Time spend processing received requests",
Unit: "ms",
Data: metricdata.Histogram{
Data: metricdata.Histogram[float64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.HistogramDataPoint{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("server", "central")),
StartTime: now,

View File

@@ -138,8 +138,13 @@ func redactAggregationTimestamps(orig metricdata.Aggregation) metricdata.Aggrega
return metricdata.Gauge[int64]{
DataPoints: redactDataPointTimestamps(a.DataPoints),
}
case metricdata.Histogram:
return metricdata.Histogram{
case metricdata.Histogram[int64]:
return metricdata.Histogram[int64]{
Temporality: a.Temporality,
DataPoints: redactHistogramTimestamps(a.DataPoints),
}
case metricdata.Histogram[float64]:
return metricdata.Histogram[float64]{
Temporality: a.Temporality,
DataPoints: redactHistogramTimestamps(a.DataPoints),
}
@@ -149,10 +154,10 @@ func redactAggregationTimestamps(orig metricdata.Aggregation) metricdata.Aggrega
}
}
func redactHistogramTimestamps(hdp []metricdata.HistogramDataPoint) []metricdata.HistogramDataPoint {
out := make([]metricdata.HistogramDataPoint, len(hdp))
func redactHistogramTimestamps[T int64 | float64](hdp []metricdata.HistogramDataPoint[T]) []metricdata.HistogramDataPoint[T] {
out := make([]metricdata.HistogramDataPoint[T], len(hdp))
for i, dp := range hdp {
out[i] = metricdata.HistogramDataPoint{
out[i] = metricdata.HistogramDataPoint[T]{
Attributes: dp.Attributes,
Count: dp.Count,
Sum: dp.Sum,

View File

@@ -141,12 +141,12 @@ func (s *deltaHistogram[N]) Aggregation() metricdata.Aggregation {
// Do not allow modification of our copy of bounds.
bounds := make([]float64, len(s.bounds))
copy(bounds, s.bounds)
h := metricdata.Histogram{
h := metricdata.Histogram[N]{
Temporality: metricdata.DeltaTemporality,
DataPoints: make([]metricdata.HistogramDataPoint, 0, len(s.values)),
DataPoints: make([]metricdata.HistogramDataPoint[N], 0, len(s.values)),
}
for a, b := range s.values {
hdp := metricdata.HistogramDataPoint{
hdp := metricdata.HistogramDataPoint[N]{
Attributes: a,
StartTime: s.start,
Time: t,
@@ -204,9 +204,9 @@ func (s *cumulativeHistogram[N]) Aggregation() metricdata.Aggregation {
// Do not allow modification of our copy of bounds.
bounds := make([]float64, len(s.bounds))
copy(bounds, s.bounds)
h := metricdata.Histogram{
h := metricdata.Histogram[N]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: make([]metricdata.HistogramDataPoint, 0, len(s.values)),
DataPoints: make([]metricdata.HistogramDataPoint[N], 0, len(s.values)),
}
for a, b := range s.values {
// The HistogramDataPoint field values returned need to be copies of
@@ -217,7 +217,7 @@ func (s *cumulativeHistogram[N]) Aggregation() metricdata.Aggregation {
counts := make([]uint64, len(b.counts))
copy(counts, b.counts)
hdp := metricdata.HistogramDataPoint{
hdp := metricdata.HistogramDataPoint[N]{
Attributes: a,
StartTime: s.start,
Time: t,

View File

@@ -49,31 +49,31 @@ func testHistogram[N int64 | float64](t *testing.T) {
}
incr := monoIncr
eFunc := deltaHistExpecter(incr)
eFunc := deltaHistExpecter[N](incr)
t.Run("Delta", tester.Run(NewDeltaHistogram[N](histConf), incr, eFunc))
eFunc = cumuHistExpecter(incr)
eFunc = cumuHistExpecter[N](incr)
t.Run("Cumulative", tester.Run(NewCumulativeHistogram[N](histConf), incr, eFunc))
}
func deltaHistExpecter(incr setMap) expectFunc {
h := metricdata.Histogram{Temporality: metricdata.DeltaTemporality}
func deltaHistExpecter[N int64 | float64](incr setMap) expectFunc {
h := metricdata.Histogram[N]{Temporality: metricdata.DeltaTemporality}
return func(m int) metricdata.Aggregation {
h.DataPoints = make([]metricdata.HistogramDataPoint, 0, len(incr))
h.DataPoints = make([]metricdata.HistogramDataPoint[N], 0, len(incr))
for a, v := range incr {
h.DataPoints = append(h.DataPoints, hPoint(a, float64(v), uint64(m)))
h.DataPoints = append(h.DataPoints, hPoint[N](a, float64(v), uint64(m)))
}
return h
}
}
func cumuHistExpecter(incr setMap) expectFunc {
func cumuHistExpecter[N int64 | float64](incr setMap) expectFunc {
var cycle int
h := metricdata.Histogram{Temporality: metricdata.CumulativeTemporality}
h := metricdata.Histogram[N]{Temporality: metricdata.CumulativeTemporality}
return func(m int) metricdata.Aggregation {
cycle++
h.DataPoints = make([]metricdata.HistogramDataPoint, 0, len(incr))
h.DataPoints = make([]metricdata.HistogramDataPoint[N], 0, len(incr))
for a, v := range incr {
h.DataPoints = append(h.DataPoints, hPoint(a, float64(v), uint64(cycle*m)))
h.DataPoints = append(h.DataPoints, hPoint[N](a, float64(v), uint64(cycle*m)))
}
return h
}
@@ -81,11 +81,11 @@ func cumuHistExpecter(incr setMap) expectFunc {
// hPoint returns an HistogramDataPoint that started and ended now with multi
// number of measurements values v. It includes a min and max (set to v).
func hPoint(a attribute.Set, v float64, multi uint64) metricdata.HistogramDataPoint {
func hPoint[N int64 | float64](a attribute.Set, v float64, multi uint64) metricdata.HistogramDataPoint[N] {
idx := sort.SearchFloat64s(bounds, v)
counts := make([]uint64, len(bounds)+1)
counts[idx] += multi
return metricdata.HistogramDataPoint{
return metricdata.HistogramDataPoint[N]{
Attributes: a,
StartTime: now(),
Time: now(),
@@ -128,7 +128,7 @@ func testHistImmutableBounds[N int64 | float64](newA func(aggregation.ExplicitBu
assert.Equal(t, cpB, getBounds(a), "modifying the bounds argument should not change the bounds")
a.Aggregate(5, alice)
hdp := a.Aggregation().(metricdata.Histogram).DataPoints[0]
hdp := a.Aggregation().(metricdata.Histogram[N]).DataPoints[0]
hdp.Bounds[1] = 10
assert.Equal(t, cpB, getBounds(a), "modifying the Aggregation bounds should not change the bounds")
}
@@ -155,7 +155,7 @@ func TestHistogramImmutableBounds(t *testing.T) {
func TestCumulativeHistogramImutableCounts(t *testing.T) {
a := NewCumulativeHistogram[int64](histConf)
a.Aggregate(5, alice)
hdp := a.Aggregation().(metricdata.Histogram).DataPoints[0]
hdp := a.Aggregation().(metricdata.Histogram[int64]).DataPoints[0]
cumuH := a.(*cumulativeHistogram[int64])
require.Equal(t, hdp.BucketCounts, cumuH.values[alice].counts)
@@ -173,8 +173,8 @@ func TestDeltaHistogramReset(t *testing.T) {
assert.Nil(t, a.Aggregation())
a.Aggregate(1, alice)
expect := metricdata.Histogram{Temporality: metricdata.DeltaTemporality}
expect.DataPoints = []metricdata.HistogramDataPoint{hPoint(alice, 1, 1)}
expect := metricdata.Histogram[int64]{Temporality: metricdata.DeltaTemporality}
expect.DataPoints = []metricdata.HistogramDataPoint[int64]{hPoint[int64](alice, 1, 1)}
metricdatatest.AssertAggregationsEqual(t, expect, a.Aggregation())
// The attr set should be forgotten once Aggregations is called.
@@ -183,7 +183,7 @@ func TestDeltaHistogramReset(t *testing.T) {
// Aggregating another set should not affect the original (alice).
a.Aggregate(1, bob)
expect.DataPoints = []metricdata.HistogramDataPoint{hPoint(bob, 1, 1)}
expect.DataPoints = []metricdata.HistogramDataPoint[int64]{hPoint[int64](bob, 1, 1)}
metricdatatest.AssertAggregationsEqual(t, expect, a.Aggregation())
}

View File

@@ -382,9 +382,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
},
want: metricdata.Metrics{
Name: "histogram",
Data: metricdata.Histogram{
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{
DataPoints: []metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.Set{},
Count: 1,
@@ -446,9 +446,9 @@ func TestMeterCreatesInstruments(t *testing.T) {
},
want: metricdata.Metrics{
Name: "histogram",
Data: metricdata.Histogram{
Data: metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.Set{},
Count: 1,
@@ -1124,8 +1124,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) {
},
wantMetric: metricdata.Metrics{
Name: "sfhistogram",
Data: metricdata.Histogram{
DataPoints: []metricdata.HistogramDataPoint{
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000},
@@ -1206,8 +1206,8 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) {
},
wantMetric: metricdata.Metrics{
Name: "sihistogram",
Data: metricdata.Histogram{
DataPoints: []metricdata.HistogramDataPoint{
Data: metricdata.Histogram[int64]{
DataPoints: []metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000},

View File

@@ -59,7 +59,8 @@ type Aggregation interface {
// Gauge represents a measurement of the current value of an instrument.
type Gauge[N int64 | float64] struct {
// DataPoints reprents individual aggregated measurements with unique Attributes.
// DataPoints are the individual aggregated measurements with unique
// Attributes.
DataPoints []DataPoint[N]
}
@@ -67,7 +68,8 @@ func (Gauge[N]) privateAggregation() {}
// Sum represents the sum of all measurements of values from an instrument.
type Sum[N int64 | float64] struct {
// DataPoints reprents individual aggregated measurements with unique Attributes.
// DataPoints are the individual aggregated measurements with unique
// Attributes.
DataPoints []DataPoint[N]
// Temporality describes if the aggregation is reported as the change from the
// last report time, or the cumulative changes since a fixed start time.
@@ -89,21 +91,25 @@ type DataPoint[N int64 | float64] struct {
Time time.Time `json:",omitempty"`
// Value is the value of this data point.
Value N
// Exemplars is the sampled Exemplars collected during the timeseries.
Exemplars []Exemplar[N] `json:",omitempty"`
}
// Histogram represents the histogram of all measurements of values from an instrument.
type Histogram struct {
// DataPoints reprents individual aggregated measurements with unique Attributes.
DataPoints []HistogramDataPoint
type Histogram[N int64 | float64] struct {
// DataPoints are the individual aggregated measurements with unique
// Attributes.
DataPoints []HistogramDataPoint[N]
// Temporality describes if the aggregation is reported as the change from the
// last report time, or the cumulative changes since a fixed start time.
Temporality Temporality
}
func (Histogram) privateAggregation() {}
func (Histogram[N]) privateAggregation() {}
// HistogramDataPoint is a single histogram data point in a timeseries.
type HistogramDataPoint struct {
type HistogramDataPoint[N int64 | float64] struct {
// Attributes is the set of key value pairs that uniquely identify the
// timeseries.
Attributes attribute.Set
@@ -126,6 +132,9 @@ type HistogramDataPoint struct {
Max Extrema
// Sum is the sum of the values recorded.
Sum float64
// Exemplars is the sampled Exemplars collected during the timeseries.
Exemplars []Exemplar[N] `json:",omitempty"`
}
// Extrema is the minimum or maximum value of a dataset.
@@ -144,3 +153,22 @@ func NewExtrema(v float64) Extrema {
func (e Extrema) Value() (v float64, defined bool) {
return e.value, e.valid
}
// Exemplar is a measurement sampled from a timeseries providing a typical
// example.
type Exemplar[N int64 | float64] struct {
// FilteredAttributes are the attributes recorded with the measurement but
// filtered out of the timeseries' aggregated data.
FilteredAttributes []attribute.KeyValue
// Time is the time when the measurement was recorded.
Time time.Time
// Value is the measured value.
Value N
// SpanID is the ID of the span that was active during the measurement. If
// no span was active or the span was not sampled this will be empty.
SpanID []byte `json:",omitempty"`
// TraceID is the ID of the trace the active span belonged to during the
// measurement. If no span was active or the span was not sampled this will
// be empty.
TraceID []byte `json:",omitempty"`
}

View File

@@ -30,14 +30,18 @@ type Datatypes interface {
metricdata.DataPoint[int64] |
metricdata.Gauge[float64] |
metricdata.Gauge[int64] |
metricdata.Histogram |
metricdata.HistogramDataPoint |
metricdata.Histogram[float64] |
metricdata.Histogram[int64] |
metricdata.HistogramDataPoint[float64] |
metricdata.HistogramDataPoint[int64] |
metricdata.Extrema |
metricdata.Metrics |
metricdata.ResourceMetrics |
metricdata.ScopeMetrics |
metricdata.Sum[float64] |
metricdata.Sum[int64]
metricdata.Sum[int64] |
metricdata.Exemplar[float64] |
metricdata.Exemplar[int64]
// Interface types are not allowed in union types, therefore the
// Aggregation and Value type from metricdata are not included here.
@@ -45,6 +49,15 @@ type Datatypes interface {
type config struct {
ignoreTimestamp bool
ignoreExemplars bool
}
func newConfig(opts []Option) config {
var cfg config
for _, opt := range opts {
cfg = opt.apply(cfg)
}
return cfg
}
// Option allows for fine grain control over how AssertEqual operates.
@@ -66,21 +79,30 @@ func IgnoreTimestamp() Option {
})
}
// IgnoreExemplars disables checking if Exemplars are different.
func IgnoreExemplars() Option {
return fnOption(func(cfg config) config {
cfg.ignoreExemplars = true
return cfg
})
}
// AssertEqual asserts that the two concrete data-types from the metricdata
// package are equal.
func AssertEqual[T Datatypes](t *testing.T, expected, actual T, opts ...Option) bool {
t.Helper()
cfg := config{}
for _, opt := range opts {
cfg = opt.apply(cfg)
}
cfg := newConfig(opts)
// Generic types cannot be type asserted. Use an interface instead.
aIface := interface{}(actual)
var r []string
switch e := interface{}(expected).(type) {
case metricdata.Exemplar[int64]:
r = equalExemplars(e, aIface.(metricdata.Exemplar[int64]), cfg)
case metricdata.Exemplar[float64]:
r = equalExemplars(e, aIface.(metricdata.Exemplar[float64]), cfg)
case metricdata.DataPoint[int64]:
r = equalDataPoints(e, aIface.(metricdata.DataPoint[int64]), cfg)
case metricdata.DataPoint[float64]:
@@ -89,10 +111,14 @@ func AssertEqual[T Datatypes](t *testing.T, expected, actual T, opts ...Option)
r = equalGauges(e, aIface.(metricdata.Gauge[int64]), cfg)
case metricdata.Gauge[float64]:
r = equalGauges(e, aIface.(metricdata.Gauge[float64]), cfg)
case metricdata.Histogram:
r = equalHistograms(e, aIface.(metricdata.Histogram), cfg)
case metricdata.HistogramDataPoint:
r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint), cfg)
case metricdata.Histogram[float64]:
r = equalHistograms(e, aIface.(metricdata.Histogram[float64]), cfg)
case metricdata.Histogram[int64]:
r = equalHistograms(e, aIface.(metricdata.Histogram[int64]), cfg)
case metricdata.HistogramDataPoint[float64]:
r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint[float64]), cfg)
case metricdata.HistogramDataPoint[int64]:
r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint[int64]), cfg)
case metricdata.Extrema:
r = equalExtrema(e, aIface.(metricdata.Extrema), cfg)
case metricdata.Metrics:
@@ -122,11 +148,7 @@ func AssertEqual[T Datatypes](t *testing.T, expected, actual T, opts ...Option)
func AssertAggregationsEqual(t *testing.T, expected, actual metricdata.Aggregation, opts ...Option) bool {
t.Helper()
cfg := config{}
for _, opt := range opts {
cfg = opt.apply(cfg)
}
cfg := newConfig(opts)
if r := equalAggregations(expected, actual, cfg); len(r) > 0 {
t.Error(r)
return false
@@ -141,6 +163,10 @@ func AssertHasAttributes[T Datatypes](t *testing.T, actual T, attrs ...attribute
var reasons []string
switch e := interface{}(actual).(type) {
case metricdata.Exemplar[int64]:
reasons = hasAttributesExemplars(e, attrs...)
case metricdata.Exemplar[float64]:
reasons = hasAttributesExemplars(e, attrs...)
case metricdata.DataPoint[int64]:
reasons = hasAttributesDataPoints(e, attrs...)
case metricdata.DataPoint[float64]:
@@ -153,11 +179,15 @@ func AssertHasAttributes[T Datatypes](t *testing.T, actual T, attrs ...attribute
reasons = hasAttributesSum(e, attrs...)
case metricdata.Sum[float64]:
reasons = hasAttributesSum(e, attrs...)
case metricdata.HistogramDataPoint:
case metricdata.HistogramDataPoint[int64]:
reasons = hasAttributesHistogramDataPoints(e, attrs...)
case metricdata.HistogramDataPoint[float64]:
reasons = hasAttributesHistogramDataPoints(e, attrs...)
case metricdata.Extrema:
// Nothing to check.
case metricdata.Histogram:
case metricdata.Histogram[int64]:
reasons = hasAttributesHistogram(e, attrs...)
case metricdata.Histogram[float64]:
reasons = hasAttributesHistogram(e, attrs...)
case metricdata.Metrics:
reasons = hasAttributesMetrics(e, attrs...)

View File

@@ -38,15 +38,19 @@ func TestFailAssertEqual(t *testing.T) {
t.Run("ResourceMetrics", testFailDatatype(resourceMetricsA, resourceMetricsB))
t.Run("ScopeMetrics", testFailDatatype(scopeMetricsA, scopeMetricsB))
t.Run("Metrics", testFailDatatype(metricsA, metricsB))
t.Run("Histogram", testFailDatatype(histogramA, histogramB))
t.Run("HistogramInt64", testFailDatatype(histogramInt64A, histogramInt64B))
t.Run("HistogramFloat64", testFailDatatype(histogramFloat64A, histogramFloat64B))
t.Run("SumInt64", testFailDatatype(sumInt64A, sumInt64B))
t.Run("SumFloat64", testFailDatatype(sumFloat64A, sumFloat64B))
t.Run("GaugeInt64", testFailDatatype(gaugeInt64A, gaugeInt64B))
t.Run("GaugeFloat64", testFailDatatype(gaugeFloat64A, gaugeFloat64B))
t.Run("HistogramDataPoint", testFailDatatype(histogramDataPointA, histogramDataPointB))
t.Run("HistogramDataPointInt64", testFailDatatype(histogramDataPointInt64A, histogramDataPointInt64B))
t.Run("HistogramDataPointFloat64", testFailDatatype(histogramDataPointFloat64A, histogramDataPointFloat64B))
t.Run("DataPointInt64", testFailDatatype(dataPointInt64A, dataPointInt64B))
t.Run("DataPointFloat64", testFailDatatype(dataPointFloat64A, dataPointFloat64B))
t.Run("ExemplarInt64", testFailDatatype(exemplarInt64A, exemplarInt64B))
t.Run("ExemplarFloat64", testFailDatatype(exemplarFloat64A, exemplarFloat64B))
t.Run("Extrema", testFailDatatype(minA, minB))
}
func TestFailAssertAggregationsEqual(t *testing.T) {
@@ -57,20 +61,23 @@ func TestFailAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, sumFloat64A, sumFloat64B)
AssertAggregationsEqual(t, gaugeInt64A, gaugeInt64B)
AssertAggregationsEqual(t, gaugeFloat64A, gaugeFloat64B)
AssertAggregationsEqual(t, histogramA, histogramB)
AssertAggregationsEqual(t, histogramInt64A, histogramInt64B)
AssertAggregationsEqual(t, histogramFloat64A, histogramFloat64B)
}
func TestFailAssertAttribute(t *testing.T) {
AssertHasAttributes(t, exemplarInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, exemplarFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, dataPointInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, dataPointFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, gaugeInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, gaugeFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, sumInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, sumFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, histogramDataPointA, attribute.Bool("A", false))
AssertHasAttributes(t, histogramDataPointA, attribute.Bool("B", true))
AssertHasAttributes(t, histogramA, attribute.Bool("A", false))
AssertHasAttributes(t, histogramA, attribute.Bool("B", true))
AssertHasAttributes(t, histogramDataPointInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, histogramDataPointFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, histogramInt64A, attribute.Bool("A", false))
AssertHasAttributes(t, histogramFloat64A, attribute.Bool("B", true))
AssertHasAttributes(t, metricsA, attribute.Bool("A", false))
AssertHasAttributes(t, metricsA, attribute.Bool("B", true))
AssertHasAttributes(t, resourceMetricsA, attribute.Bool("A", false))

View File

@@ -30,53 +30,110 @@ var (
attrA = attribute.NewSet(attribute.Bool("A", true))
attrB = attribute.NewSet(attribute.Bool("B", true))
fltrAttrA = []attribute.KeyValue{attribute.Bool("filter A", true)}
fltrAttrB = []attribute.KeyValue{attribute.Bool("filter B", true)}
startA = time.Now()
startB = startA.Add(time.Millisecond)
endA = startA.Add(time.Second)
endB = startB.Add(time.Second)
spanIDA = []byte{0, 0, 0, 0, 0, 0, 0, 1}
spanIDB = []byte{0, 0, 0, 0, 0, 0, 0, 2}
traceIDA = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
traceIDB = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
exemplarInt64A = metricdata.Exemplar[int64]{
FilteredAttributes: fltrAttrA,
Time: endA,
Value: -10,
SpanID: spanIDA,
TraceID: traceIDA,
}
exemplarFloat64A = metricdata.Exemplar[float64]{
FilteredAttributes: fltrAttrA,
Time: endA,
Value: -10.0,
SpanID: spanIDA,
TraceID: traceIDA,
}
exemplarInt64B = metricdata.Exemplar[int64]{
FilteredAttributes: fltrAttrB,
Time: endB,
Value: 12,
SpanID: spanIDB,
TraceID: traceIDB,
}
exemplarFloat64B = metricdata.Exemplar[float64]{
FilteredAttributes: fltrAttrB,
Time: endB,
Value: 12.0,
SpanID: spanIDB,
TraceID: traceIDB,
}
exemplarInt64C = metricdata.Exemplar[int64]{
FilteredAttributes: fltrAttrA,
Time: endB,
Value: -10,
SpanID: spanIDA,
TraceID: traceIDA,
}
exemplarFloat64C = metricdata.Exemplar[float64]{
FilteredAttributes: fltrAttrA,
Time: endB,
Value: -10.0,
SpanID: spanIDA,
TraceID: traceIDA,
}
dataPointInt64A = metricdata.DataPoint[int64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
Value: -1,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64A},
}
dataPointFloat64A = metricdata.DataPoint[float64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
Value: -1.0,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64A},
}
dataPointInt64B = metricdata.DataPoint[int64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
Value: 2,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64B},
}
dataPointFloat64B = metricdata.DataPoint[float64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
Value: 2.0,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64B},
}
dataPointInt64C = metricdata.DataPoint[int64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
Value: -1,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64C},
}
dataPointFloat64C = metricdata.DataPoint[float64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
Value: -1.0,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64C},
}
minA = metricdata.NewExtrema(-1.)
minB, maxB = metricdata.NewExtrema(3.), metricdata.NewExtrema(99.)
minC = metricdata.NewExtrema(-1.)
histogramDataPointA = metricdata.HistogramDataPoint{
histogramDataPointInt64A = metricdata.HistogramDataPoint[int64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
@@ -85,8 +142,20 @@ var (
BucketCounts: []uint64{1, 1},
Min: minA,
Sum: 2,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64A},
}
histogramDataPointB = metricdata.HistogramDataPoint{
histogramDataPointFloat64A = metricdata.HistogramDataPoint[float64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 2,
Bounds: []float64{0, 10},
BucketCounts: []uint64{1, 1},
Min: minA,
Sum: 2,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64A},
}
histogramDataPointInt64B = metricdata.HistogramDataPoint[int64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
@@ -96,8 +165,21 @@ var (
Max: maxB,
Min: minB,
Sum: 3,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64B},
}
histogramDataPointC = metricdata.HistogramDataPoint{
histogramDataPointFloat64B = metricdata.HistogramDataPoint[float64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
Count: 3,
Bounds: []float64{0, 10, 100},
BucketCounts: []uint64{1, 1, 1},
Max: maxB,
Min: minB,
Sum: 3,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64B},
}
histogramDataPointInt64C = metricdata.HistogramDataPoint[int64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
@@ -106,6 +188,18 @@ var (
BucketCounts: []uint64{1, 1},
Min: minC,
Sum: 2,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64C},
}
histogramDataPointFloat64C = metricdata.HistogramDataPoint[float64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
Count: 2,
Bounds: []float64{0, 10},
BucketCounts: []uint64{1, 1},
Min: minC,
Sum: 2,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64C},
}
gaugeInt64A = metricdata.Gauge[int64]{
@@ -158,17 +252,29 @@ var (
DataPoints: []metricdata.DataPoint[float64]{dataPointFloat64C},
}
histogramA = metricdata.Histogram{
histogramInt64A = metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{histogramDataPointA},
DataPoints: []metricdata.HistogramDataPoint[int64]{histogramDataPointInt64A},
}
histogramB = metricdata.Histogram{
histogramFloat64A = metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint[float64]{histogramDataPointFloat64A},
}
histogramInt64B = metricdata.Histogram[int64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.HistogramDataPoint{histogramDataPointB},
DataPoints: []metricdata.HistogramDataPoint[int64]{histogramDataPointInt64B},
}
histogramC = metricdata.Histogram{
histogramFloat64B = metricdata.Histogram[float64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.HistogramDataPoint[float64]{histogramDataPointFloat64B},
}
histogramInt64C = metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{histogramDataPointC},
DataPoints: []metricdata.HistogramDataPoint[int64]{histogramDataPointInt64C},
}
histogramFloat64C = metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint[float64]{histogramDataPointFloat64C},
}
metricsA = metricdata.Metrics{
@@ -224,7 +330,7 @@ func testDatatype[T Datatypes](a, b T, f equalFunc[T]) func(*testing.T) {
AssertEqual(t, a, a)
AssertEqual(t, b, b)
r := f(a, b, config{})
r := f(a, b, newConfig(nil))
assert.Greaterf(t, len(r), 0, "%v == %v", a, b)
}
}
@@ -234,8 +340,20 @@ func testDatatypeIgnoreTime[T Datatypes](a, b T, f equalFunc[T]) func(*testing.T
AssertEqual(t, a, a)
AssertEqual(t, b, b)
r := f(a, b, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", a, b)
c := newConfig([]Option{IgnoreTimestamp()})
r := f(a, b, c)
assert.Len(t, r, 0, "unexpected inequality")
}
}
func testDatatypeIgnoreExemplars[T Datatypes](a, b T, f equalFunc[T]) func(*testing.T) {
return func(t *testing.T) {
AssertEqual(t, a, a)
AssertEqual(t, b, b)
c := newConfig([]Option{IgnoreExemplars()})
r := f(a, b, c)
assert.Len(t, r, 0, "unexpected inequality")
}
}
@@ -243,30 +361,56 @@ func TestAssertEqual(t *testing.T) {
t.Run("ResourceMetrics", testDatatype(resourceMetricsA, resourceMetricsB, equalResourceMetrics))
t.Run("ScopeMetrics", testDatatype(scopeMetricsA, scopeMetricsB, equalScopeMetrics))
t.Run("Metrics", testDatatype(metricsA, metricsB, equalMetrics))
t.Run("Histogram", testDatatype(histogramA, histogramB, equalHistograms))
t.Run("HistogramInt64", testDatatype(histogramInt64A, histogramInt64B, equalHistograms[int64]))
t.Run("HistogramFloat64", testDatatype(histogramFloat64A, histogramFloat64B, equalHistograms[float64]))
t.Run("SumInt64", testDatatype(sumInt64A, sumInt64B, equalSums[int64]))
t.Run("SumFloat64", testDatatype(sumFloat64A, sumFloat64B, equalSums[float64]))
t.Run("GaugeInt64", testDatatype(gaugeInt64A, gaugeInt64B, equalGauges[int64]))
t.Run("GaugeFloat64", testDatatype(gaugeFloat64A, gaugeFloat64B, equalGauges[float64]))
t.Run("HistogramDataPoint", testDatatype(histogramDataPointA, histogramDataPointB, equalHistogramDataPoints))
t.Run("HistogramDataPointInt64", testDatatype(histogramDataPointInt64A, histogramDataPointInt64B, equalHistogramDataPoints[int64]))
t.Run("HistogramDataPointFloat64", testDatatype(histogramDataPointFloat64A, histogramDataPointFloat64B, equalHistogramDataPoints[float64]))
t.Run("DataPointInt64", testDatatype(dataPointInt64A, dataPointInt64B, equalDataPoints[int64]))
t.Run("DataPointFloat64", testDatatype(dataPointFloat64A, dataPointFloat64B, equalDataPoints[float64]))
t.Run("Extrema", testDatatype(minA, minB, equalExtrema))
t.Run("ExemplarInt64", testDatatype(exemplarInt64A, exemplarInt64B, equalExemplars[int64]))
t.Run("ExemplarFloat64", testDatatype(exemplarFloat64A, exemplarFloat64B, equalExemplars[float64]))
}
func TestAssertEqualIgnoreTime(t *testing.T) {
t.Run("ResourceMetrics", testDatatypeIgnoreTime(resourceMetricsA, resourceMetricsC, equalResourceMetrics))
t.Run("ScopeMetrics", testDatatypeIgnoreTime(scopeMetricsA, scopeMetricsC, equalScopeMetrics))
t.Run("Metrics", testDatatypeIgnoreTime(metricsA, metricsC, equalMetrics))
t.Run("Histogram", testDatatypeIgnoreTime(histogramA, histogramC, equalHistograms))
t.Run("HistogramInt64", testDatatypeIgnoreTime(histogramInt64A, histogramInt64C, equalHistograms[int64]))
t.Run("HistogramFloat64", testDatatypeIgnoreTime(histogramFloat64A, histogramFloat64C, equalHistograms[float64]))
t.Run("SumInt64", testDatatypeIgnoreTime(sumInt64A, sumInt64C, equalSums[int64]))
t.Run("SumFloat64", testDatatypeIgnoreTime(sumFloat64A, sumFloat64C, equalSums[float64]))
t.Run("GaugeInt64", testDatatypeIgnoreTime(gaugeInt64A, gaugeInt64C, equalGauges[int64]))
t.Run("GaugeFloat64", testDatatypeIgnoreTime(gaugeFloat64A, gaugeFloat64C, equalGauges[float64]))
t.Run("HistogramDataPoint", testDatatypeIgnoreTime(histogramDataPointA, histogramDataPointC, equalHistogramDataPoints))
t.Run("HistogramDataPointInt64", testDatatypeIgnoreTime(histogramDataPointInt64A, histogramDataPointInt64C, equalHistogramDataPoints[int64]))
t.Run("HistogramDataPointFloat64", testDatatypeIgnoreTime(histogramDataPointFloat64A, histogramDataPointFloat64C, equalHistogramDataPoints[float64]))
t.Run("DataPointInt64", testDatatypeIgnoreTime(dataPointInt64A, dataPointInt64C, equalDataPoints[int64]))
t.Run("DataPointFloat64", testDatatypeIgnoreTime(dataPointFloat64A, dataPointFloat64C, equalDataPoints[float64]))
t.Run("Extrema", testDatatypeIgnoreTime(minA, minC, equalExtrema))
t.Run("ExemplarInt64", testDatatypeIgnoreTime(exemplarInt64A, exemplarInt64C, equalExemplars[int64]))
t.Run("ExemplarFloat64", testDatatypeIgnoreTime(exemplarFloat64A, exemplarFloat64C, equalExemplars[float64]))
}
func TestAssertEqualIgnoreExemplars(t *testing.T) {
hdpInt64 := histogramDataPointInt64A
hdpInt64.Exemplars = []metricdata.Exemplar[int64]{exemplarInt64B}
t.Run("HistogramDataPointInt64", testDatatypeIgnoreExemplars(histogramDataPointInt64A, hdpInt64, equalHistogramDataPoints[int64]))
hdpFloat64 := histogramDataPointFloat64A
hdpFloat64.Exemplars = []metricdata.Exemplar[float64]{exemplarFloat64B}
t.Run("HistogramDataPointFloat64", testDatatypeIgnoreExemplars(histogramDataPointFloat64A, hdpFloat64, equalHistogramDataPoints[float64]))
dpInt64 := dataPointInt64A
dpInt64.Exemplars = []metricdata.Exemplar[int64]{exemplarInt64B}
t.Run("DataPointInt64", testDatatypeIgnoreExemplars(dataPointInt64A, dpInt64, equalDataPoints[int64]))
dpFloat64 := dataPointFloat64A
dpFloat64.Exemplars = []metricdata.Exemplar[float64]{exemplarFloat64B}
t.Run("DataPointFloat64", testDatatypeIgnoreExemplars(dataPointFloat64A, dpFloat64, equalDataPoints[float64]))
}
type unknownAggregation struct {
@@ -279,7 +423,8 @@ func TestAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, sumFloat64A, sumFloat64A)
AssertAggregationsEqual(t, gaugeInt64A, gaugeInt64A)
AssertAggregationsEqual(t, gaugeFloat64A, gaugeFloat64A)
AssertAggregationsEqual(t, histogramA, histogramA)
AssertAggregationsEqual(t, histogramInt64A, histogramInt64A)
AssertAggregationsEqual(t, histogramFloat64A, histogramFloat64A)
r := equalAggregations(sumInt64A, nil, config{})
assert.Len(t, r, 1, "should return nil comparison mismatch only")
@@ -291,46 +436,56 @@ func TestAssertAggregationsEqual(t *testing.T) {
assert.Len(t, r, 1, "should return with unknown aggregation only")
r = equalAggregations(sumInt64A, sumInt64B, config{})
assert.Greaterf(t, len(r), 0, "%v == %v", sumInt64A, sumInt64B)
assert.Greaterf(t, len(r), 0, "sums should not be equal: %v == %v", sumInt64A, sumInt64B)
r = equalAggregations(sumInt64A, sumInt64C, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", sumInt64A, sumInt64C)
assert.Len(t, r, 0, "sums should be equal: %v", r)
r = equalAggregations(sumFloat64A, sumFloat64B, config{})
assert.Greaterf(t, len(r), 0, "%v == %v", sumFloat64A, sumFloat64B)
assert.Greaterf(t, len(r), 0, "sums should not be equal: %v == %v", sumFloat64A, sumFloat64B)
r = equalAggregations(sumFloat64A, sumFloat64C, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", sumFloat64A, sumFloat64C)
assert.Len(t, r, 0, "sums should be equal: %v", r)
r = equalAggregations(gaugeInt64A, gaugeInt64B, config{})
assert.Greaterf(t, len(r), 0, "%v == %v", gaugeInt64A, gaugeInt64B)
assert.Greaterf(t, len(r), 0, "gauges should not be equal: %v == %v", gaugeInt64A, gaugeInt64B)
r = equalAggregations(gaugeInt64A, gaugeInt64C, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", gaugeInt64A, gaugeInt64C)
assert.Len(t, r, 0, "gauges should be equal: %v", r)
r = equalAggregations(gaugeFloat64A, gaugeFloat64B, config{})
assert.Greaterf(t, len(r), 0, "%v == %v", gaugeFloat64A, gaugeFloat64B)
assert.Greaterf(t, len(r), 0, "gauges should not be equal: %v == %v", gaugeFloat64A, gaugeFloat64B)
r = equalAggregations(gaugeFloat64A, gaugeFloat64C, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", gaugeFloat64A, gaugeFloat64C)
assert.Len(t, r, 0, "gauges should be equal: %v", r)
r = equalAggregations(histogramA, histogramB, config{})
assert.Greaterf(t, len(r), 0, "%v == %v", histogramA, histogramB)
r = equalAggregations(histogramInt64A, histogramInt64B, config{})
assert.Greaterf(t, len(r), 0, "histograms should not be equal: %v == %v", histogramInt64A, histogramInt64B)
r = equalAggregations(histogramA, histogramC, config{ignoreTimestamp: true})
assert.Equalf(t, len(r), 0, "%v == %v", histogramA, histogramC)
r = equalAggregations(histogramInt64A, histogramInt64C, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "histograms should be equal: %v", r)
r = equalAggregations(histogramFloat64A, histogramFloat64B, config{})
assert.Greaterf(t, len(r), 0, "histograms should not be equal: %v == %v", histogramFloat64A, histogramFloat64B)
r = equalAggregations(histogramFloat64A, histogramFloat64C, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "histograms should be equal: %v", r)
}
func TestAssertAttributes(t *testing.T) {
AssertHasAttributes(t, minA, attribute.Bool("A", true)) // No-op, always pass.
AssertHasAttributes(t, exemplarInt64A, attribute.Bool("filter A", true))
AssertHasAttributes(t, exemplarFloat64A, attribute.Bool("filter A", true))
AssertHasAttributes(t, dataPointInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, dataPointFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, gaugeInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, gaugeFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, sumInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, sumFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, histogramDataPointA, attribute.Bool("A", true))
AssertHasAttributes(t, histogramA, attribute.Bool("A", true))
AssertHasAttributes(t, histogramDataPointInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, histogramDataPointFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, histogramInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, histogramFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, metricsA, attribute.Bool("A", true))
AssertHasAttributes(t, scopeMetricsA, attribute.Bool("A", true))
AssertHasAttributes(t, resourceMetricsA, attribute.Bool("A", true))
@@ -343,8 +498,10 @@ func TestAssertAttributes(t *testing.T) {
assert.Equal(t, len(r), 0, "sumInt64A has A=True")
r = hasAttributesAggregation(sumFloat64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "sumFloat64A has A=True")
r = hasAttributesAggregation(histogramA, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "histogramA has A=True")
r = hasAttributesAggregation(histogramInt64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "histogramInt64A has A=True")
r = hasAttributesAggregation(histogramFloat64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "histogramFloat64A has A=True")
r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have A=False")
@@ -354,8 +511,10 @@ func TestAssertAttributes(t *testing.T) {
assert.Greater(t, len(r), 0, "sumInt64A does not have A=False")
r = hasAttributesAggregation(sumFloat64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "sumFloat64A does not have A=False")
r = hasAttributesAggregation(histogramA, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "histogramA does not have A=False")
r = hasAttributesAggregation(histogramInt64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "histogramInt64A does not have A=False")
r = hasAttributesAggregation(histogramFloat64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "histogramFloat64A does not have A=False")
r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have Attribute B")
@@ -365,22 +524,26 @@ func TestAssertAttributes(t *testing.T) {
assert.Greater(t, len(r), 0, "sumInt64A does not have Attribute B")
r = hasAttributesAggregation(sumFloat64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "sumFloat64A does not have Attribute B")
r = hasAttributesAggregation(histogramA, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "histogramA does not have Attribute B")
r = hasAttributesAggregation(histogramInt64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "histogramIntA does not have Attribute B")
r = hasAttributesAggregation(histogramFloat64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "histogramFloatA does not have Attribute B")
}
func TestAssertAttributesFail(t *testing.T) {
fakeT := &testing.T{}
assert.False(t, AssertHasAttributes(fakeT, dataPointInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, dataPointFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, exemplarInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, exemplarFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, gaugeInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, gaugeFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, sumInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, sumFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, histogramDataPointA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, histogramDataPointA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, histogramA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, histogramA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, histogramDataPointInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, histogramDataPointFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, histogramInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, histogramFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, metricsA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, metricsA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, resourceMetricsA, attribute.Bool("A", false)))

View File

@@ -131,8 +131,14 @@ func equalAggregations(a, b metricdata.Aggregation, cfg config) (reasons []strin
reasons = append(reasons, "Sum[float64] not equal:")
reasons = append(reasons, r...)
}
case metricdata.Histogram:
r := equalHistograms(v, b.(metricdata.Histogram), cfg)
case metricdata.Histogram[int64]:
r := equalHistograms(v, b.(metricdata.Histogram[int64]), cfg)
if len(r) > 0 {
reasons = append(reasons, "Histogram not equal:")
reasons = append(reasons, r...)
}
case metricdata.Histogram[float64]:
r := equalHistograms(v, b.(metricdata.Histogram[float64]), cfg)
if len(r) > 0 {
reasons = append(reasons, "Histogram not equal:")
reasons = append(reasons, r...)
@@ -195,7 +201,7 @@ func equalSums[N int64 | float64](a, b metricdata.Sum[N], cfg config) (reasons [
//
// The DataPoints each Histogram contains are compared based on containing the
// same HistogramDataPoint, not the order they are stored in.
func equalHistograms(a, b metricdata.Histogram, cfg config) (reasons []string) {
func equalHistograms[N int64 | float64](a, b metricdata.Histogram[N], cfg config) (reasons []string) {
if a.Temporality != b.Temporality {
reasons = append(reasons, notEqualStr("Temporality", a.Temporality, b.Temporality))
}
@@ -203,7 +209,7 @@ func equalHistograms(a, b metricdata.Histogram, cfg config) (reasons []string) {
r := compareDiff(diffSlices(
a.DataPoints,
b.DataPoints,
func(a, b metricdata.HistogramDataPoint) bool {
func(a, b metricdata.HistogramDataPoint[N]) bool {
r := equalHistogramDataPoints(a, b, cfg)
return len(r) == 0
},
@@ -237,12 +243,26 @@ func equalDataPoints[N int64 | float64](a, b metricdata.DataPoint[N], cfg config
if a.Value != b.Value {
reasons = append(reasons, notEqualStr("Value", a.Value, b.Value))
}
if !cfg.ignoreExemplars {
r := compareDiff(diffSlices(
a.Exemplars,
b.Exemplars,
func(a, b metricdata.Exemplar[N]) bool {
r := equalExemplars(a, b, cfg)
return len(r) == 0
},
))
if r != "" {
reasons = append(reasons, fmt.Sprintf("Exemplars not equal:\n%s", r))
}
}
return reasons
}
// equalHistogramDataPoints returns reasons HistogramDataPoints are not equal.
// If they are equal, the returned reasons will be empty.
func equalHistogramDataPoints(a, b metricdata.HistogramDataPoint, cfg config) (reasons []string) { // nolint: revive // Intentional internal control flag
func equalHistogramDataPoints[N int64 | float64](a, b metricdata.HistogramDataPoint[N], cfg config) (reasons []string) { // nolint: revive // Intentional internal control flag
if !a.Attributes.Equals(&b.Attributes) {
reasons = append(reasons, notEqualStr(
"Attributes",
@@ -276,6 +296,19 @@ func equalHistogramDataPoints(a, b metricdata.HistogramDataPoint, cfg config) (r
if a.Sum != b.Sum {
reasons = append(reasons, notEqualStr("Sum", a.Sum, b.Sum))
}
if !cfg.ignoreExemplars {
r := compareDiff(diffSlices(
a.Exemplars,
b.Exemplars,
func(a, b metricdata.Exemplar[N]) bool {
r := equalExemplars(a, b, cfg)
return len(r) == 0
},
))
if r != "" {
reasons = append(reasons, fmt.Sprintf("Exemplars not equal:\n%s", r))
}
}
return reasons
}
@@ -312,6 +345,82 @@ func eqExtrema(a, b metricdata.Extrema) bool {
return aV == bV
}
func equalKeyValue(a, b []attribute.KeyValue) bool {
// Comparison of []attribute.KeyValue as a comparable requires Go >= 1.20.
// To support Go < 1.20 use this function instead.
if len(a) != len(b) {
return false
}
for i, v := range a {
if v.Key != b[i].Key {
return false
}
if v.Value.Type() != b[i].Value.Type() {
return false
}
switch v.Value.Type() {
case attribute.BOOL:
if v.Value.AsBool() != b[i].Value.AsBool() {
return false
}
case attribute.INT64:
if v.Value.AsInt64() != b[i].Value.AsInt64() {
return false
}
case attribute.FLOAT64:
if v.Value.AsFloat64() != b[i].Value.AsFloat64() {
return false
}
case attribute.STRING:
if v.Value.AsString() != b[i].Value.AsString() {
return false
}
case attribute.BOOLSLICE:
if ok := equalSlices(v.Value.AsBoolSlice(), b[i].Value.AsBoolSlice()); !ok {
return false
}
case attribute.INT64SLICE:
if ok := equalSlices(v.Value.AsInt64Slice(), b[i].Value.AsInt64Slice()); !ok {
return false
}
case attribute.FLOAT64SLICE:
if ok := equalSlices(v.Value.AsFloat64Slice(), b[i].Value.AsFloat64Slice()); !ok {
return false
}
case attribute.STRINGSLICE:
if ok := equalSlices(v.Value.AsStringSlice(), b[i].Value.AsStringSlice()); !ok {
return false
}
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
panic(fmt.Sprintf("unknown attribute value type: %s", v.Value.Type()))
}
}
return true
}
func equalExemplars[N int64 | float64](a, b metricdata.Exemplar[N], cfg config) (reasons []string) {
if !equalKeyValue(a.FilteredAttributes, b.FilteredAttributes) {
reasons = append(reasons, notEqualStr("FilteredAttributes", a.FilteredAttributes, b.FilteredAttributes))
}
if !cfg.ignoreTimestamp {
if !a.Time.Equal(b.Time) {
reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano()))
}
}
if a.Value != b.Value {
reasons = append(reasons, notEqualStr("Value", a.Value, b.Value))
}
if !equalSlices(a.SpanID, b.SpanID) {
reasons = append(reasons, notEqualStr("SpanID", a.SpanID, b.SpanID))
}
if !equalSlices(a.TraceID, b.TraceID) {
reasons = append(reasons, notEqualStr("TraceID", a.TraceID, b.TraceID))
}
return reasons
}
func diffSlices[T any](a, b []T, equal func(T, T) bool) (extraA, extraB []T) {
visited := make([]bool, len(b))
for i := 0; i < len(a); i++ {
@@ -372,6 +481,21 @@ func missingAttrStr(name string) string {
return fmt.Sprintf("missing attribute %s", name)
}
func hasAttributesExemplars[T int64 | float64](exemplar metricdata.Exemplar[T], attrs ...attribute.KeyValue) (reasons []string) {
s := attribute.NewSet(exemplar.FilteredAttributes...)
for _, attr := range attrs {
val, ok := s.Value(attr.Key)
if !ok {
reasons = append(reasons, missingAttrStr(string(attr.Key)))
continue
}
if val != attr.Value {
reasons = append(reasons, notEqualStr(string(attr.Key), attr.Value.Emit(), val.Emit()))
}
}
return reasons
}
func hasAttributesDataPoints[T int64 | float64](dp metricdata.DataPoint[T], attrs ...attribute.KeyValue) (reasons []string) {
for _, attr := range attrs {
val, ok := dp.Attributes.Value(attr.Key)
@@ -408,7 +532,7 @@ func hasAttributesSum[T int64 | float64](sum metricdata.Sum[T], attrs ...attribu
return reasons
}
func hasAttributesHistogramDataPoints(dp metricdata.HistogramDataPoint, attrs ...attribute.KeyValue) (reasons []string) {
func hasAttributesHistogramDataPoints[T int64 | float64](dp metricdata.HistogramDataPoint[T], attrs ...attribute.KeyValue) (reasons []string) {
for _, attr := range attrs {
val, ok := dp.Attributes.Value(attr.Key)
if !ok {
@@ -422,7 +546,7 @@ func hasAttributesHistogramDataPoints(dp metricdata.HistogramDataPoint, attrs ..
return reasons
}
func hasAttributesHistogram(histogram metricdata.Histogram, attrs ...attribute.KeyValue) (reasons []string) {
func hasAttributesHistogram[T int64 | float64](histogram metricdata.Histogram[T], attrs ...attribute.KeyValue) (reasons []string) {
for n, dp := range histogram.DataPoints {
reas := hasAttributesHistogramDataPoints(dp, attrs...)
if len(reas) > 0 {
@@ -443,7 +567,9 @@ func hasAttributesAggregation(agg metricdata.Aggregation, attrs ...attribute.Key
reasons = hasAttributesSum(agg, attrs...)
case metricdata.Sum[float64]:
reasons = hasAttributesSum(agg, attrs...)
case metricdata.Histogram:
case metricdata.Histogram[int64]:
reasons = hasAttributesHistogram(agg, attrs...)
case metricdata.Histogram[float64]:
reasons = hasAttributesHistogram(agg, attrs...)
default:
reasons = []string{fmt.Sprintf("unknown aggregation %T", agg)}