Skip to content

Commit

Permalink
Move micrometer shim library instrumentation back (#6538)
Browse files Browse the repository at this point in the history
* Move micrometer shim library instrumentation back

* Switch package to io.opentelemetry.instrumentation.micrometer.v1_5

* Change instrumentation name
  • Loading branch information
jack-berg authored Sep 7, 2022
1 parent 56f4e52 commit 886f503
Show file tree
Hide file tree
Showing 50 changed files with 4,280 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("otel.library-instrumentation")
}

dependencies {
library("io.micrometer:micrometer-core:1.5.0")

testImplementation(project(":instrumentation:micrometer:micrometer-1.5:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.micrometer.v1_5;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.config.NamingConvention;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

final class Bridging {

private static final ConcurrentMap<String, String> descriptionsCache = new ConcurrentHashMap<>();

static Attributes tagsAsAttributes(Meter.Id id, NamingConvention namingConvention) {
Iterable<Tag> tags = id.getTagsAsIterable();
if (!tags.iterator().hasNext()) {
return Attributes.empty();
}
AttributesBuilder builder = Attributes.builder();
for (Tag tag : tags) {
String tagKey = namingConvention.tagKey(tag.getKey());
String tagValue = namingConvention.tagValue(tag.getValue());
builder.put(tagKey, tagValue);
}
return builder.build();
}

static String name(Meter.Id id, NamingConvention namingConvention) {
return namingConvention.name(id.getName(), id.getType(), id.getBaseUnit());
}

static String description(Meter.Id id) {
return descriptionsCache.computeIfAbsent(
id.getName(),
n -> {
String description = id.getDescription();
return description != null ? description : "";
});
}

static String baseUnit(Meter.Id id) {
String baseUnit = id.getBaseUnit();
return baseUnit == null ? "1" : baseUnit;
}

static String statisticInstrumentName(
Meter.Id id, Statistic statistic, NamingConvention namingConvention) {
String prefix = id.getName() + ".";
// use "total_time" instead of "total" to avoid clashing with Statistic.TOTAL
String statisticStr =
statistic == Statistic.TOTAL_TIME ? "total_time" : statistic.getTagValueRepresentation();
return namingConvention.name(prefix + statisticStr, id.getType(), id.getBaseUnit());
}

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.instrumentation.micrometer.v1_5;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import java.util.function.ToDoubleFunction;
import javax.annotation.Nullable;

final class DoubleMeasurementRecorder<T> implements Consumer<ObservableDoubleMeasurement> {

// using a weak reference here so that the existence of the micrometer Meter does not block the
// measured object from being GC'd; e.g. a Gauge (or any other async instrument) must not block
// garbage collection of the object that it measures
private final WeakReference<T> objWeakRef;
private final ToDoubleFunction<T> metricFunction;
private final Attributes attributes;

DoubleMeasurementRecorder(
@Nullable T obj, ToDoubleFunction<T> metricFunction, Attributes attributes) {
this.objWeakRef = new WeakReference<>(obj);
this.metricFunction = metricFunction;
this.attributes = attributes;
}

@Override
public void accept(ObservableDoubleMeasurement measurement) {
T obj = objWeakRef.get();
if (obj != null) {
measurement.record(metricFunction.applyAsDouble(obj), attributes);
}
}
}
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.instrumentation.micrometer.v1_5;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import java.util.function.ToLongFunction;
import javax.annotation.Nullable;

final class LongMeasurementRecorder<T> implements Consumer<ObservableLongMeasurement> {

// using a weak reference here so that the existence of the micrometer Meter does not block the
// measured object from being GC'd; e.g. a Gauge (or any other async instrument) must not block
// garbage collection of the object that it measures
private final WeakReference<T> objWeakRef;
private final ToLongFunction<T> metricFunction;
private final Attributes attributes;

LongMeasurementRecorder(
@Nullable T obj, ToLongFunction<T> metricFunction, Attributes attributes) {
this.objWeakRef = new WeakReference<>(obj);
this.metricFunction = metricFunction;
this.attributes = attributes;
}

@Override
public void accept(ObservableLongMeasurement measurement) {
T obj = objWeakRef.get();
if (obj != null) {
measurement.record(metricFunction.applyAsLong(obj), attributes);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;

import io.micrometer.core.instrument.AbstractMeter;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.config.NamingConvention;
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 extends AbstractMeter implements Counter, RemovableMeter {

// TODO: use bound instruments when they're available
private final DoubleCounter otelCounter;
private final Attributes attributes;

private volatile boolean removed = false;

OpenTelemetryCounter(Id id, NamingConvention namingConvention, Meter otelMeter) {
super(id);

this.attributes = tagsAsAttributes(id, namingConvention);
String conventionName = name(id, namingConvention);
this.otelCounter =
otelMeter
.counterBuilder(conventionName)
.setDescription(Bridging.description(id))
.setUnit(baseUnit(id))
.ofDoubles()
.build();
}

@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 void onRemove() {
removed = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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.name;
import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes;

import io.micrometer.core.instrument.AbstractDistributionSummary;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.NoopHistogram;
import io.micrometer.core.instrument.distribution.TimeWindowMax;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
import java.util.Collections;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;

final class OpenTelemetryDistributionSummary extends AbstractDistributionSummary
implements RemovableMeter {

private final Measurements measurements;
private final TimeWindowMax max;
// TODO: use bound instruments when they're available
private final DoubleHistogram otelHistogram;
private final Attributes attributes;
private final ObservableDoubleGauge observableMax;

private volatile boolean removed = false;

OpenTelemetryDistributionSummary(
Id id,
NamingConvention namingConvention,
Clock clock,
DistributionStatisticConfig distributionStatisticConfig,
double scale,
Meter otelMeter) {
super(id, clock, distributionStatisticConfig, scale, false);

if (isUsingMicrometerHistograms()) {
measurements = new MicrometerHistogramMeasurements();
} else {
measurements = NoopMeasurements.INSTANCE;
}
max = new TimeWindowMax(clock, distributionStatisticConfig);

this.attributes = tagsAsAttributes(id, namingConvention);

String name = name(id, namingConvention);
this.otelHistogram =
otelMeter
.histogramBuilder(name)
.setDescription(Bridging.description(id))
.setUnit(baseUnit(id))
.build();
this.observableMax =
otelMeter
.gaugeBuilder(name + ".max")
.setDescription(Bridging.description(id))
.setUnit(baseUnit(id))
.buildWithCallback(
new DoubleMeasurementRecorder<>(max, TimeWindowMax::poll, attributes));
}

boolean isUsingMicrometerHistograms() {
return histogram != NoopHistogram.INSTANCE;
}

@Override
protected void recordNonNegative(double amount) {
if (!removed) {
otelHistogram.record(amount, attributes);
measurements.record(amount);
max.record(amount);
}
}

@Override
public long count() {
return measurements.count();
}

@Override
public double totalAmount() {
return measurements.totalAmount();
}

@Override
public double max() {
return max.poll();
}

@Override
public Iterable<Measurement> measure() {
UnsupportedReadLogger.logWarning();
return Collections.emptyList();
}

@Override
public void onRemove() {
removed = true;
observableMax.close();
}

private interface Measurements {
void record(double amount);

long count();

double totalAmount();
}

// 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(double amount) {}

@Override
public long count() {
UnsupportedReadLogger.logWarning();
return 0;
}

@Override
public double totalAmount() {
UnsupportedReadLogger.logWarning();
return Double.NaN;
}
}

// calculate count and totalAmount value for the use of micrometer histograms
// kinda similar to how DropwizardDistributionSummary does that
private static final class MicrometerHistogramMeasurements implements Measurements {

private final LongAdder count = new LongAdder();
private final DoubleAdder totalAmount = new DoubleAdder();

@Override
public void record(double amount) {
count.increment();
totalAmount.add(amount);
}

@Override
public long count() {
return count.sum();
}

@Override
public double totalAmount() {
return totalAmount.sum();
}
}
}
Loading

0 comments on commit 886f503

Please sign in to comment.