diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java new file mode 100644 index 000000000..47a34ac18 --- /dev/null +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java @@ -0,0 +1,71 @@ +package io.prometheus.metrics.benchmarks; + +import io.prometheus.metrics.config.EscapingScheme; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; +import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot.HistogramDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmarks for writing a classic histogram (10 label combinations × 12 buckets) to text formats. + * Output goes to /dev/null to isolate pure formatting CPU cost with zero IO overhead. + */ +@Fork(3) +@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS) +public class HistogramTextFormatBenchmark { + + private static final MetricSnapshots SNAPSHOTS; + + static { + double[] upperBounds = { + .005, .01, .025, .05, .1, .25, .5, 1.0, 2.5, 5.0, 10.0, Double.POSITIVE_INFINITY + }; + Number[] counts = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L}; + ClassicHistogramBuckets buckets = ClassicHistogramBuckets.of(upperBounds, counts); + + HistogramSnapshot.Builder builder = + HistogramSnapshot.builder().name("http_request_duration_seconds"); + + for (int i = 0; i < 10; i++) { + builder.dataPoint( + HistogramDataPointSnapshot.builder() + .classicHistogramBuckets(buckets) + .labels(Labels.of("status", "value_" + i)) + .sum(123.456) + .createdTimestampMillis(1000L) + .build()); + } + + SNAPSHOTS = MetricSnapshots.of(builder.build()); + } + + private static final OpenMetricsTextFormatWriter OPEN_METRICS_TEXT_FORMAT_WRITER = + OpenMetricsTextFormatWriter.create(); + private static final PrometheusTextFormatWriter PROMETHEUS_TEXT_FORMAT_WRITER = + PrometheusTextFormatWriter.create(); + + @Benchmark + public OutputStream openMetricsWriteToNull() throws IOException { + OutputStream nullOutputStream = TextFormatUtilBenchmark.NullOutputStream.INSTANCE; + OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); + return nullOutputStream; + } + + @Benchmark + public OutputStream prometheusWriteToNull() throws IOException { + OutputStream nullOutputStream = TextFormatUtilBenchmark.NullOutputStream.INSTANCE; + PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); + return nullOutputStream; + } +} diff --git a/mise.toml b/mise.toml index 70cfecbaa..803f0e09b 100644 --- a/mise.toml +++ b/mise.toml @@ -59,8 +59,8 @@ run = "./mvnw install -DskipTests -Dcoverage.skip=true" [tasks."lint"] description = "Run all lints" -raw_args = true depends = ["lint:bom"] +raw_args = true run = "flint run" [tasks."lint:fix"] @@ -95,11 +95,11 @@ run = ["hugo --gc --minify --baseURL ${BASE_URL}/", "echo 'ls ./public/api' && l [tasks."benchmark:quick"] description = "Run benchmarks with reduced iterations (quick smoke test, ~10 min)" -run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 1 -wi 1 -i 3'" +run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 1 -wi 1 -i 3 -prof gc'" [tasks."benchmark:ci"] description = "Run benchmarks with CI configuration (3 forks, 3 warmup, 5 measurement iterations (~60 min total)" -run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 3 -wi 3 -i 5'" +run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 3 -wi 3 -i 5 -prof gc'" [tasks."benchmark:ci-json"] description = "Run benchmarks with CI configuration and JSON output (for workflow/testing)" diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java index 963b18507..19e1e4c93 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java @@ -258,6 +258,7 @@ private void writeClassicHistogramDataPoints( HistogramSnapshot snapshot, EscapingScheme scheme) throws IOException { + String bucketName = name + "_bucket"; for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { ClassicHistogramBuckets buckets = getClassicBuckets(data); Exemplars exemplars = data.getExemplars(); @@ -265,7 +266,7 @@ private void writeClassicHistogramDataPoints( for (int i = 0; i < buckets.size(); i++) { cumulativeCount += buckets.getCount(i); writeNameAndLabels( - writer, name, "_bucket", data.getLabels(), scheme, "le", buckets.getUpperBound(i)); + writer, bucketName, null, data.getLabels(), scheme, "le", buckets.getUpperBound(i)); writeLong(writer, cumulativeCount); Exemplar exemplar; if (i == 0) { @@ -636,7 +637,7 @@ private void writeNameAndLabels( metricInsideBraces = true; writer.write('{'); } - writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); + writeName(writer, suffix != null ? name + suffix : name, NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { writeLabels( writer, diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 1bc7e101f..cd299cb94 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -157,8 +157,9 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "gauge", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); @@ -190,6 +191,10 @@ private void writeClassicHistogramBuckets( List dataList, EscapingScheme scheme) throws IOException { + String name = getMetadataName(metadata, scheme); + String bucketName = name + "_bucket"; + String countName = name + countSuffix; + String sumName = name + sumSuffix; for (HistogramSnapshot.HistogramDataPointSnapshot data : dataList) { ClassicHistogramBuckets buckets = getClassicBuckets(data); Exemplars exemplars = data.getExemplars(); @@ -197,13 +202,7 @@ private void writeClassicHistogramBuckets( for (int i = 0; i < buckets.size(); i++) { cumulativeCount += buckets.getCount(i); writeNameAndLabels( - writer, - getMetadataName(metadata, scheme), - "_bucket", - data.getLabels(), - scheme, - "le", - buckets.getUpperBound(i)); + writer, bucketName, null, data.getLabels(), scheme, "le", buckets.getUpperBound(i)); writeLong(writer, cumulativeCount); Exemplar exemplar; if (i == 0) { @@ -215,9 +214,9 @@ private void writeClassicHistogramBuckets( } // In OpenMetrics format, histogram _count and _sum are either both present or both absent. if (data.hasCount() && data.hasSum()) { - writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars, scheme); + writeCountAndSum(writer, countName, sumName, data, exemplars, scheme); } - writeCreated(writer, metadata, data, scheme); + writeCreated(writer, name, data, scheme); } } @@ -235,6 +234,9 @@ void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); + String name = getMetadataName(metadata, scheme); + String countName = name + "_count"; + String sumName = name + "_sum"; for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) { continue; @@ -252,13 +254,7 @@ void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme int exemplarIndex = 1; for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, - getMetadataName(metadata, scheme), - null, - data.getLabels(), - scheme, - "quantile", - quantile.getQuantile()); + writer, name, null, data.getLabels(), scheme, "quantile", quantile.getQuantile()); writeDouble(writer, quantile.getValue()); if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) { exemplarIndex = (exemplarIndex + 1) % exemplars.size(); @@ -268,8 +264,8 @@ void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme } } // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics. - writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars, scheme); - writeCreated(writer, metadata, data, scheme); + writeCountAndSum(writer, countName, sumName, data, exemplars, scheme); + writeCreated(writer, name, data, scheme); } } @@ -290,9 +286,10 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "stateset", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(getMetadataName(metadata, scheme)); + writer.write(name); writer.write('{'); Labels labels = data.getLabels(); for (int j = 0; j < labels.size(); j++) { @@ -307,7 +304,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch if (!labels.isEmpty()) { writer.write(","); } - writer.write(getMetadataName(metadata, scheme)); + writer.write(name); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -325,8 +322,9 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingSchem throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "unknown", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); @@ -338,16 +336,14 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingSchem private void writeCountAndSum( Writer writer, - MetricMetadata metadata, + String countName, + String sumName, DistributionDataPointSnapshot data, - String countSuffix, - String sumSuffix, Exemplars exemplars, EscapingScheme scheme) throws IOException { if (data.hasCount()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), countSuffix, data.getLabels(), scheme); + writeNameAndLabels(writer, countName, null, data.getLabels(), scheme); writeLong(writer, data.getCount()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme); @@ -356,19 +352,12 @@ private void writeCountAndSum( } } if (data.hasSum()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), sumSuffix, data.getLabels(), scheme); + writeNameAndLabels(writer, sumName, null, data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } - private void writeCreated( - Writer writer, MetricMetadata metadata, DataPointSnapshot data, EscapingScheme scheme) - throws IOException { - writeCreated(writer, getMetadataName(metadata, scheme), data, scheme); - } - private void writeCreated( Writer writer, String baseName, DataPointSnapshot data, EscapingScheme scheme) throws IOException { @@ -409,7 +398,7 @@ private void writeNameAndLabels( metricInsideBraces = true; writer.write('{'); } - writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); + writeName(writer, suffix != null ? name + suffix : name, NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { writeLabels( writer, diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java index b40dcfdf2..69f214829 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java @@ -193,8 +193,9 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "gauge", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -204,32 +205,28 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingS throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "histogram", metadata, scheme); + String name = getMetadataName(metadata, scheme); + String bucketName = name + "_bucket"; + String countName = name + "_count"; + String sumName = name + "_sum"; for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { ClassicHistogramBuckets buckets = getClassicBuckets(data); long cumulativeCount = 0; for (int i = 0; i < buckets.size(); i++) { cumulativeCount += buckets.getCount(i); writeNameAndLabels( - writer, - getMetadataName(metadata, scheme), - "_bucket", - data.getLabels(), - scheme, - "le", - buckets.getUpperBound(i)); + writer, bucketName, null, data.getLabels(), scheme, "le", buckets.getUpperBound(i)); writeLong(writer, cumulativeCount); writeScrapeTimestampAndNewline(writer, data); } if (!snapshot.isGaugeHistogram()) { if (data.hasCount()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); + writeNameAndLabels(writer, countName, null, data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); + writeNameAndLabels(writer, sumName, null, data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -255,6 +252,9 @@ private void writeGaugeCountSum( throws IOException { // Prometheus text format does not support gaugehistogram's _gcount and _gsum. // So we append _gcount and _gsum as gauge metrics. + String baseName = getMetadataName(metadata, scheme); + String gaugeCountName = baseName + "_gcount"; + String gaugeSumName = baseName + "_gsum"; boolean metadataWritten = false; for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { if (data.hasCount()) { @@ -262,8 +262,7 @@ private void writeGaugeCountSum( writeMetadata(writer, "_gcount", "gauge", metadata, scheme); metadataWritten = true; } - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_gcount", data.getLabels(), scheme); + writeNameAndLabels(writer, gaugeCountName, null, data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } @@ -275,8 +274,7 @@ private void writeGaugeCountSum( writeMetadata(writer, "_gsum", "gauge", metadata, scheme); metadataWritten = true; } - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_gsum", data.getLabels(), scheme); + writeNameAndLabels(writer, gaugeSumName, null, data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -287,6 +285,9 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); + String name = getMetadataName(metadata, scheme); + String countName = name + "_count"; + String sumName = name + "_sum"; for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) { continue; @@ -297,25 +298,17 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem } for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, - getMetadataName(metadata, scheme), - null, - data.getLabels(), - scheme, - "quantile", - quantile.getQuantile()); + writer, name, null, data.getLabels(), scheme, "quantile", quantile.getQuantile()); writeDouble(writer, quantile.getValue()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasCount()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); + writeNameAndLabels(writer, countName, null, data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels( - writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); + writeNameAndLabels(writer, sumName, null, data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -338,9 +331,10 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "gauge", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(getMetadataName(metadata, scheme)); + writer.write(name); writer.write('{'); for (int j = 0; j < data.getLabels().size(); j++) { if (j > 0) { @@ -354,7 +348,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch if (!data.getLabels().isEmpty()) { writer.write(","); } - writer.write(getMetadataName(metadata, scheme)); + writer.write(name); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -372,8 +366,9 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingSchem throws IOException { MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "untyped", metadata, scheme); + String name = getMetadataName(metadata, scheme); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -405,7 +400,7 @@ private void writeNameAndLabels( metricInsideBraces = true; writer.write('{'); } - writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); + writeName(writer, suffix != null ? name + suffix : name, NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { writeLabels( writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces, scheme); @@ -422,7 +417,8 @@ private void writeMetadata( MetricMetadata metadata, EscapingScheme scheme) throws IOException { - String name = getMetadataName(metadata, scheme) + (suffix != null ? suffix : ""); + String baseName = getMetadataName(metadata, scheme); + String name = suffix != null ? baseName + suffix : baseName; if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); writeName(writer, name, NameType.Metric); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 5f5f05e8b..8cfed2b29 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -67,7 +67,22 @@ public static MetricSnapshots mergeDuplicates(MetricSnapshots metricSnapshots) { } static void writeLong(Writer writer, long value) throws IOException { - writer.append(Long.toString(value)); + if (value == Long.MIN_VALUE) { + writer.write("-9223372036854775808"); + return; + } + char[] buf = new char[20]; + int pos = 20; + boolean negative = value < 0; + long v = negative ? -value : value; + do { + buf[--pos] = (char) ('0' + (v % 10)); + v /= 10; + } while (v > 0); + if (negative) { + buf[--pos] = '-'; + } + writer.write(buf, pos, 20 - pos); } static void writeDouble(Writer writer, double d) throws IOException { @@ -77,7 +92,6 @@ static void writeDouble(Writer writer, double d) throws IOException { writer.write("-Inf"); } else { writer.write(Double.toString(d)); - // FloatingDecimal.getBinaryToASCIIConverter(d).appendTo(writer); } } @@ -86,7 +100,7 @@ static void writePrometheusTimestamp(Writer writer, long timestampMs, boolean ti if (timestampsInMs) { // correct for prometheus exposition format // https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details - writer.write(Long.toString(timestampMs)); + writeLong(writer, timestampMs); } else { // incorrect for prometheus exposition format - // but we need to support it for backwards compatibility @@ -95,7 +109,7 @@ static void writePrometheusTimestamp(Writer writer, long timestampMs, boolean ti } static void writeOpenMetricsTimestamp(Writer writer, long timestampMs) throws IOException { - writer.write(Long.toString(timestampMs / 1000L)); + writeLong(writer, timestampMs / 1000L); writer.write("."); long ms = timestampMs % 1000; if (ms < 100) { @@ -104,7 +118,7 @@ static void writeOpenMetricsTimestamp(Writer writer, long timestampMs) throws IO if (ms < 10) { writer.write("0"); } - writer.write(Long.toString(ms)); + writeLong(writer, ms); } static void writeEscapedString(Writer writer, String s) throws IOException {