Skip to content

Commit

Permalink
Add a dropwizard-metrics -> OTel metrics bridge (open-telemetry#6259)
Browse files Browse the repository at this point in the history
* Add a dropwizard-metrics -> OTel metrics bridge

* disable by default

* enable metrics for test
  • Loading branch information
Mateusz Rzeszutek authored and LironKS committed Oct 31, 2022
1 parent 6f22962 commit 0a7e023
Show file tree
Hide file tree
Showing 13 changed files with 781 additions and 108 deletions.
209 changes: 106 additions & 103 deletions docs/supported-libraries.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.dropwizard.metrics")
module.set("metrics-core")
versions.set("[4.0.0,)")
assertInverse.set(true)
}
}

dependencies {
library("io.dropwizard.metrics:metrics-core:4.0.0")
}

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.dropwizard-metrics.enabled=true")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.dropwizardmetrics;

import static io.opentelemetry.javaagent.instrumentation.dropwizardmetrics.DropwizardSingletons.metrics;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.codahale.metrics.Counter;
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 CounterInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.codahale.metrics.Counter");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("inc").and(takesArguments(long.class)), this.getClass().getName() + "$IncAdvice");
transformer.applyAdviceToMethod(
named("dec").and(takesArguments(long.class)), this.getClass().getName() + "$DecAdvice");
}

@SuppressWarnings("unused")
public static class IncAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This Counter counter, @Advice.Argument(0) long increment) {
metrics().counterAdd(counter, increment);
}
}

