Change the way Micrometer LongTaskTimer is bridged (#5338)

This commit is contained in:
Mateusz Rzeszutek 2022-02-18 01:46:23 +01:00 committed by GitHub
parent cc60ffb6e0
commit 6774ce5791
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 196 additions and 228 deletions

View File

@ -64,6 +64,8 @@ public final class AsyncInstrumentRegistry {
private final Map<String, LongMeasurementsRecorder> longCounters = new ConcurrentHashMap<>(); private final Map<String, LongMeasurementsRecorder> longCounters = new ConcurrentHashMap<>();
private final Map<String, DoubleMeasurementsRecorder> upDownDoubleCounters = private final Map<String, DoubleMeasurementsRecorder> upDownDoubleCounters =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private final Map<String, LongMeasurementsRecorder> upDownLongCounters =
new ConcurrentHashMap<>();
AsyncInstrumentRegistry(Meter meter) { AsyncInstrumentRegistry(Meter meter) {
this.meter = new WeakReference<>(meter); this.meter = new WeakReference<>(meter);
@ -150,7 +152,7 @@ public final class AsyncInstrumentRegistry {
String description, String description,
String baseUnit, String baseUnit,
Attributes attributes, Attributes attributes,
T obj, @Nullable T obj,
ToDoubleFunction<T> objMetric) { ToDoubleFunction<T> objMetric) {
DoubleMeasurementsRecorder recorder = DoubleMeasurementsRecorder recorder =
@ -171,6 +173,31 @@ public final class AsyncInstrumentRegistry {
return new AsyncMeasurementHandle(recorder, attributes); return new AsyncMeasurementHandle(recorder, attributes);
} }
public <T> AsyncMeasurementHandle buildUpDownLongCounter(
String name,
String description,
String baseUnit,
Attributes attributes,
@Nullable T obj,
ToLongFunction<T> objMetric) {
LongMeasurementsRecorder recorder =
upDownLongCounters.computeIfAbsent(
name,
n -> {
LongMeasurementsRecorder recorderCallback = new LongMeasurementsRecorder();
otelMeter()
.upDownCounterBuilder(name)
.setDescription(description)
.setUnit(baseUnit)
.buildWithCallback(recorderCallback);
return recorderCallback;
});
recorder.addMeasurement(attributes, new LongMeasurementSource<>(obj, objMetric));
return new AsyncMeasurementHandle(recorder, attributes);
}
private Meter otelMeter() { private Meter otelMeter() {
Meter otelMeter = meter.get(); Meter otelMeter = meter.get();
if (otelMeter == null) { if (otelMeter == null) {

View File

@ -33,11 +33,7 @@ final class Bridging {
} }
static String name(Meter.Id id, NamingConvention namingConvention) { static String name(Meter.Id id, NamingConvention namingConvention) {
return name(id.getName(), id, namingConvention); return namingConvention.name(id.getName(), id.getType(), id.getBaseUnit());
}
private static String name(String name, Meter.Id id, NamingConvention namingConvention) {
return namingConvention.name(name, id.getType(), id.getBaseUnit());
} }
static String description(Meter.Id id) { static String description(Meter.Id id) {
@ -56,7 +52,7 @@ final class Bridging {
// use "total_time" instead of "total" to avoid clashing with Statistic.TOTAL // use "total_time" instead of "total" to avoid clashing with Statistic.TOTAL
String statisticStr = String statisticStr =
statistic == Statistic.TOTAL_TIME ? "total_time" : statistic.getTagValueRepresentation(); statistic == Statistic.TOTAL_TIME ? "total_time" : statistic.getTagValueRepresentation();
return name(prefix + statisticStr, id, namingConvention); return namingConvention.name(prefix + statisticStr, id.getType(), id.getBaseUnit());
} }
private Bridging() {} private Bridging() {}

View File

@ -8,14 +8,12 @@ package io.opentelemetry.instrumentation.micrometer.v1_5;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.statisticInstrumentName;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;
import io.micrometer.core.instrument.AbstractDistributionSummary; import io.micrometer.core.instrument.AbstractDistributionSummary;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.NoopHistogram; import io.micrometer.core.instrument.distribution.NoopHistogram;
@ -59,15 +57,17 @@ final class OpenTelemetryDistributionSummary extends AbstractDistributionSummary
max = new TimeWindowMax(clock, distributionStatisticConfig); max = new TimeWindowMax(clock, distributionStatisticConfig);
this.attributes = tagsAsAttributes(id, namingConvention); this.attributes = tagsAsAttributes(id, namingConvention);
String conventionName = name(id, namingConvention);
this.otelHistogram = this.otelHistogram =
otelMeter otelMeter
.histogramBuilder(name(id, namingConvention)) .histogramBuilder(conventionName)
.setDescription(description(id)) .setDescription(description(id))
.setUnit(baseUnit(id)) .setUnit(baseUnit(id))
.build(); .build();
this.maxHandle = this.maxHandle =
asyncInstrumentRegistry.buildGauge( asyncInstrumentRegistry.buildGauge(
statisticInstrumentName(id, Statistic.MAX, namingConvention), conventionName + ".max",
description(id), description(id),
baseUnit(id), baseUnit(id),
attributes, attributes,

View File

@ -6,13 +6,12 @@
package io.opentelemetry.instrumentation.micrometer.v1_5; package io.opentelemetry.instrumentation.micrometer.v1_5;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.statisticInstrumentName; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;
import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString; import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString;
import io.micrometer.core.instrument.FunctionTimer; import io.micrometer.core.instrument.FunctionTimer;
import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.util.MeterEquivalence; import io.micrometer.core.instrument.util.MeterEquivalence;
import io.micrometer.core.instrument.util.TimeUtils; import io.micrometer.core.instrument.util.TimeUtils;
@ -46,8 +45,8 @@ final class OpenTelemetryFunctionTimer<T> implements FunctionTimer, RemovableMet
this.id = id; this.id = id;
this.baseTimeUnit = baseTimeUnit; this.baseTimeUnit = baseTimeUnit;
String countMeterName = statisticInstrumentName(id, Statistic.COUNT, namingConvention); String countMeterName = name(id, namingConvention) + ".count";
String totalTimeMeterName = statisticInstrumentName(id, Statistic.TOTAL_TIME, namingConvention); String totalTimeMeterName = name(id, namingConvention) + ".sum";
Attributes attributes = tagsAsAttributes(id, namingConvention); Attributes attributes = tagsAsAttributes(id, namingConvention);
countMeasurementHandle = countMeasurementHandle =

View File

@ -7,34 +7,25 @@ package io.opentelemetry.instrumentation.micrometer.v1_5;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.statisticInstrumentName;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;
import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString; import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.internal.DefaultLongTaskTimer; import io.micrometer.core.instrument.internal.DefaultLongTaskTimer;
import io.micrometer.core.instrument.util.TimeUtils;
import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.instrumentation.api.internal.AsyncInstrumentRegistry;
import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.instrumentation.api.internal.AsyncInstrumentRegistry.AsyncMeasurementHandle;
import io.opentelemetry.api.metrics.Meter;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements RemovableMeter { final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements RemovableMeter {
private final TimeUnit baseTimeUnit;
private final DistributionStatisticConfig distributionStatisticConfig; private final DistributionStatisticConfig distributionStatisticConfig;
// TODO: use bound instruments when they're available private final AsyncMeasurementHandle activeTasksHandle;
private final DoubleHistogram otelHistogram; private final AsyncMeasurementHandle durationHandle;
private final LongUpDownCounter otelActiveTasksCounter;
private final Attributes attributes;
private volatile boolean removed = false;
OpenTelemetryLongTaskTimer( OpenTelemetryLongTaskTimer(
Id id, Id id,
@ -42,37 +33,29 @@ final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements R
Clock clock, Clock clock,
TimeUnit baseTimeUnit, TimeUnit baseTimeUnit,
DistributionStatisticConfig distributionStatisticConfig, DistributionStatisticConfig distributionStatisticConfig,
Meter otelMeter) { AsyncInstrumentRegistry asyncInstrumentRegistry) {
super(id, clock, baseTimeUnit, distributionStatisticConfig, false); super(id, clock, baseTimeUnit, distributionStatisticConfig, false);
this.baseTimeUnit = baseTimeUnit;
this.distributionStatisticConfig = distributionStatisticConfig; this.distributionStatisticConfig = distributionStatisticConfig;
this.otelHistogram = String conventionName = name(id, namingConvention);
otelMeter Attributes attributes = tagsAsAttributes(id, namingConvention);
.histogramBuilder(name(id, namingConvention)) this.activeTasksHandle =
.setDescription(description(id)) asyncInstrumentRegistry.buildUpDownLongCounter(
.setUnit(getUnitString(baseTimeUnit)) conventionName + ".active",
.build(); description(id),
this.otelActiveTasksCounter = "tasks",
otelMeter attributes,
.upDownCounterBuilder( this,
statisticInstrumentName(id, Statistic.ACTIVE_TASKS, namingConvention)) DefaultLongTaskTimer::activeTasks);
.setDescription(description(id)) this.durationHandle =
.setUnit("tasks") asyncInstrumentRegistry.buildUpDownDoubleCounter(
.build(); conventionName + ".duration",
this.attributes = tagsAsAttributes(id, namingConvention); description(id),
} getUnitString(baseTimeUnit),
attributes,
@Override this,
public Sample start() { t -> t.duration(baseTimeUnit));
Sample original = super.start();
if (removed) {
return original;
}
otelActiveTasksCounter.add(1, attributes);
return new OpenTelemetrySample(original);
} }
@Override @Override
@ -83,41 +66,12 @@ final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements R
@Override @Override
public void onRemove() { public void onRemove() {
removed = true; activeTasksHandle.remove();
durationHandle.remove();
} }
boolean isUsingMicrometerHistograms() { boolean isUsingMicrometerHistograms() {
return distributionStatisticConfig.isPublishingPercentiles() return distributionStatisticConfig.isPublishingPercentiles()
|| distributionStatisticConfig.isPublishingHistogram(); || distributionStatisticConfig.isPublishingHistogram();
} }
private final class OpenTelemetrySample extends Sample {
private final Sample original;
private volatile boolean stopped = false;
private OpenTelemetrySample(Sample original) {
this.original = original;
}
@Override
public long stop() {
if (stopped) {
return -1;
}
stopped = true;
long durationNanos = original.stop();
if (!removed) {
otelActiveTasksCounter.add(-1, attributes);
double time = TimeUtils.nanosToUnit(durationNanos, baseTimeUnit);
otelHistogram.record(time, attributes);
}
return durationNanos;
}
@Override
public double duration(TimeUnit unit) {
return stopped ? -1 : original.duration(unit);
}
}
} }

View File

@ -87,7 +87,7 @@ public final class OpenTelemetryMeterRegistry extends MeterRegistry {
clock, clock,
getBaseTimeUnit(), getBaseTimeUnit(),
distributionStatisticConfig, distributionStatisticConfig,
otelMeter); asyncInstrumentRegistry);
if (timer.isUsingMicrometerHistograms()) { if (timer.isUsingMicrometerHistograms()) {
HistogramGauges.registerWithCommonFormat(timer, this); HistogramGauges.registerWithCommonFormat(timer, this);
} }

View File

@ -7,14 +7,12 @@ package io.opentelemetry.instrumentation.micrometer.v1_5;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.description;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.statisticInstrumentName;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;
import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString; import static io.opentelemetry.instrumentation.micrometer.v1_5.TimeUnitHelper.getUnitString;
import io.micrometer.core.instrument.AbstractTimer; import io.micrometer.core.instrument.AbstractTimer;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.NoopHistogram; import io.micrometer.core.instrument.distribution.NoopHistogram;
@ -62,15 +60,17 @@ final class OpenTelemetryTimer extends AbstractTimer implements RemovableMeter {
this.baseTimeUnit = baseTimeUnit; this.baseTimeUnit = baseTimeUnit;
this.attributes = tagsAsAttributes(id, namingConvention); this.attributes = tagsAsAttributes(id, namingConvention);
String conventionName = name(id, namingConvention);
this.otelHistogram = this.otelHistogram =
otelMeter otelMeter
.histogramBuilder(name(id, namingConvention)) .histogramBuilder(conventionName)
.setDescription(description(id)) .setDescription(description(id))
.setUnit(getUnitString(baseTimeUnit)) .setUnit(getUnitString(baseTimeUnit))
.build(); .build();
this.maxHandle = this.maxHandle =
asyncInstrumentRegistry.buildGauge( asyncInstrumentRegistry.buildGauge(
statisticInstrumentName(id, Statistic.MAX, namingConvention), conventionName + ".max",
description(id), description(id),
getUnitString(baseTimeUnit), getUnitString(baseTimeUnit),
attributes, attributes,

View File

@ -10,6 +10,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attri
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
@ -178,7 +179,7 @@ class NamingConventionTest {
.containsOnly(attributeEntry("test.tag", "test.value"))))); .containsOnly(attributeEntry("test.tag", "test.value")))));
testing.waitAndAssertMetrics( testing.waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"test.renamedFunctionTimer.total_time", "test.renamedFunctionTimer.sum",
metrics -> metrics ->
metrics.anySatisfy( metrics.anySatisfy(
metric -> metric ->
@ -214,6 +215,43 @@ class NamingConventionTest {
.containsOnly(attributeEntry("test.tag", "test.value"))))); .containsOnly(attributeEntry("test.tag", "test.value")))));
} }
@Test
void renameLongTaskTimer() {
// when
LongTaskTimer timer = Metrics.more().longTaskTimer("renamedLongTaskTimer", "tag", "value");
timer.start().stop();
// then
testing.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"test.renamedLongTaskTimer.active",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasLongSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.attributes()
.containsOnly(attributeEntry("test.tag", "test.value")))));
testing.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"test.renamedLongTaskTimer.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDoubleSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.attributes()
.containsOnly(attributeEntry("test.tag", "test.value")))));
}
@Test @Test
void renameTimer() { void renameTimer() {
// when // when

View File

@ -31,7 +31,7 @@ public abstract class AbstractFunctionTimerSecondsTest {
} }
@Test @Test
void testFunctionCounterWithBaseUnitSeconds() throws InterruptedException { void testFunctionTimerWithBaseUnitSeconds() throws InterruptedException {
// given // given
FunctionTimer functionTimer = FunctionTimer functionTimer =
FunctionTimer.builder( FunctionTimer.builder(
@ -70,7 +70,7 @@ public abstract class AbstractFunctionTimerSecondsTest {
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testFunctionTimerSeconds.total_time", "testFunctionTimerSeconds.sum",
metrics -> metrics ->
metrics.anySatisfy( metrics.anySatisfy(
metric -> metric ->
@ -100,8 +100,6 @@ public abstract class AbstractFunctionTimerSecondsTest {
AbstractIterableAssert::isEmpty); AbstractIterableAssert::isEmpty);
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME, "testFunctionTimerSeconds.sum", AbstractIterableAssert::isEmpty);
"testFunctionTimerSeconds.total_time",
AbstractIterableAssert::isEmpty);
} }
} }

View File

@ -72,7 +72,7 @@ public abstract class AbstractFunctionTimerTest {
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testFunctionTimer.total_time", "testFunctionTimer.sum",
metrics -> metrics ->
metrics.anySatisfy( metrics.anySatisfy(
metric -> metric ->
@ -100,7 +100,7 @@ public abstract class AbstractFunctionTimerTest {
INSTRUMENTATION_NAME, "testFunctionTimer.count", AbstractIterableAssert::isEmpty); INSTRUMENTATION_NAME, "testFunctionTimer.count", AbstractIterableAssert::isEmpty);
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, "testFunctionTimer.total_time", AbstractIterableAssert::isEmpty); INSTRUMENTATION_NAME, "testFunctionTimer.sum", AbstractIterableAssert::isEmpty);
} }
@Test @Test
@ -121,7 +121,7 @@ public abstract class AbstractFunctionTimerTest {
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testNanoFunctionTimer.total_time", "testNanoFunctionTimer.sum",
metrics -> metrics ->
metrics.anySatisfy( metrics.anySatisfy(
metric -> metric ->
@ -162,7 +162,7 @@ public abstract class AbstractFunctionTimerTest {
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testFunctionTimerWithTags.total_time", "testFunctionTimerWithTags.sum",
metrics -> metrics ->
metrics.anySatisfy( metrics.anySatisfy(
metric -> metric ->

View File

@ -12,6 +12,7 @@ import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.assertj.core.api.AbstractIterableAssert;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -58,31 +59,33 @@ public abstract class AbstractLongTaskTimerSecondsTest {
.hasValue(1) .hasValue(1)
.attributes() .attributes()
.containsOnly(attributeEntry("tag", "value"))))); .containsOnly(attributeEntry("tag", "value")))));
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimerSeconds.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDescription("This is a test long task timer")
.hasUnit("s")
.hasDoubleSum()
.isNotMonotonic()
.points()
.satisfiesExactly(
point -> {
assertThat(point)
.attributes()
.containsOnly(attributeEntry("tag", "value"));
// any value >0 - duration of currently running tasks
assertThat(point.getValue()).isPositive();
})));
// when // when
TimeUnit.MILLISECONDS.sleep(100); TimeUnit.MILLISECONDS.sleep(100);
sample.stop(); sample.stop();
// then // then
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimerSeconds",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDescription("This is a test long task timer")
.hasUnit("s")
.hasDoubleHistogram()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasSumGreaterThan(0.1)
.hasCount(1)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
@ -99,46 +102,38 @@ public abstract class AbstractLongTaskTimerSecondsTest {
.hasValue(0) .hasValue(0)
.attributes() .attributes()
.containsOnly(attributeEntry("tag", "value"))))); .containsOnly(attributeEntry("tag", "value")))));
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimerSeconds.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDoubleSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasValue(0)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
testing().clearData(); testing().clearData();
// when timer is removed from the registry // when timer is removed from the registry
Metrics.globalRegistry.remove(timer); Metrics.globalRegistry.remove(timer);
sample = timer.start(); timer.start();
// then no tasks are active after starting a new sample // then no tasks are active after starting a new sample
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testLongTaskTimerSeconds.active", "testLongTaskTimerSeconds.active",
metrics -> AbstractIterableAssert::isEmpty);
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasLongSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasValue(0)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
// when
TimeUnit.MILLISECONDS.sleep(100);
sample.stop();
// then sample of a removed timer does not record any data
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
"testLongTaskTimerSeconds", "testLongTaskTimerSeconds.duration",
metrics -> AbstractIterableAssert::isEmpty);
metrics.allSatisfy(
metric ->
assertThat(metric)
.hasDoubleHistogram()
.points()
.noneSatisfy(
point -> assertThat(point).hasSumGreaterThan(0.2).hasCount(2))));
} }
} }

