Skip to content

Commit adecf60

Browse files
authored
Add metrics for PeriodicMetricReader (#8038)
1 parent 87345a1 commit adecf60

4 files changed

Lines changed: 166 additions & 4 deletions

File tree

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
2020
import io.opentelemetry.sdk.metrics.export.MetricProducer;
2121
import io.opentelemetry.sdk.metrics.export.MetricReader;
22+
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
2223
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
2324
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
2425
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilterInternal;
@@ -28,6 +29,8 @@
2829
import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry;
2930
import io.opentelemetry.sdk.resources.Resource;
3031
import java.io.Closeable;
32+
import java.lang.reflect.InvocationTargetException;
33+
import java.lang.reflect.Method;
3134
import java.util.ArrayList;
3235
import java.util.Collection;
3336
import java.util.Collections;
@@ -94,10 +97,12 @@ public static SdkMeterProviderBuilder builder() {
9497
for (RegisteredReader registeredReader : registeredReaders) {
9598
List<MetricProducer> readerMetricProducers = new ArrayList<>(metricProducers);
9699
readerMetricProducers.add(new LeasedMetricProducer(registry, sharedState, registeredReader));
97-
registeredReader
98-
.getReader()
99-
.register(new SdkCollectionRegistration(readerMetricProducers, sharedState));
100+
MetricReader reader = registeredReader.getReader();
101+
reader.register(new SdkCollectionRegistration(readerMetricProducers, sharedState));
100102
registeredReader.setLastCollectEpochNanos(startEpochNanos);
103+
if (reader instanceof PeriodicMetricReader) {
104+
setReaderMeterProvider((PeriodicMetricReader) reader, this);
105+
}
101106
}
102107
}
103108

@@ -195,6 +200,18 @@ public String toString() {
195200
+ "}";
196201
}
197202

203+
private static void setReaderMeterProvider(
204+
PeriodicMetricReader metricReader, SdkMeterProvider meterProvider) {
205+
try {
206+
Method method =
207+
PeriodicMetricReader.class.getDeclaredMethod("setMeterProvider", MeterProvider.class);
208+
method.setAccessible(true);
209+
method.invoke(metricReader, meterProvider);
210+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
211+
throw new IllegalStateException("Error calling setMeterProvider on PeriodicMetricReader", e);
212+
}
213+
}
214+
198215
/** Helper class to expose registered metric exports. */
199216
private static class LeasedMetricProducer implements MetricProducer {
200217

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.metrics.export;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.api.metrics.DoubleHistogram;
10+
import io.opentelemetry.api.metrics.Meter;
11+
import io.opentelemetry.api.metrics.MeterProvider;
12+
import io.opentelemetry.sdk.common.internal.ComponentId;
13+
import io.opentelemetry.sdk.common.internal.SemConvAttributes;
14+
import java.util.Collections;
15+
import javax.annotation.Nullable;
16+
17+
final class MetricReaderInstrumentation {
18+
19+
private final DoubleHistogram collectionDuration;
20+
private final Attributes standardAttrs;
21+
22+
MetricReaderInstrumentation(ComponentId componentId, MeterProvider meterProvider) {
23+
Meter meter = meterProvider.get("io.opentelemetry.sdk.metrics");
24+
25+
standardAttrs =
26+
Attributes.of(
27+
SemConvAttributes.OTEL_COMPONENT_TYPE,
28+
componentId.getTypeName(),
29+
SemConvAttributes.OTEL_COMPONENT_NAME,
30+
componentId.getComponentName());
31+
32+
collectionDuration =
33+
meter
34+
.histogramBuilder("otel.sdk.metric_reader.collection.duration")
35+
.setUnit("s")
36+
.setDescription("The duration of the collect operation of the metric reader.")
37+
.setExplicitBucketBoundariesAdvice(Collections.emptyList())
38+
.build();
39+
}
40+
41+
void recordCollection(double seconds, @Nullable String error) {
42+
Attributes attrs = standardAttrs;
43+
if (error != null) {
44+
attrs = attrs.toBuilder().put(SemConvAttributes.ERROR_TYPE, error).build();
45+
}
46+
47+
collectionDuration.record(seconds, attrs);
48+
}
49+
}

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReader.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
package io.opentelemetry.sdk.metrics.export;
77

8+
import io.opentelemetry.api.metrics.MeterProvider;
9+
import io.opentelemetry.sdk.common.Clock;
810
import io.opentelemetry.sdk.common.CompletableResultCode;
911
import io.opentelemetry.sdk.common.export.MemoryMode;
12+
import io.opentelemetry.sdk.common.internal.ComponentId;
1013
import io.opentelemetry.sdk.metrics.Aggregation;
1114
import io.opentelemetry.sdk.metrics.InstrumentType;
1215
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
@@ -34,11 +37,17 @@
3437
public final class PeriodicMetricReader implements MetricReader {
3538
private static final Logger logger = Logger.getLogger(PeriodicMetricReader.class.getName());
3639

40+
private static final Clock CLOCK = Clock.getDefault();
41+
42+
private static final ComponentId COMPONENT_ID =
43+
ComponentId.generateLazy("periodic_metric_reader");
44+
3745
private final MetricExporter exporter;
3846
private final long intervalNanos;
3947
private final ScheduledExecutorService scheduler;
4048
private final Scheduled scheduled;
4149
private final Object lock = new Object();
50+
4251
private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop();
4352

4453
@Nullable private volatile ScheduledFuture<?> scheduledFuture;
@@ -135,6 +144,15 @@ public void register(CollectionRegistration collectionRegistration) {
135144
start();
136145
}
137146

147+
/**
148+
* Sets the {@link MeterProvider} to export metrics about this {@link PeriodicMetricReader} to.
149+
* Automatically called by the meter provider the reader is registered to.
150+
*/
151+
@SuppressWarnings("UnusedMethod")
152+
private void setMeterProvider(MeterProvider meterProvider) {
153+
this.scheduled.setMeterProvider(meterProvider);
154+
}
155+
138156
@Override
139157
public String toString() {
140158
return "PeriodicMetricReader{"
@@ -157,10 +175,18 @@ void start() {
157175
}
158176

159177
private final class Scheduled implements Runnable {
178+
160179
private final AtomicBoolean exportAvailable = new AtomicBoolean(true);
161180

181+
private MetricReaderInstrumentation instrumentation =
182+
new MetricReaderInstrumentation(COMPONENT_ID, MeterProvider.noop());
183+
162184
private Scheduled() {}
163185

186+
void setMeterProvider(MeterProvider meterProvider) {
187+
instrumentation = new MetricReaderInstrumentation(COMPONENT_ID, meterProvider);
188+
}
189+
164190
@Override
165191
public void run() {
166192
// Ignore the CompletableResultCode from doRun() in order to keep run() asynchronous
@@ -172,7 +198,15 @@ CompletableResultCode doRun() {
172198
CompletableResultCode flushResult = new CompletableResultCode();
173199
if (exportAvailable.compareAndSet(true, false)) {
174200
try {
175-
Collection<MetricData> metricData = collectionRegistration.collectAllMetrics();
201+
long startNanoTime = CLOCK.nanoTime();
202+
String error = null;
203+
Collection<MetricData> metricData;
204+
try {
205+
metricData = collectionRegistration.collectAllMetrics();
206+
} finally {
207+
long durationNanos = CLOCK.nanoTime() - startNanoTime;
208+
instrumentation.recordCollection(durationNanos / 1_000_000_000.0, error);
209+
}
176210
if (metricData.isEmpty()) {
177211
logger.log(Level.FINE, "No metric data to export - skipping export.");
178212
flushResult.succeed();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.metrics;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
11+
import io.opentelemetry.api.metrics.LongCounter;
12+
import io.opentelemetry.api.metrics.Meter;
13+
import io.opentelemetry.sdk.common.internal.SemConvAttributes;
14+
import io.opentelemetry.sdk.metrics.data.MetricData;
15+
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
16+
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter;
17+
import java.util.List;
18+
import java.util.concurrent.TimeUnit;
19+
import org.junit.jupiter.api.Test;
20+
21+
class SdkMeterProviderMetricsTest {
22+
@Test
23+
void simple() {
24+
InMemoryMetricExporter metricExporter = InMemoryMetricExporter.create();
25+
try (SdkMeterProvider meterProvider =
26+
SdkMeterProvider.builder()
27+
.registerMetricReader(PeriodicMetricReader.create(metricExporter))
28+
.build()) {
29+
Meter meter = meterProvider.get("test");
30+
31+
LongCounter counter = meter.counterBuilder("counter").build();
32+
33+
counter.add(1);
34+
35+
meterProvider.forceFlush().join(10, TimeUnit.SECONDS);
36+
metricExporter.reset();
37+
// Export again to export the metric reader's metric.
38+
meterProvider.forceFlush().join(10, TimeUnit.SECONDS);
39+
40+
List<MetricData> metrics = metricExporter.getFinishedMetricItems();
41+
assertThat(metrics)
42+
.satisfiesExactlyInAnyOrder(
43+
m -> assertThat(m).hasName("counter"),
44+
m -> {
45+
assertThat(m)
46+
.hasName("otel.sdk.metric_reader.collection.duration")
47+
.hasHistogramSatisfying(
48+
h ->
49+
h.hasPointsSatisfying(
50+
p ->
51+
p.hasCount(1)
52+
.hasAttributesSatisfying(
53+
equalTo(
54+
SemConvAttributes.OTEL_COMPONENT_TYPE,
55+
"periodic_metric_reader"),
56+
equalTo(
57+
SemConvAttributes.OTEL_COMPONENT_NAME,
58+
"periodic_metric_reader/0"))));
59+
});
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)