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.writePrometheusTimestamp; 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.BufferedWriter; 024import java.io.IOException; 025import java.io.OutputStream; 026import java.io.OutputStreamWriter; 027import java.io.Writer; 028import java.nio.charset.StandardCharsets; 029 030/** 031 * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your 032 * Web browser. 033 */ 034public class PrometheusTextFormatWriter implements ExpositionFormatWriter { 035 036 public static final String CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8"; 037 038 private final boolean writeCreatedTimestamps; 039 private final boolean timestampsInMs; 040 041 public static class Builder { 042 boolean includeCreatedTimestamps; 043 boolean timestampsInMs = true; 044 045 private Builder() {} 046 047 /** 048 * @param includeCreatedTimestamps whether to include the _created timestamp in the output 049 */ 050 public Builder setIncludeCreatedTimestamps(boolean includeCreatedTimestamps) { 051 this.includeCreatedTimestamps = includeCreatedTimestamps; 052 return this; 053 } 054 055 @Deprecated 056 public Builder setTimestampsInMs(boolean timestampsInMs) { 057 this.timestampsInMs = timestampsInMs; 058 return this; 059 } 060 061 public PrometheusTextFormatWriter build() { 062 return new PrometheusTextFormatWriter(includeCreatedTimestamps, timestampsInMs); 063 } 064 } 065 066 /** 067 * @param writeCreatedTimestamps whether to include the _created timestamp in the output - This 068 * will produce an invalid OpenMetrics output, but is kept for backwards compatibility. 069 * @deprecated this constructor is deprecated and will be removed in the next major version - 070 * {@link #builder()} or {@link #create()} instead 071 */ 072 @Deprecated 073 public PrometheusTextFormatWriter(boolean writeCreatedTimestamps) { 074 this(writeCreatedTimestamps, false); 075 } 076 077 private PrometheusTextFormatWriter(boolean writeCreatedTimestamps, boolean timestampsInMs) { 078 this.writeCreatedTimestamps = writeCreatedTimestamps; 079 this.timestampsInMs = timestampsInMs; 080 } 081 082 public static PrometheusTextFormatWriter.Builder builder() { 083 return new Builder(); 084 } 085 086 public static PrometheusTextFormatWriter create() { 087 return builder().build(); 088 } 089 090 @Override 091 public boolean accepts(String acceptHeader) { 092 if (acceptHeader == null) { 093 return false; 094 } else { 095 return acceptHeader.contains("text/plain"); 096 } 097 } 098 099 @Override 100 public String getContentType() { 101 return CONTENT_TYPE; 102 } 103 104 @Override 105 public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException { 106 // See https://prometheus.io/docs/instrumenting/exposition_formats/ 107 // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and 108 // "summary". 109 Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); 110 for (MetricSnapshot snapshot : metricSnapshots) { 111 if (!snapshot.getDataPoints().isEmpty()) { 112 if (snapshot instanceof CounterSnapshot) { 113 writeCounter(writer, (CounterSnapshot) snapshot); 114 } else if (snapshot instanceof GaugeSnapshot) { 115 writeGauge(writer, (GaugeSnapshot) snapshot); 116 } else if (snapshot instanceof HistogramSnapshot) { 117 writeHistogram(writer, (HistogramSnapshot) snapshot); 118 } else if (snapshot instanceof SummarySnapshot) { 119 writeSummary(writer, (SummarySnapshot) snapshot); 120 } else if (snapshot instanceof InfoSnapshot) { 121 writeInfo(writer, (InfoSnapshot) snapshot); 122 } else if (snapshot instanceof StateSetSnapshot) { 123 writeStateSet(writer, (StateSetSnapshot) snapshot); 124 } else if (snapshot instanceof UnknownSnapshot) { 125 writeUnknown(writer, (UnknownSnapshot) snapshot); 126 } 127 } 128 } 129 if (writeCreatedTimestamps) { 130 for (MetricSnapshot snapshot : metricSnapshots) { 131 if (!snapshot.getDataPoints().isEmpty()) { 132 if (snapshot instanceof CounterSnapshot) { 133 writeCreated(writer, snapshot); 134 } else if (snapshot instanceof HistogramSnapshot) { 135 writeCreated(writer, snapshot); 136 } else if (snapshot instanceof SummarySnapshot) { 137 writeCreated(writer, snapshot); 138 } 139 } 140 } 141 } 142 writer.flush(); 143 } 144 145 public void writeCreated(Writer writer, MetricSnapshot snapshot) throws IOException { 146 boolean metadataWritten = false; 147 MetricMetadata metadata = snapshot.getMetadata(); 148 for (DataPointSnapshot data : snapshot.getDataPoints()) { 149 if (data.hasCreatedTimestamp()) { 150 if (!metadataWritten) { 151 writeMetadata(writer, "_created", "gauge", metadata); 152 metadataWritten = true; 153 } 154 writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels()); 155 writePrometheusTimestamp(writer, data.getCreatedTimestampMillis(), timestampsInMs); 156 writeScrapeTimestampAndNewline(writer, data); 157 } 158 } 159 } 160 161 private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOException { 162 if (!snapshot.getDataPoints().isEmpty()) { 163 MetricMetadata metadata = snapshot.getMetadata(); 164 writeMetadata(writer, "_total", "counter", metadata); 165 for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { 166 writeNameAndLabels(writer, metadata.getPrometheusName(), "_total", data.getLabels()); 167 writeDouble(writer, data.getValue()); 168 writeScrapeTimestampAndNewline(writer, data); 169 } 170 } 171 } 172 173 private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOException { 174 MetricMetadata metadata = snapshot.getMetadata(); 175 writeMetadata(writer, "", "gauge", metadata); 176 for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { 177 writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); 178 writeDouble(writer, data.getValue()); 179 writeScrapeTimestampAndNewline(writer, data); 180 } 181 } 182 183 private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IOException { 184 MetricMetadata metadata = snapshot.getMetadata(); 185 writeMetadata(writer, "", "histogram", metadata); 186 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 187 ClassicHistogramBuckets buckets = getClassicBuckets(data); 188 long cumulativeCount = 0; 189 for (int i = 0; i < buckets.size(); i++) { 190 cumulativeCount += buckets.getCount(i); 191 writeNameAndLabels( 192 writer, 193 metadata.getPrometheusName(), 194 "_bucket", 195 data.getLabels(), 196 "le", 197 buckets.getUpperBound(i)); 198 writeLong(writer, cumulativeCount); 199 writeScrapeTimestampAndNewline(writer, data); 200 } 201 if (!snapshot.isGaugeHistogram()) { 202 if (data.hasCount()) { 203 writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels()); 204 writeLong(writer, data.getCount()); 205 writeScrapeTimestampAndNewline(writer, data); 206 } 207 if (data.hasSum()) { 208 writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels()); 209 writeDouble(writer, data.getSum()); 210 writeScrapeTimestampAndNewline(writer, data); 211 } 212 } 213 } 214 if (snapshot.isGaugeHistogram()) { 215 writeGaugeCountSum(writer, snapshot, metadata); 216 } 217 } 218 219 private ClassicHistogramBuckets getClassicBuckets( 220 HistogramSnapshot.HistogramDataPointSnapshot data) { 221 if (data.getClassicBuckets().isEmpty()) { 222 return ClassicHistogramBuckets.of( 223 new double[] {Double.POSITIVE_INFINITY}, new long[] {data.getCount()}); 224 } else { 225 return data.getClassicBuckets(); 226 } 227 } 228 229 private void writeGaugeCountSum( 230 Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata) throws IOException { 231 // Prometheus text format does not support gaugehistogram's _gcount and _gsum. 232 // So we append _gcount and _gsum as gauge metrics. 233 boolean metadataWritten = false; 234 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 235 if (data.hasCount()) { 236 if (!metadataWritten) { 237 writeMetadata(writer, "_gcount", "gauge", metadata); 238 metadataWritten = true; 239 } 240 writeNameAndLabels(writer, metadata.getPrometheusName(), "_gcount", data.getLabels()); 241 writeLong(writer, data.getCount()); 242 writeScrapeTimestampAndNewline(writer, data); 243 } 244 } 245 metadataWritten = false; 246 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 247 if (data.hasSum()) { 248 if (!metadataWritten) { 249 writeMetadata(writer, "_gsum", "gauge", metadata); 250 metadataWritten = true; 251 } 252 writeNameAndLabels(writer, metadata.getPrometheusName(), "_gsum", data.getLabels()); 253 writeDouble(writer, data.getSum()); 254 writeScrapeTimestampAndNewline(writer, data); 255 } 256 } 257 } 258 259 private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOException { 260 boolean metadataWritten = false; 261 MetricMetadata metadata = snapshot.getMetadata(); 262 for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { 263 if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) { 264 continue; 265 } 266 if (!metadataWritten) { 267 writeMetadata(writer, "", "summary", metadata); 268 metadataWritten = true; 269 } 270 for (Quantile quantile : data.getQuantiles()) { 271 writeNameAndLabels( 272 writer, 273 metadata.getPrometheusName(), 274 null, 275 data.getLabels(), 276 "quantile", 277 quantile.getQuantile()); 278 writeDouble(writer, quantile.getValue()); 279 writeScrapeTimestampAndNewline(writer, data); 280 } 281 if (data.hasCount()) { 282 writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels()); 283 writeLong(writer, data.getCount()); 284 writeScrapeTimestampAndNewline(writer, data); 285 } 286 if (data.hasSum()) { 287 writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels()); 288 writeDouble(writer, data.getSum()); 289 writeScrapeTimestampAndNewline(writer, data); 290 } 291 } 292 } 293 294 private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException { 295 MetricMetadata metadata = snapshot.getMetadata(); 296 writeMetadata(writer, "_info", "gauge", metadata); 297 for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { 298 writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels()); 299 writer.write("1"); 300 writeScrapeTimestampAndNewline(writer, data); 301 } 302 } 303 304 private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOException { 305 MetricMetadata metadata = snapshot.getMetadata(); 306 writeMetadata(writer, "", "gauge", metadata); 307 for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { 308 for (int i = 0; i < data.size(); i++) { 309 writer.write(metadata.getPrometheusName()); 310 writer.write('{'); 311 for (int j = 0; j < data.getLabels().size(); j++) { 312 if (j > 0) { 313 writer.write(","); 314 } 315 writer.write(data.getLabels().getPrometheusName(j)); 316 writer.write("=\""); 317 writeEscapedLabelValue(writer, data.getLabels().getValue(j)); 318 writer.write("\""); 319 } 320 if (!data.getLabels().isEmpty()) { 321 writer.write(","); 322 } 323 writer.write(metadata.getPrometheusName()); 324 writer.write("=\""); 325 writeEscapedLabelValue(writer, data.getName(i)); 326 writer.write("\"} "); 327 if (data.isTrue(i)) { 328 writer.write("1"); 329 } else { 330 writer.write("0"); 331 } 332 writeScrapeTimestampAndNewline(writer, data); 333 } 334 } 335 } 336 337 private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOException { 338 MetricMetadata metadata = snapshot.getMetadata(); 339 writeMetadata(writer, "", "untyped", metadata); 340 for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { 341 writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); 342 writeDouble(writer, data.getValue()); 343 writeScrapeTimestampAndNewline(writer, data); 344 } 345 } 346 347 private void writeNameAndLabels(Writer writer, String name, String suffix, Labels labels) 348 throws IOException { 349 writeNameAndLabels(writer, name, suffix, labels, null, 0.0); 350 } 351 352 private void writeNameAndLabels( 353 Writer writer, 354 String name, 355 String suffix, 356 Labels labels, 357 String additionalLabelName, 358 double additionalLabelValue) 359 throws IOException { 360 writer.write(name); 361 if (suffix != null) { 362 writer.write(suffix); 363 } 364 if (!labels.isEmpty() || additionalLabelName != null) { 365 writeLabels(writer, labels, additionalLabelName, additionalLabelValue); 366 } 367 writer.write(' '); 368 } 369 370 private void writeMetadata( 371 Writer writer, String suffix, String typeString, MetricMetadata metadata) throws IOException { 372 if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { 373 writer.write("# HELP "); 374 writer.write(metadata.getPrometheusName()); 375 if (suffix != null) { 376 writer.write(suffix); 377 } 378 writer.write(' '); 379 writeEscapedHelp(writer, metadata.getHelp()); 380 writer.write('\n'); 381 } 382 writer.write("# TYPE "); 383 writer.write(metadata.getPrometheusName()); 384 if (suffix != null) { 385 writer.write(suffix); 386 } 387 writer.write(' '); 388 writer.write(typeString); 389 writer.write('\n'); 390 } 391 392 private void writeEscapedHelp(Writer writer, String s) throws IOException { 393 for (int i = 0; i < s.length(); i++) { 394 char c = s.charAt(i); 395 switch (c) { 396 case '\\': 397 writer.append("\\\\"); 398 break; 399 case '\n': 400 writer.append("\\n"); 401 break; 402 default: 403 writer.append(c); 404 } 405 } 406 } 407 408 private void writeScrapeTimestampAndNewline(Writer writer, DataPointSnapshot data) 409 throws IOException { 410 if (data.hasScrapeTimestamp()) { 411 writer.write(' '); 412 writePrometheusTimestamp(writer, data.getScrapeTimestampMillis(), timestampsInMs); 413 } 414 writer.write('\n'); 415 } 416}