11package com .linkedin .davinci .stats ;
22
3+ import static com .linkedin .davinci .stats .OtelVersionedStatsUtils .classifyVersion ;
34import static com .linkedin .venice .meta .Store .NON_EXISTING_VERSION ;
5+ import static com .linkedin .venice .stats .dimensions .VeniceMetricsDimensions .VENICE_STORE_NAME ;
46
57import com .linkedin .venice .exceptions .validation .CorruptDataException ;
68import com .linkedin .venice .exceptions .validation .DataValidationException ;
79import com .linkedin .venice .exceptions .validation .DuplicateDataException ;
810import com .linkedin .venice .exceptions .validation .MissingDataException ;
911import com .linkedin .venice .meta .ReadOnlyStoreRepository ;
12+ import com .linkedin .venice .server .VersionRole ;
13+ import com .linkedin .venice .stats .OpenTelemetryMetricsSetup ;
14+ import com .linkedin .venice .stats .VeniceOpenTelemetryMetricsRepository ;
15+ import com .linkedin .venice .stats .dimensions .VeniceDIVResult ;
16+ import com .linkedin .venice .stats .dimensions .VeniceDIVSeverity ;
17+ import com .linkedin .venice .stats .dimensions .VeniceMetricsDimensions ;
18+ import com .linkedin .venice .stats .metrics .MetricEntityStateOneEnum ;
19+ import com .linkedin .venice .stats .metrics .MetricEntityStateTwoEnums ;
1020import com .linkedin .venice .utils .Utils ;
21+ import com .linkedin .venice .utils .concurrent .VeniceConcurrentHashMap ;
1122import io .tehuti .metrics .MetricsRepository ;
1223import it .unimi .dsi .fastutil .ints .IntOpenHashSet ;
1324import it .unimi .dsi .fastutil .ints .IntSet ;
25+ import java .util .Collections ;
26+ import java .util .HashMap ;
27+ import java .util .Map ;
1428import java .util .concurrent .atomic .AtomicLong ;
1529import java .util .function .BiConsumer ;
1630import java .util .function .Function ;
1731import java .util .function .IntConsumer ;
1832
1933
34+ /**
35+ * Aggregated versioned DIV stats with dual Tehuti + OTel recording.
36+ *
37+ * <p><b>Recording architecture:</b> Each public recording method (e.g., {@link #recordSuccessMsg})
38+ * records to <b>both</b> Tehuti (via {@code recordVersionedAndTotalStat} into total + per-version
39+ * {@link DIVStats} objects) and OTel (once per call, with store/cluster/version-role dimensions).
40+ * OTel totals are derived at query time by aggregating across the version-role dimension —
41+ * no separate OTel recording for total stats.
42+ *
43+ * <p><b>Version classification:</b> The version number passed to each recording method is classified
44+ * as CURRENT, FUTURE, or BACKUP for the OTel {@code VERSION_ROLE} dimension. Versions not matching
45+ * the registered current or future version default to BACKUP.
46+ */
2047public class AggVersionedDIVStats extends AbstractVeniceAggVersionedStats <DIVStats , DIVStatsReporter > {
48+ private final boolean emitOtelMetrics ;
49+ private final VeniceOpenTelemetryMetricsRepository otelRepository ;
50+ private final Map <VeniceMetricsDimensions , String > baseDimensionsMap ;
51+
52+ /**
53+ * Per-store OTel metric state maps. Each map grows lazily via {@code computeIfAbsent} and is bounded
54+ * by the number of stores the server is actively ingesting. Entries are removed when a store is
55+ * deleted via {@link #handleStoreDeleted(String)}. These maps are OTel-only; Tehuti recording is
56+ * handled by the parent class via {@code recordVersionedAndTotalStat}.
57+ */
58+ private final Map <String , MetricEntityStateTwoEnums <VersionRole , VeniceDIVResult >> messageCountPerStore =
59+ new VeniceConcurrentHashMap <>();
60+ private final Map <String , MetricEntityStateTwoEnums <VersionRole , VeniceDIVSeverity >> offsetRewindCountPerStore =
61+ new VeniceConcurrentHashMap <>();
62+ private final Map <String , MetricEntityStateOneEnum <VersionRole >> producerFailureCountPerStore =
63+ new VeniceConcurrentHashMap <>();
64+ private final Map <String , MetricEntityStateOneEnum <VersionRole >> benignProducerFailureCountPerStore =
65+ new VeniceConcurrentHashMap <>();
66+
67+ /**
68+ * Per-store version info for classifying versions as CURRENT, FUTURE, or BACKUP.
69+ * Updated via {@link #onVersionInfoUpdated(String, int, int)}.
70+ */
71+ private final Map <String , OtelVersionedStatsUtils .VersionInfo > versionInfoMap = new VeniceConcurrentHashMap <>();
72+
2173 public AggVersionedDIVStats (
2274 MetricsRepository metricsRepository ,
2375 ReadOnlyStoreRepository metadataRepository ,
24- boolean unregisterMetricForDeletedStoreEnabled ) {
76+ boolean unregisterMetricForDeletedStoreEnabled ,
77+ String clusterName ) {
2578 super (
2679 metricsRepository ,
2780 metadataRepository ,
2881 DIVStats ::new ,
2982 DIVStatsReporter ::new ,
3083 unregisterMetricForDeletedStoreEnabled );
84+
85+ OpenTelemetryMetricsSetup .OpenTelemetryMetricsSetupInfo otelData =
86+ OpenTelemetryMetricsSetup .builder (metricsRepository ).setClusterName (clusterName ).build ();
87+ this .emitOtelMetrics = otelData .emitOpenTelemetryMetrics ();
88+ this .otelRepository = otelData .getOtelRepository ();
89+ this .baseDimensionsMap = Collections .unmodifiableMap (otelData .getBaseDimensionsMap ());
3190 }
3291
3392 public void recordException (String storeName , int version , DataValidationException e ) {
@@ -42,34 +101,68 @@ public void recordException(String storeName, int version, DataValidationExcepti
42101
43102 public void recordDuplicateMsg (String storeName , int version ) {
44103 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordDuplicateMsg );
104+ recordOtelMessageCount (storeName , version , VeniceDIVResult .DUPLICATE );
45105 }
46106
47107 public void recordMissingMsg (String storeName , int version ) {
48108 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordMissingMsg );
109+ recordOtelMessageCount (storeName , version , VeniceDIVResult .MISSING );
49110 }
50111
51112 public void recordCorruptedMsg (String storeName , int version ) {
52113 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordCorruptedMsg );
114+ recordOtelMessageCount (storeName , version , VeniceDIVResult .CORRUPTED );
53115 }
54116
55117 public void recordSuccessMsg (String storeName , int version ) {
56118 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordSuccessMsg );
119+ recordOtelMessageCount (storeName , version , VeniceDIVResult .SUCCESS );
57120 }
58121
59122 public void recordBenignLeaderOffsetRewind (String storeName , int version ) {
60123 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordBenignLeaderOffsetRewind );
124+ recordOtelOffsetRewindCount (storeName , version , VeniceDIVSeverity .BENIGN );
61125 }
62126
63127 public void recordPotentiallyLossyLeaderOffsetRewind (String storeName , int version ) {
64128 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordPotentiallyLossyLeaderOffsetRewind );
129+ recordOtelOffsetRewindCount (storeName , version , VeniceDIVSeverity .POTENTIALLY_LOSSY );
65130 }
66131
67132 public void recordLeaderProducerFailure (String storeName , int version ) {
68133 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordLeaderProducerFailure );
134+ recordOtelOneEnumMetric (
135+ storeName ,
136+ version ,
137+ producerFailureCountPerStore ,
138+ DIVOtelMetricEntity .PRODUCER_FAILURE_COUNT );
69139 }
70140
71141 public void recordBenignLeaderProducerFailure (String storeName , int version ) {
72142 recordVersionedAndTotalStat (storeName , version , DIVStats ::recordBenignLeaderProducerFailure );
143+ recordOtelOneEnumMetric (
144+ storeName ,
145+ version ,
146+ benignProducerFailureCountPerStore ,
147+ DIVOtelMetricEntity .BENIGN_PRODUCER_FAILURE_COUNT );
148+ }
149+
150+ @ Override
151+ protected void onVersionInfoUpdated (String storeName , int currentVersion , int futureVersion ) {
152+ versionInfoMap .put (storeName , new OtelVersionedStatsUtils .VersionInfo (currentVersion , futureVersion ));
153+ }
154+
155+ @ Override
156+ public void handleStoreDeleted (String storeName ) {
157+ try {
158+ super .handleStoreDeleted (storeName );
159+ } finally {
160+ messageCountPerStore .remove (storeName );
161+ offsetRewindCountPerStore .remove (storeName );
162+ producerFailureCountPerStore .remove (storeName );
163+ benignProducerFailureCountPerStore .remove (storeName );
164+ versionInfoMap .remove (storeName );
165+ }
73166 }
74167
75168 @ Override
@@ -124,4 +217,61 @@ private void resetTotalStats(
124217 existingVersions .forEach (versionConsumer );
125218 Utils .computeIfNotNull (getTotalStats (storeName ), stat -> statsUpdater .accept (stat , totalStatCount .get ()));
126219 }
220+
221+ // --- OTel recording helpers ---
222+
223+ private Map <VeniceMetricsDimensions , String > buildStoreDimensionsMap (String storeName ) {
224+ Map <VeniceMetricsDimensions , String > map = new HashMap <>(baseDimensionsMap );
225+ map .put (VENICE_STORE_NAME , OpenTelemetryMetricsSetup .sanitizeStoreName (storeName ));
226+ return Collections .unmodifiableMap (map );
227+ }
228+
229+ private void recordOtelMessageCount (String storeName , int version , VeniceDIVResult result ) {
230+ if (!emitOtelMetrics ) {
231+ return ;
232+ }
233+ VersionRole role = classifyVersion (version , versionInfoMap .get (storeName ));
234+ messageCountPerStore .computeIfAbsent (
235+ storeName ,
236+ k -> MetricEntityStateTwoEnums .create (
237+ DIVOtelMetricEntity .MESSAGE_COUNT .getMetricEntity (),
238+ otelRepository ,
239+ buildStoreDimensionsMap (k ),
240+ VersionRole .class ,
241+ VeniceDIVResult .class ))
242+ .record (1 , role , result );
243+ }
244+
245+ private void recordOtelOffsetRewindCount (String storeName , int version , VeniceDIVSeverity severity ) {
246+ if (!emitOtelMetrics ) {
247+ return ;
248+ }
249+ VersionRole role = classifyVersion (version , versionInfoMap .get (storeName ));
250+ offsetRewindCountPerStore .computeIfAbsent (
251+ storeName ,
252+ k -> MetricEntityStateTwoEnums .create (
253+ DIVOtelMetricEntity .OFFSET_REWIND_COUNT .getMetricEntity (),
254+ otelRepository ,
255+ buildStoreDimensionsMap (k ),
256+ VersionRole .class ,
257+ VeniceDIVSeverity .class ))
258+ .record (1 , role , severity );
259+ }
260+
261+ private void recordOtelOneEnumMetric (
262+ String storeName ,
263+ int version ,
264+ Map <String , MetricEntityStateOneEnum <VersionRole >> perStoreMap ,
265+ DIVOtelMetricEntity metricEntity ) {
266+ if (!emitOtelMetrics ) {
267+ return ;
268+ }
269+ VersionRole role = classifyVersion (version , versionInfoMap .get (storeName ));
270+ perStoreMap
271+ .computeIfAbsent (
272+ storeName ,
273+ k -> MetricEntityStateOneEnum
274+ .create (metricEntity .getMetricEntity (), otelRepository , buildStoreDimensionsMap (k ), VersionRole .class ))
275+ .record (1 , role );
276+ }
127277}
0 commit comments