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}