11package com .linkedin .venice .stats ;
22
33import static com .linkedin .venice .stats .VeniceOpenTelemetryMetricNamingFormat .SNAKE_CASE ;
4+ import static io .opentelemetry .sdk .metrics .InstrumentType .GAUGE ;
45
56import com .linkedin .venice .stats .metrics .MetricEntity ;
67import io .opentelemetry .exporter .otlp .internal .OtlpConfigUtil ;
8+ import io .opentelemetry .sdk .metrics .data .AggregationTemporality ;
79import io .opentelemetry .sdk .metrics .export .AggregationTemporalitySelector ;
810import io .opentelemetry .sdk .metrics .export .MetricExporter ;
911import io .opentelemetry .sdk .metrics .export .MetricReader ;
@@ -126,6 +128,16 @@ public class VeniceMetricsConfig {
126128 public static final String OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE =
127129 "otel.exporter.otlp.metrics.temporality.preference" ;
128130
131+ /**
132+ * Make Synchronous Gauge instruments export the last recorded value even if the instrument is not
133+ * recorded in the current collection interval.
134+ * - If true, it will export the last recorded value even if the instrument is not recorded in the current collection interval.
135+ * - If false, it will use the default behavior of the selected {@link #otelAggregationTemporalitySelector}, which is
136+ * {@link AggregationTemporalitySelector#deltaPreferred()} by default.
137+ */
138+ public static final String OTEL_VENICE_EXPORT_LAST_RECORDED_VALUE_FOR_SYNCHRONOUS_GAUGE =
139+ "otel.venice.export.last.recorded.value.for.synchronous.gauge" ;
140+
129141 /**
130142 * Default histogram aggregation to be used for all histograms: Select one of the below <br>
131143 * 1. base2_exponential_bucket_histogram <br>
@@ -208,6 +220,11 @@ public class VeniceMetricsConfig {
208220 /** Metric naming conventions for OpenTelemetry metrics */
209221 private final VeniceOpenTelemetryMetricNamingFormat metricNamingFormat ;
210222
223+ /**
224+ * Whether to export the last recorded value for synchronous Gauge instruments.
225+ */
226+ private final boolean exportLastRecordedValueForSynchronousGauge ;
227+
211228 /** Aggregation Temporality selector to export only the delta or cumulate or different */
212229 private final AggregationTemporalitySelector otelAggregationTemporalitySelector ;
213230
@@ -234,6 +251,7 @@ private VeniceMetricsConfig(Builder builder) {
234251 this .otelHeaders = builder .otelHeaders ;
235252 this .exportOtelMetricsToLog = builder .exportOtelMetricsToLog ;
236253 this .metricNamingFormat = builder .metricNamingFormat ;
254+ this .exportLastRecordedValueForSynchronousGauge = builder .exportLastRecordedValueForSynchronousGauge ;
237255 this .otelAggregationTemporalitySelector = builder .otelAggregationTemporalitySelector ;
238256 this .useOtelExponentialHistogram = builder .useOtelExponentialHistogram ;
239257 this .otelExponentialHistogramMaxScale = builder .otelExponentialHistogramMaxScale ;
@@ -257,6 +275,7 @@ public static class Builder {
257275 Map <String , String > otelHeaders = new HashMap <>();
258276 private boolean exportOtelMetricsToLog = false ;
259277 private VeniceOpenTelemetryMetricNamingFormat metricNamingFormat = SNAKE_CASE ;
278+ private boolean exportLastRecordedValueForSynchronousGauge = true ;
260279 private AggregationTemporalitySelector otelAggregationTemporalitySelector =
261280 AggregationTemporalitySelector .deltaPreferred ();
262281 private boolean useOtelExponentialHistogram = true ;
@@ -331,6 +350,11 @@ public Builder setMetricNamingFormat(String metricNamingFormat) {
331350 return this ;
332351 }
333352
353+ public Builder setExportLastRecordedValueForSynchronousGauge (boolean exportLastRecordedValueForSynchronousGauge ) {
354+ this .exportLastRecordedValueForSynchronousGauge = exportLastRecordedValueForSynchronousGauge ;
355+ return this ;
356+ }
357+
334358 public Builder setOtelAggregationTemporalitySelector (
335359 AggregationTemporalitySelector otelAggregationTemporalitySelector ) {
336360 this .otelAggregationTemporalitySelector = otelAggregationTemporalitySelector ;
@@ -439,6 +463,10 @@ public Builder extractAndSetOtelConfigs(Map<String, String> configs) {
439463 otelHeaders .put (headers [0 ], headers [1 ]);
440464 }
441465
466+ if ((configValue = configs .get (OTEL_VENICE_EXPORT_LAST_RECORDED_VALUE_FOR_SYNCHRONOUS_GAUGE )) != null ) {
467+ setExportLastRecordedValueForSynchronousGauge (Boolean .parseBoolean (configValue ));
468+ }
469+
442470 if ((configValue = configs .get (OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE )) != null ) {
443471 switch (configValue .toLowerCase (Locale .ROOT )) {
444472 case "cumulative" :
@@ -507,6 +535,12 @@ private void checkAndSetDefaults() {
507535 } else {
508536 LOGGER .warn ("OpenTelemetry metrics are disabled" );
509537 }
538+
539+ if (exportLastRecordedValueForSynchronousGauge ) {
540+ // Override the configured temporality selector to ensure synchronous gauges export last recorded value
541+ otelAggregationTemporalitySelector =
542+ getTemporalitySelector (exportLastRecordedValueForSynchronousGauge , otelAggregationTemporalitySelector );
543+ }
510544 }
511545
512546 public VeniceMetricsConfig build () {
@@ -572,6 +606,10 @@ public VeniceOpenTelemetryMetricNamingFormat getMetricNamingFormat() {
572606 return metricNamingFormat ;
573607 }
574608
609+ public boolean exportLastRecordedValueForSynchronousGauge () {
610+ return exportLastRecordedValueForSynchronousGauge ;
611+ }
612+
575613 public AggregationTemporalitySelector getOtelAggregationTemporalitySelector () {
576614 return otelAggregationTemporalitySelector ;
577615 }
@@ -609,4 +647,23 @@ public String toString() {
609647 + otelExponentialHistogramMaxScale + ", otelExponentialHistogramMaxBuckets="
610648 + otelExponentialHistogramMaxBuckets + ", tehutiMetricConfig=" + tehutiMetricConfig + '}' ;
611649 }
650+
651+ /**
652+ * Custom AggregationTemporalitySelector which enforces that if the instrument type is
653+ * GAUGE and {@link #exportLastRecordedValueForSynchronousGauge} is true,
654+ * it returns CUMULATIVE for GAUGE type instruments.
655+ * This is to support Synchronous GAUGE exporting the last set value even when
656+ * it is not set during the last export interval
657+ * Check https://github.com/open-telemetry/opentelemetry-java/pull/7634 for more details
658+ */
659+ public static AggregationTemporalitySelector getTemporalitySelector (
660+ boolean exportLastRecordedValueForSynchronousGauge ,
661+ AggregationTemporalitySelector configuredTemporalitySelector ) {
662+ return instrumentType -> {
663+ if (exportLastRecordedValueForSynchronousGauge && instrumentType == GAUGE ) {
664+ return AggregationTemporality .CUMULATIVE ;
665+ }
666+ return configuredTemporalitySelector .getAggregationTemporality (instrumentType );
667+ };
668+ }
612669}
0 commit comments