33import static com .linkedin .venice .stats .VeniceOpenTelemetryMetricNamingFormat .transformMetricName ;
44import static com .linkedin .venice .stats .VeniceOpenTelemetryMetricNamingFormat .validateMetricName ;
55
6+ import com .google .common .annotations .VisibleForTesting ;
67import com .linkedin .venice .exceptions .VeniceException ;
78import com .linkedin .venice .stats .dimensions .VeniceDimensionInterface ;
89import com .linkedin .venice .stats .dimensions .VeniceMetricsDimensions ;
1112import com .linkedin .venice .stats .metrics .MetricType ;
1213import com .linkedin .venice .stats .metrics .MetricUnit ;
1314import com .linkedin .venice .utils .concurrent .VeniceConcurrentHashMap ;
15+ import io .opentelemetry .api .GlobalOpenTelemetry ;
1416import io .opentelemetry .api .OpenTelemetry ;
1517import io .opentelemetry .api .common .Attributes ;
1618import 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 }
0 commit comments