2323import io .prometheus .metrics .model .snapshots .MetricMetadata ;
2424import io .prometheus .metrics .model .snapshots .MetricSnapshot ;
2525import io .prometheus .metrics .model .snapshots .MetricSnapshots ;
26+ import io .prometheus .metrics .model .snapshots .NativeHistogramBuckets ;
2627import io .prometheus .metrics .model .snapshots .PrometheusNaming ;
2728import io .prometheus .metrics .model .snapshots .Quantile ;
2829import io .prometheus .metrics .model .snapshots .SnapshotEscaper ;
@@ -63,7 +64,7 @@ public Builder setOpenMetrics2Properties(OpenMetrics2Properties openMetrics2Prop
6364 }
6465
6566 /**
66- * @param createdTimestampsEnabled whether to include the start timestamp in the output
67+ * @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics
6768 */
6869 public Builder setCreatedTimestampsEnabled (boolean createdTimestampsEnabled ) {
6970 this .createdTimestampsEnabled = createdTimestampsEnabled ;
@@ -88,21 +89,19 @@ public OpenMetrics2TextFormatWriter build() {
8889 public static final String CONTENT_TYPE =
8990 "application/openmetrics-text; version=2.0.0; charset=utf-8" ;
9091 private final OpenMetrics2Properties openMetrics2Properties ;
91- private final boolean createdTimestampsEnabled ;
9292 private final boolean exemplarsOnAllMetricTypesEnabled ;
9393 private final OpenMetricsTextFormatWriter om1Writer ;
9494
9595 /**
9696 * @param openMetrics2Properties OpenMetrics 2.0 feature flags
97- * @param createdTimestampsEnabled whether to include the start timestamp in the output.
97+ * @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics
9898 * @param exemplarsOnAllMetricTypesEnabled whether to include exemplars on all metric types
9999 */
100100 public OpenMetrics2TextFormatWriter (
101101 OpenMetrics2Properties openMetrics2Properties ,
102102 boolean createdTimestampsEnabled ,
103103 boolean exemplarsOnAllMetricTypesEnabled ) {
104104 this .openMetrics2Properties = openMetrics2Properties ;
105- this .createdTimestampsEnabled = createdTimestampsEnabled ;
106105 this .exemplarsOnAllMetricTypesEnabled = exemplarsOnAllMetricTypesEnabled ;
107106 this .om1Writer =
108107 new OpenMetricsTextFormatWriter (createdTimestampsEnabled , exemplarsOnAllMetricTypesEnabled );
@@ -126,8 +125,8 @@ public boolean accepts(@Nullable String acceptHeader) {
126125
127126 @ Override
128127 public String getContentType () {
129- // When contentNegotiation=false (default), masquerade as OM1 for compatibility
130- // When contentNegotiation=true, use proper OM2 version
128+ // When contentNegotiation=false (default), masquerade as OM1 for compatibility.
129+ // When contentNegotiation=true, use proper OM2 version.
131130 if (openMetrics2Properties .getContentNegotiation ()) {
132131 return CONTENT_TYPE ;
133132 } else {
@@ -181,7 +180,7 @@ private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingSchem
181180 writer .write (' ' );
182181 writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
183182 }
184- if (createdTimestampsEnabled && data .hasCreatedTimestamp ()) {
183+ if (data .hasCreatedTimestamp ()) {
185184 writer .write (" st@" );
186185 writeOpenMetricsTimestamp (writer , data .getCreatedTimestampMillis ());
187186 }
@@ -208,8 +207,9 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc
208207
209208 private void writeHistogram (Writer writer , HistogramSnapshot snapshot , EscapingScheme scheme )
210209 throws IOException {
211- if (!openMetrics2Properties .getCompositeValues ()
212- && !openMetrics2Properties .getExemplarCompliance ()) {
210+ boolean compositeHistogram =
211+ openMetrics2Properties .getCompositeValues () || openMetrics2Properties .getNativeHistograms ();
212+ if (!compositeHistogram && !openMetrics2Properties .getExemplarCompliance ()) {
213213 om1Writer .writeHistogram (writer , snapshot , scheme );
214214 return ;
215215 }
@@ -218,12 +218,20 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingS
218218 if (snapshot .isGaugeHistogram ()) {
219219 writeMetadataWithName (writer , name , "gaugehistogram" , metadata );
220220 for (HistogramSnapshot .HistogramDataPointSnapshot data : snapshot .getDataPoints ()) {
221- writeCompositeHistogramDataPoint (writer , name , "gcount" , "gsum" , data , scheme );
221+ if (openMetrics2Properties .getNativeHistograms () && data .hasNativeHistogramData ()) {
222+ writeNativeHistogramDataPoint (writer , name , "gcount" , "gsum" , data , scheme , false );
223+ } else {
224+ writeCompositeHistogramDataPoint (writer , name , "gcount" , "gsum" , data , scheme , false );
225+ }
222226 }
223227 } else {
224228 writeMetadataWithName (writer , name , "histogram" , metadata );
225229 for (HistogramSnapshot .HistogramDataPointSnapshot data : snapshot .getDataPoints ()) {
226- writeCompositeHistogramDataPoint (writer , name , "count" , "sum" , data , scheme );
230+ if (openMetrics2Properties .getNativeHistograms () && data .hasNativeHistogramData ()) {
231+ writeNativeHistogramDataPoint (writer , name , "count" , "sum" , data , scheme , true );
232+ } else {
233+ writeCompositeHistogramDataPoint (writer , name , "count" , "sum" , data , scheme , true );
234+ }
227235 }
228236 }
229237 }
@@ -234,7 +242,8 @@ private void writeCompositeHistogramDataPoint(
234242 String countKey ,
235243 String sumKey ,
236244 HistogramSnapshot .HistogramDataPointSnapshot data ,
237- EscapingScheme scheme )
245+ EscapingScheme scheme ,
246+ boolean includeStartTimestamp )
238247 throws IOException {
239248 writeNameAndLabels (writer , name , null , data .getLabels (), scheme );
240249 writer .write ('{' );
@@ -245,28 +254,59 @@ private void writeCompositeHistogramDataPoint(
245254 writer .write (sumKey );
246255 writer .write (':' );
247256 writeDouble (writer , data .getSum ());
248- writer .write (",bucket:[" );
249- ClassicHistogramBuckets buckets = getClassicBuckets (data );
250- long cumulativeCount = 0 ;
251- for (int i = 0 ; i < buckets .size (); i ++) {
252- if (i > 0 ) {
253- writer .write (',' );
254- }
255- cumulativeCount += buckets .getCount (i );
256- writeDouble (writer , buckets .getUpperBound (i ));
257- writer .write (':' );
258- writeLong (writer , cumulativeCount );
257+ writeClassicBucketsField (writer , data );
258+ writer .write ('}' );
259+ if (data .hasScrapeTimestamp ()) {
260+ writer .write (' ' );
261+ writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
259262 }
260- writer .write ("]}" );
263+ if (includeStartTimestamp && data .hasCreatedTimestamp ()) {
264+ writer .write (" st@" );
265+ writeOpenMetricsTimestamp (writer , data .getCreatedTimestampMillis ());
266+ }
267+ writeExemplars (writer , data .getExemplars (), scheme );
268+ writer .write ('\n' );
269+ }
270+
271+ private void writeNativeHistogramDataPoint (
272+ Writer writer ,
273+ String name ,
274+ String countKey ,
275+ String sumKey ,
276+ HistogramSnapshot .HistogramDataPointSnapshot data ,
277+ EscapingScheme scheme ,
278+ boolean includeStartTimestamp )
279+ throws IOException {
280+ writeNameAndLabels (writer , name , null , data .getLabels (), scheme );
281+ writer .write ('{' );
282+ writer .write (countKey );
283+ writer .write (':' );
284+ writeLong (writer , data .getCount ());
285+ writer .write (',' );
286+ writer .write (sumKey );
287+ writer .write (':' );
288+ writeDouble (writer , data .getSum ());
289+ writer .write (",schema:" );
290+ writer .write (Integer .toString (data .getNativeSchema ()));
291+ writer .write (",zero_threshold:" );
292+ writeDouble (writer , data .getNativeZeroThreshold ());
293+ writer .write (",zero_count:" );
294+ writeLong (writer , data .getNativeZeroCount ());
295+ writeNativeBucketFields (writer , "negative" , data .getNativeBucketsForNegativeValues ());
296+ writeNativeBucketFields (writer , "positive" , data .getNativeBucketsForPositiveValues ());
297+ if (data .hasClassicHistogramData ()) {
298+ writeClassicBucketsField (writer , data );
299+ }
300+ writer .write ('}' );
261301 if (data .hasScrapeTimestamp ()) {
262302 writer .write (' ' );
263303 writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
264304 }
265- if (data .hasCreatedTimestamp ()) {
305+ if (includeStartTimestamp && data .hasCreatedTimestamp ()) {
266306 writer .write (" st@" );
267307 writeOpenMetricsTimestamp (writer , data .getCreatedTimestampMillis ());
268308 }
269- writeExemplar (writer , data .getExemplars (). getLatest (), scheme );
309+ writeExemplars (writer , data .getExemplars (), scheme );
270310 writer .write ('\n' );
271311 }
272312
@@ -280,6 +320,75 @@ private ClassicHistogramBuckets getClassicBuckets(
280320 }
281321 }
282322
323+ private void writeClassicBucketsField (
324+ Writer writer , HistogramSnapshot .HistogramDataPointSnapshot data ) throws IOException {
325+ writer .write (",bucket:[" );
326+ ClassicHistogramBuckets buckets = getClassicBuckets (data );
327+ long cumulativeCount = 0 ;
328+ for (int i = 0 ; i < buckets .size (); i ++) {
329+ if (i > 0 ) {
330+ writer .write (',' );
331+ }
332+ cumulativeCount += buckets .getCount (i );
333+ writeDouble (writer , buckets .getUpperBound (i ));
334+ writer .write (':' );
335+ writeLong (writer , cumulativeCount );
336+ }
337+ writer .write (']' );
338+ }
339+
340+ private void writeNativeBucketFields (Writer writer , String prefix , NativeHistogramBuckets buckets )
341+ throws IOException {
342+ if (buckets .size () == 0 ) {
343+ return ;
344+ }
345+ writer .write (',' );
346+ writer .write (prefix );
347+ writer .write ("_spans:[" );
348+ writeNativeBucketSpans (writer , buckets );
349+ writer .write ("]," );
350+ writer .write (prefix );
351+ writer .write ("_buckets:[" );
352+ for (int i = 0 ; i < buckets .size (); i ++) {
353+ if (i > 0 ) {
354+ writer .write (',' );
355+ }
356+ writeLong (writer , buckets .getCount (i ));
357+ }
358+ writer .write (']' );
359+ }
360+
361+ private void writeNativeBucketSpans (Writer writer , NativeHistogramBuckets buckets )
362+ throws IOException {
363+ int spanOffset = buckets .getBucketIndex (0 );
364+ int spanLength = 1 ;
365+ int previousIndex = buckets .getBucketIndex (0 );
366+ boolean firstSpan = true ;
367+ for (int i = 1 ; i < buckets .size (); i ++) {
368+ int bucketIndex = buckets .getBucketIndex (i );
369+ if (bucketIndex == previousIndex + 1 ) {
370+ spanLength ++;
371+ } else {
372+ firstSpan = writeNativeBucketSpan (writer , spanOffset , spanLength , firstSpan );
373+ spanOffset = bucketIndex - previousIndex - 1 ;
374+ spanLength = 1 ;
375+ }
376+ previousIndex = bucketIndex ;
377+ }
378+ writeNativeBucketSpan (writer , spanOffset , spanLength , firstSpan );
379+ }
380+
381+ private boolean writeNativeBucketSpan (Writer writer , int offset , int length , boolean firstSpan )
382+ throws IOException {
383+ if (!firstSpan ) {
384+ writer .write (',' );
385+ }
386+ writer .write (Integer .toString (offset ));
387+ writer .write (':' );
388+ writer .write (Integer .toString (length ));
389+ return false ;
390+ }
391+
283392 private void writeSummary (Writer writer , SummarySnapshot snapshot , EscapingScheme scheme )
284393 throws IOException {
285394 if (!openMetrics2Properties .getCompositeValues ()
0 commit comments