io.opentelemetry.sdk.metrics.SdkMeter Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics;
import static java.util.stream.Collectors.toMap;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.internal.GuardedBy;
import io.opentelemetry.api.metrics.BatchCallback;
import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongCounterBuilder;
import io.opentelemetry.api.metrics.LongUpDownCounterBuilder;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.metrics.ObservableMeasurement;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
import io.opentelemetry.sdk.metrics.internal.state.AsynchronousMetricStorage;
import io.opentelemetry.sdk.metrics.internal.state.CallbackRegistration;
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorageRegistry;
import io.opentelemetry.sdk.metrics.internal.state.SdkObservableMeasurement;
import io.opentelemetry.sdk.metrics.internal.state.SynchronousMetricStorage;
import io.opentelemetry.sdk.metrics.internal.state.WriteableMetricStorage;
import io.opentelemetry.sdk.metrics.internal.view.RegisteredView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/** {@link SdkMeter} is SDK implementation of {@link Meter}. */
final class SdkMeter implements Meter {
private static final Logger logger = Logger.getLogger(SdkMeter.class.getName());
/**
* Instrument names MUST conform to the following syntax.
*
*
* - They are not null or empty strings.
*
- They are case-insensitive, ASCII strings.
*
- The first character must be an alphabetic character.
*
- Subsequent characters must belong to the alphanumeric characters, '_', '.', '/', and '-'.
*
- They can have a maximum length of 255 characters.
*
*/
private static final Pattern VALID_INSTRUMENT_NAME_PATTERN =
Pattern.compile("([A-Za-z]){1}([A-Za-z0-9\\_\\-\\./]){0,254}");
private static final Meter NOOP_METER = MeterProvider.noop().get("noop");
private static final String NOOP_INSTRUMENT_NAME = "noop";
private final Object collectLock = new Object();
private final Object callbackLock = new Object();
@GuardedBy("callbackLock")
private final List callbackRegistrations = new ArrayList<>();
private final MeterProviderSharedState meterProviderSharedState;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final Map readerStorageRegistries;
private final boolean meterEnabled;
SdkMeter(
MeterProviderSharedState meterProviderSharedState,
InstrumentationScopeInfo instrumentationScopeInfo,
List registeredReaders,
MeterConfig meterConfig) {
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.meterProviderSharedState = meterProviderSharedState;
this.readerStorageRegistries =
registeredReaders.stream()
.collect(toMap(Function.identity(), unused -> new MetricStorageRegistry()));
this.meterEnabled = meterConfig.isEnabled();
}
// Visible for testing
InstrumentationScopeInfo getInstrumentationScopeInfo() {
return instrumentationScopeInfo;
}
/** Collect all metrics for the meter. */
Collection collectAll(RegisteredReader registeredReader, long epochNanos) {
// Short circuit collection process if meter is disabled
if (!meterEnabled) {
return Collections.emptyList();
}
List currentRegisteredCallbacks;
synchronized (callbackLock) {
currentRegisteredCallbacks = new ArrayList<>(callbackRegistrations);
}
// Collections across all readers are sequential
synchronized (collectLock) {
for (CallbackRegistration callbackRegistration : currentRegisteredCallbacks) {
callbackRegistration.invokeCallback(
registeredReader, meterProviderSharedState.getStartEpochNanos(), epochNanos);
}
Collection storages =
Objects.requireNonNull(readerStorageRegistries.get(registeredReader)).getStorages();
List result = new ArrayList<>(storages.size());
for (MetricStorage storage : storages) {
MetricData current =
storage.collect(
meterProviderSharedState.getResource(),
getInstrumentationScopeInfo(),
meterProviderSharedState.getStartEpochNanos(),
epochNanos);
// Ignore if the metric data doesn't have any data points, for example when aggregation is
// Aggregation#drop()
if (!current.isEmpty()) {
result.add(current);
}
}
return Collections.unmodifiableList(result);
}
}
/** Reset the meter, clearing all registered callbacks and storages. */
void resetForTest() {
synchronized (collectLock) {
synchronized (callbackLock) {
callbackRegistrations.clear();
}
this.readerStorageRegistries.values().forEach(MetricStorageRegistry::resetForTest);
}
}
@Override
public LongCounterBuilder counterBuilder(String name) {
return checkValidInstrumentName(name)
? new SdkLongCounter.SdkLongCounterBuilder(this, name)
: NOOP_METER.counterBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public LongUpDownCounterBuilder upDownCounterBuilder(String name) {
return checkValidInstrumentName(name)
? new SdkLongUpDownCounter.SdkLongUpDownCounterBuilder(this, name)
: NOOP_METER.upDownCounterBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public DoubleHistogramBuilder histogramBuilder(String name) {
return checkValidInstrumentName(name)
? new SdkDoubleHistogram.SdkDoubleHistogramBuilder(this, name)
: NOOP_METER.histogramBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public DoubleGaugeBuilder gaugeBuilder(String name) {
return checkValidInstrumentName(name)
? new SdkDoubleGauge.SdkDoubleGaugeBuilder(this, name)
: NOOP_METER.gaugeBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public BatchCallback batchCallback(
Runnable callback,
ObservableMeasurement observableMeasurement,
ObservableMeasurement... additionalMeasurements) {
Set measurements = new HashSet<>();
measurements.add(observableMeasurement);
Collections.addAll(measurements, additionalMeasurements);
List sdkMeasurements = new ArrayList<>();
for (ObservableMeasurement measurement : measurements) {
if (!(measurement instanceof SdkObservableMeasurement)) {
logger.log(
Level.WARNING,
"batchCallback called with instruments that were not created by the SDK.");
continue;
}
SdkObservableMeasurement sdkMeasurement = (SdkObservableMeasurement) measurement;
if (!instrumentationScopeInfo.equals(sdkMeasurement.getInstrumentationScopeInfo())) {
logger.log(
Level.WARNING,
"batchCallback called with instruments that belong to a different Meter.");
continue;
}
sdkMeasurements.add(sdkMeasurement);
}
CallbackRegistration callbackRegistration =
CallbackRegistration.create(sdkMeasurements, callback);
registerCallback(callbackRegistration);
return new SdkObservableInstrument(this, callbackRegistration);
}
/**
* Unregister the callback.
*
* Callbacks are originally registered via {@link #registerCallback(CallbackRegistration)}.
*/
void removeCallback(CallbackRegistration callbackRegistration) {
synchronized (callbackLock) {
this.callbackRegistrations.remove(callbackRegistration);
}
}
/**
* Register the callback.
*
*
The callback will be invoked once per collection until unregistered via {@link
* #removeCallback(CallbackRegistration)}.
*/
void registerCallback(CallbackRegistration callbackRegistration) {
synchronized (callbackLock) {
callbackRegistrations.add(callbackRegistration);
}
}
/** Returns {@code true} if the {@link MeterConfig#enabled()} of the meter is {@code true}. */
boolean isMeterEnabled() {
return meterEnabled;
}
/** Registers new synchronous storage associated with a given instrument. */
WriteableMetricStorage registerSynchronousMetricStorage(InstrumentDescriptor instrument) {
List registeredStorages = new ArrayList<>();
for (Map.Entry entry :
readerStorageRegistries.entrySet()) {
RegisteredReader reader = entry.getKey();
MetricStorageRegistry registry = entry.getValue();
for (RegisteredView registeredView :
reader.getViewRegistry().findViews(instrument, getInstrumentationScopeInfo())) {
if (Aggregation.drop() == registeredView.getView().getAggregation()) {
continue;
}
registeredStorages.add(
registry.register(
SynchronousMetricStorage.create(
reader,
registeredView,
instrument,
meterProviderSharedState.getExemplarFilter())));
}
}
if (registeredStorages.size() == 1) {
return registeredStorages.get(0);
}
return new MultiWritableMetricStorage(registeredStorages);
}
/** Register new asynchronous storage associated with a given instrument. */
SdkObservableMeasurement registerObservableMeasurement(
InstrumentDescriptor instrumentDescriptor) {
List> registeredStorages = new ArrayList<>();
for (Map.Entry entry :
readerStorageRegistries.entrySet()) {
RegisteredReader reader = entry.getKey();
MetricStorageRegistry registry = entry.getValue();
for (RegisteredView registeredView :
reader.getViewRegistry().findViews(instrumentDescriptor, getInstrumentationScopeInfo())) {
if (Aggregation.drop() == registeredView.getView().getAggregation()) {
continue;
}
registeredStorages.add(
registry.register(
AsynchronousMetricStorage.create(reader, registeredView, instrumentDescriptor)));
}
}
return SdkObservableMeasurement.create(
instrumentationScopeInfo, instrumentDescriptor, registeredStorages);
}
@Override
public String toString() {
return "SdkMeter{instrumentationScopeInfo=" + instrumentationScopeInfo + "}";
}
/** Check if the instrument name is valid. If invalid, log a warning. */
// Visible for testing
static boolean checkValidInstrumentName(String name) {
if (name != null && VALID_INSTRUMENT_NAME_PATTERN.matcher(name).matches()) {
return true;
}
if (logger.isLoggable(Level.WARNING)) {
logger.log(
Level.WARNING,
"Instrument name \""
+ name
+ "\" is invalid, returning noop instrument. Instrument names must consist of 255 or fewer characters including alphanumeric, _, ., -, /, and start with a letter.",
new AssertionError());
}
return false;
}
private static class MultiWritableMetricStorage implements WriteableMetricStorage {
private final List extends WriteableMetricStorage> storages;
private MultiWritableMetricStorage(List extends WriteableMetricStorage> storages) {
this.storages = storages;
}
@Override
public void recordLong(long value, Attributes attributes, Context context) {
for (WriteableMetricStorage storage : storages) {
storage.recordLong(value, attributes, context);
}
}
@Override
public void recordDouble(double value, Attributes attributes, Context context) {
for (WriteableMetricStorage storage : storages) {
storage.recordDouble(value, attributes, context);
}
}
@Override
public boolean isEnabled() {
for (WriteableMetricStorage storage : storages) {
if (storage.isEnabled()) {
return true;
}
}
return false;
}
}
}