diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index 197920733..e46084693 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -9,8 +9,9 @@ otelJava.moduleName.set("io.opentelemetry.contrib.metrics.micrometer") dependencies { api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-sdk-metrics") + api("io.opentelemetry:opentelemetry-extension-incubator") - compileOnly("io.micrometer:micrometer-core:1.1.0") // do not auto-update this version + compileOnly("io.micrometer:micrometer-core:1.5.0") // do not auto-update this version compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") annotationProcessor("com.google.auto.service:auto-service") diff --git a/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java b/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java index 4b0ec0d8c..e44f39615 100644 --- a/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java +++ b/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java @@ -12,11 +12,19 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.DoubleUpDownCounter; +import io.opentelemetry.api.metrics.DoubleUpDownCounterBuilder; import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongGaugeBuilder; import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.LongHistogramBuilder; import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.metrics.ObservableDoubleCounter; @@ -25,6 +33,16 @@ import io.opentelemetry.api.metrics.ObservableLongCounter; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.api.metrics.ObservableLongUpDownCounter; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleCounterBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleGaugeBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleUpDownCounterBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongCounterBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongGaugeBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongHistogramBuilder; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongUpDownCounterBuilder; +import java.util.Arrays; +import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; @@ -32,6 +50,12 @@ public class PrometheusIntegrationTest { private static final AttributeKey KEY1 = AttributeKey.stringKey("key1"); + private static final AttributeKey KEY2 = AttributeKey.stringKey("key2"); + private static final String VALUE1 = "value1"; + private static final String VALUE2 = "value2"; + + private static final Attributes FIRST_ATTRIBUTES = Attributes.of(KEY1, VALUE1, KEY2, VALUE1); + private static final Attributes SECOND_ATTRIBUTES = Attributes.of(KEY1, VALUE1, KEY2, VALUE2); PrometheusMeterRegistry prometheusMeterRegistry; @@ -60,8 +84,8 @@ void longCounter() { .setUnit("units") .build(); - longCounter.add(1, Attributes.of(KEY1, "value1")); - longCounter.add(2, Attributes.of(KEY1, "value2")); + longCounter.add(1, FIRST_ATTRIBUTES); + longCounter.add(2, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); @@ -69,9 +93,32 @@ void longCounter() { .contains("# HELP longCounter_units_total LongCounter test") .contains("# TYPE longCounter_units_total counter") .contains( - "longCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longCounter_units_total{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longCounter_units_total{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longCounter_units_total{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + } + + @Test + void longCounterWithAttributesAdvice() { + LongCounterBuilder builder = + meter.counterBuilder("longCounter").setDescription("LongCounter test").setUnit("units"); + + ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + LongCounter longCounter = builder.build(); + + longCounter.add(1, FIRST_ATTRIBUTES); + longCounter.add(2, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + + assertThat(output) + .contains("# HELP longCounter_units_total LongCounter test") + .contains("# TYPE longCounter_units_total counter") + .contains( + "longCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 3.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } @Test @@ -84,8 +131,8 @@ void observableLongCounter() { .buildWithCallback( onlyOnce( measurement -> { - measurement.record(1, Attributes.of(KEY1, "value1")); - measurement.record(2, Attributes.of(KEY1, "value2")); + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(2, SECOND_ATTRIBUTES); }))) { String output = scrapeFor("longCounter"); @@ -95,9 +142,37 @@ void observableLongCounter() { .contains("# TYPE longCounter_units_total counter") .contains("# HELP longCounter_units_total LongCounter test") .contains( - "longCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longCounter_units_total{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longCounter_units_total{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + } + } + + @Test + void observableLongCounterWithAttributesAdvice() { + LongCounterBuilder builder = + meter.counterBuilder("longCounter").setDescription("LongCounter test").setUnit("units"); + + ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableLongCounter observableLongCounter = + builder.buildWithCallback( + onlyOnce( + measurement -> { + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(2, SECOND_ATTRIBUTES); + }))) { + + String output = scrapeFor("longCounter"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE longCounter_units_total counter") + .contains("# HELP longCounter_units_total LongCounter test") .contains( - "longCounter_units_total{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } @@ -111,8 +186,8 @@ void doubleCounter() { .setUnit("units") .build(); - doubleCounter.add(1.5, Attributes.of(KEY1, "value1")); - doubleCounter.add(2.5, Attributes.of(KEY1, "value2")); + doubleCounter.add(1.5, FIRST_ATTRIBUTES); + doubleCounter.add(2.5, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); @@ -120,9 +195,37 @@ void doubleCounter() { .contains("# HELP doubleCounter_units_total DoubleCounter test") .contains("# TYPE doubleCounter_units_total counter") .contains( - "doubleCounter_units_total{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") + "doubleCounter_units_total{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5"); + "doubleCounter_units_total{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + } + + @Test + void doubleCounterWithAttributesAdvice() { + + DoubleCounterBuilder builder = + meter + .counterBuilder("doubleCounter") + .ofDoubles() + .setDescription("DoubleCounter test") + .setUnit("units"); + + ((ExtendedDoubleCounterBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + DoubleCounter doubleCounter = builder.build(); + + doubleCounter.add(1.5, FIRST_ATTRIBUTES); + doubleCounter.add(2.5, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + + assertThat(output) + .contains("# HELP doubleCounter_units_total DoubleCounter test") + .contains("# TYPE doubleCounter_units_total counter") + .contains( + "doubleCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } @Test @@ -136,8 +239,8 @@ void observableDoubleCounter() { .buildWithCallback( onlyOnce( measurement -> { - measurement.record(1.5, Attributes.of(KEY1, "value1")); - measurement.record(2.5, Attributes.of(KEY1, "value2")); + measurement.record(1.5, FIRST_ATTRIBUTES); + measurement.record(2.5, SECOND_ATTRIBUTES); }))) { String output = scrapeFor("doubleCounter"); @@ -147,9 +250,41 @@ void observableDoubleCounter() { .contains("# TYPE doubleCounter_units_total counter") .contains("# HELP doubleCounter_units_total DoubleCounter test") .contains( - "doubleCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleCounter_units_total{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleCounter_units_total{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + "doubleCounter_units_total{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + } + } + + @Test + void observableDoubleCounterWithAttributesAdvice() { + DoubleCounterBuilder builder = + meter + .counterBuilder("doubleCounter") + .ofDoubles() + .setDescription("DoubleCounter test") + .setUnit("units"); + + ((ExtendedDoubleCounterBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableDoubleCounter observableDoubleCounter = + builder.buildWithCallback( + onlyOnce( + measurement -> { + measurement.record(1.0, FIRST_ATTRIBUTES); + measurement.record(2.0, SECOND_ATTRIBUTES); + }))) { + + String output = scrapeFor("doubleCounter"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE doubleCounter_units_total counter") + .contains("# HELP doubleCounter_units_total DoubleCounter test") + .contains( + "doubleCounter_units_total{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } @@ -162,8 +297,8 @@ void longUpDownCounter() { .setUnit("units") .build(); - longUpDownCounter.add(1, Attributes.of(KEY1, "value1")); - longUpDownCounter.add(2, Attributes.of(KEY1, "value2")); + longUpDownCounter.add(1, FIRST_ATTRIBUTES); + longUpDownCounter.add(2, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); @@ -171,20 +306,59 @@ void longUpDownCounter() { .contains("# HELP longUpDownCounter_units LongUpDownCounter test") .contains("# TYPE longUpDownCounter_units gauge") .contains( - "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); - longUpDownCounter.add(1, Attributes.of(KEY1, "value1")); - longUpDownCounter.add(2, Attributes.of(KEY1, "value2")); + longUpDownCounter.add(1, FIRST_ATTRIBUTES); + longUpDownCounter.add(2, SECOND_ATTRIBUTES); output = prometheusMeterRegistry.scrape(); assertThat(output) .contains( - "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + "longUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains( + "longUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0"); + } + + @Test + void longUpDownCounterWithAttributesAdvice() { + + LongUpDownCounterBuilder builder = + meter + .upDownCounterBuilder("longUpDownCounter") + .setDescription("LongUpDownCounter test") + .setUnit("units"); + + ((ExtendedLongUpDownCounterBuilder) builder) + .setAttributesAdvice(Collections.singletonList(KEY1)); + + LongUpDownCounter longUpDownCounter = builder.build(); + + longUpDownCounter.add(1, FIRST_ATTRIBUTES); + longUpDownCounter.add(2, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + + assertThat(output) + .contains("# HELP longUpDownCounter_units LongUpDownCounter test") + .contains("# TYPE longUpDownCounter_units gauge") + .contains( + "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 3.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); + + longUpDownCounter.add(1, FIRST_ATTRIBUTES); + longUpDownCounter.add(2, SECOND_ATTRIBUTES); + + output = prometheusMeterRegistry.scrape(); + + assertThat(output) .contains( - "longUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0"); + "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 6.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } @Test @@ -196,8 +370,8 @@ void observableLongUpDownCounter() { .setUnit("units") .buildWithCallback( measurement -> { - measurement.record(1, Attributes.of(KEY1, "value1")); - measurement.record(-2, Attributes.of(KEY1, "value2")); + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(-2, SECOND_ATTRIBUTES); })) { String output = scrapeFor("longUpDownCounter"); @@ -207,9 +381,40 @@ void observableLongUpDownCounter() { .contains("# TYPE longUpDownCounter_units gauge") .contains("# HELP longUpDownCounter_units LongUpDownCounter test") .contains( - "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.0"); + } + } + + @Test + void observableLongUpDownCounterWithAttributesAdvice() { + LongUpDownCounterBuilder builder = + meter + .upDownCounterBuilder("longUpDownCounter") + .setDescription("LongUpDownCounter test") + .setUnit("units"); + + ((ExtendedLongUpDownCounterBuilder) builder) + .setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableLongUpDownCounter observableLongUpDownCounter = + builder.buildWithCallback( + measurement -> { + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(-2, SECOND_ATTRIBUTES); + })) { + + String output = scrapeFor("longUpDownCounter"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE longUpDownCounter_units gauge") + .contains("# HELP longUpDownCounter_units LongUpDownCounter test") .contains( - "longUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.0"); + "longUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } @@ -223,8 +428,8 @@ void doubleUpDownCounter() { .setUnit("units") .build(); - doubleUpDownCounter.add(1.5, Attributes.of(KEY1, "value1")); - doubleUpDownCounter.add(2.5, Attributes.of(KEY1, "value2")); + doubleUpDownCounter.add(1.5, FIRST_ATTRIBUTES); + doubleUpDownCounter.add(2.5, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); @@ -232,20 +437,48 @@ void doubleUpDownCounter() { .contains("# HELP doubleUpDownCounter_units DoubleUpDownCounter test") .contains("# TYPE doubleUpDownCounter_units gauge") .contains( - "doubleUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); - doubleUpDownCounter.add(0.5, Attributes.of(KEY1, "value1")); - doubleUpDownCounter.add(-1.5, Attributes.of(KEY1, "value2")); + doubleUpDownCounter.add(0.5, FIRST_ATTRIBUTES); + doubleUpDownCounter.add(-1.5, SECOND_ATTRIBUTES); output = prometheusMeterRegistry.scrape(); assertThat(output) .contains( - "doubleUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") .contains( - "doubleUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0"); + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0"); + } + + @Test + void doubleUpDownCounterWithAttributesAdvice() { + DoubleUpDownCounterBuilder builder = + meter + .upDownCounterBuilder("doubleUpDownCounter") + .ofDoubles() + .setDescription("DoubleUpDownCounter test") + .setUnit("units"); + + ((ExtendedDoubleUpDownCounterBuilder) builder) + .setAttributesAdvice(Collections.singletonList(KEY1)); + + DoubleUpDownCounter doubleUpDownCounter = builder.build(); + + doubleUpDownCounter.add(1.5, FIRST_ATTRIBUTES); + doubleUpDownCounter.add(2.5, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + + assertThat(output) + .contains("# HELP doubleUpDownCounter_units DoubleUpDownCounter test") + .contains("# TYPE doubleUpDownCounter_units gauge") + .contains( + "doubleUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } @Test @@ -259,8 +492,8 @@ void observableDoubleUpDownCounter() { .buildWithCallback( onlyOnce( measurement -> { - measurement.record(1.5, Attributes.of(KEY1, "value1")); - measurement.record(-2.5, Attributes.of(KEY1, "value2")); + measurement.record(1.5, FIRST_ATTRIBUTES); + measurement.record(-2.5, SECOND_ATTRIBUTES); }))) { String output = scrapeFor("doubleUpDownCounter"); @@ -270,9 +503,42 @@ void observableDoubleUpDownCounter() { .contains("# TYPE doubleUpDownCounter_units gauge") .contains("# HELP doubleUpDownCounter_units DoubleUpDownCounter test") .contains( - "doubleUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleUpDownCounter_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.5"); + "doubleUpDownCounter_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.5"); + } + } + + @Test + void observableDoubleUpDownCounterWithAttributesAdvice() { + DoubleUpDownCounterBuilder builder = + meter + .upDownCounterBuilder("doubleUpDownCounter") + .ofDoubles() + .setDescription("DoubleUpDownCounter test") + .setUnit("units"); + + ((ExtendedDoubleUpDownCounterBuilder) builder) + .setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableDoubleUpDownCounter observableDoubleUpDownCounter = + builder.buildWithCallback( + onlyOnce( + measurement -> { + measurement.record(1.5, FIRST_ATTRIBUTES); + measurement.record(-2.5, SECOND_ATTRIBUTES); + }))) { + + String output = scrapeFor("doubleUpDownCounter"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE doubleUpDownCounter_units gauge") + .contains("# HELP doubleUpDownCounter_units DoubleUpDownCounter test") + .contains( + "doubleUpDownCounter_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} -2.5") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } @@ -285,38 +551,138 @@ void doubleHistogram() { .setUnit("units") .build(); - doubleHistogram.record(1.5, Attributes.of(KEY1, "value1")); - doubleHistogram.record(2.5, Attributes.of(KEY1, "value2")); + doubleHistogram.record(1.5, FIRST_ATTRIBUTES); + doubleHistogram.record(2.5, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); assertThat(output) .contains("# HELP doubleHistogram_units DoubleHistogram test") .contains("# TYPE doubleHistogram_units summary") .contains( - "doubleHistogram_units_count{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "doubleHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "doubleHistogram_units_sum{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleHistogram_units_count{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "doubleHistogram_units_count{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "doubleHistogram_units_sum{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") .contains("# HELP doubleHistogram_units_max DoubleHistogram test") .contains("# TYPE doubleHistogram_units_max gauge") .contains( - "doubleHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleHistogram_units_max{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + "doubleHistogram_units_max{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); - doubleHistogram.record(2.5, Attributes.of(KEY1, "value1")); + doubleHistogram.record(2.5, FIRST_ATTRIBUTES); output = prometheusMeterRegistry.scrape(); assertThat(output) + .contains( + "doubleHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains( + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0") + .contains( + "doubleHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + } + + @Test + void doubleHistogramWithAttributesAdvice() { + + DoubleHistogramBuilder builder = + meter + .histogramBuilder("doubleHistogram") + .setDescription("DoubleHistogram test") + .setUnit("units"); + + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + DoubleHistogram doubleHistogram = builder.build(); + + doubleHistogram.record(1.5, FIRST_ATTRIBUTES); + doubleHistogram.record(2.5, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains("# HELP doubleHistogram_units DoubleHistogram test") + .contains("# TYPE doubleHistogram_units summary") .contains( "doubleHistogram_units_count{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") .contains( "doubleHistogram_units_sum{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0") + .contains("# HELP doubleHistogram_units_max DoubleHistogram test") + .contains("# TYPE doubleHistogram_units_max gauge") .contains( - "doubleHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + "doubleHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); + } + + @Test + void doubleHistogramWithExplicitBucketBoundaries() { + DoubleHistogram doubleHistogram = + meter + .histogramBuilder("doubleHistogram") + .setExplicitBucketBoundariesAdvice(Arrays.asList(1.0, 2.0, 3.0)) + .setDescription("DoubleHistogram test") + .setUnit("units") + .build(); + + doubleHistogram.record(1.5, FIRST_ATTRIBUTES); + doubleHistogram.record(2.5, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains("# HELP doubleHistogram_units DoubleHistogram test") + .contains("# TYPE doubleHistogram_units histogram") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 0.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 1.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 1.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 1.0") + .contains( + "doubleHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 0.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 0.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 1.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 1.0") + .contains( + "doubleHistogram_units_count{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") + .contains("# HELP doubleHistogram_units_max DoubleHistogram test") + .contains("# TYPE doubleHistogram_units_max gauge") + .contains( + "doubleHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + .contains( + "doubleHistogram_units_max{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + + doubleHistogram.record(2.5, FIRST_ATTRIBUTES); + + output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 0.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 1.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 2.0") + .contains( + "doubleHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 2.0") + .contains( + "doubleHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains( + "doubleHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 4.0") + .contains( + "doubleHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); } @Test @@ -329,38 +695,139 @@ void longHistogram() { .setUnit("units") .build(); - longHistogram.record(1, Attributes.of(KEY1, "value1")); - longHistogram.record(2, Attributes.of(KEY1, "value2")); + longHistogram.record(1, FIRST_ATTRIBUTES); + longHistogram.record(2, SECOND_ATTRIBUTES); String output = prometheusMeterRegistry.scrape(); assertThat(output) .contains("# HELP longHistogram_units LongHistogram test") .contains("# TYPE longHistogram_units summary") .contains( - "longHistogram_units_count{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longHistogram_units_sum{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longHistogram_units_count{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longHistogram_units_count{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longHistogram_units_sum{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + "longHistogram_units_sum{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") .contains("# HELP longHistogram_units_max LongHistogram test") .contains("# TYPE longHistogram_units_max gauge") .contains( - "longHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") .contains( - "longHistogram_units_max{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longHistogram_units_max{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); - longHistogram.record(2, Attributes.of(KEY1, "value1")); + longHistogram.record(2, FIRST_ATTRIBUTES); output = prometheusMeterRegistry.scrape(); assertThat(output) + .contains( + "longHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains( + "longHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 3.0") + .contains( + "longHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + } + + @Test + void longHistogramWithAttributesAdvice() { + LongHistogramBuilder builder = + meter + .histogramBuilder("longHistogram") + .ofLongs() + .setDescription("LongHistogram test") + .setUnit("units"); + + ((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + LongHistogram longHistogram = builder.build(); + + longHistogram.record(1, FIRST_ATTRIBUTES); + longHistogram.record(2, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains("# HELP longHistogram_units LongHistogram test") + .contains("# TYPE longHistogram_units summary") .contains( "longHistogram_units_count{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") .contains( "longHistogram_units_sum{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 3.0") + .contains("# HELP longHistogram_units_max LongHistogram test") + .contains("# TYPE longHistogram_units_max gauge") + .contains( + "longHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); + } + + @Test + void longHistogramWithExplicitBucketBoundaries() { + LongHistogram longHistogram = + meter + .histogramBuilder("longHistogram") + .ofLongs() + .setExplicitBucketBoundariesAdvice(Arrays.asList(1L, 2L, 3L)) + .setDescription("LongHistogram test") + .setUnit("units") + .build(); + + longHistogram.record(1, FIRST_ATTRIBUTES); + longHistogram.record(2, SECOND_ATTRIBUTES); + + String output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains("# HELP longHistogram_units LongHistogram test") + .contains("# TYPE longHistogram_units histogram") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 1.0") + .contains( + "longHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 0.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 1.0") + .contains( + "longHistogram_units_count{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longHistogram_units_sum{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains("# HELP longHistogram_units_max LongHistogram test") + .contains("# TYPE longHistogram_units_max gauge") + .contains( + "longHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longHistogram_units_max{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + + longHistogram.record(2, FIRST_ATTRIBUTES); + + output = prometheusMeterRegistry.scrape(); + assertThat(output) + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"1.0\",} 1.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"2.0\",} 2.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"3.0\",} 2.0") + .contains( + "longHistogram_units_bucket{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",le=\"+Inf\",} 2.0") .contains( - "longHistogram_units_max{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longHistogram_units_count{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .contains( + "longHistogram_units_sum{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 3.0") + .contains( + "longHistogram_units_max{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); } @Test @@ -372,8 +839,8 @@ void observableDoubleGauge() { .setUnit("units") .buildWithCallback( measurement -> { - measurement.record(1.5, Attributes.of(KEY1, "value1")); - measurement.record(2.5, Attributes.of(KEY1, "value2")); + measurement.record(1.5, FIRST_ATTRIBUTES); + measurement.record(2.5, SECOND_ATTRIBUTES); })) { String output = scrapeFor("doubleGauge"); @@ -383,9 +850,36 @@ void observableDoubleGauge() { .contains("# TYPE doubleGauge_units gauge") .contains("# HELP doubleGauge_units DoubleGauge test") .contains( - "doubleGauge_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") + "doubleGauge_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.5") .contains( - "doubleGauge_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + "doubleGauge_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5"); + } + } + + @Test + void observableDoubleGaugeWithAttributesAdvice() { + DoubleGaugeBuilder builder = + meter.gaugeBuilder("doubleGauge").setDescription("DoubleGauge test").setUnit("units"); + + ((ExtendedDoubleGaugeBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableDoubleGauge observableDoubleGauge = + builder.buildWithCallback( + measurement -> { + measurement.record(1.5, FIRST_ATTRIBUTES); + measurement.record(2.5, SECOND_ATTRIBUTES); + })) { + + String output = scrapeFor("doubleGauge"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE doubleGauge_units gauge") + .contains("# HELP doubleGauge_units DoubleGauge test") + .contains( + "doubleGauge_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.5") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } @@ -399,8 +893,8 @@ void observableLongGauge() { .setUnit("units") .buildWithCallback( measurement -> { - measurement.record(1, Attributes.of(KEY1, "value1")); - measurement.record(2, Attributes.of(KEY1, "value2")); + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(2, SECOND_ATTRIBUTES); })) { String output = scrapeFor("longGauge"); @@ -410,9 +904,36 @@ void observableLongGauge() { .contains("# TYPE longGauge_units gauge") .contains("# HELP longGauge_units LongGauge test") .contains( - "longGauge_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + "longGauge_units{key1=\"value1\",key2=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 1.0") + .contains( + "longGauge_units{key1=\"value1\",key2=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + } + } + + @Test + void observableLongGaugeWithAttributesAdvice() { + LongGaugeBuilder builder = + meter.gaugeBuilder("longGauge").ofLongs().setDescription("LongGauge test").setUnit("units"); + + ((ExtendedLongGaugeBuilder) builder).setAttributesAdvice(Collections.singletonList(KEY1)); + + try (ObservableLongGauge observableLongGauge = + builder.buildWithCallback( + measurement -> { + measurement.record(1, FIRST_ATTRIBUTES); + measurement.record(2, SECOND_ATTRIBUTES); + })) { + + String output = scrapeFor("longGauge"); + assertThat(output) + .contains("# HELP otel_polling_meter") + .contains("# TYPE otel_polling_meter untyped") + .contains("# TYPE longGauge_units gauge") + .contains("# HELP longGauge_units LongGauge test") .contains( - "longGauge_units{key1=\"value2\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0"); + "longGauge_units{key1=\"value1\",otel_instrumentation_name=\"integrationTest\",otel_instrumentation_version=\"1.0\",} 2.0") + .doesNotContain("key2=\"value1\"") + .doesNotContain("key2=\"value2\""); } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractCounter.java index 608438169..2a6698118 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractCounter.java @@ -27,9 +27,15 @@ protected final Counter counter(Attributes attributes) { .register(meterRegistry()); } - protected final void record(double value, Attributes attributes) { + protected final void increment(Attributes attributes, double value) { + if (value >= 0.0) { + counter(attributes).increment(value); + } + } + + protected final void setMonotonically(Attributes attributes, double value) { counterMap - .computeIfAbsent(attributesOrEmpty(attributes), this::createAsyncCounter) + .computeIfAbsent(effectiveAttributes(attributes), this::createAsyncCounter) .setMonotonically(value); } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractGauge.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractGauge.java index bb1266424..bde99fca2 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractGauge.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractGauge.java @@ -18,8 +18,8 @@ protected AbstractGauge(InstrumentState instrumentState) { super(instrumentState); } - protected final void record(double value, Attributes attributes) { - gaugeMap.computeIfAbsent(attributesOrEmpty(attributes), this::createAsyncGauge).set(value); + protected final void record(Attributes attributes, double value) { + gaugeMap.computeIfAbsent(effectiveAttributes(attributes), this::createAsyncGauge).set(value); } private AtomicDoubleCounter createAsyncGauge(Attributes attributes) { diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractHistogram.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractHistogram.java index 6c6ce2667..daf9d37cc 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractHistogram.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractHistogram.java @@ -8,10 +8,15 @@ import io.micrometer.core.instrument.DistributionSummary; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; +import java.util.List; +import javax.annotation.Nullable; abstract class AbstractHistogram extends AbstractInstrument { + @Nullable private final List explicitBucketBoundaries; + protected AbstractHistogram(InstrumentState instrumentState) { super(instrumentState); + this.explicitBucketBoundaries = instrumentState.explicitBucketBoundariesAdvice(); } public DistributionSummary distribution(Attributes attributes) { @@ -19,6 +24,20 @@ public DistributionSummary distribution(Attributes attributes) { .tags(attributesToTags(attributes)) .description(description()) .baseUnit(unit()) + .serviceLevelObjectives(serviceLevelObjectives(explicitBucketBoundaries)) .register(meterRegistry()); } + + @Nullable + private static double[] serviceLevelObjectives( + @Nullable List explicitBucketBoundaries) { + if (explicitBucketBoundaries == null) { + return null; + } + double[] slos = new double[explicitBucketBoundaries.size()]; + for (int i = 0; i < explicitBucketBoundaries.size(); i++) { + slos[i] = explicitBucketBoundaries.get(i).doubleValue(); + } + return slos; + } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrument.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrument.java index 05994034c..80aa56465 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrument.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrument.java @@ -7,6 +7,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; import io.opentelemetry.api.metrics.ObservableLongMeasurement; @@ -14,9 +15,11 @@ import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.logging.Level; @@ -28,6 +31,7 @@ abstract class AbstractInstrument { private final Logger logger; private final Tag instrumentationNameTag; private final Tag instrumentationVersionTag; + @Nullable private final Set> attributeKeySet; private final Map> attributesTagsCache; protected AbstractInstrument(InstrumentState instrumentState) { @@ -35,6 +39,12 @@ protected AbstractInstrument(InstrumentState instrumentState) { this.logger = Logger.getLogger(getClass().getName()); this.instrumentationNameTag = instrumentState.instrumentationScopeNameTag(); this.instrumentationVersionTag = instrumentState.instrumentationScopeVersionTag(); + List> attributes = instrumentState.attributesAdvice(); + if (attributes != null) { + attributeKeySet = new HashSet<>(attributes); + } else { + attributeKeySet = null; + } this.attributesTagsCache = new ConcurrentHashMap<>(); } @@ -56,19 +66,27 @@ protected final String unit() { return instrumentState.unit(); } - protected final Attributes attributesOrEmpty(@Nullable Attributes attributes) { - return attributes != null ? attributes : Attributes.empty(); + protected final Attributes effectiveAttributes(@Nullable Attributes attributes) { + if (attributes == null) { + return Attributes.empty(); + } + if (attributeKeySet == null) { + return attributes; + } + return attributes.toBuilder().removeIf(key -> !attributeKeySet.contains(key)).build(); } @SuppressWarnings("PreferredInterfaceType") protected final Iterable attributesToTags(Attributes attributes) { - return attributesTagsCache.computeIfAbsent(attributesOrEmpty(attributes), this::calculateTags); + return attributesTagsCache.computeIfAbsent( + effectiveAttributes(attributes), this::calculateTags); } @SuppressWarnings("PreferredInterfaceType") private Iterable calculateTags(Attributes attributes) { - List list = new ArrayList<>(attributes.size() + 2); - attributes.forEach( + Attributes effectiveAttributes = effectiveAttributes(attributes); + List list = new ArrayList<>(effectiveAttributes.size() + 2); + effectiveAttributes.forEach( (attributeKey, value) -> list.add(Tag.of(attributeKey.getKey(), Objects.toString(value)))); list.add(instrumentationNameTag); diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrumentBuilder.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrumentBuilder.java index 6582a3f2a..ac1cdca13 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrumentBuilder.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractInstrumentBuilder.java @@ -6,8 +6,10 @@ package io.opentelemetry.contrib.metrics.micrometer.internal.instruments; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import java.util.List; import javax.annotation.Nullable; abstract class AbstractInstrumentBuilder> { @@ -15,21 +17,21 @@ abstract class AbstractInstrumentBuilder> attributes; + @Nullable protected List explicitBucketBoundaries; protected AbstractInstrumentBuilder(MeterSharedState meterSharedState, String name) { this.meterSharedState = meterSharedState; this.name = name; } - protected AbstractInstrumentBuilder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - this.meterSharedState = meterSharedState; - this.name = name; - this.description = description; - this.unit = unit; + protected AbstractInstrumentBuilder(AbstractInstrumentBuilder parent) { + this.meterSharedState = parent.meterSharedState; + this.name = parent.name; + this.description = parent.description; + this.unit = parent.unit; + this.attributes = parent.attributes; + this.explicitBucketBoundaries = parent.explicitBucketBoundaries; } protected abstract BUILDER self(); @@ -46,7 +48,20 @@ public BUILDER setUnit(String unit) { return self(); } + @CanIgnoreReturnValue + public BUILDER setAttributesAdvice(List> attributes) { + this.attributes = attributes; + return self(); + } + + @CanIgnoreReturnValue + public BUILDER setExplicitBucketBoundaries(List explicitBucketBoundaries) { + this.explicitBucketBoundaries = explicitBucketBoundaries; + return self(); + } + protected InstrumentState createInstrumentState() { - return new InstrumentState(meterSharedState, name, description, unit); + return new InstrumentState( + meterSharedState, name, description, unit, attributes, explicitBucketBoundaries); } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractUpDownCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractUpDownCounter.java index 2c574d13c..058bf5f2e 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractUpDownCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/AbstractUpDownCounter.java @@ -19,11 +19,15 @@ protected AbstractUpDownCounter(InstrumentState instrumentState) { } protected final void add(Attributes attributes, double value) { - counterMap.computeIfAbsent(attributesOrEmpty(attributes), this::createCounter).increment(value); + counter(attributes).increment(value); } - protected final void record(double value, Attributes attributes) { - counterMap.computeIfAbsent(attributesOrEmpty(attributes), this::createCounter).set(value); + protected final void record(Attributes attributes, double value) { + counter(attributes).set(value); + } + + protected final AtomicDoubleCounter counter(Attributes attributes) { + return counterMap.computeIfAbsent(effectiveAttributes(attributes), this::createCounter); } private AtomicDoubleCounter createCounter(Attributes attributes) { diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounter.java index 87d33e3b6..302f1ca23 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounter.java @@ -12,11 +12,11 @@ import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; -import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleCounterBuilder; import java.util.function.Consumer; -import javax.annotation.Nullable; -final class MicrometerDoubleCounter extends AbstractCounter implements DoubleCounter { +final class MicrometerDoubleCounter extends AbstractCounter + implements DoubleCounter, ObservableDoubleMeasurement { private MicrometerDoubleCounter(InstrumentState instrumentState) { super(instrumentState); @@ -24,41 +24,37 @@ private MicrometerDoubleCounter(InstrumentState instrumentState) { @Override public void add(double value) { - if (value >= 0.0) { - counter(Attributes.empty()).increment(value); - } + increment(Attributes.empty(), value); } @Override public void add(double value, Attributes attributes) { - if (value >= 0.0) { - counter(attributes).increment(value); - } + increment(attributes, value); } @Override public void add(double value, Attributes attributes, Context context) { - if (value >= 0.0) { - counter(attributes).increment(value); - } + increment(attributes, value); + } + + @Override + public void record(double value) { + setMonotonically(Attributes.empty(), value); } - public static DoubleCounterBuilder builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - return new Builder(meterSharedState, name, description, unit); + @Override + public void record(double value, Attributes attributes) { + setMonotonically(attributes, value); } - private static class Builder extends AbstractInstrumentBuilder - implements DoubleCounterBuilder { - private Builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - super(meterSharedState, name, description, unit); + public static DoubleCounterBuilder builder(MicrometerLongCounter.Builder parent) { + return new Builder(parent); + } + + static final class Builder extends AbstractInstrumentBuilder + implements DoubleCounterBuilder, ExtendedDoubleCounterBuilder { + private Builder(MicrometerLongCounter.Builder parent) { + super(parent); } @Override @@ -75,19 +71,7 @@ public MicrometerDoubleCounter build() { public ObservableDoubleCounter buildWithCallback( Consumer callback) { MicrometerDoubleCounter instrument = build(); - return instrument.registerDoubleCallback( - callback, - new ObservableDoubleMeasurement() { - @Override - public void record(double value) { - record(value, Attributes.empty()); - } - - @Override - public void record(double value, Attributes attributes) { - instrument.record(value, attributes); - } - }); + return instrument.registerDoubleCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGauge.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGauge.java index 1d6a9ba47..b8f9a0d50 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGauge.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGauge.java @@ -12,51 +12,72 @@ import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.DoubleGauge; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleGaugeBuilder; import java.util.function.Consumer; -public final class MicrometerDoubleGauge extends AbstractGauge { +public final class MicrometerDoubleGauge extends AbstractGauge + implements DoubleGauge, ObservableDoubleMeasurement { private MicrometerDoubleGauge(InstrumentState instrumentState) { super(instrumentState); } + @Override + public void set(double value) { + record(Attributes.empty(), value); + } + + @Override + public void set(double value, Attributes attributes) { + record(attributes, value); + } + + @Override + public void record(double value) { + record(Attributes.empty(), value); + } + + @Override + public void record(double value, Attributes attributes) { + record(attributes, value); + } + public static DoubleGaugeBuilder builder(MeterSharedState meterSharedState, String name) { - return new DoubleBuilder(meterSharedState, name); + return new Builder(meterSharedState, name); } - private static class DoubleBuilder extends AbstractInstrumentBuilder - implements DoubleGaugeBuilder { + static final class Builder extends AbstractInstrumentBuilder + implements DoubleGaugeBuilder, ExtendedDoubleGaugeBuilder { - private DoubleBuilder(MeterSharedState meterSharedState, String name) { + private Builder(MeterSharedState meterSharedState, String name) { super(meterSharedState, name); } @Override - public DoubleBuilder self() { + public Builder self() { return this; } @Override public LongGaugeBuilder ofLongs() { - return MicrometerLongGauge.builder(meterSharedState, name, description, unit); + return MicrometerLongGauge.builder(this); + } + + @Override + public DoubleGauge build() { + return new MicrometerDoubleGauge(createInstrumentState()); + } + + @Override + public ObservableDoubleMeasurement buildObserver() { + return new MicrometerDoubleGauge(createInstrumentState()); } @Override public ObservableDoubleGauge buildWithCallback(Consumer callback) { MicrometerDoubleGauge instrument = new MicrometerDoubleGauge(createInstrumentState()); - return instrument.registerDoubleCallback( - callback, - new ObservableDoubleMeasurement() { - @Override - public void record(double value) { - record(value, Attributes.empty()); - } - - @Override - public void record(double value, Attributes attributes) { - instrument.record(value, attributes); - } - }); + return instrument.registerDoubleCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogram.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogram.java index ca0fcbc8d..ab994c7f8 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogram.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogram.java @@ -12,6 +12,8 @@ import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; +import java.util.List; public final class MicrometerDoubleHistogram extends AbstractHistogram implements DoubleHistogram { @@ -38,12 +40,17 @@ public static DoubleHistogramBuilder builder(MeterSharedState meterSharedState, return new Builder(meterSharedState, name); } - private static class Builder extends AbstractInstrumentBuilder - implements DoubleHistogramBuilder { + static final class Builder extends AbstractInstrumentBuilder + implements DoubleHistogramBuilder, ExtendedDoubleHistogramBuilder { private Builder(MeterSharedState meterSharedState, String name) { super(meterSharedState, name); } + @Override + public DoubleHistogramBuilder setExplicitBucketBoundariesAdvice(List bucketBoundaries) { + return super.setExplicitBucketBoundaries(bucketBoundaries); + } + @Override public Builder self() { return this; @@ -51,7 +58,7 @@ public Builder self() { @Override public LongHistogramBuilder ofLongs() { - return MicrometerLongHistogram.builder(meterSharedState, name, description, unit); + return MicrometerLongHistogram.builder(this); } @Override diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounter.java index a30f2016c..b5e153adb 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounter.java @@ -12,12 +12,11 @@ import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; -import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleUpDownCounterBuilder; import java.util.function.Consumer; -import javax.annotation.Nullable; final class MicrometerDoubleUpDownCounter extends AbstractUpDownCounter - implements DoubleUpDownCounter { + implements DoubleUpDownCounter, ObservableDoubleMeasurement { private MicrometerDoubleUpDownCounter(InstrumentState instrumentState) { super(instrumentState); } @@ -37,22 +36,24 @@ public void add(double value, Attributes attributes, Context context) { add(attributes, value); } - static DoubleUpDownCounterBuilder builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - return new Builder(meterSharedState, name, description, unit); + @Override + public void record(double value) { + record(Attributes.empty(), value); + } + + @Override + public void record(double value, Attributes attributes) { + record(attributes, value); } - private static class Builder extends AbstractInstrumentBuilder - implements DoubleUpDownCounterBuilder { - private Builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - super(meterSharedState, name, description, unit); + static DoubleUpDownCounterBuilder builder(MicrometerLongUpDownCounter.Builder parent) { + return new Builder(parent); + } + + static final class Builder extends AbstractInstrumentBuilder + implements DoubleUpDownCounterBuilder, ExtendedDoubleUpDownCounterBuilder { + private Builder(MicrometerLongUpDownCounter.Builder parent) { + super(parent); } @Override @@ -69,19 +70,7 @@ public MicrometerDoubleUpDownCounter build() { public ObservableDoubleUpDownCounter buildWithCallback( Consumer callback) { MicrometerDoubleUpDownCounter instrument = build(); - return instrument.registerDoubleCallback( - callback, - new ObservableDoubleMeasurement() { - @Override - public void record(double value) { - instrument.record(value, Attributes.empty()); - } - - @Override - public void record(double value, Attributes attributes) { - instrument.record(value, attributes); - } - }); + return instrument.registerDoubleCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounter.java index e973deb79..6cfa54b75 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounter.java @@ -14,9 +14,11 @@ import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongCounterBuilder; import java.util.function.Consumer; -public final class MicrometerLongCounter extends AbstractCounter implements LongCounter { +public final class MicrometerLongCounter extends AbstractCounter + implements LongCounter, ObservableLongMeasurement { private MicrometerLongCounter(InstrumentState instrumentState) { super(instrumentState); @@ -24,31 +26,35 @@ private MicrometerLongCounter(InstrumentState instrumentState) { @Override public void add(long value) { - if (value > 0L) { - counter(Attributes.empty()).increment((double) value); - } + increment(Attributes.empty(), (double) value); } @Override public void add(long value, Attributes attributes) { - if (value > 0L) { - counter(attributes).increment((double) value); - } + increment(attributes, (double) value); } @Override public void add(long value, Attributes attributes, Context context) { - if (value > 0L) { - counter(attributes).increment((double) value); - } + increment(attributes, (double) value); + } + + @Override + public void record(long value) { + setMonotonically(Attributes.empty(), (double) value); + } + + @Override + public void record(long value, Attributes attributes) { + setMonotonically(attributes, (double) value); } public static LongCounterBuilder builder(MeterSharedState meterSharedState, String name) { return new Builder(meterSharedState, name); } - private static class Builder extends AbstractInstrumentBuilder - implements LongCounterBuilder { + static final class Builder extends AbstractInstrumentBuilder + implements LongCounterBuilder, ExtendedLongCounterBuilder { private Builder(MeterSharedState meterSharedState, String name) { super(meterSharedState, name); } @@ -60,7 +66,7 @@ public Builder self() { @Override public DoubleCounterBuilder ofDoubles() { - return MicrometerDoubleCounter.builder(meterSharedState, name, description, unit); + return MicrometerDoubleCounter.builder(this); } @Override @@ -71,19 +77,7 @@ public MicrometerLongCounter build() { @Override public ObservableLongCounter buildWithCallback(Consumer callback) { MicrometerLongCounter instrument = build(); - return instrument.registerLongCallback( - callback, - new ObservableLongMeasurement() { - @Override - public void record(long value) { - record(value, Attributes.empty()); - } - - @Override - public void record(long value, Attributes attributes) { - instrument.record((double) value, attributes); - } - }); + return instrument.registerLongCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGauge.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGauge.java index 5f9b7cf5b..834376127 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGauge.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGauge.java @@ -10,31 +10,44 @@ import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.api.metrics.ObservableLongMeasurement; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; -import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongGaugeBuilder; +import io.opentelemetry.extension.incubator.metrics.LongGauge; import java.util.function.Consumer; -import javax.annotation.Nullable; -public final class MicrometerLongGauge extends AbstractGauge { +public final class MicrometerLongGauge extends AbstractGauge + implements LongGauge, ObservableLongMeasurement { public MicrometerLongGauge(InstrumentState instrumentState) { super(instrumentState); } - public static LongGaugeBuilder builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - return new Builder(meterSharedState, name, description, unit); + static LongGaugeBuilder builder(MicrometerDoubleGauge.Builder parent) { + return new Builder(parent); } - private static class Builder extends AbstractInstrumentBuilder - implements LongGaugeBuilder { - private Builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - super(meterSharedState, name, description, unit); + @Override + public void set(long value) { + record(Attributes.empty(), (double) value); + } + + @Override + public void set(long value, Attributes attributes) { + record(attributes, (double) value); + } + + @Override + public void record(long value) { + record(Attributes.empty(), (double) value); + } + + @Override + public void record(long value, Attributes attributes) { + record(attributes, (double) value); + } + + static final class Builder extends AbstractInstrumentBuilder + implements LongGaugeBuilder, ExtendedLongGaugeBuilder { + private Builder(MicrometerDoubleGauge.Builder parent) { + super(parent); } @Override @@ -42,22 +55,20 @@ public Builder self() { return this; } + @Override + public LongGauge build() { + return new MicrometerLongGauge(createInstrumentState()); + } + + @Override + public ObservableLongMeasurement buildObserver() { + return new MicrometerLongGauge(createInstrumentState()); + } + @Override public ObservableLongGauge buildWithCallback(Consumer callback) { MicrometerLongGauge instrument = new MicrometerLongGauge(createInstrumentState()); - return instrument.registerLongCallback( - callback, - new ObservableLongMeasurement() { - @Override - public void record(long value) { - record(value, Attributes.empty()); - } - - @Override - public void record(long value, Attributes attributes) { - instrument.record((double) value, attributes); - } - }); + return instrument.registerLongCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogram.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogram.java index 4e8e70e82..c8b6704b4 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogram.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogram.java @@ -10,8 +10,8 @@ import io.opentelemetry.api.metrics.LongHistogramBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; -import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; -import javax.annotation.Nullable; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongHistogramBuilder; +import java.util.List; final class MicrometerLongHistogram extends AbstractHistogram implements LongHistogram { @@ -34,22 +34,19 @@ public void record(long value, Attributes attributes, Context context) { distribution(attributes).record((double) value); } - public static LongHistogramBuilder builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - return new Builder(meterSharedState, name, description, unit); + public static LongHistogramBuilder builder(MicrometerDoubleHistogram.Builder parent) { + return new Builder(parent); } - private static class Builder extends AbstractInstrumentBuilder - implements LongHistogramBuilder { - private Builder( - MeterSharedState meterSharedState, - String name, - @Nullable String description, - @Nullable String unit) { - super(meterSharedState, name, description, unit); + static final class Builder extends AbstractInstrumentBuilder + implements LongHistogramBuilder, ExtendedLongHistogramBuilder { + private Builder(MicrometerDoubleHistogram.Builder parent) { + super(parent); + } + + @Override + public LongHistogramBuilder setExplicitBucketBoundariesAdvice(List bucketBoundaries) { + return super.setExplicitBucketBoundaries(bucketBoundaries); } @Override diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounter.java index 577881c3a..5e54455c2 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounter.java @@ -14,10 +14,11 @@ import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.internal.state.InstrumentState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongUpDownCounterBuilder; import java.util.function.Consumer; public final class MicrometerLongUpDownCounter extends AbstractUpDownCounter - implements LongUpDownCounter { + implements LongUpDownCounter, ObservableLongMeasurement { private MicrometerLongUpDownCounter(InstrumentState instrumentState) { super(instrumentState); } @@ -37,12 +38,22 @@ public void add(long value, Attributes attributes, Context context) { add(attributes, (double) value); } + @Override + public void record(long value) { + record(Attributes.empty(), (double) value); + } + + @Override + public void record(long value, Attributes attributes) { + record(attributes, (double) value); + } + public static LongUpDownCounterBuilder builder(MeterSharedState meterSharedState, String name) { return new Builder(meterSharedState, name); } - private static class Builder extends AbstractInstrumentBuilder - implements LongUpDownCounterBuilder { + static final class Builder extends AbstractInstrumentBuilder + implements LongUpDownCounterBuilder, ExtendedLongUpDownCounterBuilder { private Builder(MeterSharedState meterSharedState, String name) { super(meterSharedState, name); } @@ -54,7 +65,7 @@ protected Builder self() { @Override public DoubleUpDownCounterBuilder ofDoubles() { - return MicrometerDoubleUpDownCounter.builder(meterSharedState, name, description, unit); + return MicrometerDoubleUpDownCounter.builder(this); } @Override @@ -66,19 +77,7 @@ public MicrometerLongUpDownCounter build() { public ObservableLongUpDownCounter buildWithCallback( Consumer callback) { MicrometerLongUpDownCounter instrument = build(); - return instrument.registerLongCallback( - callback, - new ObservableLongMeasurement() { - @Override - public void record(long value) { - instrument.record((double) value, Attributes.empty()); - } - - @Override - public void record(long value, Attributes attributes) { - instrument.record((double) value, attributes); - } - }); + return instrument.registerLongCallback(callback, instrument); } } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/state/InstrumentState.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/state/InstrumentState.java index 73be8f476..ee1fe0a38 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/state/InstrumentState.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/state/InstrumentState.java @@ -7,7 +7,9 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.contrib.metrics.micrometer.CallbackRegistration; +import java.util.List; import javax.annotation.Nullable; /** @@ -21,16 +23,22 @@ public final class InstrumentState { private final String name; @Nullable private final String description; @Nullable private final String unit; + @Nullable private final List> attributes; + @Nullable private final List explicitBucketBoundaries; public InstrumentState( MeterSharedState meterSharedState, String name, @Nullable String description, - @Nullable String unit) { + @Nullable String unit, + @Nullable List> attributes, + @Nullable List explicitBucketBoundaries) { this.meterSharedState = meterSharedState; this.name = name; this.description = description; this.unit = unit; + this.attributes = attributes; + this.explicitBucketBoundaries = explicitBucketBoundaries; } public MeterRegistry meterRegistry() { @@ -62,4 +70,14 @@ public String description() { public String unit() { return unit; } + + @Nullable + public List> attributesAdvice() { + return attributes; + } + + @Nullable + public List explicitBucketBoundariesAdvice() { + return explicitBucketBoundaries; + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java index 4e49e9767..3981f38e3 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java @@ -12,15 +12,19 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; import io.opentelemetry.api.metrics.ObservableDoubleCounter; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleCounterBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -174,6 +178,59 @@ void addWithAttributesAndContext() { } } + @Test + void addWithAttributesAndAttributesAdvice() { + DoubleCounterBuilder builder = + MicrometerLongCounter.builder(meterSharedState, "counter") + .ofDoubles() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + DoubleCounter underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.add(10.0, attributes); + + Counter counter = meterRegistry.find("counter").counter(); + assertThat(counter).isNotNull(); + Meter.Id id = counter.getId(); + assertThat(id.getName()).isEqualTo("counter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(counter.count()).isEqualTo(10.0); + + // test that counter can be increased + underTest.add(10.0, attributes); + assertThat(counter.count()).isEqualTo(20.0); + + // test that counter cannot be decreased + underTest.add(-5.0, attributes); + assertThat(counter.count()).isEqualTo(20.0); + + double expectedCount = 20.0; + for (double value : RandomUtils.randomDoubles(10, 0.0, 10.0)) { + expectedCount += value; + + underTest.add(value, attributes); + assertThat(counter.count()).isEqualTo(expectedCount); + } + } + @Test void observable() { AtomicDoubleCounter atomicDoubleCounter = new AtomicDoubleCounter(); @@ -280,4 +337,69 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + DoubleCounterBuilder builder = + MicrometerLongCounter.builder(meterSharedState, "counter") + .ofDoubles() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + AtomicDoubleCounter atomicDoubleCounter = new AtomicDoubleCounter(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + ObservableDoubleCounter underTest = + builder.buildWithCallback( + measurement -> measurement.record(atomicDoubleCounter.current(), attributes)); + + assertThat(callbacks).hasSize(1); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + atomicDoubleCounter.set(10.0); + callbackRegistrar.run(); + FunctionCounter counter = meterRegistry.find("counter").functionCounter(); + assertThat(counter).isNotNull(); + Meter.Id id = counter.getId(); + assertThat(id.getName()).isEqualTo("counter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(counter.count()).isEqualTo(10.0); + + // test that counter can be increased + atomicDoubleCounter.set(20.0); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo(20.0); + + // test that counter cannot be decreased + atomicDoubleCounter.set(5.0); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo(20.0); + + double expectedCount = 10.0; + for (double value : RandomUtils.randomDoubles(10, 0.0, 500.0)) { + expectedCount += value; + + atomicDoubleCounter.set(expectedCount); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo(expectedCount); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java index 18d0cc8aa..59b3de963 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java @@ -11,13 +11,17 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; import io.opentelemetry.api.metrics.ObservableDoubleGauge; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleGaugeBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -117,4 +121,53 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + AtomicDoubleCounter atomicDoubleCounter = new AtomicDoubleCounter(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + DoubleGaugeBuilder builder = + MicrometerDoubleGauge.builder(meterSharedState, "gauge") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleGaugeBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + ObservableDoubleGauge underTest = + builder.buildWithCallback( + measurement -> measurement.record(atomicDoubleCounter.current(), attributes)); + + assertThat(callbacks).hasSize(1); + + atomicDoubleCounter.set(10.0); + callbackRegistrar.run(); + Gauge gauge = meterRegistry.find("gauge").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("gauge"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + for (double value : RandomUtils.randomDoubles(10, 0.0, 500.0)) { + atomicDoubleCounter.set(value); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo(value); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java index 545bd77d6..a7396e170 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java @@ -10,14 +10,20 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.distribution.CountAtBucket; +import io.micrometer.core.instrument.distribution.HistogramSnapshot; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; +import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -152,4 +158,88 @@ void addWithAttributesAndContext() { assertThat(summary.totalAmount()).isEqualTo(expectedTotal); } } + + @Test + void addWithAttributesAndAttributesAdvice() { + + DoubleHistogramBuilder builder = + MicrometerDoubleHistogram.builder(meterSharedState, "histogram") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleHistogramBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + DoubleHistogram underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.record(10.0, attributes); + + DistributionSummary summary = meterRegistry.find("histogram").summary(); + assertThat(summary).isNotNull(); + Meter.Id id = summary.getId(); + assertThat(id.getName()).isEqualTo("histogram"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(summary.count()).isEqualTo(1); + assertThat(summary.totalAmount()).isEqualTo(10.0); + + long expectedCount = 1; + double expectedTotal = 10.0; + for (double value : RandomUtils.randomDoubles(10, 0.0, 10.0)) { + expectedCount += 1; + expectedTotal += value; + + underTest.record(value, attributes); + assertThat(summary.count()).isEqualTo(expectedCount); + assertThat(summary.totalAmount()).isEqualTo(expectedTotal); + } + } + + @Test + void addWithExplicitBucketBoundaries() { + DoubleHistogram underTest = + MicrometerDoubleHistogram.builder(meterSharedState, "histogram") + .setDescription("description") + .setUnit("unit") + .setExplicitBucketBoundariesAdvice(Arrays.asList(10.0, 20.0, 30.0)) + .build(); + + underTest.record(5.0); + underTest.record(15.0); + underTest.record(25.0); + underTest.record(35.0); + + DistributionSummary summary = meterRegistry.find("histogram").summary(); + assertThat(summary).isNotNull(); + Meter.Id id = summary.getId(); + assertThat(id.getName()).isEqualTo("histogram"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(summary.count()).isEqualTo(4); + assertThat(summary.totalAmount()).isEqualTo(80.0); + + HistogramSnapshot snapshot = summary.takeSnapshot(); + CountAtBucket[] counts = snapshot.histogramCounts(); + assertThat(counts) + .hasSize(3) + .containsExactly( + new CountAtBucket(10.0, 1), new CountAtBucket(20.0, 2), new CountAtBucket(30.0, 3)); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java index bef6cbbb9..a22e95dba 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java @@ -11,15 +11,19 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleUpDownCounter; +import io.opentelemetry.api.metrics.DoubleUpDownCounterBuilder; import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleUpDownCounterBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -158,6 +162,54 @@ void addWithAttributesAndContext() { } } + @Test + void addWithAttributesAndAttributesAdvice() { + DoubleUpDownCounterBuilder builder = + MicrometerLongUpDownCounter.builder(meterSharedState, "upDownCounter") + .ofDoubles() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleUpDownCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + DoubleUpDownCounter underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.add(10.0, attributes); + + Gauge gauge = meterRegistry.find("upDownCounter").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("upDownCounter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + underTest.add(-10.0, attributes); + assertThat(gauge.value()).isEqualTo(0.0); + + double expectedCount = 0.0; + for (double value : RandomUtils.randomDoubles(10, -500.0, 500.0)) { + expectedCount += value; + + underTest.add(value, attributes); + assertThat(gauge.value()).isEqualTo(expectedCount); + } + } + @Test void observable() { AtomicDoubleCounter atomicDoubleCounter = new AtomicDoubleCounter(); @@ -246,4 +298,60 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + DoubleUpDownCounterBuilder builder = + MicrometerLongUpDownCounter.builder(meterSharedState, "upDownCounter") + .ofDoubles() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedDoubleUpDownCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + AtomicDoubleCounter atomicDoubleCounter = new AtomicDoubleCounter(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + ObservableDoubleUpDownCounter underTest = + builder.buildWithCallback( + measurement -> measurement.record(atomicDoubleCounter.current(), attributes)); + + assertThat(callbacks).hasSize(1); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + atomicDoubleCounter.set(10.0); + callbackRegistrar.run(); + Gauge gauge = meterRegistry.find("upDownCounter").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("upDownCounter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + atomicDoubleCounter.set(0.0); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo(0.0); + + for (double value : RandomUtils.randomDoubles(10, -500.0, 500.0)) { + atomicDoubleCounter.set(value); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo(value); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java index ca4aa6e8f..596c20a20 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java @@ -12,14 +12,17 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; import io.opentelemetry.api.metrics.ObservableLongCounter; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongCounterBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -176,6 +179,59 @@ void addWithAttributesAndContext() { } } + @Test + void addWithAttributesAndAttributesAdvice() { + LongCounterBuilder builder = + MicrometerLongCounter.builder(meterSharedState, "counter") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + LongCounter underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.add(10, attributes); + + Counter counter = meterRegistry.find("counter").counter(); + assertThat(counter).isNotNull(); + Meter.Id id = counter.getId(); + assertThat(id.getName()).isEqualTo("counter"); + assertThat(id.getTags()) + .isEqualTo( + Arrays.asList( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0"))); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(counter.count()).isEqualTo(10.0); + + // test that counter can be increased + underTest.add(10, attributes); + assertThat(counter.count()).isEqualTo(20.0); + + // test that counter cannot be decreased + underTest.add(-5, attributes); + assertThat(counter.count()).isEqualTo(20.0); + + double expectedCount = 20.0; + for (long value : RandomUtils.randomLongs(10, 0L, 500L)) { + expectedCount += value; + + underTest.add(value, attributes); + assertThat(counter.count()).isEqualTo(expectedCount); + } + } + @Test void observable() { AtomicLong atomicLong = new AtomicLong(); @@ -277,4 +333,66 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + LongCounterBuilder builder = + MicrometerLongCounter.builder(meterSharedState, "counter") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + AtomicLong atomicLong = new AtomicLong(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + ObservableLongCounter underTest = + builder.buildWithCallback(measurement -> measurement.record(atomicLong.get(), attributes)); + + assertThat(callbacks).hasSize(1); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + atomicLong.set(10L); + callbackRegistrar.run(); + FunctionCounter counter = meterRegistry.find("counter").functionCounter(); + assertThat(counter).isNotNull(); + Meter.Id id = counter.getId(); + assertThat(id.getName()).isEqualTo("counter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(counter.count()).isEqualTo(10.0); + + // test that counter can be increased + atomicLong.set(20L); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo(20.0); + + // test that counter cannot be decreased + atomicLong.set(5L); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo(20.0); + + long value = 20L; + for (long increment : RandomUtils.randomLongs(10, 0L, 500L)) { + value += increment; + atomicLong.set(value); + callbackRegistrar.run(); + assertThat(counter.count()).isEqualTo((double) value); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java index 18f1a7fa6..3f9b65561 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java @@ -11,13 +11,17 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGaugeBuilder; import io.opentelemetry.api.metrics.ObservableLongGauge; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongGaugeBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.BeforeEach; @@ -118,4 +122,53 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + LongGaugeBuilder builder = + MicrometerDoubleGauge.builder(meterSharedState, "gauge") + .ofLongs() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongGaugeBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + AtomicLong atomicLong = new AtomicLong(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + ObservableLongGauge underTest = + builder.buildWithCallback(measurement -> measurement.record(atomicLong.get(), attributes)); + + assertThat(callbacks).hasSize(1); + + atomicLong.set(10L); + callbackRegistrar.run(); + Gauge gauge = meterRegistry.find("gauge").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("gauge"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + for (long value : RandomUtils.randomLongs(10, -500L, 500L)) { + atomicLong.set(value); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo((double) value); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java index c3e119a59..e49501d51 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java @@ -10,14 +10,20 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.distribution.CountAtBucket; +import io.micrometer.core.instrument.distribution.HistogramSnapshot; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.LongHistogramBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongHistogramBuilder; +import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -155,4 +161,89 @@ void addWithAttributesAndContext() { assertThat(summary.totalAmount()).isEqualTo(expectedTotal); } } + + @Test + void addWithAttributesAndAttributesAdvice() { + LongHistogramBuilder builder = + MicrometerDoubleHistogram.builder(meterSharedState, "histogram") + .ofLongs() + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongHistogramBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + LongHistogram underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.record(10, attributes); + + DistributionSummary summary = meterRegistry.find("histogram").summary(); + assertThat(summary).isNotNull(); + Meter.Id id = summary.getId(); + assertThat(id.getName()).isEqualTo("histogram"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(summary.count()).isEqualTo(1); + assertThat(summary.totalAmount()).isEqualTo(10.0); + + long expectedCount = 1; + double expectedTotal = 10.0; + for (long value : RandomUtils.randomLongs(10, 0L, 10L)) { + expectedCount += 1; + expectedTotal += value; + + underTest.record(value, attributes); + assertThat(summary.count()).isEqualTo(expectedCount); + assertThat(summary.totalAmount()).isEqualTo(expectedTotal); + } + } + + @Test + void addWithExplicitBucketBoundaries() { + LongHistogram underTest = + MicrometerDoubleHistogram.builder(meterSharedState, "histogram") + .ofLongs() + .setDescription("description") + .setUnit("unit") + .setExplicitBucketBoundariesAdvice(Arrays.asList(10L, 20L, 30L)) + .build(); + + underTest.record(5L); + underTest.record(15L); + underTest.record(25L); + underTest.record(35L); + + DistributionSummary summary = meterRegistry.find("histogram").summary(); + assertThat(summary).isNotNull(); + Meter.Id id = summary.getId(); + assertThat(id.getName()).isEqualTo("histogram"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(summary.count()).isEqualTo(4); + assertThat(summary.totalAmount()).isEqualTo(80.0); + + HistogramSnapshot snapshot = summary.takeSnapshot(); + CountAtBucket[] counts = snapshot.histogramCounts(); + assertThat(counts) + .hasSize(3) + .containsExactly( + new CountAtBucket(10.0, 1), new CountAtBucket(20.0, 2), new CountAtBucket(30.0, 3)); + } } diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java index 6404daaf9..f79c1d2ef 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java @@ -11,15 +11,19 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; import io.opentelemetry.api.metrics.ObservableLongUpDownCounter; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.metrics.micrometer.TestCallbackRegistrar; import io.opentelemetry.contrib.metrics.micrometer.internal.Constants; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongUpDownCounterBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.BeforeEach; @@ -171,6 +175,58 @@ void addWithAttributesAndContext() { } } + @Test + void addWithAttributesAndAttributesAdvice() { + LongUpDownCounterBuilder builder = + MicrometerLongUpDownCounter.builder(meterSharedState, "upDownCounter") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongUpDownCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + LongUpDownCounter underTest = builder.build(); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + underTest.add(10, attributes); + + Gauge gauge = meterRegistry.find("upDownCounter").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("upDownCounter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + // test that counter can be increased + underTest.add(10, attributes); + assertThat(gauge.value()).isEqualTo(20.0); + + // test that counter can be decreased + underTest.add(-5, attributes); + assertThat(gauge.value()).isEqualTo(15.0); + + double expectedCount = 15.0; + for (long value : RandomUtils.randomLongs(10, -500L, 500L)) { + expectedCount += value; + + underTest.add(value, attributes); + assertThat(gauge.value()).isEqualTo(expectedCount); + } + } + @Test void observable() { AtomicLong atomicLong = new AtomicLong(); @@ -268,4 +324,64 @@ void observableWithAttributes() { assertThat(callbacks).isEmpty(); } + + @Test + void observableWithAttributesAndAttributesAdvice() { + LongUpDownCounterBuilder builder = + MicrometerLongUpDownCounter.builder(meterSharedState, "upDownCounter") + .setDescription("description") + .setUnit("unit"); + + ((ExtendedLongUpDownCounterBuilder) builder) + .setAttributesAdvice( + Arrays.asList( + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_NAME), + AttributeKey.stringKey(Constants.OTEL_INSTRUMENTATION_VERSION), + AttributeKey.stringKey("key"))); + + AtomicLong atomicLong = new AtomicLong(); + Attributes attributes = + Attributes.builder().put("key", "value").put("unwanted", "value").build(); + ObservableLongUpDownCounter underTest = + builder.buildWithCallback(measurement -> measurement.record(atomicLong.get(), attributes)); + + assertThat(callbacks).hasSize(1); + + assertThat(meterRegistry.getMeters()).isEmpty(); + + atomicLong.set(10L); + callbackRegistrar.run(); + Gauge gauge = meterRegistry.find("upDownCounter").gauge(); + assertThat(gauge).isNotNull(); + Meter.Id id = gauge.getId(); + assertThat(id.getName()).isEqualTo("upDownCounter"); + assertThat(id.getTags()) + .containsExactlyInAnyOrder( + Tag.of("key", "value"), + Tag.of(Constants.OTEL_INSTRUMENTATION_NAME, "meter"), + Tag.of(Constants.OTEL_INSTRUMENTATION_VERSION, "1.0")); + assertThat(id.getDescription()).isEqualTo("description"); + assertThat(id.getBaseUnit()).isEqualTo("unit"); + assertThat(gauge.value()).isEqualTo(10.0); + + // test that counter can be increased + atomicLong.set(20L); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo(20.0); + + // test that counter can be decreased + atomicLong.set(15L); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo(15.0); + + for (long value : RandomUtils.randomLongs(10, 0L, 500L)) { + atomicLong.set(value); + callbackRegistrar.run(); + assertThat(gauge.value()).isEqualTo((double) value); + } + + underTest.close(); + + assertThat(callbacks).isEmpty(); + } }