From 0bc02afbc93edf26d9b1ff78f7c6b4c75aa2b0ab Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 5 Jun 2023 15:19:36 -0500 Subject: [PATCH] Add experimental synchronous gauge --- .../incubator/metrics/DoubleGauge.java | 30 ++ .../metrics/ExtendedDoubleGaugeBuilder.java | 24 ++ .../metrics/ExtendedLongGaugeBuilder.java | 24 ++ .../incubator/metrics/LongGauge.java | 30 ++ .../sdk/metrics/SdkDoubleGauge.java | 85 +++++ .../sdk/metrics/SdkDoubleGaugeBuilder.java | 52 --- .../sdk/metrics/SdkLongGauge.java | 84 +++++ .../sdk/metrics/SdkLongGaugeBuilder.java | 51 --- .../opentelemetry/sdk/metrics/SdkMeter.java | 3 +- .../metrics/SdkDoubleGaugeBuilderTest.java | 113 ------- .../sdk/metrics/SdkDoubleGaugeTest.java | 305 +++++++++++++++++ .../sdk/metrics/SdkLongGaugeBuilderTest.java | 111 ------- .../sdk/metrics/SdkLongGaugeTest.java | 312 ++++++++++++++++++ 13 files changed, 896 insertions(+), 328 deletions(-) create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/DoubleGauge.java create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedDoubleGaugeBuilder.java create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedLongGaugeBuilder.java create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/LongGauge.java create mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGauge.java delete mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilder.java create mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGauge.java delete mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilder.java delete mode 100644 sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilderTest.java create mode 100644 sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeTest.java delete mode 100644 sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilderTest.java create mode 100644 sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeTest.java diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/DoubleGauge.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/DoubleGauge.java new file mode 100644 index 00000000000..1842f7d90e1 --- /dev/null +++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/DoubleGauge.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.extension.incubator.metrics; + +import io.opentelemetry.api.common.Attributes; +import javax.annotation.concurrent.ThreadSafe; + +/** A gauge instrument that synchronously records {@code double} values. */ +@ThreadSafe +public interface DoubleGauge { + /** + * Set the gauge value. + * + * @param value The current gauge value. + */ + void set(double value); + + /** + * Records a value with a set of attributes. + * + * @param value The current gauge value. + * @param attributes A set of attributes to associate with the value. + */ + void set(double value, Attributes attributes); + + // TODO(jack-berg): should we add overload with Context argument? +} diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedDoubleGaugeBuilder.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedDoubleGaugeBuilder.java new file mode 100644 index 00000000000..494f0a555ec --- /dev/null +++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedDoubleGaugeBuilder.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.extension.incubator.metrics; + +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import java.util.function.Consumer; + +/** Extended {@link DoubleGaugeBuilder} with experimental APIs. */ +public interface ExtendedDoubleGaugeBuilder extends DoubleGaugeBuilder { + + /** + * Builds and returns a DoubleGauge instrument with the configuration. + * + *

NOTE: This produces a synchronous gauge which records gauge values as they occur. Most users + * will want to instead register an {@link #buildWithCallback(Consumer)} to asynchronously observe + * the value of the gauge when metrics are collected. + * + * @return The DoubleGauge instrument. + */ + DoubleGauge build(); +} diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedLongGaugeBuilder.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedLongGaugeBuilder.java new file mode 100644 index 00000000000..830a7162bfa --- /dev/null +++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/ExtendedLongGaugeBuilder.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.extension.incubator.metrics; + +import io.opentelemetry.api.metrics.LongGaugeBuilder; +import java.util.function.Consumer; + +/** Extended {@link LongGaugeBuilder} with experimental APIs. */ +public interface ExtendedLongGaugeBuilder extends LongGaugeBuilder { + + /** + * Builds and returns a LongGauge instrument with the configuration. + * + *

NOTE: This produces a synchronous gauge which records gauge values as they occur. Most users + * will want to instead register an {@link #buildWithCallback(Consumer)} to asynchronously observe + * the value of the gauge when metrics are collected. + * + * @return The LongGauge instrument. + */ + LongGauge build(); +} diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/LongGauge.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/LongGauge.java new file mode 100644 index 00000000000..1aba6d1d160 --- /dev/null +++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/metrics/LongGauge.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.extension.incubator.metrics; + +import io.opentelemetry.api.common.Attributes; +import javax.annotation.concurrent.ThreadSafe; + +/** A gauge instrument that synchronously records {@code long} values. */ +@ThreadSafe +public interface LongGauge { + /** + * Set the gauge value. + * + * @param value The current gauge value. + */ + void set(long value); + + /** + * Records a value with a set of attributes. + * + * @param value The current gauge value. + * @param attributes A set of attributes to associate with the value. + */ + void set(long value, Attributes attributes); + + // TODO(jack-berg): should we add overload with Context argument? +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGauge.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGauge.java new file mode 100644 index 00000000000..48acd8d839e --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGauge.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGaugeBuilder; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import io.opentelemetry.context.Context; +import io.opentelemetry.extension.incubator.metrics.DoubleGauge; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleGaugeBuilder; +import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor; +import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState; +import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState; +import io.opentelemetry.sdk.metrics.internal.state.WriteableMetricStorage; +import java.util.function.Consumer; + +final class SdkDoubleGauge extends AbstractInstrument implements DoubleGauge { + + private final WriteableMetricStorage storage; + + private SdkDoubleGauge(InstrumentDescriptor descriptor, WriteableMetricStorage storage) { + super(descriptor); + this.storage = storage; + } + + @Override + public void set(double increment, Attributes attributes) { + storage.recordDouble(increment, attributes, Context.root()); + } + + @Override + public void set(double increment) { + set(increment, Attributes.empty()); + } + + static final class SdkDoubleGaugeBuilder extends AbstractInstrumentBuilder + implements ExtendedDoubleGaugeBuilder { + + SdkDoubleGaugeBuilder( + MeterProviderSharedState meterProviderSharedState, + MeterSharedState meterSharedState, + String name) { + super( + meterProviderSharedState, + meterSharedState, + // TODO: use InstrumentType.GAUGE when available + InstrumentType.OBSERVABLE_GAUGE, + InstrumentValueType.DOUBLE, + name, + "", + DEFAULT_UNIT); + } + + @Override + protected SdkDoubleGaugeBuilder getThis() { + return this; + } + + @Override + public SdkDoubleGauge build() { + return buildSynchronousInstrument(SdkDoubleGauge::new); + } + + @Override + public LongGaugeBuilder ofLongs() { + return swapBuilder(SdkLongGauge.SdkLongGaugeBuilder::new); + } + + @Override + public ObservableDoubleGauge buildWithCallback(Consumer callback) { + // TODO: use InstrumentType.GAUGE when available + return registerDoubleAsynchronousInstrument(InstrumentType.OBSERVABLE_GAUGE, callback); + } + + @Override + public ObservableDoubleMeasurement buildObserver() { + // TODO: use InstrumentType.GAUGE when available + return buildObservableMeasurement(InstrumentType.OBSERVABLE_GAUGE); + } + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilder.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilder.java deleted file mode 100644 index fda925d4c32..00000000000 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics; - -import io.opentelemetry.api.metrics.DoubleGaugeBuilder; -import io.opentelemetry.api.metrics.LongGaugeBuilder; -import io.opentelemetry.api.metrics.ObservableDoubleGauge; -import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; -import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState; -import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState; -import java.util.function.Consumer; - -final class SdkDoubleGaugeBuilder extends AbstractInstrumentBuilder - implements DoubleGaugeBuilder { - - SdkDoubleGaugeBuilder( - MeterProviderSharedState meterProviderSharedState, - MeterSharedState meterSharedState, - String name) { - super( - meterProviderSharedState, - meterSharedState, - InstrumentType.OBSERVABLE_GAUGE, - InstrumentValueType.DOUBLE, - name, - "", - DEFAULT_UNIT); - } - - @Override - protected SdkDoubleGaugeBuilder getThis() { - return this; - } - - @Override - public LongGaugeBuilder ofLongs() { - return swapBuilder(SdkLongGaugeBuilder::new); - } - - @Override - public ObservableDoubleGauge buildWithCallback(Consumer callback) { - return registerDoubleAsynchronousInstrument(InstrumentType.OBSERVABLE_GAUGE, callback); - } - - @Override - public ObservableDoubleMeasurement buildObserver() { - return buildObservableMeasurement(InstrumentType.OBSERVABLE_GAUGE); - } -} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGauge.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGauge.java new file mode 100644 index 00000000000..c8afb08f047 --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGauge.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableLongGauge; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.context.Context; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongGaugeBuilder; +import io.opentelemetry.extension.incubator.metrics.LongGauge; +import io.opentelemetry.sdk.metrics.internal.descriptor.Advice; +import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor; +import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState; +import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState; +import io.opentelemetry.sdk.metrics.internal.state.WriteableMetricStorage; +import java.util.function.Consumer; + +final class SdkLongGauge extends AbstractInstrument implements LongGauge { + + private final WriteableMetricStorage storage; + + private SdkLongGauge(InstrumentDescriptor descriptor, WriteableMetricStorage storage) { + super(descriptor); + this.storage = storage; + } + + @Override + public void set(long increment, Attributes attributes) { + storage.recordLong(increment, attributes, Context.root()); + } + + @Override + public void set(long increment) { + set(increment, Attributes.empty()); + } + + static final class SdkLongGaugeBuilder extends AbstractInstrumentBuilder + implements ExtendedLongGaugeBuilder { + + SdkLongGaugeBuilder( + MeterProviderSharedState meterProviderSharedState, + MeterSharedState sharedState, + String name, + String description, + String unit, + Advice advice) { + super( + meterProviderSharedState, + sharedState, + // TODO: use InstrumentType.GAUGE when available + InstrumentType.OBSERVABLE_GAUGE, + InstrumentValueType.LONG, + name, + description, + unit, + advice); + } + + @Override + protected SdkLongGaugeBuilder getThis() { + return this; + } + + @Override + public SdkLongGauge build() { + return buildSynchronousInstrument(SdkLongGauge::new); + } + + @Override + public ObservableLongGauge buildWithCallback(Consumer callback) { + // TODO: use InstrumentType.GAUGE when available + return registerLongAsynchronousInstrument(InstrumentType.OBSERVABLE_GAUGE, callback); + } + + @Override + public ObservableLongMeasurement buildObserver() { + // TODO: use InstrumentType.GAUGE when available + return buildObservableMeasurement(InstrumentType.OBSERVABLE_GAUGE); + } + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilder.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilder.java deleted file mode 100644 index ea6bad737c8..00000000000 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilder.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics; - -import io.opentelemetry.api.metrics.LongGaugeBuilder; -import io.opentelemetry.api.metrics.ObservableLongGauge; -import io.opentelemetry.api.metrics.ObservableLongMeasurement; -import io.opentelemetry.sdk.metrics.internal.descriptor.Advice; -import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState; -import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState; -import java.util.function.Consumer; - -final class SdkLongGaugeBuilder extends AbstractInstrumentBuilder - implements LongGaugeBuilder { - - SdkLongGaugeBuilder( - MeterProviderSharedState meterProviderSharedState, - MeterSharedState sharedState, - String name, - String description, - String unit, - Advice advice) { - super( - meterProviderSharedState, - sharedState, - InstrumentType.OBSERVABLE_GAUGE, - InstrumentValueType.LONG, - name, - description, - unit, - advice); - } - - @Override - protected SdkLongGaugeBuilder getThis() { - return this; - } - - @Override - public ObservableLongGauge buildWithCallback(Consumer callback) { - return registerLongAsynchronousInstrument(InstrumentType.OBSERVABLE_GAUGE, callback); - } - - @Override - public ObservableLongMeasurement buildObserver() { - return buildObservableMeasurement(InstrumentType.OBSERVABLE_GAUGE); - } -} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java index 906c5a41a02..7a1258259b4 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java @@ -108,7 +108,8 @@ public DoubleHistogramBuilder histogramBuilder(String name) { public DoubleGaugeBuilder gaugeBuilder(String name) { return !checkValidInstrumentName(name) ? NOOP_METER.gaugeBuilder(NOOP_INSTRUMENT_NAME) - : new SdkDoubleGaugeBuilder(meterProviderSharedState, meterSharedState, name); + : new SdkDoubleGauge.SdkDoubleGaugeBuilder( + meterProviderSharedState, meterSharedState, name); } @Override diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilderTest.java deleted file mode 100644 index ec98c2c0f64..00000000000 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeBuilderTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.metrics.ObservableDoubleGauge; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.testing.time.TestClock; -import java.time.Duration; -import org.junit.jupiter.api.Test; - -/** Unit tests for SDK {@link ObservableDoubleGauge}. */ -class SdkDoubleGaugeBuilderTest { - private static final Resource RESOURCE = - Resource.create(Attributes.of(stringKey("resource_key"), "resource_value")); - private static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = - InstrumentationScopeInfo.create(SdkDoubleGaugeBuilderTest.class.getName()); - private final TestClock testClock = TestClock.create(); - private final InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.create(); - private final SdkMeterProvider sdkMeterProvider = - SdkMeterProvider.builder() - .setClock(testClock) - .setResource(RESOURCE) - .registerMetricReader(sdkMeterReader) - .build(); - private final Meter sdkMeter = sdkMeterProvider.get(getClass().getName()); - - @Test - void removeCallback() { - ObservableDoubleGauge gauge = - sdkMeter.gaugeBuilder("testGauge").buildWithCallback(measurement -> measurement.record(10)); - - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasName("testGauge") - .hasDoubleGaugeSatisfying( - doubleGauge -> doubleGauge.hasPointsSatisfying(poit -> {}))); - - gauge.close(); - - assertThat(sdkMeterReader.collectAllMetrics()).hasSize(0); - } - - @Test - void collectMetrics_NoRecords() { - sdkMeter - .gaugeBuilder("testObserver") - .setDescription("My own DoubleValueObserver") - .setUnit("ms") - .buildWithCallback(result -> {}); - assertThat(sdkMeterReader.collectAllMetrics()).isEmpty(); - } - - @Test - void collectMetrics_WithOneRecord() { - sdkMeter - .gaugeBuilder("testObserver") - .setDescription("My own DoubleValueObserver") - .setUnit("ms") - .buildWithCallback( - result -> result.record(12.1d, Attributes.builder().put("k", "v").build())); - testClock.advance(Duration.ofSeconds(1)); - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasResource(RESOURCE) - .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) - .hasName("testObserver") - .hasDescription("My own DoubleValueObserver") - .hasUnit("ms") - .hasDoubleGaugeSatisfying( - gauge -> - gauge.hasPointsSatisfying( - point -> - point - .hasStartEpochNanos(testClock.now() - 1000000000L) - .hasEpochNanos(testClock.now()) - .hasAttributes(attributeEntry("k", "v")) - .hasValue(12.1d)))); - testClock.advance(Duration.ofSeconds(1)); - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasResource(RESOURCE) - .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) - .hasName("testObserver") - .hasDescription("My own DoubleValueObserver") - .hasUnit("ms") - .hasDoubleGaugeSatisfying( - gauge -> - gauge.hasPointsSatisfying( - point -> - point - .hasStartEpochNanos(testClock.now() - 2000000000L) - .hasEpochNanos(testClock.now()) - .hasAttributes(attributeEntry("k", "v")) - .hasValue(12.1d)))); - } -} diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeTest.java new file mode 100644 index 00000000000..404ed738d83 --- /dev/null +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleGaugeTest.java @@ -0,0 +1,305 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import io.opentelemetry.extension.incubator.metrics.DoubleGauge; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleGaugeBuilder; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.testing.time.TestClock; +import java.time.Duration; +import java.util.stream.IntStream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link SdkDoubleGauge}. */ +class SdkDoubleGaugeTest { + private static final long SECOND_NANOS = 1_000_000_000; + private static final Resource RESOURCE = + Resource.create(Attributes.of(stringKey("resource_key"), "resource_value")); + private static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = + InstrumentationScopeInfo.create(SdkDoubleGaugeTest.class.getName()); + private final TestClock testClock = TestClock.create(); + private final InMemoryMetricReader cumulativeReader = InMemoryMetricReader.create(); + private final InMemoryMetricReader deltaReader = InMemoryMetricReader.createDelta(); + private final SdkMeterProvider sdkMeterProvider = + SdkMeterProvider.builder() + .setClock(testClock) + .registerMetricReader(cumulativeReader) + .registerMetricReader(deltaReader) + .setResource(RESOURCE) + .build(); + private final Meter sdkMeter = sdkMeterProvider.get(getClass().getName()); + + @Test + void set_PreventNullAttributes() { + assertThatThrownBy( + () -> + ((ExtendedDoubleGaugeBuilder) sdkMeter.gaugeBuilder("testGauge")) + .build() + .set(1.0, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("attributes"); + } + + @Test + void observable_RemoveCallback() { + ObservableDoubleGauge gauge = + sdkMeter.gaugeBuilder("testGauge").buildWithCallback(measurement -> measurement.record(10)); + + Assertions.assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasName("testGauge") + .hasDoubleGaugeSatisfying( + doubleGauge -> doubleGauge.hasPointsSatisfying(point -> {}))); + + gauge.close(); + + Assertions.assertThat(cumulativeReader.collectAllMetrics()).hasSize(0); + } + + @Test + void collectMetrics_NoRecords() { + ((ExtendedDoubleGaugeBuilder) sdkMeter.gaugeBuilder("testGauge")).build(); + assertThat(cumulativeReader.collectAllMetrics()).isEmpty(); + } + + @Test + void collectMetrics_WithEmptyAttributes() { + DoubleGauge doubleGauge = + ((ExtendedDoubleGaugeBuilder) + sdkMeter.gaugeBuilder("testGauge").setDescription("description").setUnit("K")) + .build(); + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + doubleGauge.set(12d, Attributes.empty()); + doubleGauge.set(13d); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("description") + .hasUnit("K") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now() - SECOND_NANOS) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(13d)))); + } + + @Test + void collectMetrics_WithMultipleCollects() { + long startTime = testClock.now(); + DoubleGauge doubleGauge = + ((ExtendedDoubleGaugeBuilder) sdkMeter.gaugeBuilder("testGauge")).build(); + doubleGauge.set(12.1d, Attributes.empty()); + doubleGauge.set(123.3d, Attributes.builder().put("K", "V").build()); + doubleGauge.set(21.4d, Attributes.empty()); + // Advancing time here should not matter. + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + doubleGauge.set(321.5d, Attributes.builder().put("K", "V").build()); + doubleGauge.set(111.1d, Attributes.builder().put("K", "V").build()); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("") + .hasUnit("") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21.4d), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(111.1d) + .hasAttributes(attributeEntry("K", "V"))))); + assertThat(deltaReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("") + .hasUnit("") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21.4d), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(111.1d) + .hasAttributes(attributeEntry("K", "V"))))); + + // Repeat to prove we keep previous values. + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + doubleGauge.set(222d, Attributes.builder().put("K", "V").build()); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21.4d), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(222d) + .hasAttributes(attributeEntry("K", "V"))))); + // Delta reader should only have point for {K=V} series, since the {} did not have any + // measurements + assertThat(deltaReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime + SECOND_NANOS) + .hasEpochNanos(testClock.now()) + .hasValue(222d) + .hasAttributes(attributeEntry("K", "V"))))); + } + + @Test + void stressTest() { + DoubleGauge doubleGauge = + ((ExtendedDoubleGaugeBuilder) sdkMeter.gaugeBuilder("testGauge")).build(); + + StressTestRunner.Builder stressTestBuilder = + StressTestRunner.builder().setCollectionIntervalMs(100); + + for (int i = 0; i < 4; i++) { + stressTestBuilder.addOperation( + StressTestRunner.Operation.create( + 1_000, + 1, + () -> { + doubleGauge.set(10, Attributes.builder().put("K", "V").build()); + doubleGauge.set(11, Attributes.builder().put("K", "V").build()); + })); + } + + stressTestBuilder.build().run(); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry("K", "V"))))); + } + + @Test + void stressTest_WithDifferentLabelSet() { + String[] keys = {"Key_1", "Key_2", "Key_3", "Key_4"}; + String[] values = {"Value_1", "Value_2", "Value_3", "Value_4"}; + DoubleGauge doubleGauge = + ((ExtendedDoubleGaugeBuilder) sdkMeter.gaugeBuilder("testGauge")).build(); + + StressTestRunner.Builder stressTestBuilder = + StressTestRunner.builder().setCollectionIntervalMs(100); + + IntStream.range(0, 4) + .forEach( + i -> + stressTestBuilder.addOperation( + StressTestRunner.Operation.create( + 2_000, + 1, + () -> { + doubleGauge.set(10, Attributes.builder().put(keys[i], values[i]).build()); + doubleGauge.set(11, Attributes.builder().put(keys[i], values[i]).build()); + }))); + + stressTestBuilder.build().run(); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[0], values[0])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[1], values[1])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[2], values[2])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[3], values[3]))))); + } +} diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilderTest.java deleted file mode 100644 index c3c5d3d05b3..00000000000 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeBuilderTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.metrics.ObservableLongGauge; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.testing.time.TestClock; -import java.time.Duration; -import org.junit.jupiter.api.Test; - -/** Unit tests for SDK {@link ObservableLongGauge}. */ -class SdkLongGaugeBuilderTest { - private static final Resource RESOURCE = - Resource.create(Attributes.of(stringKey("resource_key"), "resource_value")); - private static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = - InstrumentationScopeInfo.create(SdkLongGaugeBuilderTest.class.getName()); - private final TestClock testClock = TestClock.create(); - private final InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.create(); - private final SdkMeterProvider sdkMeterProvider = - SdkMeterProvider.builder() - .setClock(testClock) - .setResource(RESOURCE) - .registerMetricReader(sdkMeterReader) - .build(); - private final Meter sdkMeter = sdkMeterProvider.get(getClass().getName()); - - @Test - void removeCallback() { - ObservableLongGauge gauge = - sdkMeter - .gaugeBuilder("testGauge") - .ofLongs() - .buildWithCallback(measurement -> measurement.record(10)); - - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasName("testGauge") - .hasLongGaugeSatisfying( - longGauge -> longGauge.hasPointsSatisfying(point -> {}))); - - gauge.close(); - - assertThat(sdkMeterReader.collectAllMetrics()).hasSize(0); - } - - @Test - void collectMetrics_NoRecords() { - sdkMeter - .gaugeBuilder("testObserver") - .ofLongs() - .setDescription("My own LongValueObserver") - .setUnit("ms") - .buildWithCallback(result -> {}); - assertThat(sdkMeterReader.collectAllMetrics()).isEmpty(); - } - - @Test - void collectMetrics_WithOneRecord() { - sdkMeter - .gaugeBuilder("testObserver") - .ofLongs() - .buildWithCallback(result -> result.record(12, Attributes.builder().put("k", "v").build())); - testClock.advance(Duration.ofSeconds(1)); - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasResource(RESOURCE) - .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) - .hasName("testObserver") - .hasLongGaugeSatisfying( - gauge -> - gauge.hasPointsSatisfying( - point -> - point - .hasStartEpochNanos(testClock.now() - 1000000000L) - .hasEpochNanos(testClock.now()) - .hasAttributes(attributeEntry("k", "v")) - .hasValue(12)))); - testClock.advance(Duration.ofSeconds(1)); - assertThat(sdkMeterReader.collectAllMetrics()) - .satisfiesExactly( - metric -> - assertThat(metric) - .hasResource(RESOURCE) - .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) - .hasName("testObserver") - .hasLongGaugeSatisfying( - gauge -> - gauge.hasPointsSatisfying( - point -> - point - .hasStartEpochNanos(testClock.now() - 2000000000L) - .hasEpochNanos(testClock.now()) - .hasAttributes(attributeEntry("k", "v")) - .hasValue(12)))); - } -} diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeTest.java new file mode 100644 index 00000000000..dbe04000bef --- /dev/null +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongGaugeTest.java @@ -0,0 +1,312 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableLongGauge; +import io.opentelemetry.extension.incubator.metrics.ExtendedLongGaugeBuilder; +import io.opentelemetry.extension.incubator.metrics.LongGauge; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.testing.time.TestClock; +import java.time.Duration; +import java.util.stream.IntStream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link SdkLongGauge}. */ +class SdkLongGaugeTest { + private static final long SECOND_NANOS = 1_000_000_000; + private static final Resource RESOURCE = + Resource.create(Attributes.of(stringKey("resource_key"), "resource_value")); + private static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = + InstrumentationScopeInfo.create(SdkLongGaugeTest.class.getName()); + private final TestClock testClock = TestClock.create(); + private final InMemoryMetricReader cumulativeReader = InMemoryMetricReader.create(); + private final InMemoryMetricReader deltaReader = InMemoryMetricReader.createDelta(); + private final SdkMeterProvider sdkMeterProvider = + SdkMeterProvider.builder() + .setClock(testClock) + .registerMetricReader(cumulativeReader) + .registerMetricReader(deltaReader) + .setResource(RESOURCE) + .build(); + private final Meter sdkMeter = sdkMeterProvider.get(getClass().getName()); + + @Test + void set_PreventNullAttributes() { + assertThatThrownBy( + () -> + ((ExtendedLongGaugeBuilder) sdkMeter.gaugeBuilder("testGauge").ofLongs()) + .build() + .set(1, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("attributes"); + } + + @Test + void observable_RemoveCallback() { + ObservableLongGauge gauge = + sdkMeter + .gaugeBuilder("testGauge") + .ofLongs() + .buildWithCallback(measurement -> measurement.record(10)); + + Assertions.assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasName("testGauge") + .hasLongGaugeSatisfying( + longGauge -> longGauge.hasPointsSatisfying(point -> {}))); + + gauge.close(); + + Assertions.assertThat(cumulativeReader.collectAllMetrics()).hasSize(0); + } + + @Test + void collectMetrics_NoRecords() { + ((ExtendedLongGaugeBuilder) sdkMeter.gaugeBuilder("testGauge").ofLongs()).build(); + assertThat(cumulativeReader.collectAllMetrics()).isEmpty(); + } + + @Test + void collectMetrics_WithEmptyAttributes() { + LongGauge longGauge = + ((ExtendedLongGaugeBuilder) + sdkMeter + .gaugeBuilder("testGauge") + .ofLongs() + .setDescription("description") + .setUnit("K")) + .build(); + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + longGauge.set(12, Attributes.empty()); + longGauge.set(13); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("description") + .hasUnit("K") + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now() - SECOND_NANOS) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(13)))); + } + + @Test + void collectMetrics_WithMultipleCollects() { + long startTime = testClock.now(); + LongGauge longGauge = + ((ExtendedLongGaugeBuilder) sdkMeter.gaugeBuilder("testGauge").ofLongs()).build(); + longGauge.set(12, Attributes.empty()); + longGauge.set(12, Attributes.builder().put("K", "V").build()); + longGauge.set(21, Attributes.empty()); + // Advancing time here should not matter. + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + longGauge.set(321, Attributes.builder().put("K", "V").build()); + longGauge.set(111, Attributes.builder().put("K", "V").build()); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("") + .hasUnit("") + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(111) + .hasAttributes(attributeEntry("K", "V"))))); + assertThat(deltaReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasDescription("") + .hasUnit("") + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(111) + .hasAttributes(attributeEntry("K", "V"))))); + + // Repeat to prove we keep previous values. + testClock.advance(Duration.ofNanos(SECOND_NANOS)); + longGauge.set(222, Attributes.builder().put("K", "V").build()); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasAttributes(Attributes.empty()) + .hasValue(21), + point -> + point + .hasStartEpochNanos(startTime) + .hasEpochNanos(testClock.now()) + .hasValue(222) + .hasAttributes(attributeEntry("K", "V"))))); + // Delta reader should only have point for {K=V} series, since the {} did not have any + // measurements + assertThat(deltaReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(startTime + SECOND_NANOS) + .hasEpochNanos(testClock.now()) + .hasValue(222) + .hasAttributes(attributeEntry("K", "V"))))); + } + + @Test + void stressTest() { + LongGauge longGauge = + ((ExtendedLongGaugeBuilder) sdkMeter.gaugeBuilder("testGauge").ofLongs()).build(); + + StressTestRunner.Builder stressTestBuilder = + StressTestRunner.builder().setCollectionIntervalMs(100); + + for (int i = 0; i < 4; i++) { + stressTestBuilder.addOperation( + StressTestRunner.Operation.create( + 1_000, + 1, + () -> { + longGauge.set(10, Attributes.builder().put("K", "V").build()); + longGauge.set(11, Attributes.builder().put("K", "V").build()); + })); + } + + stressTestBuilder.build().run(); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasName("testGauge") + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry("K", "V"))))); + } + + @Test + void stressTest_WithDifferentLabelSet() { + String[] keys = {"Key_1", "Key_2", "Key_3", "Key_4"}; + String[] values = {"Value_1", "Value_2", "Value_3", "Value_4"}; + LongGauge longGauge = + ((ExtendedLongGaugeBuilder) sdkMeter.gaugeBuilder("testGauge").ofLongs()).build(); + + StressTestRunner.Builder stressTestBuilder = + StressTestRunner.builder().setCollectionIntervalMs(100); + + IntStream.range(0, 4) + .forEach( + i -> + stressTestBuilder.addOperation( + StressTestRunner.Operation.create( + 2_000, + 1, + () -> { + longGauge.set(10, Attributes.builder().put(keys[i], values[i]).build()); + longGauge.set(11, Attributes.builder().put(keys[i], values[i]).build()); + }))); + + stressTestBuilder.build().run(); + assertThat(cumulativeReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasResource(RESOURCE) + .hasInstrumentationScope(INSTRUMENTATION_SCOPE_INFO) + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[0], values[0])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[1], values[1])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[2], values[2])), + point -> + point + .hasStartEpochNanos(testClock.now()) + .hasEpochNanos(testClock.now()) + .hasValue(11) + .hasAttributes(attributeEntry(keys[3], values[3]))))); + } +}