Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a dropwizard-metrics -> OTel metrics bridge #6259

Merged
merged 3 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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