Micrometer bridge instrumentation (#4919)
* Micrometer bridge instrumentation * gauges with the same name and different attributes * weak ref gauge * one more test * disable by default + muzzle * code review comments * log one-time warning * make AsyncInstrumentRegistry actually thread safe * code review comments * one more minor fix
This commit is contained in:
parent
606f39c9c7
commit
a022f0ce59
|
@ -0,0 +1,21 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.javaagent-instrumentation")
|
||||||
|
}
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group.set("io.micrometer")
|
||||||
|
module.set("micrometer-core")
|
||||||
|
versions.set("[1.5.0,)")
|
||||||
|
assertInverse.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
library("io.micrometer:micrometer-core:1.5.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: disabled by default, since not all instruments are implemented
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
jvmArgs("-Dotel.instrumentation.micrometer.enabled=true")
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.baseUnit;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.description;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.metrics.Meter;
|
||||||
|
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
|
||||||
|
import io.opentelemetry.instrumentation.api.internal.GuardedBy;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
final class AsyncInstrumentRegistry {
|
||||||
|
|
||||||
|
private final Meter meter;
|
||||||
|
|
||||||
|
@GuardedBy("gauges")
|
||||||
|
private final Map<String, GaugeMeasurementsRecorder> gauges = new HashMap<>();
|
||||||
|
|
||||||
|
AsyncInstrumentRegistry(Meter meter) {
|
||||||
|
this.meter = meter;
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> void buildGauge(
|
||||||
|
io.micrometer.core.instrument.Meter.Id meterId,
|
||||||
|
Attributes attributes,
|
||||||
|
@Nullable T obj,
|
||||||
|
ToDoubleFunction<T> objMetric) {
|
||||||
|
|
||||||
|
synchronized (gauges) {
|
||||||
|
GaugeMeasurementsRecorder recorder =
|
||||||
|
gauges.computeIfAbsent(
|
||||||
|
meterId.getName(),
|
||||||
|
n -> {
|
||||||
|
GaugeMeasurementsRecorder recorderCallback = new GaugeMeasurementsRecorder();
|
||||||
|
meter
|
||||||
|
.gaugeBuilder(meterId.getName())
|
||||||
|
.setDescription(description(meterId))
|
||||||
|
.setUnit(baseUnit(meterId))
|
||||||
|
.buildWithCallback(recorderCallback);
|
||||||
|
return recorderCallback;
|
||||||
|
});
|
||||||
|
recorder.addGaugeMeasurement(attributes, obj, objMetric);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeGauge(String name, Attributes attributes) {
|
||||||
|
synchronized (gauges) {
|
||||||
|
GaugeMeasurementsRecorder recorder = gauges.get(name);
|
||||||
|
if (recorder != null) {
|
||||||
|
recorder.removeGaugeMeasurement(attributes);
|
||||||
|
// if this was the last measurement then let's remove the whole recorder
|
||||||
|
if (recorder.isEmpty()) {
|
||||||
|
gauges.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class GaugeMeasurementsRecorder implements Consumer<ObservableDoubleMeasurement> {
|
||||||
|
|
||||||
|
@GuardedBy("gauges")
|
||||||
|
private final Map<Attributes, GaugeInfo> measurements = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(ObservableDoubleMeasurement measurement) {
|
||||||
|
Map<Attributes, GaugeInfo> measurementsCopy;
|
||||||
|
synchronized (gauges) {
|
||||||
|
measurementsCopy = new HashMap<>(measurements);
|
||||||
|
}
|
||||||
|
|
||||||
|
measurementsCopy.forEach(
|
||||||
|
(attributes, gauge) -> {
|
||||||
|
Object obj = gauge.objWeakRef.get();
|
||||||
|
if (obj != null) {
|
||||||
|
measurement.record(gauge.metricFunction.applyAsDouble(obj), attributes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> void addGaugeMeasurement(
|
||||||
|
Attributes attributes, @Nullable T obj, ToDoubleFunction<T> objMetric) {
|
||||||
|
synchronized (gauges) {
|
||||||
|
measurements.put(attributes, new GaugeInfo(obj, (ToDoubleFunction<Object>) objMetric));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeGaugeMeasurement(Attributes attributes) {
|
||||||
|
synchronized (gauges) {
|
||||||
|
measurements.remove(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
synchronized (gauges) {
|
||||||
|
return measurements.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GaugeInfo {
|
||||||
|
|
||||||
|
private final WeakReference<Object> objWeakRef;
|
||||||
|
private final ToDoubleFunction<Object> metricFunction;
|
||||||
|
|
||||||
|
private GaugeInfo(@Nullable Object obj, ToDoubleFunction<Object> metricFunction) {
|
||||||
|
this.objWeakRef = new WeakReference<>(obj);
|
||||||
|
this.metricFunction = metricFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Meter;
|
||||||
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.api.cache.Cache;
|
||||||
|
|
||||||
|
final class Bridging {
|
||||||
|
|
||||||
|
private static final Cache<String, AttributeKey<String>> tagsCache = Cache.bounded(1024);
|
||||||
|
|
||||||
|
static Attributes toAttributes(Iterable<Tag> tags) {
|
||||||
|
if (!tags.iterator().hasNext()) {
|
||||||
|
return Attributes.empty();
|
||||||
|
}
|
||||||
|
AttributesBuilder builder = Attributes.builder();
|
||||||
|
for (Tag tag : tags) {
|
||||||
|
builder.put(tagsCache.computeIfAbsent(tag.getKey(), AttributeKey::stringKey), tag.getValue());
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String description(Meter.Id id) {
|
||||||
|
String description = id.getDescription();
|
||||||
|
return description == null ? "" : description;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String baseUnit(Meter.Id id) {
|
||||||
|
String baseUnit = id.getBaseUnit();
|
||||||
|
return baseUnit == null ? "1" : baseUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bridging() {}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
public class MetricsInstrumentation implements TypeInstrumentation {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return named("io.micrometer.core.instrument.Metrics");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TypeTransformer transformer) {
|
||||||
|
transformer.applyAdviceToMethod(
|
||||||
|
isTypeInitializer(), this.getClass().getName() + "$StaticInitializerAdvice");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class StaticInitializerAdvice {
|
||||||
|
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||||
|
public static void onExit() {
|
||||||
|
Metrics.addRegistry(MicrometerSingletons.meterRegistry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(InstrumentationModule.class)
|
||||||
|
public class MicrometerInstrumentationModule extends InstrumentationModule {
|
||||||
|
|
||||||
|
public MicrometerInstrumentationModule() {
|
||||||
|
super("micrometer", "micrometer-1.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
|
||||||
|
// added in 1.5
|
||||||
|
return hasClassesNamed("io.micrometer.core.instrument.config.validate.Validated");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultEnabled() {
|
||||||
|
// TODO: disabled by default, since not all instruments are implemented
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
|
return Collections.singletonList(new MetricsInstrumentation());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||||
|
|
||||||
|
public final class MicrometerSingletons {
|
||||||
|
|
||||||
|
private static final MeterRegistry METER_REGISTRY =
|
||||||
|
OpenTelemetryMeterRegistry.create(GlobalOpenTelemetry.get());
|
||||||
|
|
||||||
|
public static MeterRegistry meterRegistry() {
|
||||||
|
return METER_REGISTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MicrometerSingletons() {}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.baseUnit;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.description;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.toAttributes;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.Measurement;
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.metrics.DoubleCounter;
|
||||||
|
import io.opentelemetry.api.metrics.Meter;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
final class OpenTelemetryCounter implements Counter, RemovableMeter {
|
||||||
|
|
||||||
|
private final Id id;
|
||||||
|
// TODO: use bound instruments when they're available
|
||||||
|
private final DoubleCounter otelCounter;
|
||||||
|
private final Attributes attributes;
|
||||||
|
|
||||||
|
private volatile boolean removed = false;
|
||||||
|
|
||||||
|
OpenTelemetryCounter(Id id, Meter otelMeter) {
|
||||||
|
this.id = id;
|
||||||
|
this.otelCounter =
|
||||||
|
otelMeter
|
||||||
|
.counterBuilder(id.getName())
|
||||||
|
.setDescription(description(id))
|
||||||
|
.setUnit(baseUnit(id))
|
||||||
|
.ofDoubles()
|
||||||
|
.build();
|
||||||
|
this.attributes = toAttributes(id.getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void increment(double v) {
|
||||||
|
if (removed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
otelCounter.add(v, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double count() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Measurement> measure() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Id getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemove() {
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.toAttributes;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Gauge;
|
||||||
|
import io.micrometer.core.instrument.Measurement;
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
final class OpenTelemetryGauge<T> implements Gauge, RemovableMeter {
|
||||||
|
|
||||||
|
private final Id id;
|
||||||
|
private final Attributes attributes;
|
||||||
|
private final AsyncInstrumentRegistry asyncInstrumentRegistry;
|
||||||
|
|
||||||
|
OpenTelemetryGauge(
|
||||||
|
Id id,
|
||||||
|
@Nullable T obj,
|
||||||
|
ToDoubleFunction<T> objMetric,
|
||||||
|
AsyncInstrumentRegistry asyncInstrumentRegistry) {
|
||||||
|
this.id = id;
|
||||||
|
this.attributes = toAttributes(id.getTags());
|
||||||
|
this.asyncInstrumentRegistry = asyncInstrumentRegistry;
|
||||||
|
|
||||||
|
asyncInstrumentRegistry.buildGauge(id, attributes, obj, objMetric);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double value() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Measurement> measure() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Id getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemove() {
|
||||||
|
asyncInstrumentRegistry.removeGauge(id.getName(), attributes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Clock;
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.DistributionSummary;
|
||||||
|
import io.micrometer.core.instrument.FunctionCounter;
|
||||||
|
import io.micrometer.core.instrument.FunctionTimer;
|
||||||
|
import io.micrometer.core.instrument.Gauge;
|
||||||
|
import io.micrometer.core.instrument.LongTaskTimer;
|
||||||
|
import io.micrometer.core.instrument.Measurement;
|
||||||
|
import io.micrometer.core.instrument.Meter;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
|
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
||||||
|
import io.micrometer.core.instrument.distribution.HistogramGauges;
|
||||||
|
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import java.util.function.ToLongFunction;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public final class OpenTelemetryMeterRegistry extends MeterRegistry {
|
||||||
|
|
||||||
|
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5";
|
||||||
|
|
||||||
|
public static MeterRegistry create(OpenTelemetry openTelemetry) {
|
||||||
|
OpenTelemetryMeterRegistry openTelemetryMeterRegistry =
|
||||||
|
new OpenTelemetryMeterRegistry(
|
||||||
|
Clock.SYSTEM, openTelemetry.getMeterProvider().get(INSTRUMENTATION_NAME));
|
||||||
|
openTelemetryMeterRegistry.config().onMeterRemoved(OpenTelemetryMeterRegistry::onMeterRemoved);
|
||||||
|
return openTelemetryMeterRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final io.opentelemetry.api.metrics.Meter otelMeter;
|
||||||
|
private final AsyncInstrumentRegistry asyncInstrumentRegistry;
|
||||||
|
|
||||||
|
private OpenTelemetryMeterRegistry(Clock clock, io.opentelemetry.api.metrics.Meter otelMeter) {
|
||||||
|
super(clock);
|
||||||
|
this.otelMeter = otelMeter;
|
||||||
|
this.asyncInstrumentRegistry = new AsyncInstrumentRegistry(otelMeter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> Gauge newGauge(Meter.Id id, @Nullable T t, ToDoubleFunction<T> toDoubleFunction) {
|
||||||
|
return new OpenTelemetryGauge<>(id, t, toDoubleFunction, asyncInstrumentRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Counter newCounter(Meter.Id id) {
|
||||||
|
return new OpenTelemetryCounter(id, otelMeter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LongTaskTimer newLongTaskTimer(
|
||||||
|
Meter.Id id, DistributionStatisticConfig distributionStatisticConfig) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Timer newTimer(
|
||||||
|
Meter.Id id,
|
||||||
|
DistributionStatisticConfig distributionStatisticConfig,
|
||||||
|
PauseDetector pauseDetector) {
|
||||||
|
OpenTelemetryTimer timer =
|
||||||
|
new OpenTelemetryTimer(id, clock, distributionStatisticConfig, pauseDetector, otelMeter);
|
||||||
|
if (timer.isUsingMicrometerHistograms()) {
|
||||||
|
HistogramGauges.registerWithCommonFormat(timer, this);
|
||||||
|
}
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DistributionSummary newDistributionSummary(
|
||||||
|
Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double v) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable<Measurement> iterable) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> FunctionTimer newFunctionTimer(
|
||||||
|
Meter.Id id,
|
||||||
|
T t,
|
||||||
|
ToLongFunction<T> toLongFunction,
|
||||||
|
ToDoubleFunction<T> toDoubleFunction,
|
||||||
|
TimeUnit timeUnit) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> FunctionCounter newFunctionCounter(
|
||||||
|
Meter.Id id, T t, ToDoubleFunction<T> toDoubleFunction) {
|
||||||
|
throw new UnsupportedOperationException("Not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TimeUnit getBaseTimeUnit() {
|
||||||
|
return TimeUnit.MILLISECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DistributionStatisticConfig defaultHistogramConfig() {
|
||||||
|
return DistributionStatisticConfig.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onMeterRemoved(Meter meter) {
|
||||||
|
if (meter instanceof RemovableMeter) {
|
||||||
|
((RemovableMeter) meter).onRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.description;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.Bridging.toAttributes;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.AbstractTimer;
|
||||||
|
import io.micrometer.core.instrument.Clock;
|
||||||
|
import io.micrometer.core.instrument.Measurement;
|
||||||
|
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
||||||
|
import io.micrometer.core.instrument.distribution.NoopHistogram;
|
||||||
|
import io.micrometer.core.instrument.distribution.TimeWindowMax;
|
||||||
|
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
|
||||||
|
import io.micrometer.core.instrument.util.TimeUtils;
|
||||||
|
import io.opentelemetry.api.common.Attributes;
|
||||||
|
import io.opentelemetry.api.metrics.DoubleHistogram;
|
||||||
|
import io.opentelemetry.api.metrics.Meter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
|
final class OpenTelemetryTimer extends AbstractTimer implements RemovableMeter {
|
||||||
|
|
||||||
|
private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);
|
||||||
|
|
||||||
|
// TODO: use bound instruments when they're available
|
||||||
|
private final DoubleHistogram otelHistogram;
|
||||||
|
private final Attributes attributes;
|
||||||
|
private final Measurements measurements;
|
||||||
|
|
||||||
|
private volatile boolean removed = false;
|
||||||
|
|
||||||
|
OpenTelemetryTimer(
|
||||||
|
Id id,
|
||||||
|
Clock clock,
|
||||||
|
DistributionStatisticConfig distributionStatisticConfig,
|
||||||
|
PauseDetector pauseDetector,
|
||||||
|
Meter otelMeter) {
|
||||||
|
super(id, clock, distributionStatisticConfig, pauseDetector, TimeUnit.MILLISECONDS, false);
|
||||||
|
|
||||||
|
this.otelHistogram =
|
||||||
|
otelMeter
|
||||||
|
.histogramBuilder(id.getName())
|
||||||
|
.setDescription(description(id))
|
||||||
|
.setUnit("ms")
|
||||||
|
.build();
|
||||||
|
this.attributes = toAttributes(id.getTags());
|
||||||
|
|
||||||
|
if (isUsingMicrometerHistograms()) {
|
||||||
|
measurements = new MicrometerHistogramMeasurements(clock, distributionStatisticConfig);
|
||||||
|
} else {
|
||||||
|
measurements = NoopMeasurements.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isUsingMicrometerHistograms() {
|
||||||
|
return histogram != NoopHistogram.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void recordNonNegative(long amount, TimeUnit unit) {
|
||||||
|
if (amount >= 0 && !removed) {
|
||||||
|
long nanos = unit.toNanos(amount);
|
||||||
|
double time = nanos / NANOS_PER_MS;
|
||||||
|
otelHistogram.record(time, attributes);
|
||||||
|
measurements.record(nanos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long count() {
|
||||||
|
return measurements.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double totalTime(TimeUnit unit) {
|
||||||
|
return measurements.totalTime(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double max(TimeUnit unit) {
|
||||||
|
return measurements.max(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Measurement> measure() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemove() {
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Measurements {
|
||||||
|
void record(long nanos);
|
||||||
|
|
||||||
|
long count();
|
||||||
|
|
||||||
|
double totalTime(TimeUnit unit);
|
||||||
|
|
||||||
|
double max(TimeUnit unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if micrometer histograms are not being used then there's no need to keep any local state
|
||||||
|
// OpenTelemetry metrics bridge does not support reading measurements
|
||||||
|
enum NoopMeasurements implements Measurements {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void record(long nanos) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long count() {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double totalTime(TimeUnit unit) {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double max(TimeUnit unit) {
|
||||||
|
UnsupportedReadLogger.logWarning();
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate count, totalTime and max value for the use of micrometer histograms
|
||||||
|
// kinda similar to how DropwizardTimer does that
|
||||||
|
private static final class MicrometerHistogramMeasurements implements Measurements {
|
||||||
|
|
||||||
|
private final LongAdder count = new LongAdder();
|
||||||
|
private final LongAdder totalTime = new LongAdder();
|
||||||
|
private final TimeWindowMax max;
|
||||||
|
|
||||||
|
MicrometerHistogramMeasurements(
|
||||||
|
Clock clock, DistributionStatisticConfig distributionStatisticConfig) {
|
||||||
|
this.max = new TimeWindowMax(clock, distributionStatisticConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void record(long nanos) {
|
||||||
|
count.increment();
|
||||||
|
totalTime.add(nanos);
|
||||||
|
max.record(nanos, TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long count() {
|
||||||
|
return count.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double totalTime(TimeUnit unit) {
|
||||||
|
return TimeUtils.nanosToUnit(totalTime.sum(), unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double max(TimeUnit unit) {
|
||||||
|
return max.poll(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
interface RemovableMeter {
|
||||||
|
|
||||||
|
void onRemove();
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
final class UnsupportedReadLogger {
|
||||||
|
|
||||||
|
static {
|
||||||
|
Logger logger = LoggerFactory.getLogger(OpenTelemetryMeterRegistry.class);
|
||||||
|
logger.warn("OpenTelemetry metrics bridge does not support reading measurements");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logWarning() {
|
||||||
|
// do nothing; the warning will be logged exactly once when this class is loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnsupportedReadLogger() {}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.metrics.MetricAssertions.assertThat;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
class CounterTest {
|
||||||
|
|
||||||
|
static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5";
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void cleanupMeters() {
|
||||||
|
Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCounter() {
|
||||||
|
// given
|
||||||
|
Counter counter =
|
||||||
|
Counter.builder("testCounter")
|
||||||
|
.description("This is a test counter")
|
||||||
|
.tags("tag", "value")
|
||||||
|
.baseUnit("items")
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// when
|
||||||
|
counter.increment();
|
||||||
|
counter.increment(2);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testCounter",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDescription("This is a test counter")
|
||||||
|
.hasUnit("items")
|
||||||
|
.hasDoubleSum()
|
||||||
|
.isMonotonic()
|
||||||
|
.isCumulative()
|
||||||
|
.points()
|
||||||
|
.satisfiesExactly(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(3)
|
||||||
|
.attributes()
|
||||||
|
.containsOnly(attributeEntry("tag", "value")))));
|
||||||
|
testing.clearData();
|
||||||
|
|
||||||
|
// when
|
||||||
|
Metrics.globalRegistry.remove(counter);
|
||||||
|
counter.increment();
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testCounter",
|
||||||
|
metrics ->
|
||||||
|
metrics.allSatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDoubleSum()
|
||||||
|
.points()
|
||||||
|
.noneSatisfy(point -> assertThat(point).hasValue(4))));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.metrics.MetricAssertions.assertThat;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Gauge;
|
||||||
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.opentelemetry.instrumentation.test.utils.GcUtils;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import org.assertj.core.api.AbstractIterableAssert;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
class GaugeTest {
|
||||||
|
|
||||||
|
static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5";
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void cleanupMeters() {
|
||||||
|
Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGauge() throws Exception {
|
||||||
|
// when
|
||||||
|
Gauge gauge =
|
||||||
|
Gauge.builder("testGauge", () -> 42)
|
||||||
|
.description("This is a test gauge")
|
||||||
|
.tags("tag", "value")
|
||||||
|
.baseUnit("items")
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testGauge",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDescription("This is a test gauge")
|
||||||
|
.hasUnit("items")
|
||||||
|
.hasDoubleGauge()
|
||||||
|
.points()
|
||||||
|
.satisfiesExactly(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(42)
|
||||||
|
.attributes()
|
||||||
|
.containsOnly(attributeEntry("tag", "value")))));
|
||||||
|
|
||||||
|
// when
|
||||||
|
Metrics.globalRegistry.remove(gauge);
|
||||||
|
Thread.sleep(10); // give time for any inflight metric export to be received
|
||||||
|
testing.clearData();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Thread.sleep(100); // interval of the test metrics exporter
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME, "testGauge", AbstractIterableAssert::isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void gaugesWithSameNameAndDifferentTags() {
|
||||||
|
// when
|
||||||
|
Gauge.builder("testGaugeWithTags", () -> 12)
|
||||||
|
.description("First description wins")
|
||||||
|
.baseUnit("items")
|
||||||
|
.tags("tag", "1")
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
Gauge.builder("testGaugeWithTags", () -> 42)
|
||||||
|
.description("ignored")
|
||||||
|
.baseUnit("ignored")
|
||||||
|
.tags("tag", "2")
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testGaugeWithTags",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDescription("First description wins")
|
||||||
|
.hasUnit("items")
|
||||||
|
.hasDoubleGauge()
|
||||||
|
.points()
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(12)
|
||||||
|
.attributes()
|
||||||
|
.containsOnly(attributeEntry("tag", "1")))
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(42)
|
||||||
|
.attributes()
|
||||||
|
.containsOnly(attributeEntry("tag", "2")))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWeakRefGauge() throws InterruptedException {
|
||||||
|
// when
|
||||||
|
AtomicLong num = new AtomicLong(42);
|
||||||
|
Gauge.builder("testWeakRefGauge", num, AtomicLong::get)
|
||||||
|
.strongReference(false)
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testWeakRefGauge",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDoubleGauge()
|
||||||
|
.points()
|
||||||
|
.satisfiesExactly(point -> assertThat(point).hasValue(42))));
|
||||||
|
testing.clearData();
|
||||||
|
|
||||||
|
// when
|
||||||
|
WeakReference<AtomicLong> numWeakRef = new WeakReference<>(num);
|
||||||
|
num = null;
|
||||||
|
GcUtils.awaitGc(numWeakRef);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Thread.sleep(100); // interval of the test metrics exporter
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME, "testWeakRefGauge", AbstractIterableAssert::isEmpty);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;
|
||||||
|
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
|
||||||
|
import static io.opentelemetry.sdk.testing.assertj.metrics.MetricAssertions.assertThat;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||||
|
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
@SuppressWarnings("PreferJavaTimeOverload")
|
||||||
|
class TimerTest {
|
||||||
|
|
||||||
|
static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5";
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void cleanupMeters() {
|
||||||
|
Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTimer() {
|
||||||
|
// given
|
||||||
|
Timer timer =
|
||||||
|
Timer.builder("testTimer")
|
||||||
|
.description("This is a test timer")
|
||||||
|
.tags("tag", "value")
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// when
|
||||||
|
timer.record(42, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testTimer",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDescription("This is a test timer")
|
||||||
|
.hasUnit("ms")
|
||||||
|
.hasDoubleHistogram()
|
||||||
|
.points()
|
||||||
|
.satisfiesExactly(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasSum(42_000)
|
||||||
|
.hasCount(1)
|
||||||
|
.attributes()
|
||||||
|
.containsOnly(attributeEntry("tag", "value")))));
|
||||||
|
testing.clearData();
|
||||||
|
|
||||||
|
// when
|
||||||
|
Metrics.globalRegistry.remove(timer);
|
||||||
|
timer.record(12, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testTimer",
|
||||||
|
metrics ->
|
||||||
|
metrics.allSatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDoubleHistogram()
|
||||||
|
.points()
|
||||||
|
.noneSatisfy(point -> assertThat(point).hasSum(54_000).hasCount(2))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNanoPrecision() {
|
||||||
|
// given
|
||||||
|
Timer timer = Timer.builder("testNanoTimer").register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// when
|
||||||
|
timer.record(1_234_000, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testNanoTimer",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasUnit("ms")
|
||||||
|
.hasDoubleHistogram()
|
||||||
|
.points()
|
||||||
|
.satisfiesExactly(
|
||||||
|
point -> assertThat(point).hasSum(1.234).hasCount(1).attributes())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMicrometerHistogram() {
|
||||||
|
// given
|
||||||
|
Timer timer =
|
||||||
|
Timer.builder("testHistogram")
|
||||||
|
.description("This is a test timer")
|
||||||
|
.tags("tag", "value")
|
||||||
|
.serviceLevelObjectives(
|
||||||
|
Duration.ofSeconds(1),
|
||||||
|
Duration.ofSeconds(10),
|
||||||
|
Duration.ofSeconds(100),
|
||||||
|
Duration.ofSeconds(1000))
|
||||||
|
.distributionStatisticBufferLength(10)
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// when
|
||||||
|
timer.record(500, TimeUnit.MILLISECONDS);
|
||||||
|
timer.record(5, TimeUnit.SECONDS);
|
||||||
|
timer.record(50, TimeUnit.SECONDS);
|
||||||
|
timer.record(500, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testHistogram.histogram",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDoubleGauge()
|
||||||
|
.points()
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(1)
|
||||||
|
.attributes()
|
||||||
|
.containsEntry("le", "1000"))
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(2)
|
||||||
|
.attributes()
|
||||||
|
.containsEntry("le", "10000"))
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(3)
|
||||||
|
.attributes()
|
||||||
|
.containsEntry("le", "100000"))
|
||||||
|
.anySatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point)
|
||||||
|
.hasValue(4)
|
||||||
|
.attributes()
|
||||||
|
.containsEntry("le", "1000000"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMicrometerPercentiles() {
|
||||||
|
// given
|
||||||
|
Timer timer =
|
||||||
|
Timer.builder("testPercentiles")
|
||||||
|
.description("This is a test timer")
|
||||||
|
.tags("tag", "value")
|
||||||
|
.publishPercentiles(0.5, 0.95, 0.99)
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
|
||||||
|
// when
|
||||||
|
timer.record(50, TimeUnit.MILLISECONDS);
|
||||||
|
timer.record(100, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// then
|
||||||
|
testing.waitAndAssertMetrics(
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
"testPercentiles.percentile",
|
||||||
|
metrics ->
|
||||||
|
metrics.anySatisfy(
|
||||||
|
metric ->
|
||||||
|
assertThat(metric)
|
||||||
|
.hasDoubleGauge()
|
||||||
|
.points()
|
||||||
|
.anySatisfy(
|
||||||
|
point -> assertThat(point).attributes().containsEntry("phi", "0.5"))
|
||||||
|
.anySatisfy(
|
||||||
|
point -> assertThat(point).attributes().containsEntry("phi", "0.95"))
|
||||||
|
.anySatisfy(
|
||||||
|
point -> assertThat(point).attributes().containsEntry("phi", "0.99"))));
|
||||||
|
}
|
||||||
|
}
|
|
@ -280,6 +280,7 @@ include(":instrumentation:logback:logback-mdc-1.0:javaagent")
|
||||||
include(":instrumentation:logback:logback-mdc-1.0:library")
|
include(":instrumentation:logback:logback-mdc-1.0:library")
|
||||||
include(":instrumentation:logback:logback-mdc-1.0:testing")
|
include(":instrumentation:logback:logback-mdc-1.0:testing")
|
||||||
include(":instrumentation:methods:javaagent")
|
include(":instrumentation:methods:javaagent")
|
||||||
|
include(":instrumentation:micrometer:micrometer-1.5:javaagent")
|
||||||
include(":instrumentation:mongo:mongo-3.1:javaagent")
|
include(":instrumentation:mongo:mongo-3.1:javaagent")
|
||||||
include(":instrumentation:mongo:mongo-3.1:library")
|
include(":instrumentation:mongo:mongo-3.1:library")
|
||||||
include(":instrumentation:mongo:mongo-3.1:testing")
|
include(":instrumentation:mongo:mongo-3.1:testing")
|
||||||
|
|
Loading…
Reference in New Issue