Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public ExpositionFormatWriter findWriter(@Nullable String acceptHeader) {
if ("2.0.0".equals(version)) {
return openMetrics2TextFormatWriter;
}
// version=1.0.0 or no version: fall through to OM1
// version=1.0.0 or no version: fall through to OM1.
} else {
// contentNegotiation=false: OM2 handles all OpenMetrics requests
// contentNegotiation=false: OM2 handles all OpenMetrics requests.
return openMetrics2TextFormatWriter;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.prometheus.metrics.model.snapshots.MetricMetadata;
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets;
import io.prometheus.metrics.model.snapshots.PrometheusNaming;
import io.prometheus.metrics.model.snapshots.Quantile;
import io.prometheus.metrics.model.snapshots.SnapshotEscaper;
Expand Down Expand Up @@ -63,7 +64,7 @@ public Builder setOpenMetrics2Properties(OpenMetrics2Properties openMetrics2Prop
}

/**
* @param createdTimestampsEnabled whether to include the start timestamp in the output
* @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics
*/
public Builder setCreatedTimestampsEnabled(boolean createdTimestampsEnabled) {
this.createdTimestampsEnabled = createdTimestampsEnabled;
Expand All @@ -88,21 +89,19 @@ public OpenMetrics2TextFormatWriter build() {
public static final String CONTENT_TYPE =
"application/openmetrics-text; version=2.0.0; charset=utf-8";
private final OpenMetrics2Properties openMetrics2Properties;
private final boolean createdTimestampsEnabled;
private final boolean exemplarsOnAllMetricTypesEnabled;
private final OpenMetricsTextFormatWriter om1Writer;

/**
* @param openMetrics2Properties OpenMetrics 2.0 feature flags
* @param createdTimestampsEnabled whether to include the start timestamp in the output.
* @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics
* @param exemplarsOnAllMetricTypesEnabled whether to include exemplars on all metric types
*/
public OpenMetrics2TextFormatWriter(
OpenMetrics2Properties openMetrics2Properties,
boolean createdTimestampsEnabled,
boolean exemplarsOnAllMetricTypesEnabled) {
this.openMetrics2Properties = openMetrics2Properties;
this.createdTimestampsEnabled = createdTimestampsEnabled;
this.exemplarsOnAllMetricTypesEnabled = exemplarsOnAllMetricTypesEnabled;
this.om1Writer =
new OpenMetricsTextFormatWriter(createdTimestampsEnabled, exemplarsOnAllMetricTypesEnabled);
Expand All @@ -126,8 +125,8 @@ public boolean accepts(@Nullable String acceptHeader) {

@Override
public String getContentType() {
// When contentNegotiation=false (default), masquerade as OM1 for compatibility
// When contentNegotiation=true, use proper OM2 version
// When contentNegotiation=false (default), masquerade as OM1 for compatibility.
// When contentNegotiation=true, use proper OM2 version.
if (openMetrics2Properties.getContentNegotiation()) {
return CONTENT_TYPE;
} else {
Expand Down Expand Up @@ -181,7 +180,7 @@ private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingSchem
writer.write(' ');
writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis());
}
if (createdTimestampsEnabled && data.hasCreatedTimestamp()) {
if (data.hasCreatedTimestamp()) {
writer.write(" st@");
writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis());
}
Expand All @@ -208,8 +207,9 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc

private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme)
throws IOException {
if (!openMetrics2Properties.getCompositeValues()
&& !openMetrics2Properties.getExemplarCompliance()) {
boolean compositeHistogram =
openMetrics2Properties.getCompositeValues() || openMetrics2Properties.getNativeHistograms();
if (!compositeHistogram && !openMetrics2Properties.getExemplarCompliance()) {
om1Writer.writeHistogram(writer, snapshot, scheme);
return;
}
Expand All @@ -218,12 +218,20 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingS
if (snapshot.isGaugeHistogram()) {
writeMetadataWithName(writer, name, "gaugehistogram", metadata);
for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
writeCompositeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme);
if (openMetrics2Properties.getNativeHistograms() && data.hasNativeHistogramData()) {
writeNativeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme, false);
} else {
writeCompositeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme, false);
}
}
} else {
writeMetadataWithName(writer, name, "histogram", metadata);
for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
writeCompositeHistogramDataPoint(writer, name, "count", "sum", data, scheme);
if (openMetrics2Properties.getNativeHistograms() && data.hasNativeHistogramData()) {
writeNativeHistogramDataPoint(writer, name, "count", "sum", data, scheme, true);
} else {
writeCompositeHistogramDataPoint(writer, name, "count", "sum", data, scheme, true);
}
}
}
}
Expand All @@ -234,7 +242,8 @@ private void writeCompositeHistogramDataPoint(
String countKey,
String sumKey,
HistogramSnapshot.HistogramDataPointSnapshot data,
EscapingScheme scheme)
EscapingScheme scheme,
boolean includeStartTimestamp)
throws IOException {
writeNameAndLabels(writer, name, null, data.getLabels(), scheme);
writer.write('{');
Expand All @@ -245,28 +254,59 @@ private void writeCompositeHistogramDataPoint(
writer.write(sumKey);
writer.write(':');
writeDouble(writer, data.getSum());
writer.write(",bucket:[");
ClassicHistogramBuckets buckets = getClassicBuckets(data);
long cumulativeCount = 0;
for (int i = 0; i < buckets.size(); i++) {
if (i > 0) {
writer.write(',');
}
cumulativeCount += buckets.getCount(i);
writeDouble(writer, buckets.getUpperBound(i));
writer.write(':');
writeLong(writer, cumulativeCount);
writeClassicBucketsField(writer, data);
writer.write('}');
if (data.hasScrapeTimestamp()) {
writer.write(' ');
writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis());
}
writer.write("]}");
if (includeStartTimestamp && data.hasCreatedTimestamp()) {
writer.write(" st@");
writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis());
}
writeExemplars(writer, data.getExemplars(), scheme);
writer.write('\n');
}

