Skip to content

Commit

Permalink
Merge branch 'main' into explicit_bucket_advisory
Browse files Browse the repository at this point in the history
  • Loading branch information
MadVikingGod committed Oct 25, 2023
2 parents c39a55b + cdd9353 commit 34366d5
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `go.opentelemetry.io/otel/metric.WithExplicitBucketBoundaries`, which allows defining default explicit bucket boundaries when creating histogram instruments. (#4603)
- Add `Version` function in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4660)
- Add `Version` function in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4660)
- Add Summary, SummaryDataPoint, and QuantileValue to `go.opentelemetry.io/sdk/metric/metricdata`. (#4622)

### Deprecated

Expand Down
51 changes: 51 additions & 0 deletions sdk/metric/metricdata/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,54 @@ type Exemplar[N int64 | float64] struct {
// be empty.
TraceID []byte `json:",omitempty"`
}

// Summary metric data are used to convey quantile summaries,
// a Prometheus (see: https://prometheus.io/docs/concepts/metric_types/#summary)
// data type.
//
// These data points cannot always be merged in a meaningful way. The Summary
// type is only used by bridges from other metrics libraries, and cannot be
// produced using OpenTelemetry instrumentation.
type Summary struct {
// DataPoints are the individual aggregated measurements with unique
// attributes.
DataPoints []SummaryDataPoint
}

func (Summary) privateAggregation() {}

Check warning on line 257 in sdk/metric/metricdata/data.go

View check run for this annotation

Codecov / codecov/patch

sdk/metric/metricdata/data.go#L257

Added line #L257 was not covered by tests

// SummaryDataPoint is a single data point in a timeseries that describes the
// time-varying values of a Summary metric.
type SummaryDataPoint 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 summary has been calculated with.
Count uint64

// Sum is the sum of the values recorded.
Sum float64

// (Optional) list of values at different quantiles of the distribution calculated
// from the current snapshot. The quantiles must be strictly increasing.
QuantileValues []QuantileValue
}

