2121import java .nio .charset .Charset ;
2222import java .nio .charset .StandardCharsets ;
2323import java .text .MessageFormat ;
24+ import java .util .EnumMap ;
25+ import java .util .Map ;
2426import java .util .Optional ;
27+ import java .util .concurrent .ConcurrentHashMap ;
28+ import java .util .concurrent .ConcurrentMap ;
2529import java .util .function .Function ;
2630
2731import javax .annotation .Nullable ;
4044import org .eclipse .ditto .base .model .json .JsonSchemaVersion ;
4145import org .eclipse .ditto .base .model .json .Jsonifiable ;
4246import org .eclipse .ditto .base .model .signals .JsonParsable ;
47+ import org .eclipse .ditto .base .model .signals .WithResource ;
4348import org .eclipse .ditto .base .model .signals .WithType ;
49+ import org .eclipse .ditto .base .model .signals .acks .Acknowledgement ;
50+ import org .eclipse .ditto .base .model .signals .acks .Acknowledgements ;
51+ import org .eclipse .ditto .base .model .signals .announcements .Announcement ;
4452import org .eclipse .ditto .base .model .signals .commands .Command ;
53+ import org .eclipse .ditto .base .model .signals .commands .CommandResponse ;
54+ import org .eclipse .ditto .base .model .signals .events .Event ;
4555import org .eclipse .ditto .internal .utils .metrics .DittoMetrics ;
4656import org .eclipse .ditto .internal .utils .metrics .instruments .counter .Counter ;
4757import org .eclipse .ditto .internal .utils .metrics .instruments .tag .Tag ;
@@ -90,14 +100,19 @@ public abstract class AbstractJsonifiableWithDittoHeadersSerializer extends Seri
90100
91101 private static final String METRIC_NAME_SUFFIX = "_serializer_messages" ;
92102 private static final String METRIC_DIRECTION = "direction" ;
103+ private static final String METRIC_CATEGORY = "category" ;
104+ private static final String METRIC_RESOURCE_TYPE = "resource_type" ;
105+ private static final String DIRECTION_IN = "in" ;
106+ private static final String DIRECTION_OUT = "out" ;
107+ private static final String RESOURCE_TYPE_OTHER = "other" ;
93108
94109 private final int identifier ;
95110 private final MappingStrategies mappingStrategies ;
96111 private final Function <Object , String > manifestProvider ;
97112 private final BufferPool byteBufferPool ;
98113 private final Long defaultBufferSize ;
99- private final Counter inCounter ;
100- private final Counter outCounter ;
114+ private final Map < Category , ConcurrentMap < String , Counter >> inCounters ;
115+ private final Map < Category , ConcurrentMap < String , Counter >> outCounters ;
101116 private final String serializerName ;
102117
103118 /**
@@ -127,10 +142,54 @@ protected AbstractJsonifiableWithDittoHeadersSerializer(
127142 final var maxPoolEntries = config .withFallback (FALLBACK_CONF ).getInt (CONFIG_DIRECT_BUFFER_POOL_LIMIT );
128143 byteBufferPool = new DirectByteBufferPool (defaultBufferSize .intValue (), maxPoolEntries );
129144
130- inCounter = DittoMetrics .counter (serializerName .toLowerCase () + METRIC_NAME_SUFFIX )
131- .tag (METRIC_DIRECTION , "in" );
132- outCounter = DittoMetrics .counter (serializerName .toLowerCase () + METRIC_NAME_SUFFIX )
133- .tag (METRIC_DIRECTION , "out" );
145+ inCounters = newCategoryCounterCache ();
146+ outCounters = newCategoryCounterCache ();
147+ }
148+
149+ private static Map <Category , ConcurrentMap <String , Counter >> newCategoryCounterCache () {
150+ final Map <Category , ConcurrentMap <String , Counter >> cache = new EnumMap <>(Category .class );
151+ for (final Category category : Category .values ()) {
152+ cache .put (category , new ConcurrentHashMap <>());
153+ }
154+ return cache ;
155+ }
156+
157+ /**
158+ * Increments the {@code <serializer>_serializer_messages} counter tagged with the {@code direction}, the coarse
159+ * signal {@code category} and the {@code resource_type} of the passed {@code object}. The fully tagged counters
160+ * are cached per {@code (category, resource_type)} combination so that on the hot (de)serialization path only a map
161+ * lookup and the increment are performed; the tagged counter is built at most once per combination and serializer.
162+ *
163+ * @param countersByCategory the direction-specific counter cache ({@link #inCounters} or {@link #outCounters}).
164+ * @param direction the {@code direction} tag value ({@value #DIRECTION_IN} or {@value #DIRECTION_OUT}).
165+ * @param object the (de)serialized object to classify.
166+ */
167+ private void incrementCounter (final Map <Category , ConcurrentMap <String , Counter >> countersByCategory ,
168+ final String direction , final Object object ) {
169+
170+ final var category = Category .of (object );
171+ final var resourceType = resourceTypeOf (object );
172+ final var countersByResourceType = countersByCategory .get (category );
173+ var counter = countersByResourceType .get (resourceType );
174+ if (null == counter ) {
175+ // slow path, hit at most once per (category, resource_type) combination for a serializer's lifetime:
176+ counter = countersByResourceType .computeIfAbsent (resourceType ,
177+ rt -> DittoMetrics .counter (serializerName .toLowerCase () + METRIC_NAME_SUFFIX )
178+ .tag (METRIC_DIRECTION , direction )
179+ .tag (METRIC_CATEGORY , category .getTag ())
180+ .tag (METRIC_RESOURCE_TYPE , rt ));
181+ }
182+ counter .increment ();
183+ }
184+
185+ private static String resourceTypeOf (final Object object ) {
186+ final String result ;
187+ if (object instanceof WithResource withResource ) {
188+ result = withResource .getResourceType ();
189+ } else {
190+ result = RESOURCE_TYPE_OTHER ;
191+ }
192+ return result ;
134193 }
135194
136195 @ Override
@@ -155,7 +214,7 @@ public void toBinary(final Object object, final ByteBuffer buf) {
155214 try {
156215 serializeIntoByteBuffer (jsonObject , buf );
157216 LOG .trace ("toBinary jsonStr about to send 'out': {}" , jsonObject );
158- outCounter . increment ( );
217+ incrementCounter ( outCounters , DIRECTION_OUT , object );
159218 } catch (final BufferOverflowException e ) {
160219 final var errorMessage = MessageFormat .format (
161220 "Could not put bytes of JSON string <{0}> into ByteBuffer due to BufferOverflow" ,
@@ -282,7 +341,7 @@ public Object fromBinary(final ByteBuffer buf, final String manifest) {
282341 serializerName ,
283342 BinaryToHexConverter .createDebugMessageByTryingToConvertToHexString (buf ));
284343 }
285- inCounter . increment ( );
344+ incrementCounter ( inCounters , DIRECTION_IN , jsonifiable );
286345 return jsonifiable ;
287346 } catch (final NotSerializableException e ) {
288347 return e ;
@@ -448,4 +507,51 @@ private static String getDefaultManifestOrThrow(final JsonObject jsonObject) thr
448507 .orElseThrow (() -> new NotSerializableException ("No type found for inner JSON!" ));
449508 }
450509
510+ /**
511+ * Coarse, low-cardinality classification of a (de)serialized message, used as the {@code category} metric tag to
512+ * understand the composition of cluster (de)serialization traffic - e.g. distinguishing event fan-out from command
513+ * load. It is combined with the {@code resource_type} tag (derived from {@link WithResource#getResourceType()}).
514+ */
515+ private enum Category {
516+
517+ EVENT ("event" ),
518+ COMMAND ("command" ),
519+ RESPONSE ("response" ),
520+ ACKNOWLEDGEMENT ("acknowledgement" ),
521+ ANNOUNCEMENT ("announcement" ),
522+ ERROR ("error" ),
523+ OTHER ("other" );
524+
525+ private final String tag ;
526+
527+ Category (final String tag ) {
528+ this .tag = tag ;
529+ }
530+
531+ private String getTag () {
532+ return tag ;
533+ }
534+
535+ private static Category of (final Object object ) {
536+ final Category result ;
537+ // order matters: more specific types must be checked first (e.g. an Acknowledgement is a CommandResponse):
538+ if (object instanceof Event ) {
539+ result = EVENT ;
540+ } else if (object instanceof Announcement ) {
541+ result = ANNOUNCEMENT ;
542+ } else if (object instanceof Acknowledgement || object instanceof Acknowledgements ) {
543+ result = ACKNOWLEDGEMENT ;
544+ } else if (object instanceof CommandResponse ) {
545+ result = RESPONSE ;
546+ } else if (object instanceof Command ) {
547+ result = COMMAND ;
548+ } else if (object instanceof DittoRuntimeException ) {
549+ result = ERROR ;
550+ } else {
551+ result = OTHER ;
552+ }
553+ return result ;
554+ }
555+ }
556+
451557}
0 commit comments