private void writeNativeHistogramDataPoint(
Writer writer,
String name,
String countKey,
String sumKey,
HistogramSnapshot.HistogramDataPointSnapshot data,
EscapingScheme scheme,
boolean includeStartTimestamp)
throws IOException {
writeNameAndLabels(writer, name, null, data.getLabels(), scheme);
writer.write('{');
writer.write(countKey);
writer.write(':');
writeLong(writer, data.getCount());
writer.write(',');
writer.write(sumKey);
writer.write(':');
writeDouble(writer, data.getSum());
writer.write(",schema:");
writer.write(Integer.toString(data.getNativeSchema()));
writer.write(",zero_threshold:");
writeDouble(writer, data.getNativeZeroThreshold());
writer.write(",zero_count:");
writeLong(writer, data.getNativeZeroCount());
writeNativeBucketFields(writer, "negative", data.getNativeBucketsForNegativeValues());
writeNativeBucketFields(writer, "positive", data.getNativeBucketsForPositiveValues());
if (data.hasClassicHistogramData()) {
writeClassicBucketsField(writer, data);
}
writer.write('}');
if (data.hasScrapeTimestamp()) {
writer.write(' ');
writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis());
}
if (data.hasCreatedTimestamp()) {
if (includeStartTimestamp && data.hasCreatedTimestamp()) {
writer.write(" st@");
writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis());
}
writeExemplar(writer, data.getExemplars().getLatest(), scheme);
writeExemplars(writer, data.getExemplars(), scheme);
writer.write('\n');
}

Expand All @@ -280,6 +320,75 @@ private ClassicHistogramBuckets getClassicBuckets(
}
}

private void writeClassicBucketsField(
Writer writer, HistogramSnapshot.HistogramDataPointSnapshot data) throws IOException {
writer.write(",bucket:[");
ClassicHistogramBuckets buckets = getClassicBuckets(data);
long cumulativeCount = 0;
for (int i = 0; i < buckets.size(); i++) {
if (i > 0) {
writer.write(',');
}
cumulativeCount += buckets.getCount(i);
writeDouble(writer, buckets.getUpperBound(i));
writer.write(':');
writeLong(writer, cumulativeCount);
}
writer.write(']');
}

private void writeNativeBucketFields(Writer writer, String prefix, NativeHistogramBuckets buckets)
throws IOException {
if (buckets.size() == 0) {
return;
}
writer.write(',');
writer.write(prefix);
writer.write("_spans:[");
writeNativeBucketSpans(writer, buckets);
writer.write("],");
writer.write(prefix);
writer.write("_buckets:[");
for (int i = 0; i < buckets.size(); i++) {
if (i > 0) {
writer.write(',');
}
writeLong(writer, buckets.getCount(i));
}
writer.write(']');
}

private void writeNativeBucketSpans(Writer writer, NativeHistogramBuckets buckets)
throws IOException {
int spanOffset = buckets.getBucketIndex(0);
int spanLength = 1;
int previousIndex = buckets.getBucketIndex(0);
boolean firstSpan = true;
for (int i = 1; i < buckets.size(); i++) {
int bucketIndex = buckets.getBucketIndex(i);
if (bucketIndex == previousIndex + 1) {
spanLength++;
} else {
firstSpan = writeNativeBucketSpan(writer, spanOffset, spanLength, firstSpan);
spanOffset = bucketIndex - previousIndex - 1;
spanLength = 1;
}
previousIndex = bucketIndex;
}
writeNativeBucketSpan(writer, spanOffset, spanLength, firstSpan);
}

private boolean writeNativeBucketSpan(Writer writer, int offset, int length, boolean firstSpan)
throws IOException {
if (!firstSpan) {
writer.write(',');
}
writer.write(Integer.toString(offset));
writer.write(':');
writer.write(Integer.toString(length));
return false;
}

private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme)
throws IOException {
if (!openMetrics2Properties.getCompositeValues()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,47 @@ void testOM2EnabledWithFeatureFlags() {
assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class);
}

@Test
void testOM2ContentNegotiationWithNativeHistogramOutput() throws IOException {
PrometheusProperties props =
PrometheusProperties.builder()
.openMetrics2Properties(
OpenMetrics2Properties.builder()
.enabled(true)
.contentNegotiation(true)
.nativeHistograms(true)
.build())
.build();
ExpositionFormats formats = ExpositionFormats.init(props);
ExpositionFormatWriter writer =
formats.findWriter("application/openmetrics-text; version=2.0.0");

ByteArrayOutputStream out = new ByteArrayOutputStream();
writer.write(
out,
MetricSnapshots.of(
HistogramSnapshot.builder()
.name("latency_seconds")
.dataPoint(
HistogramSnapshot.HistogramDataPointSnapshot.builder()
.sum(1.5)
.nativeSchema(5)
.nativeZeroCount(1)
.nativeBucketsForPositiveValues(
NativeHistogramBuckets.builder().bucket(2, 3).build())
.build())
.build()),
EscapingScheme.ALLOW_UTF8);

assertThat(writer).isInstanceOf(OpenMetrics2TextFormatWriter.class);
assertThat(out.toString(UTF_8))
.isEqualTo(
"# TYPE latency_seconds histogram\n"
+ "latency_seconds {count:4,sum:1.5,schema:5,zero_threshold:0.0,zero_count:1,"
+ "positive_spans:[2:1],positive_buckets:[3]}\n"
+ "# EOF\n");
}

@Test
void testProtobufWriterTakesPrecedence() {
PrometheusProperties props =
Expand Down
Loading