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