Skip to content

Commit 3136926

Browse files
committed
add useOpenTelemetryInitializedByApplication config
1 parent 4cf5954 commit 3136926

4 files changed

Lines changed: 273 additions & 66 deletions

File tree

internal/venice-client-common/src/main/java/com/linkedin/venice/stats/VeniceMetricsConfig.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,27 @@ public class VeniceMetricsConfig {
3232
*/
3333
public static final String OTEL_VENICE_METRICS_ENABLED = "otel.venice.metrics.enabled";
3434

35+
/**
36+
* Config to reuse the {@link io.opentelemetry.api.OpenTelemetry} initialized by the application
37+
* or other libraries and set as {@link io.opentelemetry.api.GlobalOpenTelemetry} rather than
38+
* initializing it individually. This is particularly useful for clients use cases where one
39+
* application can initialize multiple venice client libraries which would otherwise initialize
40+
* OpenTelemetry multiple times, which could take more resources.
41+
*/
42+
public static final String OTEL_VENICE_USE_OPENTELEMETRY_INITIALIZED_BY_APPLICATION =
43+
"otel.venice.use.opentelemetry.initialized.by.application";
44+
3545
/**
3646
* Config to set the metric prefix for OpenTelemetry metrics
3747
*/
3848
public static final String OTEL_VENICE_METRICS_PREFIX = "otel.venice.metrics.prefix";
3949

50+
/**
51+
* Config to set custom description for all Histogram metrics.
52+
*/
53+
public static final String OTEL_VENICE_METRICS_CUSTOM_DESCRIPTION_FOR_HISTOGRAM =
54+
"otel.venice.metrics.custom.description.for.histogram";
55+
4056
/**
4157
* Config to set the naming format for OpenTelemetry metrics
4258
* {@link VeniceOpenTelemetryMetricNamingFormat}
@@ -139,6 +155,17 @@ public class VeniceMetricsConfig {
139155
/** Feature flag to use OpenTelemetry instrumentation for metrics or not */
140156
private final boolean emitOTelMetrics;
141157

158+
/**
159+
* Feature flag to use OpenTelemetry initialized by the application or not.
160+
* If true, it will use the GlobalOpenTelemetry instance. It could be initialized
161+
* by the application or by some other library
162+
* If false, it will initialize its own OpenTelemetry instance.
163+
*/
164+
private final boolean useOpenTelemetryInitializedByApplication;
165+
166+
/** custom description for all Histogram metrics */
167+
private final String otelCustomDescriptionForHistogramMetrics;
168+
142169
/** extra configs for OpenTelemetry. Supports 2 exporter currently <br>
143170
* 1. {@link MetricExporter} for exporting to Http/Grpc endpoint. More details are supported via configs,
144171
* check {@link Builder#extractAndSetOtelConfigs} and {@link VeniceOpenTelemetryMetricsRepository#getOtlpHttpMetricExporter}<br>
@@ -183,6 +210,8 @@ private VeniceMetricsConfig(Builder builder) {
183210
this.metricPrefix = builder.metricPrefix;
184211
this.metricEntities = builder.metricEntities;
185212
this.emitOTelMetrics = builder.emitOtelMetrics;
213+
this.useOpenTelemetryInitializedByApplication = builder.useOpenTelemetryInitializedByApplication;
214+
this.otelCustomDescriptionForHistogramMetrics = builder.otelCustomDescriptionForHistogramMetrics;
186215
this.exportOtelMetricsToEndpoint = builder.exportOtelMetricsToEndpoint;
187216
this.exportOtelMetricsIntervalInSeconds = builder.exportOtelMetricsIntervalInSeconds;
188217
this.otelCustomDimensionsMap = builder.otelCustomDimensionsMap;
@@ -204,6 +233,8 @@ public static class Builder {
204233
private String metricPrefix = null;
205234
private Collection<MetricEntity> metricEntities = new ArrayList<>();
206235
private boolean emitOtelMetrics = false;
236+
private boolean useOpenTelemetryInitializedByApplication = false;
237+
private String otelCustomDescriptionForHistogramMetrics = null;
207238
private boolean exportOtelMetricsToEndpoint = false;
208239
private int exportOtelMetricsIntervalInSeconds = 60;
209240
private Map<String, String> otelCustomDimensionsMap = new HashMap<>();
@@ -240,6 +271,16 @@ public Builder setEmitOtelMetrics(boolean emitOtelMetrics) {
240271
return this;
241272
}
242273

274+
public Builder setUseOpenTelemetryInitializedByApplication(boolean useOpenTelemetryInitializedByApplication) {
275+
this.useOpenTelemetryInitializedByApplication = useOpenTelemetryInitializedByApplication;
276+
return this;
277+
}
278+
279+
public Builder setOtelCustomDescriptionForHistogramMetrics(String otelCustomDescriptionForHistogramMetrics) {
280+
this.otelCustomDescriptionForHistogramMetrics = otelCustomDescriptionForHistogramMetrics;
281+
return this;
282+
}
283+
243284
public Builder setExportOtelMetricsToEndpoint(boolean exportOtelMetricsToEndpoint) {
244285
this.exportOtelMetricsToEndpoint = exportOtelMetricsToEndpoint;
245286
return this;
@@ -325,6 +366,14 @@ public Builder extractAndSetOtelConfigs(Map<String, String> configs) {
325366
setMetricPrefix(configValue);
326367
}
327368

369+
if ((configValue = configs.get(OTEL_VENICE_USE_OPENTELEMETRY_INITIALIZED_BY_APPLICATION)) != null) {
370+
setUseOpenTelemetryInitializedByApplication(Boolean.parseBoolean(configValue));
371+
}
372+
373+
if ((configValue = configs.get(OTEL_VENICE_METRICS_CUSTOM_DESCRIPTION_FOR_HISTOGRAM)) != null) {
374+
setOtelCustomDescriptionForHistogramMetrics(configValue);
375+
}
376+
328377
if ((configValue = configs.get(OTEL_VENICE_METRICS_EXPORT_TO_LOG)) != null) {
329378
setExportOtelMetricsToLog(Boolean.parseBoolean(configValue));
330379
}
@@ -469,6 +518,14 @@ public boolean emitOtelMetrics() {
469518
return emitOTelMetrics;
470519
}
471520

521+
public boolean useOpenTelemetryInitializedByApplication() {
522+
return useOpenTelemetryInitializedByApplication;
523+
}
524+
525+
public String getOtelCustomDescriptionForHistogramMetrics() {
526+
return otelCustomDescriptionForHistogramMetrics;
527+
}
528+
472529
public boolean exportOtelMetricsToEndpoint() {
473530
return exportOtelMetricsToEndpoint;
474531
}

internal/venice-client-common/src/main/java/com/linkedin/venice/stats/VeniceOpenTelemetryMetricsRepository.java

Lines changed: 95 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.linkedin.venice.stats.VeniceOpenTelemetryMetricNamingFormat.transformMetricName;
44
import static com.linkedin.venice.stats.VeniceOpenTelemetryMetricNamingFormat.validateMetricName;
55

6+
import com.google.common.annotations.VisibleForTesting;
67
import com.linkedin.venice.exceptions.VeniceException;
78
import com.linkedin.venice.stats.dimensions.VeniceDimensionInterface;
89
import com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions;
@@ -11,6 +12,7 @@
1112
import com.linkedin.venice.stats.metrics.MetricType;
1213
import com.linkedin.venice.stats.metrics.MetricUnit;
1314
import com.linkedin.venice.utils.concurrent.VeniceConcurrentHashMap;
15+
import io.opentelemetry.api.GlobalOpenTelemetry;
1416
import io.opentelemetry.api.OpenTelemetry;
1517
import io.opentelemetry.api.common.Attributes;
1618
import io.opentelemetry.api.common.AttributesBuilder;
@@ -55,6 +57,7 @@ public class VeniceOpenTelemetryMetricsRepository {
5557
public static final String DEFAULT_METRIC_PREFIX = "venice.";
5658
private final VeniceMetricsConfig metricsConfig;
5759
private SdkMeterProvider sdkMeterProvider = null;
60+
private OpenTelemetry openTelemetry = null;
5861
private final boolean emitOpenTelemetryMetrics;
5962
private final VeniceOpenTelemetryMetricNamingFormat metricFormat;
6063
private Meter meter;
@@ -80,62 +83,75 @@ public VeniceOpenTelemetryMetricsRepository(VeniceMetricsConfig metricsConfig) {
8083
metricsConfig.toString());
8184
this.metricPrefix = metricsConfig.getMetricPrefix();
8285
validateMetricName(getMetricPrefix());
83-
try {
84-
SdkMeterProviderBuilder builder = SdkMeterProvider.builder();
85-
86-
if (metricsConfig.exportOtelMetricsToEndpoint()) {
87-
MetricExporter httpExporter = getOtlpHttpMetricExporter(metricsConfig);
88-
builder.registerMetricReader(
89-
PeriodicMetricReader.builder(httpExporter)
90-
.setInterval(metricsConfig.getExportOtelMetricsIntervalInSeconds(), TimeUnit.SECONDS)
91-
.build());
86+
if (metricsConfig.useOpenTelemetryInitializedByApplication()) {
87+
LOGGER.warn("Configured to use GlobalOpenTelemetry set by the application");
88+
openTelemetry = GlobalOpenTelemetry.get();
89+
if (openTelemetry == null) {
90+
// this is an extra safety check as GlobalOpenTelemetry.get() will return a default noop instance
91+
// if it was not originally initialized by the application
92+
throw new VeniceException("OpenTelemetry is not initialized globally, but it is required for metrics.");
9293
}
93-
94-
if (metricsConfig.exportOtelMetricsToLog()) {
95-
// internal to test: Disabled by default
96-
builder.registerMetricReader(
97-
PeriodicMetricReader.builder(new LogBasedMetricExporter(metricsConfig))
98-
.setInterval(metricsConfig.getExportOtelMetricsIntervalInSeconds(), TimeUnit.SECONDS)
99-
.build());
100-
}
101-
102-
if (metricsConfig.getOtelAdditionalMetricsReader() != null) {
103-
// additional metrics reader apart from the above. For instance,
104-
// an in-memory metric reader can be passed in for testing purposes.
105-
builder.registerMetricReader(metricsConfig.getOtelAdditionalMetricsReader());
106-
}
107-
108-
if (metricsConfig.useOtelExponentialHistogram()) {
109-
setExponentialHistogramAggregation(builder, metricsConfig);
110-
}
111-
112-
// Set resource to empty to avoid adding any default resource attributes. The receiver
113-
// pipeline can choose to add the respective resource attributes if needed.
114-
builder.setResource(Resource.empty());
115-
116-
sdkMeterProvider = builder.build();
117-
118-
// Register MeterProvider with the OpenTelemetry instance
119-
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
120-
121-
this.meter = openTelemetry.getMeter(transformMetricName(getMetricPrefix(), metricFormat));
122-
123-
this.recordFailureMetric = MetricEntityStateBase.create(
124-
CommonMetricsEntity.METRIC_RECORD_FAILURE.getMetricEntity(),
125-
this,
126-
Collections.EMPTY_MAP,
127-
Attributes.empty());
128-
94+
LOGGER.info("Using globally initialized OpenTelemetry for {}", metricsConfig.getServiceName());
95+
} else {
12996
LOGGER.info(
130-
"OpenTelemetry initialization for {} completed with config: {}",
97+
"OpenTelemetry initialization for {} started with config: {}",
13198
metricsConfig.getServiceName(),
132-
metricsConfig);
133-
} catch (Exception e) {
134-
String err = "OpenTelemetry initialization for " + metricsConfig.getServiceName() + " failed with config: "
135-
+ metricsConfig;
136-
LOGGER.error(err, e);
137-
throw new VeniceException(err, e);
99+
metricsConfig.toString());
100+
try {
101+
SdkMeterProviderBuilder builder = SdkMeterProvider.builder();
102+
103+
if (metricsConfig.exportOtelMetricsToEndpoint()) {
104+
MetricExporter httpExporter = getOtlpHttpMetricExporter(metricsConfig);
105+
builder.registerMetricReader(
106+
PeriodicMetricReader.builder(httpExporter)
107+
.setInterval(metricsConfig.getExportOtelMetricsIntervalInSeconds(), TimeUnit.SECONDS)
108+
.build());
109+
}
110+
111+
if (metricsConfig.exportOtelMetricsToLog()) {
112+
// internal to test: Disabled by default
113+
builder.registerMetricReader(
114+
PeriodicMetricReader.builder(new LogBasedMetricExporter(metricsConfig))
115+
.setInterval(metricsConfig.getExportOtelMetricsIntervalInSeconds(), TimeUnit.SECONDS)
116+
.build());
117+
}
118+
119+
if (metricsConfig.getOtelAdditionalMetricsReader() != null) {
120+
// additional metrics reader apart from the above. For instance,
121+
// an in-memory metric reader can be passed in for testing purposes.
122+
builder.registerMetricReader(metricsConfig.getOtelAdditionalMetricsReader());
123+
}
124+
125+
if (metricsConfig.useOtelExponentialHistogram()) {
126+
setExponentialHistogramAggregation(builder, metricsConfig);
127+
}
128+
129+
// Set resource to empty to avoid adding any default resource attributes. The receiver
130+
// pipeline can choose to add the respective resource attributes if needed.
131+
builder.setResource(Resource.empty());
132+
133+
sdkMeterProvider = builder.build();
134+
135+
// Register MeterProvider with the OpenTelemetry instance
136+
openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
137+
LOGGER.info(
138+
"OpenTelemetry initialization for {} completed with config: {}",
139+
metricsConfig.getServiceName(),
140+
metricsConfig);
141+
} catch (Exception e) {
142+
String err = "OpenTelemetry initialization for " + metricsConfig.getServiceName() + " failed with config: "
143+
+ metricsConfig;
144+
LOGGER.error(err, e);
145+
throw new VeniceException(err, e);
146+
}
138147
}
148+
149+
this.meter = openTelemetry.getMeter(transformMetricName(getMetricPrefix(), metricFormat));
150+
this.recordFailureMetric = MetricEntityStateBase.create(
151+
CommonMetricsEntity.METRIC_RECORD_FAILURE.getMetricEntity(),
152+
this,
153+
Collections.EMPTY_MAP,
154+
Attributes.empty());
139155
}
140156

141157
/**
@@ -162,14 +178,12 @@ MetricExporter getOtlpHttpMetricExporter(VeniceMetricsConfig metricsConfig) {
162178
}
163179

164180
/**
165-
* Setting Exponential Histogram aggregation for {@link MetricType#HISTOGRAM} by looping through all
166-
* the metric entities set for this service to registering the view with exponential histogram aggregation for
167-
* all the {@link MetricType#HISTOGRAM} metrics.
181+
* Setting Exponential Histogram aggregation for each {@link MetricType#HISTOGRAM} metric by looping through all
182+
* the metric entities set for this service using views.
168183
*
169-
* There is a limitation in opentelemetry sdk to configure different histogram aggregation for different
170-
* instruments, so {@link OtlpHttpMetricExporterBuilder#setDefaultAggregationSelector} to enable exponential
171-
* histogram aggregation is not used here to not convert the histograms of type {@link MetricType#MIN_MAX_COUNT_SUM_AGGREGATIONS}
172-
* to exponential histograms to be able to follow explict boundaries.
184+
* Because the OpenTelemetry SDK cannot currently assign different histogram aggregations to different histogram
185+
* instruments, we deliberately avoid {@link OtlpHttpMetricExporterBuilder#setDefaultAggregationSelector}. Using it
186+
* would also convert {@link MetricType#MIN_MAX_COUNT_SUM_AGGREGATIONS} to exponential histograms.
173187
*
174188
* If the metric entities are empty, it will throw an exception. Failing fast here as
175189
* 1. If we configure exponential histogram aggregation for every histogram: it could lead to increased memory usage
@@ -226,6 +240,16 @@ String getMetricPrefix(MetricEntity metricEntity) {
226240
: createFullMetricPrefix(metricEntity.getCustomMetricPrefix()));
227241
}
228242

243+
static String getMetricDescription(MetricEntity metricEntity, VeniceMetricsConfig metricsConfig) {
244+
String customDescription = metricsConfig.getOtelCustomDescriptionForHistogramMetrics();
245+
if (metricEntity.getMetricType() == MetricType.HISTOGRAM && customDescription != null
246+
&& !customDescription.isEmpty()) {
247+
return customDescription;
248+
} else {
249+
return metricEntity.getDescription();
250+
}
251+
}
252+
229253
public DoubleHistogram createHistogram(MetricEntity metricEntity) {
230254
if (!emitOpenTelemetryMetrics()) {
231255
return null;
@@ -234,10 +258,10 @@ public DoubleHistogram createHistogram(MetricEntity metricEntity) {
234258
String fullMetricName = getFullMetricName(metricEntity);
235259
DoubleHistogramBuilder builder = meter.histogramBuilder(fullMetricName)
236260
.setUnit(metricEntity.getUnit().name())
237-
.setDescription(metricEntity.getDescription());
261+
.setDescription(getMetricDescription(metricEntity, metricsConfig));
238262
if (metricEntity.getMetricType() == MetricType.MIN_MAX_COUNT_SUM_AGGREGATIONS) {
239263
// No buckets needed to get only min/max/count/sum aggregations
240-
builder.setExplicitBucketBoundariesAdvice(new ArrayList<>());
264+
builder.setExplicitBucketBoundariesAdvice(new ArrayList<>()).setDescription(metricEntity.getDescription());
241265
}
242266
return builder.build();
243267
});
@@ -251,7 +275,7 @@ public LongCounter createCounter(MetricEntity metricEntity) {
251275
String fullMetricName = getFullMetricName(metricEntity);
252276
LongCounterBuilder builder = meter.counterBuilder(fullMetricName)
253277
.setUnit(metricEntity.getUnit().name())
254-
.setDescription(metricEntity.getDescription());
278+
.setDescription(getMetricDescription(metricEntity, metricsConfig));
255279
return builder.build();
256280
});
257281
}
@@ -264,7 +288,7 @@ public DoubleGauge createGuage(MetricEntity metricEntity) {
264288
String fullMetricName = getFullMetricName(metricEntity);
265289
DoubleGaugeBuilder builder = meter.gaugeBuilder(fullMetricName)
266290
.setUnit(metricEntity.getUnit().name())
267-
.setDescription(metricEntity.getDescription());
291+
.setDescription(getMetricDescription(metricEntity, metricsConfig));
268292
return builder.build();
269293
});
270294
}
@@ -433,17 +457,22 @@ public MetricEntity getMetricEntity() {
433457
}
434458
}
435459

436-
/** for testing purposes */
460+
@VisibleForTesting
437461
SdkMeterProvider getSdkMeterProvider() {
438462
return sdkMeterProvider;
439463
}
440464

441-
/** for testing purposes */
465+
@VisibleForTesting
466+
OpenTelemetry getOpenTelemetry() {
467+
return openTelemetry;
468+
}
469+
470+
@VisibleForTesting
442471
Meter getMeter() {
443472
return meter;
444473
}
445474

446-
/** for testing purposes */
475+
@VisibleForTesting
447476
public MetricEntityStateBase getRecordFailureMetric() {
448477
return this.recordFailureMetric;
449478
}

internal/venice-client-common/src/test/java/com/linkedin/venice/stats/VeniceMetricsConfigTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static org.testng.Assert.assertEquals;
1616
import static org.testng.Assert.assertFalse;
1717
import static org.testng.Assert.assertNotNull;
18+
import static org.testng.Assert.assertNull;
1819
import static org.testng.Assert.assertTrue;
1920

2021
import com.linkedin.venice.stats.VeniceMetricsConfig.Builder;
@@ -49,6 +50,9 @@ public void testDefaultValuesWithBasicConfig() {
4950
assertEquals(config.useOtelExponentialHistogram(), true);
5051
assertEquals(config.getOtelExponentialHistogramMaxScale(), 3);
5152
assertEquals(config.getOtelExponentialHistogramMaxBuckets(), 250);
53+
assertTrue(config.getOtelCustomDimensionsMap().isEmpty());
54+
assertFalse(config.useOpenTelemetryInitializedByApplication());
55+
assertNull(config.getOtelCustomDescriptionForHistogramMetrics());
5256
assertNotNull(config.getTehutiMetricConfig());
5357
}
5458

@@ -213,4 +217,23 @@ public void testSetOtelHeaders() {
213217
new Builder().setServiceName("TestService").setMetricPrefix("TestPrefix").setOtelHeaders(otelHeaders).build();
214218
assertEquals(config.getOtelHeaders().get("key1"), "value1");
215219
}
220+
221+
@Test
222+
public void testSetOtelCustomDescription() {
223+
String customDescription = "This is a custom description for OpenTelemetry metrics.";
224+
VeniceMetricsConfig config = new Builder().setServiceName("TestService")
225+
.setMetricPrefix("TestPrefix")
226+
.setOtelCustomDescriptionForHistogramMetrics(customDescription)
227+
.build();
228+
assertEquals(config.getOtelCustomDescriptionForHistogramMetrics(), customDescription);
229+
}
230+
231+
@Test
232+
public void testUseOpenTelemetryInitializedByApplication() {
233+
VeniceMetricsConfig config = new Builder().setServiceName("TestService")
234+
.setMetricPrefix("TestPrefix")
235+
.setUseOpenTelemetryInitializedByApplication(true)
236+
.build();
237+
assertTrue(config.useOpenTelemetryInitializedByApplication());
238+
}
216239
}

0 commit comments

Comments
 (0)