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

Move micrometer shim library instrumentation back #6538

Merged
merged 3 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.micrometer1shim;
jack-berg marked this conversation as resolved.
Show resolved Hide resolved

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.micrometer1shim;

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.micrometer1shim;

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.micrometer1shim;

import static io.opentelemetry.instrumentation.micrometer1shim.Bridging.baseUnit;
import static io.opentelemetry.instrumentation.micrometer1shim.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer1shim.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.micrometer1shim;

import static io.opentelemetry.instrumentation.micrometer1shim.Bridging.baseUnit;
import static io.opentelemetry.instrumentation.micrometer1shim.Bridging.name;
import static io.opentelemetry.instrumentation.micrometer1shim.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