diff --git a/schema/labels_test.go b/schema/labels_test.go index 91206d9702..56d812d2cc 100644 --- a/schema/labels_test.go +++ b/schema/labels_test.go @@ -26,20 +26,22 @@ import ( func TestMetadata(t *testing.T) { testMeta := Metadata{ - Name: "metric_total", - Type: model.MetricTypeCounter, - Unit: "seconds", - Temporality: "delta", + Name: "metric_total", + Type: model.MetricTypeCounter, + Unit: "seconds", + Temporality: "delta", + Monotonicity: "true", } for _, tcase := range []struct { - emptyName, emptyType, emptyUnit, emptyTemporality bool + emptyName, emptyType, emptyUnit, emptyTemporality, emptyMonotonicity bool }{ {}, {emptyName: true}, {emptyType: true}, {emptyUnit: true}, {emptyTemporality: true}, + {emptyMonotonicity: true}, {emptyName: true, emptyType: true, emptyUnit: true, emptyTemporality: true}, } { var ( @@ -69,6 +71,10 @@ func TestMetadata(t *testing.T) { lb.Add(metricTemporality, testMeta.Temporality) expectedMeta.Temporality = testMeta.Temporality } + if !tcase.emptyMonotonicity { + lb.Add(metricMonotonicity, testMeta.Monotonicity) + expectedMeta.Monotonicity = testMeta.Monotonicity + } lb.Sort() expectedLabels = lb.Labels() } @@ -86,6 +92,7 @@ func TestMetadata(t *testing.T) { require.Equal(t, tcase.emptyType, expectedMeta.IsTypeEmpty()) require.Equal(t, tcase.emptyUnit, expectedMeta.IsEmptyFor(metricUnit)) require.Equal(t, tcase.emptyTemporality, expectedMeta.IsEmptyFor(metricTemporality)) + require.Equal(t, tcase.emptyMonotonicity, expectedMeta.IsEmptyFor(metricMonotonicity)) } { // From Metadata to labels for various builders. @@ -117,12 +124,13 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) { }, { highPrioMeta: Metadata{ - Name: "metric_total", - Type: model.MetricTypeCounter, - Unit: "seconds", - Temporality: "delta", + Name: "metric_total", + Type: model.MetricTypeCounter, + Unit: "seconds", + Temporality: "delta", + Monotonicity: "true", }, - expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", metricTemporality, "delta", "foo", "bar"), + expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", metricTemporality, "delta", metricMonotonicity, "true", "foo", "bar"), }, { highPrioMeta: Metadata{ @@ -169,6 +177,7 @@ func TestIsMetadataLabel(t *testing.T) { {"__type__", true}, {"__unit__", true}, {"__temporality__", true}, + {"__monotonicity__", true}, {"foo", false}, {"bar", false}, {"__other__", false}, diff --git a/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw_test.go b/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw_test.go index 6f0f6d001a..21b551d4ae 100644 --- a/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw_test.go +++ b/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw_test.go @@ -404,8 +404,8 @@ func TestTemporality(t *testing.T) { createOtelSum("test_metric_2", pmetric.AggregationTemporalityDelta, ts), }, expectedSeries: []prompb.TimeSeries{ - createPromFloatSeriesWithTemporality("test_metric_1", "delta", ts), - createPromFloatSeriesWithTemporality("test_metric_2", "delta", ts), + createPromFloatSeriesWithTemporalityAndMonotonicity("test_metric_1", "delta", "false", ts), + createPromFloatSeriesWithTemporalityAndMonotonicity("test_metric_2", "delta", "false", ts), }, }, { @@ -416,7 +416,7 @@ func TestTemporality(t *testing.T) { createOtelSum("test_metric_2", pmetric.AggregationTemporalityCumulative, ts), }, expectedSeries: []prompb.TimeSeries{ - createPromFloatSeriesWithTemporality("test_metric_1", "delta", ts), + createPromFloatSeriesWithTemporalityAndMonotonicity("test_metric_1", "delta", "false", ts), createPromFloatSeriesWithTemporality("test_metric_2", "cumulative", ts), }, }, @@ -563,6 +563,37 @@ func TestTemporality(t *testing.T) { expectedSeries: []prompb.TimeSeries{}, expectedError: `could not get aggregation temporality for test_empty as it has unsupported metric type Empty`, }, + { + name: "delta sum with monotonic and non-monotonic metrics when allowed", + allowDelta: true, + inputSeries: []pmetric.Metric{ + createOtelSumWithMonotonicity("test_monotonic_sum", pmetric.AggregationTemporalityDelta, true, ts), + createOtelSumWithMonotonicity("test_non_monotonic_sum", pmetric.AggregationTemporalityDelta, false, ts), + }, + expectedSeries: []prompb.TimeSeries{ + createPromFloatSeriesWithTemporalityAndMonotonicity("test_monotonic_sum", "delta", "true", ts), + createPromFloatSeriesWithTemporalityAndMonotonicity("test_non_monotonic_sum", "delta", "false", ts), + }, + }, + { + name: "cumulative sum does not have monotonicity label", + allowDelta: true, + inputSeries: []pmetric.Metric{ + createOtelSumWithMonotonicity("test_cumulative_sum", pmetric.AggregationTemporalityCumulative, true, ts), + }, + expectedSeries: []prompb.TimeSeries{ + createPromFloatSeriesWithTemporality("test_cumulative_sum", "cumulative", ts), + }, + }, + { + name: "delta sum does not have monotonicity label when AllowDeltaTemporality disabled", + allowDelta: false, + inputSeries: []pmetric.Metric{ + createOtelSumWithMonotonicity("test_delta_sum", pmetric.AggregationTemporalityDelta, true, ts), + }, + expectedSeries: []prompb.TimeSeries{}, + expectedError: `invalid temporality and type combination for metric "test_delta_sum"`, + }, } for _, tc := range tests { @@ -610,6 +641,20 @@ func createOtelSum(name string, temporality pmetric.AggregationTemporality, ts t return m } +func createOtelSumWithMonotonicity(name string, temporality pmetric.AggregationTemporality, isMonotonic bool, ts time.Time) pmetric.Metric { + metrics := pmetric.NewMetricSlice() + m := metrics.AppendEmpty() + m.SetName(name) + sum := m.SetEmptySum() + sum.SetAggregationTemporality(temporality) + sum.SetIsMonotonic(isMonotonic) + dp := sum.DataPoints().AppendEmpty() + dp.SetDoubleValue(5) + dp.SetTimestamp(pcommon.NewTimestampFromTime(ts)) + dp.Attributes().PutStr("test_label", "test_value") + return m +} + func createPromFloatSeries(name string, ts time.Time) prompb.TimeSeries { return prompb.TimeSeries{ Labels: []prompb.Label{ @@ -637,6 +682,21 @@ func createPromFloatSeriesWithTemporality(name, temporality string, ts time.Time } } +func createPromFloatSeriesWithTemporalityAndMonotonicity(name, temporality, monotonicity string, ts time.Time) prompb.TimeSeries { + return prompb.TimeSeries{ + Labels: []prompb.Label{ + {Name: "__monotonicity__", Value: monotonicity}, + {Name: "__name__", Value: name}, + {Name: "__temporality__", Value: temporality}, + {Name: "test_label", Value: "test_value"}, + }, + Samples: []prompb.Sample{{ + Value: 5, + Timestamp: ts.UnixMilli(), + }}, + } +} + func createOtelGauge(name string, ts time.Time) pmetric.Metric { metrics := pmetric.NewMetricSlice() m := metrics.AppendEmpty() diff --git a/storage/remote/otlptranslator/prometheusremotewrite/number_data_points_test.go b/storage/remote/otlptranslator/prometheusremotewrite/number_data_points_test.go index d00730005f..287e792404 100644 --- a/storage/remote/otlptranslator/prometheusremotewrite/number_data_points_test.go +++ b/storage/remote/otlptranslator/prometheusremotewrite/number_data_points_test.go @@ -353,6 +353,7 @@ func TestPrometheusConverter_addSumNumberDataPoints(t *testing.T) { prompb.MetricMetadata{MetricFamilyName: metric.Name()}, tt.scope, pmetric.AggregationTemporalityCumulative, + metric.Sum().IsMonotonic(), ) require.Equal(t, tt.want(), converter.unique) diff --git a/storage/remote/write_test.go b/storage/remote/write_test.go index de77ba1c98..49e9770720 100644 --- a/storage/remote/write_test.go +++ b/storage/remote/write_test.go @@ -462,6 +462,7 @@ func TestOTLPWriteHandler(t *testing.T) { labels.Label{Name: "__type__", Value: "gauge"}, labels.Label{Name: "__unit__", Value: "bytes"}, labels.Label{Name: "__temporality__", Value: "delta"}, + labels.Label{Name: "__monotonicity__", Value: "true"}, labels.Label{Name: "foo.bar", Value: "baz"}, labels.Label{Name: "instance", Value: "test-instance"}, labels.Label{Name: "job", Value: "test-service"}), @@ -578,6 +579,7 @@ func TestOTLPWriteHandler(t *testing.T) { labels.Label{Name: "__type__", Value: "gauge"}, labels.Label{Name: "__unit__", Value: "bytes"}, labels.Label{Name: "__temporality__", Value: "delta"}, + labels.Label{Name: "__monotonicity__", Value: "true"}, labels.Label{Name: "foo_bar", Value: "baz"}, labels.Label{Name: "instance", Value: "test-instance"}, labels.Label{Name: "job", Value: "test-service"}), @@ -667,6 +669,7 @@ func TestOTLPWriteHandler(t *testing.T) { labels.Label{Name: "__type__", Value: "gauge"}, labels.Label{Name: "__unit__", Value: "bytes"}, labels.Label{Name: "__temporality__", Value: "delta"}, + labels.Label{Name: "__monotonicity__", Value: "true"}, labels.Label{Name: "foo.bar", Value: "baz"}, labels.Label{Name: "instance", Value: "test-instance"}, labels.Label{Name: "job", Value: "test-service"}),