1414
1515package com .instaclustr .kafka .helpers ;
1616
17+ import com .instaclustr .kafka .logging .KafkaClientMetricsLogger ;
18+ import org .apache .kafka .common .compress .Compression ;
19+ import org .apache .kafka .common .record .CompressionType ;
1720import org .apache .kafka .common .requests .RequestContext ;
21+ import org .apache .kafka .common .utils .BufferSupplier ;
1822import org .apache .kafka .server .authorizer .AuthorizableRequestContext ;
23+ import org .apache .kafka .shaded .com .google .protobuf .InvalidProtocolBufferException ;
1924import org .apache .kafka .shaded .io .opentelemetry .proto .common .v1 .AnyValue ;
2025import org .apache .kafka .shaded .io .opentelemetry .proto .common .v1 .KeyValue ;
2126import org .apache .kafka .shaded .io .opentelemetry .proto .metrics .v1 .MetricsData ;
2227import org .apache .kafka .shaded .io .opentelemetry .proto .resource .v1 .Resource ;
23- import org .slf4j .Logger ;
24- import org .slf4j .LoggerFactory ;
2528
29+ import java .io .ByteArrayOutputStream ;
30+ import java .io .InputStream ;
2631import java .nio .ByteBuffer ;
27- import java .util .HashMap ;
32+ import java .util .ArrayList ;
33+ import java .util .Arrays ;
34+ import java .util .Collections ;
35+ import java .util .List ;
2836import java .util .Map ;
2937
3038public class MetricsMetaDataProcessor {
31- private static final Logger logger = LoggerFactory .getLogger (MetricsMetaDataProcessor .class );
32- private final Map <String , Object > metadata ;
39+ private static final KafkaClientMetricsLogger logger = KafkaClientMetricsLogger .getLogger (MetricsMetaDataProcessor .class );
40+ private final List <KeyValue > staticMetadataAttributes ;
41+
3342
3443 public MetricsMetaDataProcessor (final Map <String , Object > metadata ) {
35- this .metadata = metadata ;
44+ this .staticMetadataAttributes = toStaticAttributes ( metadata ) ;
3645 }
3746
38- public byte [] processMetricsData (final AuthorizableRequestContext requestContext , final ByteBuffer buffer ) {
39- final byte [] rawBytes = bufferToBytes (buffer );
47+ public static List <KeyValue > toStaticAttributes (final Map <String , Object > metadata ) {
48+ if (metadata == null || metadata .isEmpty ()) {
49+ return Collections .emptyList ();
50+ }
51+
52+ final List <KeyValue > attrs = new ArrayList <>(metadata .size ());
53+ metadata .forEach ((key , value ) -> attrs .add (toKeyValue (key , value )));
54+ return attrs ;
55+ }
4056
57+ public byte [] processMetricsData (final AuthorizableRequestContext requestContext , final ByteBuffer buffer ) {
58+ final int bufferRemaining = buffer .remaining ();
59+ byte [] rawBytes = null ;
4160 try {
42- MetricsData metricsData = MetricsData .parseFrom (rawBytes );
61+ rawBytes = bufferToBytes (buffer );
62+ final MetricsData metricsData = decodeMetricsData (rawBytes );
63+ if (metricsData == null ) {
64+ return rawBytes ;
65+ }
4366 return enrichMetricsData (requestContext , metricsData );
4467 } catch (Exception e ) {
45- logger .error ("Error processing metrics data: {}" , e .getMessage (), e );
68+ logger .error ("Error processing metrics data (bufferRemaining={}, rawBytesLength={}): {}" ,
69+ bufferRemaining , rawBytes != null ? rawBytes .length : "null" , e .getMessage (), e );
4670 }
4771
48- return rawBytes ;
72+ return rawBytes != null ? rawBytes : bufferToBytes (buffer );
73+ }
74+
75+ private MetricsData decodeMetricsData (final byte [] rawBytes ) {
76+ if (rawBytes == null || rawBytes .length == 0 ) {
77+ return null ;
78+ }
79+
80+ // Fast path: payload is uncompressed OTLP protobuf.
81+ try {
82+ return MetricsData .parseFrom (rawBytes );
83+ } catch (final InvalidProtocolBufferException ignored ) {
84+ }
85+
86+ // Slow path: try to decompress with common Kafka compression types.
87+ for (final CompressionType compressionType : Arrays .asList (
88+ CompressionType .ZSTD ,
89+ CompressionType .GZIP ,
90+ CompressionType .LZ4 ,
91+ CompressionType .SNAPPY
92+ )) {
93+ try {
94+ final byte [] decompressed = decompress (rawBytes , compressionType );
95+ final MetricsData parsed = MetricsData .parseFrom (decompressed );
96+ logger .debug ("Decoded client telemetry payload using compression={}" , compressionType .name );
97+ return parsed ;
98+ } catch (final Exception ignored ) {
99+ }
100+ }
101+
102+ logger .error ("Unable to decode client telemetry payload as raw protobuf or with common compression codecs" );
103+ return null ;
104+ }
105+
106+ private byte [] decompress (final byte [] data , final CompressionType compressionType ) throws Exception {
107+ if (compressionType == CompressionType .NONE ) {
108+ return data ;
109+ }
110+
111+ final ByteBuffer buffer = ByteBuffer .wrap (data );
112+ final Compression compression = Compression .of (compressionType ).build ();
113+
114+ try (InputStream inputStream = compression .wrapForInput (buffer , (byte ) 0 , BufferSupplier .NO_CACHING );
115+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream ()) {
116+
117+ inputStream .transferTo (outputStream );
118+ return outputStream .toByteArray ();
119+ }
49120 }
50121
51122 private byte [] bufferToBytes (ByteBuffer buffer ) {
52- if (buffer .hasArray ()) {
123+ if (buffer .hasArray () && buffer . arrayOffset () == 0 && buffer . remaining () == buffer . array (). length ) {
53124 return buffer .array ();
54125 } else {
55126 byte [] bytes = new byte [buffer .remaining ()];
56- buffer .get (bytes );
127+ buffer .duplicate (). get (bytes );
57128 return bytes ;
58129 }
59130 }
60131
61132 private boolean shouldEnrichStaticMetaData (final MetricsData metricsData ) {
62- return !this .metadata .isEmpty () && metricsData .getResourceMetricsCount () > 0 ;
133+ return !this .staticMetadataAttributes .isEmpty () && metricsData .getResourceMetricsCount () > 0 ;
63134 }
64135
65136 private byte [] enrichMetricsData (final AuthorizableRequestContext context , MetricsData metricsData ) {
@@ -78,44 +149,61 @@ private void enrichStaticMetadata(MetricsData.Builder dataBuilder) {
78149 Resource .Builder resourceBuilder =
79150 dataBuilder .getResourceMetricsBuilder (i ).getResourceBuilder ();
80151
81- this .metadata .forEach ((key , value ) ->
82- resourceBuilder .addAttributes (toKeyValue (key , value ))
83- );
152+ resourceBuilder .addAllAttributes (staticMetadataAttributes );
84153 }
85154 }
86155
87156 private void enrichDynamicMetadata (final AuthorizableRequestContext context , MetricsData .Builder dataBuilder ) {
88157
89158 final RequestContext requestContext = (RequestContext ) context ;
90-
91- Map <String , String > dynamicMetadata = new HashMap <>();
92- if (requestContext .clientId () != null ) {
93- dynamicMetadata .put ("clientId" , requestContext .clientId ());
94- }
95- if (requestContext .clientInformation != null ) {
96- dynamicMetadata .put ("clientSoftwareName" , requestContext .clientInformation .softwareName ());
97- dynamicMetadata .put ("clientSoftwareVersion" , requestContext .clientInformation .softwareVersion ());
159+ final List <KeyValue > dynamicAttributes = buildDynamicAttributes (requestContext );
160+ if (dynamicAttributes .isEmpty ()) {
161+ return ;
98162 }
99163
100164 for (int i = 0 ; i < dataBuilder .getResourceMetricsCount (); i ++) {
101165 Resource .Builder resourceBuilder = dataBuilder .getResourceMetricsBuilder (i ).getResourceBuilder ();
102- dynamicMetadata .forEach ((key , value ) -> resourceBuilder .addAttributes (
103- KeyValue .newBuilder ()
104- .setKey (key )
105- .setValue (AnyValue .newBuilder ().setStringValue (value ))
106- .build ()
107- ));
166+ resourceBuilder .addAllAttributes (dynamicAttributes );
167+ }
168+ }
169+
170+ private static List <KeyValue > buildDynamicAttributes (final RequestContext requestContext ) {
171+ final String clientId = requestContext .clientId ();
172+ final String softwareName = requestContext .clientInformation != null ? requestContext .clientInformation .softwareName () : null ;
173+ final String softwareVersion = requestContext .clientInformation != null ? requestContext .clientInformation .softwareVersion () : null ;
174+
175+ if (clientId == null && softwareName == null && softwareVersion == null ) {
176+ return Collections .emptyList ();
108177 }
178+
179+ final List <KeyValue > attrs = new ArrayList <>(3 );
180+ if (clientId != null ) {
181+ attrs .add (toStringKeyValue ("clientId" , clientId ));
182+ }
183+ if (softwareName != null ) {
184+ attrs .add (toStringKeyValue ("clientSoftwareName" , softwareName ));
185+ }
186+ if (softwareVersion != null ) {
187+ attrs .add (toStringKeyValue ("clientSoftwareVersion" , softwareVersion ));
188+ }
189+ return attrs ;
190+ }
191+
192+ private static KeyValue toStringKeyValue (final String key , final String value ) {
193+ return KeyValue .newBuilder ()
194+ .setKey (key )
195+ .setValue (AnyValue .newBuilder ().setStringValue (value ).build ())
196+ .build ();
109197 }
110198
111- private KeyValue toKeyValue (final String key , final Object value ) {
199+ private static KeyValue toKeyValue (final String key , final Object value ) {
112200 return KeyValue .newBuilder ()
113201 .setKey (key )
114202 .setValue (toAnyValue (value ))
115203 .build ();
116204 }
117205
118- private AnyValue toAnyValue (final Object value ) {
206+ private static AnyValue toAnyValue (final Object value ) {
119207 AnyValue .Builder b = AnyValue .newBuilder ();
120208 if (value instanceof String ) {
121209 b .setStringValue ((String ) value );
0 commit comments