// QuantileValue is the value at a given quantile of a summary.
type QuantileValue struct {
// Quantile is the quantile of this value.
//
// Must be in the interval [0.0, 1.0].
Quantile float64

// Value is the value at the given quantile of a summary.
//
// Quantile values must NOT be negative.
Value float64
}
17 changes: 16 additions & 1 deletion sdk/metric/metricdata/metricdatatest/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ type Datatypes interface {
metricdata.ExponentialHistogram[int64] |
metricdata.ExponentialHistogramDataPoint[float64] |
metricdata.ExponentialHistogramDataPoint[int64] |
metricdata.ExponentialBucket
metricdata.ExponentialBucket |
metricdata.Summary |
metricdata.SummaryDataPoint |
metricdata.QuantileValue

// Interface types are not allowed in union types, therefore the
// Aggregation and Value type from metricdata are not included here.
Expand Down Expand Up @@ -177,6 +180,12 @@ func AssertEqual[T Datatypes](t TestingT, expected, actual T, opts ...Option) bo
r = equalExponentialHistogramDataPoints(e, aIface.(metricdata.ExponentialHistogramDataPoint[int64]), cfg)
case metricdata.ExponentialBucket:
r = equalExponentialBuckets(e, aIface.(metricdata.ExponentialBucket), cfg)
case metricdata.Summary:
r = equalSummary(e, aIface.(metricdata.Summary), cfg)
case metricdata.SummaryDataPoint:
r = equalSummaryDataPoint(e, aIface.(metricdata.SummaryDataPoint), cfg)
case metricdata.QuantileValue:
r = equalQuantileValue(e, aIface.(metricdata.QuantileValue), cfg)
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
Expand Down Expand Up @@ -251,6 +260,12 @@ func AssertHasAttributes[T Datatypes](t TestingT, actual T, attrs ...attribute.K
reasons = hasAttributesExponentialHistogramDataPoints(e, attrs...)
case metricdata.ExponentialBucket:
// Nothing to check.
case metricdata.Summary:
reasons = hasAttributesSummary(e, attrs...)
case metricdata.SummaryDataPoint:
reasons = hasAttributesSummaryDataPoint(e, attrs...)
case metricdata.QuantileValue:
// Nothing to check.
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
Expand Down
85 changes: 85 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,45 @@ var (
Exemplars: []metricdata.Exemplar[float64]{exemplarFloat64A},
}

quantileValueA = metricdata.QuantileValue{
Quantile: 0.0,
Value: 0.1,
}
quantileValueB = metricdata.QuantileValue{
Quantile: 0.1,
Value: 0.2,
}
summaryDataPointA = metricdata.SummaryDataPoint{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 2,
Sum: 3,
QuantileValues: []metricdata.QuantileValue{quantileValueA},
}
summaryDataPointB = metricdata.SummaryDataPoint{
Attributes: attrB,
StartTime: startB,
Time: endB,
Count: 3,
QuantileValues: []metricdata.QuantileValue{quantileValueB},
}
summaryDataPointC = metricdata.SummaryDataPoint{
Attributes: attrA,
StartTime: startB,
Time: endB,
Count: 2,
Sum: 3,
QuantileValues: []metricdata.QuantileValue{quantileValueA},
}
summaryDataPointD = metricdata.SummaryDataPoint{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 3,
QuantileValues: []metricdata.QuantileValue{quantileValueB},
}

exponentialBucket2 = metricdata.ExponentialBucket{
Offset: 2,
Counts: []uint64{1, 1},
Expand Down Expand Up @@ -514,6 +553,22 @@ var (
DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{exponentialHistogramDataPointFloat64D},
}

summaryA = metricdata.Summary{
DataPoints: []metricdata.SummaryDataPoint{summaryDataPointA},
}

summaryB = metricdata.Summary{
DataPoints: []metricdata.SummaryDataPoint{summaryDataPointB},
}

summaryC = metricdata.Summary{
DataPoints: []metricdata.SummaryDataPoint{summaryDataPointC},
}

summaryD = metricdata.Summary{
DataPoints: []metricdata.SummaryDataPoint{summaryDataPointD},
}

metricsA = metricdata.Metrics{
Name: "A",
Description: "A desc",
Expand Down Expand Up @@ -646,6 +701,9 @@ func TestAssertEqual(t *testing.T) {
t.Run("ExponentialHistogramDataPointInt64", testDatatype(exponentialHistogramDataPointInt64A, exponentialHistogramDataPointInt64B, equalExponentialHistogramDataPoints[int64]))
t.Run("ExponentialHistogramDataPointFloat64", testDatatype(exponentialHistogramDataPointFloat64A, exponentialHistogramDataPointFloat64B, equalExponentialHistogramDataPoints[float64]))
t.Run("ExponentialBuckets", testDatatype(exponentialBucket2, exponentialBucket3, equalExponentialBuckets))
t.Run("Summary", testDatatype(summaryA, summaryB, equalSummary))
t.Run("SummaryDataPoint", testDatatype(summaryDataPointA, summaryDataPointB, equalSummaryDataPoint))
t.Run("QuantileValues", testDatatype(quantileValueA, quantileValueB, equalQuantileValue))
}

func TestAssertEqualIgnoreTime(t *testing.T) {
Expand All @@ -670,6 +728,8 @@ func TestAssertEqualIgnoreTime(t *testing.T) {
t.Run("ExponentialHistogramFloat64", testDatatypeIgnoreTime(exponentialHistogramFloat64A, exponentialHistogramFloat64C, equalExponentialHistograms[float64]))
t.Run("ExponentialHistogramDataPointInt64", testDatatypeIgnoreTime(exponentialHistogramDataPointInt64A, exponentialHistogramDataPointInt64C, equalExponentialHistogramDataPoints[int64]))
t.Run("ExponentialHistogramDataPointFloat64", testDatatypeIgnoreTime(exponentialHistogramDataPointFloat64A, exponentialHistogramDataPointFloat64C, equalExponentialHistogramDataPoints[float64]))
t.Run("Summary", testDatatypeIgnoreTime(summaryA, summaryC, equalSummary))
t.Run("SummaryDataPoint", testDatatypeIgnoreTime(summaryDataPointA, summaryDataPointC, equalSummaryDataPoint))
}

func TestAssertEqualIgnoreExemplars(t *testing.T) {
Expand Down Expand Up @@ -718,6 +778,8 @@ func TestAssertEqualIgnoreValue(t *testing.T) {
t.Run("ExponentialHistogramFloat64", testDatatypeIgnoreValue(exponentialHistogramFloat64A, exponentialHistogramFloat64D, equalExponentialHistograms[float64]))
t.Run("ExponentialHistogramDataPointInt64", testDatatypeIgnoreValue(exponentialHistogramDataPointInt64A, exponentialHistogramDataPointInt64D, equalExponentialHistogramDataPoints[int64]))
t.Run("ExponentialHistogramDataPointFloat64", testDatatypeIgnoreValue(exponentialHistogramDataPointFloat64A, exponentialHistogramDataPointFloat64D, equalExponentialHistogramDataPoints[float64]))
t.Run("Summary", testDatatypeIgnoreValue(summaryA, summaryD, equalSummary))
t.Run("SummaryDataPoint", testDatatypeIgnoreValue(summaryDataPointA, summaryDataPointD, equalSummaryDataPoint))
}

type unknownAggregation struct {
Expand All @@ -734,6 +796,7 @@ func TestAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, histogramFloat64A, histogramFloat64A)
AssertAggregationsEqual(t, exponentialHistogramInt64A, exponentialHistogramInt64A)
AssertAggregationsEqual(t, exponentialHistogramFloat64A, exponentialHistogramFloat64A)
AssertAggregationsEqual(t, summaryA, summaryA)

r := equalAggregations(sumInt64A, nil, config{})
assert.Len(t, r, 1, "should return nil comparison mismatch only")
Expand Down Expand Up @@ -815,6 +878,15 @@ func TestAssertAggregationsEqual(t *testing.T) {

r = equalAggregations(exponentialHistogramFloat64A, exponentialHistogramFloat64D, config{ignoreValue: true})
assert.Len(t, r, 0, "value should be ignored: %v == %v", exponentialHistogramFloat64A, exponentialHistogramFloat64D)

r = equalAggregations(summaryA, summaryB, config{})
assert.Greaterf(t, len(r), 0, "summaries should not be equal: %v == %v", summaryA, summaryB)

r = equalAggregations(summaryA, summaryC, config{ignoreTimestamp: true})
assert.Len(t, r, 0, "summaries should be equal: %v", r)

r = equalAggregations(summaryA, summaryD, config{ignoreValue: true})
assert.Len(t, r, 0, "value should be ignored: %v == %v", summaryA, summaryD)
}

func TestAssertAttributes(t *testing.T) {
Expand All @@ -839,6 +911,9 @@ func TestAssertAttributes(t *testing.T) {
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.
AssertHasAttributes(t, summaryDataPointA, attribute.Bool("A", true))
AssertHasAttributes(t, summaryA, attribute.Bool("A", true))
AssertHasAttributes(t, quantileValueA, 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")
Expand All @@ -856,6 +931,8 @@ func TestAssertAttributes(t *testing.T) {
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(summaryA, attribute.Bool("A", true))
assert.Equal(t, len(r), 0, "summaryA has A=True")

r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have A=False")
Expand All @@ -873,6 +950,8 @@ func TestAssertAttributes(t *testing.T) {
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(summaryA, attribute.Bool("A", false))
assert.Greater(t, len(r), 0, "summaryA does not have A=False")

r = hasAttributesAggregation(gaugeInt64A, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "gaugeInt64A does not have Attribute B")
Expand All @@ -890,6 +969,8 @@ func TestAssertAttributes(t *testing.T) {
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")
r = hasAttributesAggregation(summaryA, attribute.Bool("B", true))
assert.Greater(t, len(r), 0, "summaryA does not have Attribute B")
}

func TestAssertAttributesFail(t *testing.T) {
Expand All @@ -914,6 +995,10 @@ func TestAssertAttributesFail(t *testing.T) {
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)))
assert.False(t, AssertHasAttributes(fakeT, summaryDataPointA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, summaryDataPointA, attribute.Bool("B", true)))
assert.False(t, AssertHasAttributes(fakeT, summaryA, attribute.Bool("A", false)))
assert.False(t, AssertHasAttributes(fakeT, summaryA, attribute.Bool("B", true)))

sum := metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
Expand Down
96 changes: 96 additions & 0 deletions sdk/metric/metricdata/metricdatatest/comparisons.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ func equalAggregations(a, b metricdata.Aggregation, cfg config) (reasons []strin
reasons = append(reasons, "ExponentialHistogram not equal:")
reasons = append(reasons, r...)
}
case metricdata.Summary:
r := equalSummary(v, b.(metricdata.Summary), cfg)
if len(r) > 0 {
reasons = append(reasons, "Summary not equal:")
reasons = append(reasons, r...)
}
default:
reasons = append(reasons, fmt.Sprintf("Aggregation of unknown types %T", a))
}
Expand Down Expand Up @@ -426,6 +432,69 @@ func equalExponentialBuckets(a, b metricdata.ExponentialBucket, _ config) (reaso
return reasons
}

func equalSummary(a, b metricdata.Summary, cfg config) (reasons []string) {
r := compareDiff(diffSlices(
a.DataPoints,
b.DataPoints,
func(a, b metricdata.SummaryDataPoint) bool {
r := equalSummaryDataPoint(a, b, cfg)
return len(r) == 0
},
))
if r != "" {
reasons = append(reasons, fmt.Sprintf("Summary DataPoints not equal:\n%s", r))
}
return reasons
}

func equalSummaryDataPoint(a, b metricdata.SummaryDataPoint, cfg config) (reasons []string) {
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 !cfg.ignoreValue {
if a.Count != b.Count {
reasons = append(reasons, notEqualStr("Count", a.Count, b.Count))
}
if a.Sum != b.Sum {
reasons = append(reasons, notEqualStr("Sum", a.Sum, b.Sum))
}
r := compareDiff(diffSlices(
a.QuantileValues,
a.QuantileValues,
func(a, b metricdata.QuantileValue) bool {
r := equalQuantileValue(a, b, cfg)
return len(r) == 0
},
))
if r != "" {
reasons = append(reasons, r)
}

Check warning on line 483 in sdk/metric/metricdata/metricdatatest/comparisons.go

View check run for this annotation

Codecov / codecov/patch

sdk/metric/metricdata/metricdatatest/comparisons.go#L482-L483

Added lines #L482 - L483 were not covered by tests
}
return reasons
}

func equalQuantileValue(a, b metricdata.QuantileValue, _ config) (reasons []string) {
if a.Quantile != b.Quantile {
reasons = append(reasons, notEqualStr("Quantile", a.Quantile, b.Quantile))
}
if a.Value != b.Value {
reasons = append(reasons, notEqualStr("Value", a.Value, b.Value))
}
return reasons
}

func notEqualStr(prefix string, expected, actual interface{}) string {
return fmt.Sprintf("%s not equal:\nexpected: %v\nactual: %v", prefix, expected, actual)
}
Expand Down Expand Up @@ -716,6 +785,8 @@ func hasAttributesAggregation(agg metricdata.Aggregation, attrs ...attribute.Key
reasons = hasAttributesExponentialHistogram(agg, attrs...)
case metricdata.ExponentialHistogram[float64]:
reasons = hasAttributesExponentialHistogram(agg, attrs...)
case metricdata.Summary:
reasons = hasAttributesSummary(agg, attrs...)
default:
reasons = []string{fmt.Sprintf("unknown aggregation %T", agg)}
}
Expand Down Expand Up @@ -752,3 +823,28 @@ func hasAttributesResourceMetrics(rm metricdata.ResourceMetrics, attrs ...attrib
}
return reasons
}

func hasAttributesSummary(summary metricdata.Summary, attrs ...attribute.KeyValue) (reasons []string) {
for n, dp := range summary.DataPoints {
reas := hasAttributesSummaryDataPoint(dp, attrs...)
if len(reas) > 0 {
reasons = append(reasons, fmt.Sprintf("summary datapoint %d attributes:\n", n))
reasons = append(reasons, reas...)
}
}
return reasons
}

func hasAttributesSummaryDataPoint(dp metricdata.SummaryDataPoint, 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
}

0 comments on commit 34366d5

Please sign in to comment.