@@ -91,165 +91,6 @@ void metricMetadata(
9191 assertThat (matcher .group ("metricName" )).isEqualTo (expectedMetricName );
9292 }
9393
94- @ ParameterizedTest
95- @ MethodSource ("resourceAttributesAdditionArgs" )
96- void resourceAttributesAddition (
97- MetricData metricData ,
98- @ Nullable Predicate <String > allowedResourceAttributesFilter ,
99- String metricName ,
100- String expectedMetricLabels )
101- throws IOException {
102-
103- Otel2PrometheusConverter converter =
104- new Otel2PrometheusConverter (true , allowedResourceAttributesFilter );
105-
106- ByteArrayOutputStream out = new ByteArrayOutputStream ();
107- MetricSnapshots snapshots = converter .convert (Collections .singletonList (metricData ));
108- ExpositionFormats .init ().getPrometheusTextFormatWriter ().write (out , snapshots );
109- String expositionFormat = new String (out .toByteArray (), StandardCharsets .UTF_8 );
110-
111- // extract the only metric line
112- List <String > metricLines =
113- Arrays .stream (expositionFormat .split ("\n " ))
114- .filter (line -> line .startsWith (metricName ))
115- .collect (Collectors .toList ());
116- assertThat (metricLines ).hasSize (1 );
117- String metricLine = metricLines .get (0 );
118-
119- String metricLabels =
120- metricLine .substring (metricLine .indexOf ("{" ) + 1 , metricLine .indexOf ("}" ));
121- assertThat (metricLabels ).isEqualTo (expectedMetricLabels );
122- }
123-
124- @ Test
125- void prometheusNameCollisionTest_Issue6277 () {
126- // NOTE: Metrics with the same resolved prometheus name should merge. However,
127- // Otel2PrometheusConverter is not responsible for merging individual series, so the merge will
128- // fail if the two different metrics contain overlapping series. Users should deal with this by
129- // adding a view that renames one of the two metrics such that the conflict does not occur.
130- MetricData dotName =
131- createSampleMetricData (
132- "my.metric" ,
133- "units" ,
134- MetricDataType .LONG_SUM ,
135- Attributes .builder ().put ("key" , "a" ).build (),
136- Resource .create (Attributes .empty ()));
137- MetricData underscoreName =
138- createSampleMetricData (
139- "my_metric" ,
140- "units" ,
141- MetricDataType .LONG_SUM ,
142- Attributes .builder ().put ("key" , "b" ).build (),
143- Resource .create (Attributes .empty ()));
144-
145- List <MetricData > metricData = new ArrayList <>();
146- metricData .add (dotName );
147- metricData .add (underscoreName );
148-
149- assertThatCode (() -> converter .convert (metricData )).doesNotThrowAnyException ();
150- }
151-
152- @ ParameterizedTest
153- @ MethodSource ("labelValueSerializationArgs" )
154- void labelValueSerialization (Attributes attributes ) {
155- MetricData metricData =
156- createSampleMetricData ("sample" , "1" , MetricDataType .LONG_SUM , attributes , null );
157-
158- MetricSnapshots snapshots = converter .convert (Collections .singletonList (metricData ));
159-
160- Labels labels = snapshots .get (0 ).getDataPoints ().get (0 ).getLabels ();
161- attributes .forEach (
162- (key , value ) -> {
163- String labelValue = labels .get (key .getKey ());
164- try {
165- String expectedValue =
166- key .getType () == AttributeType .STRING
167- ? (String ) value
168- : OBJECT_MAPPER .writeValueAsString (value );
169- assertThat (labelValue ).isEqualTo (expectedValue );
170- } catch (JsonProcessingException e ) {
171- throw new RuntimeException (e );
172- }
173- });
174- }
175-
176- @ Test
177- void labelValueSerialization_Should_Handle_All_AttributeTypes () {
178- assertThat (Stream .of (AttributeType .values ()).map (Enum ::name ))
179- .isEqualTo (
180- Arrays .asList (
181- "STRING" ,
182- "BOOLEAN" ,
183- "LONG" ,
184- "DOUBLE" ,
185- "STRING_ARRAY" ,
186- "BOOLEAN_ARRAY" ,
187- "LONG_ARRAY" ,
188- "DOUBLE_ARRAY" ));
189- }
190-
191- private static Stream <Arguments > resourceAttributesAdditionArgs () {
192- List <Arguments > arguments = new ArrayList <>();
193-
194- for (MetricDataType metricDataType : MetricDataType .values ()) {
195- // Check that resource attributes are added as labels, according to allowed pattern
196- arguments .add (
197- Arguments .of (
198- createSampleMetricData (
199- "my.metric" ,
200- "units" ,
201- metricDataType ,
202- Attributes .of (stringKey ("foo1" ), "bar1" , stringKey ("foo2" ), "bar2" ),
203- Resource .create (
204- Attributes .of (
205- stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
206- /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
207- metricDataType == MetricDataType .SUMMARY
208- || metricDataType == MetricDataType .HISTOGRAM
209- || metricDataType == MetricDataType .EXPONENTIAL_HISTOGRAM
210- ? "my_metric_units_count"
211- : "my_metric_units" ,
212-
213- // "cluster" attribute is added (due to reg expr specified) and only it
214- "cluster=\" mycluster\" ,foo1=\" bar1\" ,foo2=\" bar2\" ,otel_scope_name=\" scope\" " ));
215- }
216-
217- // Resource attributes which also exists in the metric labels are not added twice
218- arguments .add (
219- Arguments .of (
220- createSampleMetricData (
221- "my.metric" ,
222- "units" ,
223- MetricDataType .LONG_SUM ,
224- Attributes .of (stringKey ("cluster" ), "mycluster2" , stringKey ("foo2" ), "bar2" ),
225- Resource .create (
226- Attributes .of (
227- stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
228- /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
229- "my_metric_units" ,
230-
231- // "cluster" attribute is present only once and the value is taken
232- // from the metric attributes and not the resource attributes
233- "cluster=\" mycluster2\" ,foo2=\" bar2\" ,otel_scope_name=\" scope\" " ));
234-
235- // Empty attributes
236- arguments .add (
237- Arguments .of (
238- createSampleMetricData (
239- "my.metric" ,
240- "units" ,
241- MetricDataType .LONG_SUM ,
242- Attributes .empty (),
243- Resource .create (
244- Attributes .of (
245- stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
246- /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
247- "my_metric_units" ,
248- "cluster=\" mycluster\" ,otel_scope_name=\" scope\" " ));
249-
250- return arguments .stream ();
251- }
252-
25394 private static Stream <Arguments > metricMetadataArgs () {
25495 return Stream .of (
25596 // the unity unit "1" is translated to "ratio"
@@ -345,6 +186,150 @@ private static Stream<Arguments> metricMetadataArgs() {
345186 "_metric_name_bytes_count" ));
346187 }
347188
189+ @ ParameterizedTest
190+ @ MethodSource ("resourceAttributesAdditionArgs" )
191+ void resourceAttributesAddition (
192+ MetricData metricData ,
193+ @ Nullable Predicate <String > allowedResourceAttributesFilter ,
194+ String metricName ,
195+ String expectedMetricLabels )
196+ throws IOException {
197+
198+ Otel2PrometheusConverter converter =
199+ new Otel2PrometheusConverter (true , allowedResourceAttributesFilter );
200+
201+ ByteArrayOutputStream out = new ByteArrayOutputStream ();
202+ MetricSnapshots snapshots = converter .convert (Collections .singletonList (metricData ));
203+ ExpositionFormats .init ().getPrometheusTextFormatWriter ().write (out , snapshots );
204+ String expositionFormat = new String (out .toByteArray (), StandardCharsets .UTF_8 );
205+
206+ // extract the only metric line
207+ List <String > metricLines =
208+ Arrays .stream (expositionFormat .split ("\n " ))
209+ .filter (line -> line .startsWith (metricName ))
210+ .collect (Collectors .toList ());
211+ assertThat (metricLines ).hasSize (1 );
212+ String metricLine = metricLines .get (0 );
213+
214+ String metricLabels =
215+ metricLine .substring (metricLine .indexOf ("{" ) + 1 , metricLine .indexOf ("}" ));
216+ assertThat (metricLabels ).isEqualTo (expectedMetricLabels );
217+ }
218+
219+ private static Stream <Arguments > resourceAttributesAdditionArgs () {
220+ List <Arguments > arguments = new ArrayList <>();
221+
222+ for (MetricDataType metricDataType : MetricDataType .values ()) {
223+ // Check that resource attributes are added as labels, according to allowed pattern
224+ arguments .add (
225+ Arguments .of (
226+ createSampleMetricData (
227+ "my.metric" ,
228+ "units" ,
229+ metricDataType ,
230+ Attributes .of (stringKey ("foo1" ), "bar1" , stringKey ("foo2" ), "bar2" ),
231+ Resource .create (
232+ Attributes .of (
233+ stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
234+ /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
235+ metricDataType == MetricDataType .SUMMARY
236+ || metricDataType == MetricDataType .HISTOGRAM
237+ || metricDataType == MetricDataType .EXPONENTIAL_HISTOGRAM
238+ ? "my_metric_units_count"
239+ : "my_metric_units" ,
240+
241+ // "cluster" attribute is added (due to reg expr specified) and only it
242+ "cluster=\" mycluster\" ,foo1=\" bar1\" ,foo2=\" bar2\" ,otel_scope_name=\" scope\" " ));
243+ }
244+
245+ // Resource attributes which also exists in the metric labels are not added twice
246+ arguments .add (
247+ Arguments .of (
248+ createSampleMetricData (
249+ "my.metric" ,
250+ "units" ,
251+ MetricDataType .LONG_SUM ,
252+ Attributes .of (stringKey ("cluster" ), "mycluster2" , stringKey ("foo2" ), "bar2" ),
253+ Resource .create (
254+ Attributes .of (
255+ stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
256+ /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
257+ "my_metric_units" ,
258+
259+ // "cluster" attribute is present only once and the value is taken
260+ // from the metric attributes and not the resource attributes
261+ "cluster=\" mycluster2\" ,foo2=\" bar2\" ,otel_scope_name=\" scope\" " ));
262+
263+ // Empty attributes
264+ arguments .add (
265+ Arguments .of (
266+ createSampleMetricData (
267+ "my.metric" ,
268+ "units" ,
269+ MetricDataType .LONG_SUM ,
270+ Attributes .empty (),
271+ Resource .create (
272+ Attributes .of (
273+ stringKey ("host" ), "localhost" , stringKey ("cluster" ), "mycluster" ))),
274+ /* allowedResourceAttributesFilter= */ Predicates .startsWith ("clu" ),
275+ "my_metric_units" ,
276+ "cluster=\" mycluster\" ,otel_scope_name=\" scope\" " ));
277+
278+ return arguments .stream ();
279+ }
280+
281+ @ Test
282+ void prometheusNameCollisionTest_Issue6277 () {
283+ // NOTE: Metrics with the same resolved prometheus name should merge. However,
284+ // Otel2PrometheusConverter is not responsible for merging individual series, so the merge will
285+ // fail if the two different metrics contain overlapping series. Users should deal with this by
286+ // adding a view that renames one of the two metrics such that the conflict does not occur.
287+ MetricData dotName =
288+ createSampleMetricData (
289+ "my.metric" ,
290+ "units" ,
291+ MetricDataType .LONG_SUM ,
292+ Attributes .builder ().put ("key" , "a" ).build (),
293+ Resource .create (Attributes .empty ()));
294+ MetricData underscoreName =
295+ createSampleMetricData (
296+ "my_metric" ,
297+ "units" ,
298+ MetricDataType .LONG_SUM ,
299+ Attributes .builder ().put ("key" , "b" ).build (),
300+ Resource .create (Attributes .empty ()));
301+
302+ List <MetricData > metricData = new ArrayList <>();
303+ metricData .add (dotName );
304+ metricData .add (underscoreName );
305+
306+ assertThatCode (() -> converter .convert (metricData )).doesNotThrowAnyException ();
307+ }
308+
309+ @ ParameterizedTest
310+ @ MethodSource ("labelValueSerializationArgs" )
311+ void labelValueSerialization (Attributes attributes ) {
312+ MetricData metricData =
313+ createSampleMetricData ("sample" , "1" , MetricDataType .LONG_SUM , attributes , null );
314+
315+ MetricSnapshots snapshots = converter .convert (Collections .singletonList (metricData ));
316+
317+ Labels labels = snapshots .get (0 ).getDataPoints ().get (0 ).getLabels ();
318+ attributes .forEach (
319+ (key , value ) -> {
320+ String labelValue = labels .get (key .getKey ());
321+ try {
322+ String expectedValue =
323+ key .getType () == AttributeType .STRING
324+ ? (String ) value
325+ : OBJECT_MAPPER .writeValueAsString (value );
326+ assertThat (labelValue ).isEqualTo (expectedValue );
327+ } catch (JsonProcessingException e ) {
328+ throw new RuntimeException (e );
329+ }
330+ });
331+ }
332+
348333 private static Stream <Arguments > labelValueSerializationArgs () {
349334 return Stream .of (
350335 Arguments .of (Attributes .of (stringKey ("key" ), "stringValue" )),
0 commit comments