@@ -204,13 +204,67 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingS
204204 String name = getExpositionBaseMetadataName (metadata , scheme );
205205 if (snapshot .isGaugeHistogram ()) {
206206 writeMetadataWithName (writer , name , "gaugehistogram" , metadata );
207- writeClassicHistogramBuckets (
208- writer , name , "_gcount" , "_gsum" , snapshot .getDataPoints (), scheme );
207+ if (openMetrics2Properties .getCompositeValues ()) {
208+ for (HistogramSnapshot .HistogramDataPointSnapshot data : snapshot .getDataPoints ()) {
209+ writeCompositeHistogramDataPoint (writer , name , "gcount" , "gsum" , data , scheme );
210+ }
211+ } else {
212+ writeClassicHistogramBuckets (
213+ writer , name , "_gcount" , "_gsum" , snapshot .getDataPoints (), scheme );
214+ }
209215 } else {
210216 writeMetadataWithName (writer , name , "histogram" , metadata );
211- writeClassicHistogramBuckets (
212- writer , name , "_count" , "_sum" , snapshot .getDataPoints (), scheme );
217+ if (openMetrics2Properties .getCompositeValues ()) {
218+ for (HistogramSnapshot .HistogramDataPointSnapshot data : snapshot .getDataPoints ()) {
219+ writeCompositeHistogramDataPoint (writer , name , "count" , "sum" , data , scheme );
220+ }
221+ } else {
222+ writeClassicHistogramBuckets (
223+ writer , name , "_count" , "_sum" , snapshot .getDataPoints (), scheme );
224+ }
225+ }
226+ }
227+
228+ private void writeCompositeHistogramDataPoint (
229+ Writer writer ,
230+ String name ,
231+ String countKey ,
232+ String sumKey ,
233+ HistogramSnapshot .HistogramDataPointSnapshot data ,
234+ EscapingScheme scheme )
235+ throws IOException {
236+ writeNameAndLabels (writer , name , null , data .getLabels (), scheme );
237+ writer .write ('{' );
238+ writer .write (countKey );
239+ writer .write (':' );
240+ writeLong (writer , data .getCount ());
241+ writer .write (',' );
242+ writer .write (sumKey );
243+ writer .write (':' );
244+ writeDouble (writer , data .getSum ());
245+ writer .write (",bucket:[" );
246+ ClassicHistogramBuckets buckets = getClassicBuckets (data );
247+ long cumulativeCount = 0 ;
248+ for (int i = 0 ; i < buckets .size (); i ++) {
249+ if (i > 0 ) {
250+ writer .write (',' );
251+ }
252+ cumulativeCount += buckets .getCount (i );
253+ writeDouble (writer , buckets .getUpperBound (i ));
254+ writer .write (':' );
255+ writeLong (writer , cumulativeCount );
256+ }
257+ writer .write ("]}" );
258+ if (data .hasScrapeTimestamp ()) {
259+ writer .write (' ' );
260+ writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
261+ }
262+ if (data .hasCreatedTimestamp ()) {
263+ writer .write (" st@" );
264+ writeOpenMetricsTimestamp (writer , data .getCreatedTimestampMillis ());
213265 }
266+ writeExemplarIfAllowed (writer , data .getExemplars ().getLatest (), scheme );
267+ writer .write ('\n' );
214268 }
215269
216270 private void writeClassicHistogramBuckets (
@@ -269,28 +323,90 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem
269323 writeMetadataWithName (writer , name , "summary" , metadata );
270324 metadataWritten = true ;
271325 }
272- Exemplars exemplars = data .getExemplars ();
273- // Exemplars for summaries are new, and there's no best practice yet which Exemplars to choose
274- // for which
275- // time series. We select exemplars[0] for _count, exemplars[1] for _sum, and exemplars[2...]
276- // for the
277- // quantiles, all indexes modulo exemplars.length.
278- int exemplarIndex = 1 ;
279- for (Quantile quantile : data .getQuantiles ()) {
280- writeNameAndLabels (
281- writer , name , null , data .getLabels (), scheme , "quantile" , quantile .getQuantile ());
282- writeDouble (writer , quantile .getValue ());
283- if (exemplars .size () > 0 && exemplarsOnAllMetricTypesEnabled ) {
284- exemplarIndex = (exemplarIndex + 1 ) % exemplars .size ();
285- writeScrapeTimestampAndExemplar (writer , data , exemplars .get (exemplarIndex ), scheme );
286- } else {
287- writeScrapeTimestampAndExemplar (writer , data , null , scheme );
326+ if (openMetrics2Properties .getCompositeValues ()) {
327+ writeCompositeSummaryDataPoint (writer , name , data , scheme );
328+ } else {
329+ writeNonCompositeSummaryDataPoint (writer , name , data , scheme );
330+ }
331+ }
332+ }
333+
334+ private void writeCompositeSummaryDataPoint (
335+ Writer writer ,
336+ String name ,
337+ SummarySnapshot .SummaryDataPointSnapshot data ,
338+ EscapingScheme scheme )
339+ throws IOException {
340+ writeNameAndLabels (writer , name , null , data .getLabels (), scheme );
341+ writer .write ('{' );
342+ boolean first = true ;
343+ if (data .hasCount ()) {
344+ writer .write ("count:" );
345+ writeLong (writer , data .getCount ());
346+ first = false ;
347+ }
348+ if (data .hasSum ()) {
349+ if (!first ) {
350+ writer .write (',' );
351+ }
352+ writer .write ("sum:" );
353+ writeDouble (writer , data .getSum ());
354+ first = false ;
355+ }
356+ if (data .getQuantiles ().size () > 0 ) {
357+ if (!first ) {
358+ writer .write (',' );
359+ }
360+ writer .write ("quantile:[" );
361+ for (int i = 0 ; i < data .getQuantiles ().size (); i ++) {
362+ if (i > 0 ) {
363+ writer .write (',' );
288364 }
365+ Quantile q = data .getQuantiles ().get (i );
366+ writeDouble (writer , q .getQuantile ());
367+ writer .write (':' );
368+ writeDouble (writer , q .getValue ());
369+ }
370+ writer .write (']' );
371+ }
372+ writer .write ('}' );
373+ if (data .hasScrapeTimestamp ()) {
374+ writer .write (' ' );
375+ writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
376+ }
377+ if (data .hasCreatedTimestamp ()) {
378+ writer .write (" st@" );
379+ writeOpenMetricsTimestamp (writer , data .getCreatedTimestampMillis ());
380+ }
381+ writeExemplarIfAllowed (writer , data .getExemplars ().getLatest (), scheme );
382+ writer .write ('\n' );
383+ }
384+
385+ private void writeNonCompositeSummaryDataPoint (
386+ Writer writer ,
387+ String name ,
388+ SummarySnapshot .SummaryDataPointSnapshot data ,
389+ EscapingScheme scheme )
390+ throws IOException {
391+ Exemplars exemplars = data .getExemplars ();
392+ // Exemplars for summaries are new, and there's no best practice yet which Exemplars to choose
393+ // for which time series. We select exemplars[0] for _count, exemplars[1] for _sum, and
394+ // exemplars[2...] for the quantiles, all indexes modulo exemplars.length.
395+ int exemplarIndex = 1 ;
396+ for (Quantile quantile : data .getQuantiles ()) {
397+ writeNameAndLabels (
398+ writer , name , null , data .getLabels (), scheme , "quantile" , quantile .getQuantile ());
399+ writeDouble (writer , quantile .getValue ());
400+ if (exemplars .size () > 0 && exemplarsOnAllMetricTypesEnabled ) {
401+ exemplarIndex = (exemplarIndex + 1 ) % exemplars .size ();
402+ writeScrapeTimestampAndExemplar (writer , data , exemplars .get (exemplarIndex ), scheme );
403+ } else {
404+ writeScrapeTimestampAndExemplar (writer , data , null , scheme );
289405 }
290- // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics.
291- writeCountAndSum (writer , name , data , "_count" , "_sum" , exemplars , scheme );
292- writeCreated (writer , name , data , scheme );
293406 }
407+ // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics.
408+ writeCountAndSum (writer , name , data , "_count" , "_sum" , exemplars , scheme );
409+ writeCreated (writer , name , data , scheme );
294410 }
295411
296412 private void writeInfo (Writer writer , InfoSnapshot snapshot , EscapingScheme scheme )
@@ -446,17 +562,27 @@ private void writeScrapeTimestampAndExemplar(
446562 writer .write (' ' );
447563 writeOpenMetricsTimestamp (writer , data .getScrapeTimestampMillis ());
448564 }
449- if (exemplar != null ) {
450- writer .write (" # " );
451- writeLabels (writer , exemplar .getLabels (), null , 0 , false , scheme );
565+ writeExemplarIfAllowed (writer , exemplar , scheme );
566+ writer .write ('\n' );
567+ }
568+
569+ private void writeExemplarIfAllowed (
570+ Writer writer , @ Nullable Exemplar exemplar , EscapingScheme scheme ) throws IOException {
571+ if (exemplar == null ) {
572+ return ;
573+ }
574+ // In exemplarCompliance mode, exemplars MUST have a timestamp per the OM2 spec.
575+ if (openMetrics2Properties .getExemplarCompliance () && !exemplar .hasTimestamp ()) {
576+ return ;
577+ }
578+ writer .write (" # " );
579+ writeLabels (writer , exemplar .getLabels (), null , 0 , false , scheme );
580+ writer .write (' ' );
581+ writeDouble (writer , exemplar .getValue ());
582+ if (exemplar .hasTimestamp ()) {
452583 writer .write (' ' );
453- writeDouble (writer , exemplar .getValue ());
454- if (exemplar .hasTimestamp ()) {
455- writer .write (' ' );
456- writeOpenMetricsTimestamp (writer , exemplar .getTimestampMillis ());
457- }
584+ writeOpenMetricsTimestamp (writer , exemplar .getTimestampMillis ());
458585 }
459- writer .write ('\n' );
460586 }
461587
462588 private void writeMetadataWithName (
0 commit comments