Skip to content

Commit

Permalink
Micrometer bridge instrumentation (open-telemetry#4919)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Mateusz Rzeszutek authored and RashmiRam committed May 23, 2022
1 parent cba25e5 commit a6b8a25
Show file tree
Hide file tree
Showing 16 changed files with 1,157 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
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.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() {}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit a6b8a25

Please sign in to comment.