1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00

Exponential Histogram Datatypes (#4165)

* Adds the Exponential histogram data type.

* Changelog

* Updated comments

* Apply suggestions from code review

Co-authored-by: Robert Pająk <pellared@hotmail.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* Split Exponential Buckets into it's own type

---------

Co-authored-by: Robert Pająk <pellared@hotmail.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
Aaron Clawson
2023-06-14 09:36:13 -05:00
committed by GitHub
parent 844b107e98
commit b757c7083b
5 changed files with 407 additions and 1 deletions
+68
View File
@@ -137,6 +137,74 @@ type HistogramDataPoint[N int64 | float64] struct {
Exemplars []Exemplar[N] `json:",omitempty"`
}
// ExponentialHistogram represents the histogram of all measurements of values from an instrument.
type ExponentialHistogram[N int64 | float64] struct {
// DataPoints are the individual aggregated measurements with unique
// attributes.
DataPoints []ExponentialHistogramDataPoint[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 (ExponentialHistogram[N]) privateAggregation() {}
// ExponentialHistogramDataPoint is a single exponential histogram data point in a timeseries.
type ExponentialHistogramDataPoint[N int64 | float64] struct {
// Attributes is the set of key value pairs that uniquely identify the
// timeseries.
Attributes attribute.Set
// StartTime is when the timeseries was started.
StartTime time.Time
// Time is the time when the timeseries was recorded.
Time time.Time
// Count is the number of updates this histogram has been calculated with.
Count uint64
// Min is the minimum value recorded. (optional)
Min Extrema[N]
// Max is the maximum value recorded. (optional)
Max Extrema[N]
// Sum is the sum of the values recorded.
Sum N
// Scale describes the resolution of the histogram. Boundaries are
// located at powers of the base, where:
//
// base = 2 ^ (2 ^ -Scale)
Scale int32
// ZeroCount is the number of values whose absolute value
// is less than or equal to [ZeroThreshold].
// When ZeroThreshold is 0, this is the number of values that
// cannot be expressed using the standard exponential formula
// as well as values that have been rounded to zero.
// ZeroCount represents the special zero count bucket.
ZeroCount uint64
// PositiveBucket is range of positive value bucket counts.
PositiveBucket ExponentialBucket
// NegativeBucket is range of negative value bucket counts.
NegativeBucket ExponentialBucket
// ZeroThreshold is the width of the zero region. Where the zero region is
// defined as the closed interval [-ZeroThreshold, ZeroThreshold].
ZeroThreshold float64
// Exemplars is the sampled Exemplars collected during the timeseries.
Exemplars []Exemplar[N] `json:",omitempty"`
}
// ExponentialBucket are a set of bucket counts, encoded in a contiguous array
// of counts.
type ExponentialBucket struct {
// Offset is the bucket index of the first entry in the Counts slice.
Offset int32
// Counts is an slice where Counts[i] carries the count of the bucket at
// index (Offset+i). Counts[i] is the count of values greater than
// base^(Offset+i) and less than or equal to base^(Offset+i+1).
Counts []uint64
}
// Extrema is the minimum or maximum value of a dataset.
type Extrema[N int64 | float64] struct {
value N
@@ -42,7 +42,12 @@ type Datatypes interface {
metricdata.Sum[float64] |
metricdata.Sum[int64] |
metricdata.Exemplar[float64] |
metricdata.Exemplar[int64]
metricdata.Exemplar[int64] |
metricdata.ExponentialHistogram[float64] |
metricdata.ExponentialHistogram[int64] |
metricdata.ExponentialHistogramDataPoint[float64] |
metricdata.ExponentialHistogramDataPoint[int64] |
metricdata.ExponentialBucket
// Interface types are not allowed in union types, therefore the
// Aggregation and Value type from metricdata are not included here.
@@ -134,6 +139,16 @@ func AssertEqual[T Datatypes](t *testing.T, expected, actual T, opts ...Option)
r = equalSums(e, aIface.(metricdata.Sum[int64]), cfg)
case metricdata.Sum[float64]:
r = equalSums(e, aIface.(metricdata.Sum[float64]), cfg)
case metricdata.ExponentialHistogram[float64]:
r = equalExponentialHistograms(e, aIface.(metricdata.ExponentialHistogram[float64]), cfg)
case metricdata.ExponentialHistogram[int64]:
r = equalExponentialHistograms(e, aIface.(metricdata.ExponentialHistogram[int64]), cfg)
case metricdata.ExponentialHistogramDataPoint[float64]:
r = equalExponentialHistogramDataPoints(e, aIface.(metricdata.ExponentialHistogramDataPoint[float64]), cfg)
case metricdata.ExponentialHistogramDataPoint[int64]:
r = equalExponentialHistogramDataPoints(e, aIface.(metricdata.ExponentialHistogramDataPoint[int64]), cfg)
case metricdata.ExponentialBucket:
r = equalExponentialBuckets(e, aIface.(metricdata.ExponentialBucket), cfg)
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
@@ -198,6 +213,16 @@ func AssertHasAttributes[T Datatypes](t *testing.T, actual T, attrs ...attribute
reasons = hasAttributesScopeMetrics(e, attrs...)
case metricdata.ResourceMetrics:
reasons = hasAttributesResourceMetrics(e, attrs...)
case metricdata.ExponentialHistogram[int64]:
reasons = hasAttributesExponentialHistogram(e, attrs...)
case metricdata.ExponentialHistogram[float64]:
reasons = hasAttributesExponentialHistogram(e, attrs...)
case metricdata.ExponentialHistogramDataPoint[int64]:
reasons = hasAttributesExponentialHistogramDataPoints(e, attrs...)
case metricdata.ExponentialHistogramDataPoint[float64]:
reasons = hasAttributesExponentialHistogramDataPoints(e, attrs...)
case metricdata.ExponentialBucket:
// Nothing to check.
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
@@ -205,6 +205,103 @@ var (
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64C},
}
exponentialBucket2 = metricdata.ExponentialBucket{
Offset: 2,
Counts: []uint64{1, 1},
}
exponentialBucket3 = metricdata.ExponentialBucket{
Offset: 3,
Counts: []uint64{1, 1},
}
exponentialBucket4 = metricdata.ExponentialBucket{
Offset: 4,
Counts: []uint64{1, 1, 1},
}
exponentialBucket5 = metricdata.ExponentialBucket{
Offset: 5,
Counts: []uint64{1, 1, 1},
}
exponentialHistogramDataPointInt64A = metricdata.ExponentialHistogramDataPoint[int64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 5,
Min: minInt64A,
Sum: 2,
Scale: 1,
ZeroCount: 1,
PositiveBucket: exponentialBucket3,
NegativeBucket: exponentialBucket2,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64A},
}
exponentialHistogramDataPointFloat64A = metricdata.ExponentialHistogramDataPoint[float64]{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 5,
Min: minFloat64A,
Sum: 2,
Scale: 1,
ZeroCount: 1,
PositiveBucket: exponentialBucket3,
NegativeBucket: exponentialBucket2,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64A},
}
exponentialHistogramDataPointInt64B = metricdata.ExponentialHistogramDataPoint[int64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
Count: 6,
Min: minInt64B,
Max: maxInt64B,
Sum: 3,
Scale: 2,
ZeroCount: 3,
PositiveBucket: exponentialBucket4,
NegativeBucket: exponentialBucket5,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64B},
}
exponentialHistogramDataPointFloat64B = metricdata.ExponentialHistogramDataPoint[float64]{
Attributes: attrB,
StartTime: startB,
Time: endB,
Count: 6,
Min: minFloat64B,
Max: maxFloat64B,
Sum: 3,
Scale: 2,
ZeroCount: 3,
PositiveBucket: exponentialBucket4,
NegativeBucket: exponentialBucket5,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64B},
}
exponentialHistogramDataPointInt64C = metricdata.ExponentialHistogramDataPoint[int64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
Count: 5,
Min: minInt64C,
Sum: 2,
Scale: 1,
ZeroCount: 1,
PositiveBucket: exponentialBucket3,
NegativeBucket: exponentialBucket2,
Exemplars: []metricdata.Exemplar[int64]{exemplarInt64C},
}
exponentialHistogramDataPointFloat64C = metricdata.ExponentialHistogramDataPoint[float64]{
Attributes: attrA,
StartTime: startB,
Time: endB,
Count: 5,
Min: minFloat64A,
Sum: 2,
Scale: 1,
ZeroCount: 1,
PositiveBucket: exponentialBucket3,
NegativeBucket: exponentialBucket2,
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64C},
}
gaugeInt64A = metricdata.Gauge[int64]{
DataPoints: []metricdata.DataPoint[int64]{dataPointInt64A},
}
@@ -280,6 +377,31 @@ var (
DataPoints: []metricdata.HistogramDataPoint[float64]{histogramDataPointFloat64C},
}
exponentialHistogramInt64A = metricdata.ExponentialHistogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[int64]{exponentialHistogramDataPointInt64A},
}
exponentialHistogramFloat64A = metricdata.ExponentialHistogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{exponentialHistogramDataPointFloat64A},
}
exponentialHistogramInt64B = metricdata.ExponentialHistogram[int64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[int64]{exponentialHistogramDataPointInt64B},
}
exponentialHistogramFloat64B = metricdata.ExponentialHistogram[float64]{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{exponentialHistogramDataPointFloat64B},
}
exponentialHistogramInt64C = metricdata.ExponentialHistogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[int64]{exponentialHistogramDataPointInt64C},
}
exponentialHistogramFloat64C = metricdata.ExponentialHistogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{exponentialHistogramDataPointFloat64C},
}
metricsA = metricdata.Metrics{
Name: "A",
Description: "A desc",
@@ -378,6 +500,11 @@ func TestAssertEqual(t *testing.T) {
t.Run("ExtremaFloat64", testDatatype(minFloat64A, minFloat64B, equalExtrema[float64]))
t.Run("ExemplarInt64", testDatatype(exemplarInt64A, exemplarInt64B, equalExemplars[int64]))
t.Run("ExemplarFloat64", testDatatype(exemplarFloat64A, exemplarFloat64B, equalExemplars[float64]))
t.Run("ExponentialHistogramInt64", testDatatype(exponentialHistogramInt64A, exponentialHistogramInt64B, equalExponentialHistograms[int64]))
t.Run("ExponentialHistogramFloat64", testDatatype(exponentialHistogramFloat64A, exponentialHistogramFloat64B, equalExponentialHistograms[float64]))
t.Run("ExponentialHistogramDataPointInt64", testDatatype(exponentialHistogramDataPointInt64A, exponentialHistogramDataPointInt64B, equalExponentialHistogramDataPoints[int64]))
t.Run("ExponentialHistogramDataPointFloat64", testDatatype(exponentialHistogramDataPointFloat64A, exponentialHistogramDataPointFloat64B, equalExponentialHistogramDataPoints[float64]))
t.Run("ExponentialBuckets", testDatatype(exponentialBucket2, exponentialBucket3, equalExponentialBuckets))
}
func TestAssertEqualIgnoreTime(t *testing.T) {
@@ -398,6 +525,10 @@ func TestAssertEqualIgnoreTime(t *testing.T) {
t.Run("ExtremaFloat64", testDatatypeIgnoreTime(minFloat64A, minFloat64C, equalExtrema[float64]))
t.Run("ExemplarInt64", testDatatypeIgnoreTime(exemplarInt64A, exemplarInt64C, equalExemplars[int64]))
t.Run("ExemplarFloat64", testDatatypeIgnoreTime(exemplarFloat64A, exemplarFloat64C, equalExemplars[float64]))
t.Run("ExponentialHistogramInt64", testDatatypeIgnoreTime(exponentialHistogramInt64A, exponentialHistogramInt64C, equalExponentialHistograms[int64]))
t.Run("ExponentialHistogramFloat64", testDatatypeIgnoreTime(exponentialHistogramFloat64A, exponentialHistogramFloat64C, equalExponentialHistograms[float64]))
t.Run("ExponentialHistogramDataPointInt64", testDatatypeIgnoreTime(exponentialHistogramDataPointInt64A, exponentialHistogramDataPointInt64C, equalExponentialHistogramDataPoints[int64]))
t.Run("ExponentialHistogramDataPointFloat64", testDatatypeIgnoreTime(exponentialHistogramDataPointFloat64A, exponentialHistogramDataPointFloat64C, equalExponentialHistogramDataPoints[float64]))
}
func TestAssertEqualIgnoreExemplars(t *testing.T) {
@@ -416,6 +547,14 @@ func TestAssertEqualIgnoreExemplars(t *testing.T) {
dpFloat64 := dataPointFloat64A
dpFloat64.Exemplars = []metricdata.Exemplar[float64]{exemplarFloat64B}
t.Run("DataPointFloat64", testDatatypeIgnoreExemplars(dataPointFloat64A, dpFloat64, equalDataPoints[float64]))
ehdpInt64 := exponentialHistogramDataPointInt64A
ehdpInt64.Exemplars = []metricdata.Exemplar[int64]{exemplarInt64B}
t.Run("ExponentialHistogramDataPointInt64", testDatatypeIgnoreExemplars(exponentialHistogramDataPointInt64A, ehdpInt64, equalExponentialHistogramDataPoints[int64]))
ehdpFloat64 := exponentialHistogramDataPointFloat64A
ehdpFloat64.Exemplars = []metricdata.Exemplar[float64]{exemplarFloat64B}
t.Run("ExponentialHistogramDataPointFloat64", testDatatypeIgnoreExemplars(exponentialHistogramDataPointFloat64A, ehdpFloat64, equalExponentialHistogramDataPoints[float64]))
}
type unknownAggregation struct {
@@ -430,6 +569,8 @@ func TestAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, gaugeFloat64A, gaugeFloat64A)
AssertAggregationsEqual(t, histogramInt64A, histogramInt64A)
AssertAggregationsEqual(t, histogramFloat64A, histogramFloat64A)
AssertAggregationsEqual(t, exponentialHistogramInt64A, exponentialHistogramInt64A)
AssertAggregationsEqual(t, exponentialHistogramFloat64A, exponentialHistogramFloat64A)
r := equalAggregations(sumInt64A, nil, config{})
assert.Len(t, r, 1, "should return nil comparison mismatch only")
@@ -475,6 +616,18 @@ func TestAssertAggregationsEqual(t *testing.T) {
r = equalAggregations(histogramFloat64A, histogramFloat64C, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "histograms should be equal: %v", r)
r = equalAggregations(exponentialHistogramInt64A, exponentialHistogramInt64B, config{})
assert.Greaterf(t, len(r), 0, "exponential histograms should not be equal: %v == %v", exponentialHistogramInt64A, exponentialHistogramInt64B)
r = equalAggregations(exponentialHistogramInt64A, exponentialHistogramInt64C, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "exponential histograms should be equal: %v", r)
r = equalAggregations(exponentialHistogramFloat64A, exponentialHistogramFloat64B, config{})
assert.Greaterf(t, len(r), 0, "exponential histograms should not be equal: %v == %v", exponentialHistogramFloat64A, exponentialHistogramFloat64B)
r = equalAggregations(exponentialHistogramFloat64A, exponentialHistogramFloat64C, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "exponential histograms should be equal: %v", r)
}
func TestAssertAttributes(t *testing.T) {
@@ -494,6 +647,11 @@ func TestAssertAttributes(t *testing.T) {
AssertHasAttributes(t, metricsA, attribute.Bool("A", true))
AssertHasAttributes(t, scopeMetricsA, attribute.Bool("A", true))
AssertHasAttributes(t, resourceMetricsA, attribute.Bool("A", true))
AssertHasAttributes(t, exponentialHistogramDataPointInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, exponentialHistogramDataPointFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, exponentialHistogramInt64A, attribute.Bool("A", true))
AssertHasAttributes(t, exponentialHistogramFloat64A, attribute.Bool("A", true))
AssertHasAttributes(t, exponentialBucket2, attribute.Bool("A", true)) // No-op, always pass.
r := hasAttributesAggregation(gaugeInt64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "gaugeInt64A has A=True")
@@ -507,6 +665,10 @@ func TestAssertAttributes(t *testing.T) {
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(exponentialHistogramInt64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "exponentialHistogramInt64A has A=True")
r = hasAttributesAggregation(exponentialHistogramFloat64A, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "exponentialHistogramFloat64A has A=True")
r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have A=False")
@@ -520,6 +682,10 @@ func TestAssertAttributes(t *testing.T) {
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(exponentialHistogramInt64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "exponentialHistogramInt64A does not have A=False")
r = hasAttributesAggregation(exponentialHistogramFloat64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "exponentialHistogramFloat64A does not have A=False")
r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have Attribute B")
@@ -533,6 +699,10 @@ func TestAssertAttributes(t *testing.T) {
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")
r = hasAttributesAggregation(exponentialHistogramInt64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "exponentialHistogramIntA does not have Attribute B")
r = hasAttributesAggregation(exponentialHistogramFloat64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "exponentialHistogramFloatA does not have Attribute B")
}
func TestAssertAttributesFail(t *testing.T) {
@@ -553,6 +723,10 @@ func TestAssertAttributesFail(t *testing.T) {
assert.False(t, AssertHasAttributes(fakeT, metricsA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, resourceMetricsA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, resourceMetricsA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, exponentialHistogramDataPointInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, exponentialHistogramDataPointFloat64A, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, exponentialHistogramInt64A, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, exponentialHistogramFloat64A, attribute.Bool("B", true)))
sum := metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
@@ -143,6 +143,18 @@ func equalAggregations(a, b metricdata.Aggregation, cfg config) (reasons []strin
reasons = append(reasons, "Histogram not equal:")
reasons = append(reasons, r...)
}
case metricdata.ExponentialHistogram[int64]:
r := equalExponentialHistograms(v, b.(metricdata.ExponentialHistogram[int64]), cfg)
if len(r) > 0 {
reasons = append(reasons, "ExponentialHistogram not equal:")
reasons = append(reasons, r...)
}
case metricdata.ExponentialHistogram[float64]:
r := equalExponentialHistograms(v, b.(metricdata.ExponentialHistogram[float64]), cfg)
if len(r) > 0 {
reasons = append(reasons, "ExponentialHistogram not equal:")
reasons = append(reasons, r...)
}
default:
reasons = append(reasons, fmt.Sprintf("Aggregation of unknown types %T", a))
}
@@ -312,6 +324,103 @@ func equalHistogramDataPoints[N int64 | float64](a, b metricdata.HistogramDataPo
return reasons
}
// equalExponentialHistograms returns reasons exponential Histograms are not equal. If they are
// equal, the returned reasons will be empty.
//
// The DataPoints each Histogram contains are compared based on containing the
// same HistogramDataPoint, not the order they are stored in.
func equalExponentialHistograms[N int64 | float64](a, b metricdata.ExponentialHistogram[N], cfg config) (reasons []string) {
if a.Temporality != b.Temporality {
reasons = append(reasons, notEqualStr("Temporality", a.Temporality, b.Temporality))
}
r := compareDiff(diffSlices(
a.DataPoints,
b.DataPoints,
func(a, b metricdata.ExponentialHistogramDataPoint[N]) bool {
r := equalExponentialHistogramDataPoints(a, b, cfg)
return len(r) == 0
},
))
if r != "" {
reasons = append(reasons, fmt.Sprintf("Histogram DataPoints not equal:\n%s", r))
}
return reasons
}
// equalExponentialHistogramDataPoints returns reasons HistogramDataPoints are not equal.
// If they are equal, the returned reasons will be empty.
func equalExponentialHistogramDataPoints[N int64 | float64](a, b metricdata.ExponentialHistogramDataPoint[N], cfg config) (reasons []string) { // nolint: revive // Intentional internal control flag
if !a.Attributes.Equals(&b.Attributes) {
reasons = append(reasons, notEqualStr(
"Attributes",
a.Attributes.Encoded(attribute.DefaultEncoder()),
b.Attributes.Encoded(attribute.DefaultEncoder()),
))
}
if !cfg.ignoreTimestamp {
if !a.StartTime.Equal(b.StartTime) {
reasons = append(reasons, notEqualStr("StartTime", a.StartTime.UnixNano(), b.StartTime.UnixNano()))
}
if !a.Time.Equal(b.Time) {
reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano()))
}
}
if a.Count != b.Count {
reasons = append(reasons, notEqualStr("Count", a.Count, b.Count))
}
if !eqExtrema(a.Min, b.Min) {
reasons = append(reasons, notEqualStr("Min", a.Min, b.Min))
}
if !eqExtrema(a.Max, b.Max) {
reasons = append(reasons, notEqualStr("Max", a.Max, b.Max))
}
if a.Sum != b.Sum {
reasons = append(reasons, notEqualStr("Sum", a.Sum, b.Sum))
}
if a.Scale != b.Scale {
reasons = append(reasons, notEqualStr("Scale", a.Scale, b.Scale))
}
if a.ZeroCount != b.ZeroCount {
reasons = append(reasons, notEqualStr("ZeroCount", a.ZeroCount, b.ZeroCount))
}
r := equalExponentialBuckets(a.PositiveBucket, b.PositiveBucket, cfg)
if len(r) > 0 {
reasons = append(reasons, r...)
}
r = equalExponentialBuckets(a.NegativeBucket, b.NegativeBucket, cfg)
if len(r) > 0 {
reasons = append(reasons, r...)
}
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
}
func equalExponentialBuckets(a, b metricdata.ExponentialBucket, _ config) (reasons []string) {
if a.Offset != b.Offset {
reasons = append(reasons, notEqualStr("Offset", a.Offset, b.Offset))
}
if !equalSlices(a.Counts, b.Counts) {
reasons = append(reasons, notEqualStr("Counts", a.Counts, b.Counts))
}
return reasons
}
func notEqualStr(prefix string, expected, actual interface{}) string {
return fmt.Sprintf("%s not equal:\nexpected: %v\nactual: %v", prefix, expected, actual)
}
@@ -557,6 +666,31 @@ func hasAttributesHistogram[T int64 | float64](histogram metricdata.Histogram[T]
return reasons
}
func hasAttributesExponentialHistogramDataPoints[T int64 | float64](dp metricdata.ExponentialHistogramDataPoint[T], attrs ...attribute.KeyValue) (reasons []string) {
for _, attr := range attrs {
val, ok := dp.Attributes.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 hasAttributesExponentialHistogram[T int64 | float64](histogram metricdata.ExponentialHistogram[T], attrs ...attribute.KeyValue) (reasons []string) {
for n, dp := range histogram.DataPoints {
reas := hasAttributesExponentialHistogramDataPoints(dp, attrs...)
if len(reas) > 0 {
reasons = append(reasons, fmt.Sprintf("histogram datapoint %d attributes:\n", n))
reasons = append(reasons, reas...)
}
}
return reasons
}
func hasAttributesAggregation(agg metricdata.Aggregation, attrs ...attribute.KeyValue) (reasons []string) {
switch agg := agg.(type) {
case metricdata.Gauge[int64]:
@@ -571,6 +705,10 @@ func hasAttributesAggregation(agg metricdata.Aggregation, attrs ...attribute.Key
reasons = hasAttributesHistogram(agg, attrs...)
case metricdata.Histogram[float64]:
reasons = hasAttributesHistogram(agg, attrs...)
case metricdata.ExponentialHistogram[int64]:
reasons = hasAttributesExponentialHistogram(agg, attrs...)
case metricdata.ExponentialHistogram[float64]:
reasons = hasAttributesExponentialHistogram(agg, attrs...)
default:
reasons = []string{fmt.Sprintf("unknown aggregation %T", agg)}
}