@SuppressWarnings("unused")
public static class DecAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This Counter counter, @Advice.Argument(0) long decrement) {
metrics().counterAdd(counter, -decrement);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.dropwizardmetrics;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistryListener;
import com.codahale.metrics.Timer;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public final class DropwizardMetricsAdapter implements MetricRegistryListener {

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);

private static final VirtualField<Counter, LongUpDownCounter> otelUpDownCounterField =
VirtualField.find(Counter.class, LongUpDownCounter.class);
private static final VirtualField<Histogram, LongHistogram> otelHistogramField =
VirtualField.find(Histogram.class, LongHistogram.class);
private static final VirtualField<Meter, LongCounter> otelCounterField =
VirtualField.find(Meter.class, LongCounter.class);
private static final VirtualField<Timer, DoubleHistogram> otelDoubleHistogramField =
VirtualField.find(Timer.class, DoubleHistogram.class);

private final io.opentelemetry.api.metrics.Meter otelMeter;

private final Map<String, DoubleHistogram> otelDoubleHistograms = new ConcurrentHashMap<>();
private final Map<String, LongCounter> otelCounters = new ConcurrentHashMap<>();
private final Map<String, LongHistogram> otelHistograms = new ConcurrentHashMap<>();
private final Map<String, LongUpDownCounter> otelUpDownCounters = new ConcurrentHashMap<>();
private final Map<String, ObservableDoubleGauge> otelGauges = new ConcurrentHashMap<>();

private final Map<String, Counter> dropwizardCounters = new ConcurrentHashMap<>();
private final Map<String, Histogram> dropwizardHistograms = new ConcurrentHashMap<>();
private final Map<String, Meter> dropwizardMeters = new ConcurrentHashMap<>();
private final Map<String, Timer> dropwizardTimers = new ConcurrentHashMap<>();

public DropwizardMetricsAdapter(OpenTelemetry openTelemetry) {
this.otelMeter = openTelemetry.getMeter("io.opentelemetry.dropwizard-metrics-4.0");
}

@Override
public void onGaugeAdded(String name, Gauge<?> gauge) {
ObservableDoubleGauge otelGauge =
otelMeter
.gaugeBuilder(name)
.buildWithCallback(
measurement -> {
Object val = gauge.getValue();
if (val instanceof Number) {
measurement.record(((Number) val).doubleValue());
}
});
otelGauges.put(name, otelGauge);
}

@Override
public void onGaugeRemoved(String name) {
ObservableDoubleGauge otelGauge = otelGauges.remove(name);
if (otelGauge != null) {
otelGauge.close();
}
}

@Override
public void onCounterAdded(String name, Counter dropwizardCounter) {
dropwizardCounters.put(name, dropwizardCounter);
LongUpDownCounter otelCounter =
otelUpDownCounters.computeIfAbsent(name, n -> otelMeter.upDownCounterBuilder(n).build());
otelUpDownCounterField.set(dropwizardCounter, otelCounter);
}

@Override
public void onCounterRemoved(String name) {
Counter dropwizardCounter = dropwizardCounters.remove(name);
otelUpDownCounters.remove(name);
if (dropwizardCounter != null) {
otelUpDownCounterField.set(dropwizardCounter, null);
}
}

public void counterAdd(Counter dropwizardCounter, long increment) {
LongUpDownCounter otelCounter = otelUpDownCounterField.get(dropwizardCounter);
if (otelCounter != null) {
otelCounter.add(increment);
}
}

@Override
public void onHistogramAdded(String name, Histogram dropwizardHistogram) {
dropwizardHistograms.put(name, dropwizardHistogram);
LongHistogram otelHistogram =
otelHistograms.computeIfAbsent(name, n -> otelMeter.histogramBuilder(n).ofLongs().build());
otelHistogramField.set(dropwizardHistogram, otelHistogram);
}

@Override
public void onHistogramRemoved(String name) {
Histogram dropwizardHistogram = dropwizardHistograms.remove(name);
otelHistograms.remove(name);
if (dropwizardHistogram != null) {
otelHistogramField.set(dropwizardHistogram, null);
}
}

public void histogramUpdate(Histogram dropwizardHistogram, long value) {
LongHistogram otelHistogram = otelHistogramField.get(dropwizardHistogram);
if (otelHistogram != null) {
otelHistogram.record(value);
}
}

@Override
public void onMeterAdded(String name, Meter dropwizardMeter) {
dropwizardMeters.put(name, dropwizardMeter);
LongCounter otelCounter =
otelCounters.computeIfAbsent(name, n -> otelMeter.counterBuilder(n).build());
otelCounterField.set(dropwizardMeter, otelCounter);
}

@Override
public void onMeterRemoved(String name) {
Meter dropwizardMeter = dropwizardMeters.remove(name);
otelCounters.remove(name);
if (dropwizardMeter != null) {
otelCounterField.set(dropwizardMeter, null);
}
}

public void meterMark(Meter dropwizardMeter, long increment) {
LongCounter otelCounter = otelCounterField.get(dropwizardMeter);
if (otelCounter != null) {
otelCounter.add(increment);
}
}

@Override
public void onTimerAdded(String name, Timer dropwizardTimer) {
dropwizardTimers.put(name, dropwizardTimer);
DoubleHistogram otelHistogram =
otelDoubleHistograms.computeIfAbsent(
name, n -> otelMeter.histogramBuilder(n).setUnit("ms").build());
otelDoubleHistogramField.set(dropwizardTimer, otelHistogram);
}

@Override
public void onTimerRemoved(String name) {
Timer dropwizardTimer = dropwizardTimers.remove(name);
otelDoubleHistograms.remove(name);
if (dropwizardTimer != null) {
otelDoubleHistogramField.set(dropwizardTimer, null);
}
}

public void timerUpdate(Timer dropwizardTimer, long nanos) {
DoubleHistogram otelHistogram = otelDoubleHistogramField.get(dropwizardTimer);
if (otelHistogram != null) {
otelHistogram.record(nanos / NANOS_PER_MS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.dropwizardmetrics;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Arrays.asList;
import static net.bytebuddy.matcher.ElementMatchers.not;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class DropwizardMetricsInstrumentationModule extends InstrumentationModule {

public DropwizardMetricsInstrumentationModule() {
super("dropwizard-metrics", "dropwizard-metrics-4.0");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// removed in 4.0
return not(hasClassesNamed("com.codahale.metrics.LongAdder"));
}

@Override
public boolean defaultEnabled() {
// the Dropwizard metrics API does not have a concept of metric labels/tags/attributes, thus the
// data produced by this integration might be of very low quality, depending on how the API is
// used in the instrumented application
return false;
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new MetricRegistryInstrumentation(),
new CounterInstrumentation(),
new HistogramInstrumentation(),
new MeterInstrumentation(),
new TimerInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.dropwizardmetrics;

import io.opentelemetry.api.GlobalOpenTelemetry;

public final class DropwizardSingletons {

private static final DropwizardMetricsAdapter METRICS =
new DropwizardMetricsAdapter(GlobalOpenTelemetry.get());

public static DropwizardMetricsAdapter metrics() {
return METRICS;
}

private DropwizardSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.dropwizardmetrics;

import static io.opentelemetry.javaagent.instrumentation.dropwizardmetrics.DropwizardSingletons.metrics;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.codahale.metrics.Histogram;
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 HistogramInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.codahale.metrics.Histogram");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("update").and(takesArguments(long.class)),
this.getClass().getName() + "$UpdateAdvice");
}

@SuppressWarnings("unused")
public static class UpdateAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.This Histogram histogram, @Advice.Argument(0) long value) {
metrics().histogramUpdate(histogram, value);
}
}
}
Loading

0 comments on commit 0a7e023

Please sign in to comment.