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