View File

@ -12,6 +12,7 @@ import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.assertj.core.api.AbstractIterableAssert;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -58,31 +59,33 @@ public abstract class AbstractLongTaskTimerTest {
.hasValue(1) .hasValue(1)
.attributes() .attributes()
.containsOnly(attributeEntry("tag", "value"))))); .containsOnly(attributeEntry("tag", "value")))));
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimer.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDescription("This is a test long task timer")
.hasUnit("ms")
.hasDoubleSum()
.isNotMonotonic()
.points()
.satisfiesExactly(
point -> {
assertThat(point)
.attributes()
.containsOnly(attributeEntry("tag", "value"));
// any value >0 - duration of currently running tasks
assertThat(point.getValue()).isPositive();
})));
// when // when
TimeUnit.MILLISECONDS.sleep(100); TimeUnit.MILLISECONDS.sleep(100);
sample.stop(); sample.stop();
// then // then
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimer",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDescription("This is a test long task timer")
.hasUnit("ms")
.hasDoubleHistogram()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasSumGreaterThan(100)
.hasCount(1)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME,
@ -99,76 +102,34 @@ public abstract class AbstractLongTaskTimerTest {
.hasValue(0) .hasValue(0)
.attributes() .attributes()
.containsOnly(attributeEntry("tag", "value"))))); .containsOnly(attributeEntry("tag", "value")))));
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimer.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasDoubleSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasValue(0)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
testing().clearData(); testing().clearData();
// when timer is removed from the registry // when timer is removed from the registry
Metrics.globalRegistry.remove(timer); Metrics.globalRegistry.remove(timer);
sample = timer.start(); timer.start();
// then no tasks are active after starting a new sample // then no tasks are active after starting a new sample
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME, "testLongTaskTimer.active", AbstractIterableAssert::isEmpty);
"testLongTaskTimer.active",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasLongSum()
.points()
.satisfiesExactly(
point ->
assertThat(point)
.hasValue(0)
.attributes()
.containsOnly(attributeEntry("tag", "value")))));
// when
TimeUnit.MILLISECONDS.sleep(100);
sample.stop();
// then sample of a removed timer does not record any data
testing() testing()
.waitAndAssertMetrics( .waitAndAssertMetrics(
INSTRUMENTATION_NAME, INSTRUMENTATION_NAME, "testLongTaskTimer.duration", AbstractIterableAssert::isEmpty);
"testLongTaskTimer",
metrics ->
metrics.allSatisfy(
metric ->
assertThat(metric)
.hasDoubleHistogram()
.points()
.noneSatisfy(
point -> assertThat(point).hasSumGreaterThan(200).hasCount(2))));
}
@Test
void testMultipleSampleStopCalls() throws InterruptedException {
// given
LongTaskTimer timer =
LongTaskTimer.builder("testLongTaskTimerSampleStop").register(Metrics.globalRegistry);
// when stop() is called multiple times
LongTaskTimer.Sample sample = timer.start();
TimeUnit.MILLISECONDS.sleep(100);
sample.stop();
sample.stop();
sample.stop();
// then only the first time is recorded
testing()
.waitAndAssertMetrics(
INSTRUMENTATION_NAME,
"testLongTaskTimerSampleStop",
metrics ->
metrics.allSatisfy(
metric ->
assertThat(metric)
.hasDoubleHistogram()
.points()
.satisfiesExactly(
point -> assertThat(point).hasSumGreaterThan(100).hasCount(1))));
} }
} }