001package io.prometheus.metrics.expositionformats;
002
003import io.prometheus.metrics.model.snapshots.CounterSnapshot;
004import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
005import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
006import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
007import io.prometheus.metrics.model.snapshots.InfoSnapshot;
008import io.prometheus.metrics.model.snapshots.Labels;
009import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
010import io.prometheus.metrics.model.snapshots.MetricMetadata;
011import io.prometheus.metrics.model.snapshots.MetricSnapshot;
012import io.prometheus.metrics.model.snapshots.MetricSnapshots;
013import io.prometheus.metrics.model.snapshots.Quantile;
014import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
015import io.prometheus.metrics.model.snapshots.SummarySnapshot;
016import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
017
018import java.io.IOException;
019import java.io.OutputStream;
020import java.io.OutputStreamWriter;
021import java.io.Writer;
022import java.nio.charset.StandardCharsets;
023
024import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
025import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedLabelValue;
026import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
027import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
028import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeTimestamp;
029
030/**
031 * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your Web browser.
032 */
033public class PrometheusTextFormatWriter implements ExpositionFormatWriter {
034
035    public static final String CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
036
037    private final boolean writeCreatedTimestamps;
038
039    public PrometheusTextFormatWriter(boolean writeCreatedTimestamps) {
040        this.writeCreatedTimestamps = writeCreatedTimestamps;
041    }
042
043    @Override
044    public boolean accepts(String acceptHeader) {
045        if (acceptHeader == null) {
046            return false;
047        } else {
048        return acceptHeader.contains("text/plain");
049        }
050    }
051
052    @Override
053    public String getContentType() {
054        return CONTENT_TYPE;
055    }
056
057    public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException {
058        // See https://prometheus.io/docs/instrumenting/exposition_formats/
059        // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and "summary".
060        OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
061        for (MetricSnapshot snapshot : metricSnapshots) {
062            if (snapshot.getDataPoints().size() > 0) {
063                if (snapshot instanceof CounterSnapshot) {
064                    writeCounter(writer, (CounterSnapshot) snapshot);
065                } else if (snapshot instanceof GaugeSnapshot) {
066                    writeGauge(writer, (GaugeSnapshot) snapshot);
067                } else if (snapshot instanceof HistogramSnapshot) {
068                    writeHistogram(writer, (HistogramSnapshot) snapshot);
069                } else if (snapshot instanceof SummarySnapshot) {
070                    writeSummary(writer, (SummarySnapshot) snapshot);
071                } else if (snapshot instanceof InfoSnapshot) {
072                    writeInfo(writer, (InfoSnapshot) snapshot);
073                } else if (snapshot instanceof StateSetSnapshot) {
074                    writeStateSet(writer, (StateSetSnapshot) snapshot);
075                } else if (snapshot instanceof UnknownSnapshot) {
076                    writeUnknown(writer, (UnknownSnapshot) snapshot);
077                }
078            }
079        }
080        if (writeCreatedTimestamps) {
081            for (MetricSnapshot snapshot : metricSnapshots) {
082                if (snapshot.getDataPoints().size() > 0) {
083                    if (snapshot instanceof CounterSnapshot) {
084                        writeCreated(writer, snapshot);
085                    } else if (snapshot instanceof HistogramSnapshot) {
086                        writeCreated(writer, snapshot);
087                    } else if (snapshot instanceof SummarySnapshot) {
088                        writeCreated(writer, snapshot);
089                    }
090                }
091            }
092        }
093        writer.flush();
094    }
095
096    public void writeCreated(OutputStreamWriter writer, MetricSnapshot snapshot) throws IOException {
097            boolean metadataWritten = false;
098            MetricMetadata metadata = snapshot.getMetadata();
099            for (DataPointSnapshot data : snapshot.getDataPoints()) {
100                if (data.hasCreatedTimestamp()) {
101                    if (!metadataWritten) {
102                        writeMetadata(writer, "_created", "gauge", metadata);
103                        metadataWritten = true;
104                    }
105                    writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels());
106                    writeTimestamp(writer, data.getCreatedTimestampMillis());
107                    writeScrapeTimestampAndNewline(writer, data);
108                }
109            }
110
111    }
112
113    private void writeCounter(OutputStreamWriter writer, CounterSnapshot snapshot) throws IOException {
114        if (snapshot.getDataPoints().size() > 0) {
115            MetricMetadata metadata = snapshot.getMetadata();
116            writeMetadata(writer, "_total", "counter", metadata);
117            for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) {
118                writeNameAndLabels(writer, metadata.getPrometheusName(), "_total", data.getLabels());
119                writeDouble(writer, data.getValue());
120                writeScrapeTimestampAndNewline(writer, data);
121            }
122        }
123    }
124
125    private void writeGauge(OutputStreamWriter writer, GaugeSnapshot snapshot) throws IOException {
126        MetricMetadata metadata = snapshot.getMetadata();
127        writeMetadata(writer, "", "gauge", metadata);
128        for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) {
129            writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels());
130            writeDouble(writer, data.getValue());
131            writeScrapeTimestampAndNewline(writer, data);
132        }
133    }
134
135    private void writeHistogram(OutputStreamWriter writer, HistogramSnapshot snapshot) throws IOException {
136        MetricMetadata metadata = snapshot.getMetadata();
137        writeMetadata(writer, "", "histogram", metadata);
138        for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
139            ClassicHistogramBuckets buckets = getClassicBuckets(data);
140            long cumulativeCount = 0;
141            for (int i = 0; i < buckets.size(); i++) {
142                cumulativeCount += buckets.getCount(i);
143                writeNameAndLabels(writer, metadata.getPrometheusName(), "_bucket", data.getLabels(), "le", buckets.getUpperBound(i));
144                writeLong(writer, cumulativeCount);
145                writeScrapeTimestampAndNewline(writer, data);
146            }
147            if (!snapshot.isGaugeHistogram()) {
148                if (data.hasCount()) {
149                    writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels());
150                    writeLong(writer, data.getCount());
151                    writeScrapeTimestampAndNewline(writer, data);
152                }
153                if (data.hasSum()) {
154                    writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels());
155                    writeDouble(writer, data.getSum());
156                    writeScrapeTimestampAndNewline(writer, data);
157                }
158            }
159        }
160        if (snapshot.isGaugeHistogram()) {
161            writeGaugeCountSum(writer, snapshot, metadata);
162        }
163    }
164
165    private ClassicHistogramBuckets getClassicBuckets(HistogramSnapshot.HistogramDataPointSnapshot data) {
166        if (data.getClassicBuckets().isEmpty()) {
167            return ClassicHistogramBuckets.of(
168                    new double[]{Double.POSITIVE_INFINITY},
169                    new long[]{data.getCount()}
170            );
171        } else {
172            return data.getClassicBuckets();
173        }
174    }
175
176    private void writeGaugeCountSum(OutputStreamWriter writer, HistogramSnapshot snapshot, MetricMetadata metadata) throws IOException {
177        // Prometheus text format does not support gaugehistogram's _gcount and _gsum.
178        // So we append _gcount and _gsum as gauge metrics.
179        boolean metadataWritten = false;
180        for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
181            if (data.hasCount()) {
182                if (!metadataWritten) {
183                    writeMetadata(writer, "_gcount", "gauge", metadata);
184                    metadataWritten = true;
185                }
186                writeNameAndLabels(writer, metadata.getPrometheusName(), "_gcount", data.getLabels());
187                writeLong(writer, data.getCount());
188                writeScrapeTimestampAndNewline(writer, data);
189            }
190        }
191        metadataWritten = false;
192        for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
193            if (data.hasSum()) {
194                if (!metadataWritten) {
195                    writeMetadata(writer, "_gsum", "gauge", metadata);
196                    metadataWritten = true;
197                }
198                writeNameAndLabels(writer, metadata.getPrometheusName(), "_gsum", data.getLabels());
199                writeDouble(writer, data.getSum());
200                writeScrapeTimestampAndNewline(writer, data);
201            }
202        }
203    }
204
205    private void writeSummary(OutputStreamWriter writer, SummarySnapshot snapshot) throws IOException {
206        boolean metadataWritten = false;
207        MetricMetadata metadata = snapshot.getMetadata();
208        for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) {
209            if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) {
210                continue;
211            }
212            if (!metadataWritten) {
213                writeMetadata(writer, "", "summary", metadata);
214                metadataWritten = true;
215            }
216            for (Quantile quantile : data.getQuantiles()) {
217                writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels(), "quantile", quantile.getQuantile());
218                writeDouble(writer, quantile.getValue());
219                writeScrapeTimestampAndNewline(writer, data);
220            }
221            if (data.hasCount()) {
222                writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels());
223                writeLong(writer, data.getCount());
224                writeScrapeTimestampAndNewline(writer, data);
225            }
226            if (data.hasSum()) {
227                writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels());
228                writeDouble(writer, data.getSum());
229                writeScrapeTimestampAndNewline(writer, data);
230            }
231        }
232    }
233
234    private void writeInfo(OutputStreamWriter writer, InfoSnapshot snapshot) throws IOException {
235        MetricMetadata metadata = snapshot.getMetadata();
236        writeMetadata(writer, "_info", "gauge", metadata);
237        for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) {
238            writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels());
239            writer.write("1");
240            writeScrapeTimestampAndNewline(writer, data);
241        }
242    }
243
244    private void writeStateSet(OutputStreamWriter writer, StateSetSnapshot snapshot) throws IOException {
245        MetricMetadata metadata = snapshot.getMetadata();
246        writeMetadata(writer, "", "gauge", metadata);
247        for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) {
248            for (int i = 0; i < data.size(); i++) {
249                writer.write(metadata.getPrometheusName());
250                writer.write('{');
251                for (int j = 0; j < data.getLabels().size(); j++) {
252                    if (j > 0) {
253                        writer.write(",");
254                    }
255                    writer.write(data.getLabels().getPrometheusName(j));
256                    writer.write("=\"");
257                    writeEscapedLabelValue(writer, data.getLabels().getValue(j));
258                    writer.write("\"");
259                }
260                if (!data.getLabels().isEmpty()) {
261                    writer.write(",");
262                }
263                writer.write(metadata.getPrometheusName());
264                writer.write("=\"");
265                writeEscapedLabelValue(writer, data.getName(i));
266                writer.write("\"} ");
267                if (data.isTrue(i)) {
268                    writer.write("1");
269                } else {
270                    writer.write("0");
271                }
272                writeScrapeTimestampAndNewline(writer, data);
273            }
274        }
275    }
276
277    private void writeUnknown(OutputStreamWriter writer, UnknownSnapshot snapshot) throws IOException {
278        MetricMetadata metadata = snapshot.getMetadata();
279        writeMetadata(writer, "", "untyped", metadata);
280        for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) {
281            writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels());
282            writeDouble(writer, data.getValue());
283            writeScrapeTimestampAndNewline(writer, data);
284        }
285    }
286
287    private void writeNameAndLabels(OutputStreamWriter writer, String name, String suffix, Labels labels) throws IOException {
288        writeNameAndLabels(writer, name, suffix, labels, null, 0.0);
289    }
290
291    private void writeNameAndLabels(OutputStreamWriter writer, String name, String suffix, Labels labels,
292                                    String additionalLabelName, double additionalLabelValue) throws IOException {
293        writer.write(name);
294        if (suffix != null) {
295            writer.write(suffix);
296        }
297        if (!labels.isEmpty() || additionalLabelName != null) {
298            writeLabels(writer, labels, additionalLabelName, additionalLabelValue);
299        }
300        writer.write(' ');
301    }
302
303    private void writeMetadata(OutputStreamWriter writer, String suffix, String typeString, MetricMetadata metadata) throws IOException {
304        if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) {
305            writer.write("# HELP ");
306            writer.write(metadata.getPrometheusName());
307            if (suffix != null) {
308                writer.write(suffix);
309            }
310            writer.write(' ');
311            writeEscapedHelp(writer, metadata.getHelp());
312            writer.write('\n');
313        }
314        writer.write("# TYPE ");
315        writer.write(metadata.getPrometheusName());
316        if (suffix != null) {
317            writer.write(suffix);
318        }
319        writer.write(' ');
320        writer.write(typeString);
321        writer.write('\n');
322    }
323
324    private void writeEscapedHelp(Writer writer, String s) throws IOException {
325        for (int i = 0; i < s.length(); i++) {
326            char c = s.charAt(i);
327            switch (c) {
328                case '\\':
329                    writer.append("\\\\");
330                    break;
331                case '\n':
332                    writer.append("\\n");
333                    break;
334                default:
335                    writer.append(c);
336            }
337        }
338    }
339
340    private void writeScrapeTimestampAndNewline(OutputStreamWriter writer, DataPointSnapshot data) throws IOException {
341        if (data.hasScrapeTimestamp()) {
342            writer.write(' ');
343            writeTimestamp(writer, data.getScrapeTimestampMillis());
344        }
345        writer.write('\n');
346    }
347}