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 for (MetricSnapshot s : metricSnapshots) { 119 MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme); 120 if (!snapshot.getDataPoints().isEmpty()) { 121 if (snapshot instanceof CounterSnapshot) { 122 writeCounter(writer, (CounterSnapshot) snapshot, scheme); 123 } else if (snapshot instanceof GaugeSnapshot) { 124 writeGauge(writer, (GaugeSnapshot) snapshot, scheme); 125 } else if (snapshot instanceof HistogramSnapshot) { 126 writeHistogram(writer, (HistogramSnapshot) snapshot, scheme); 127 } else if (snapshot instanceof SummarySnapshot) { 128 writeSummary(writer, (SummarySnapshot) snapshot, scheme); 129 } else if (snapshot instanceof InfoSnapshot) { 130 writeInfo(writer, (InfoSnapshot) snapshot, scheme); 131 } else if (snapshot instanceof StateSetSnapshot) { 132 writeStateSet(writer, (StateSetSnapshot) snapshot, scheme); 133 } else if (snapshot instanceof UnknownSnapshot) { 134 writeUnknown(writer, (UnknownSnapshot) snapshot, scheme); 135 } 136 } 137 } 138 if (writeCreatedTimestamps) { 139 for (MetricSnapshot s : metricSnapshots) { 140 MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme); 141 if (!snapshot.getDataPoints().isEmpty()) { 142 if (snapshot instanceof CounterSnapshot) { 143 writeCreated(writer, snapshot, scheme); 144 } else if (snapshot instanceof HistogramSnapshot) { 145 writeCreated(writer, snapshot, scheme); 146 } else if (snapshot instanceof SummarySnapshot) { 147 writeCreated(writer, snapshot, scheme); 148 } 149 } 150 } 151 } 152 writer.flush(); 153 } 154 155 public void writeCreated(Writer writer, MetricSnapshot snapshot, EscapingScheme scheme) 156 throws IOException { 157 boolean metadataWritten = false; 158 MetricMetadata metadata = snapshot.getMetadata(); 159 for (DataPointSnapshot data : snapshot.getDataPoints()) { 160 if (data.hasCreatedTimestamp()) { 161 if (!metadataWritten) { 162 writeMetadata(writer, "_created", "gauge", metadata, scheme); 163 metadataWritten = true; 164 } 165 writeNameAndLabels( 166 writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme); 167 writePrometheusTimestamp(writer, data.getCreatedTimestampMillis(), timestampsInMs); 168 writeScrapeTimestampAndNewline(writer, data); 169 } 170 } 171 } 172 173 private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme) 174 throws IOException { 175 if (!snapshot.getDataPoints().isEmpty()) { 176 MetricMetadata metadata = snapshot.getMetadata(); 177 writeMetadata(writer, "_total", "counter", metadata, scheme); 178 for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { 179 writeNameAndLabels( 180 writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme); 181 writeDouble(writer, data.getValue()); 182 writeScrapeTimestampAndNewline(writer, data); 183 } 184 } 185 } 186 187 private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme) 188 throws IOException { 189 MetricMetadata metadata = snapshot.getMetadata(); 190 writeMetadata(writer, "", "gauge", metadata, scheme); 191 for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { 192 writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); 193 writeDouble(writer, data.getValue()); 194 writeScrapeTimestampAndNewline(writer, data); 195 } 196 } 197 198 private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) 199 throws IOException { 200 MetricMetadata metadata = snapshot.getMetadata(); 201 writeMetadata(writer, "", "histogram", metadata, scheme); 202 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 203 ClassicHistogramBuckets buckets = getClassicBuckets(data); 204 long cumulativeCount = 0; 205 for (int i = 0; i < buckets.size(); i++) { 206 cumulativeCount += buckets.getCount(i); 207 writeNameAndLabels( 208 writer, 209 getMetadataName(metadata, scheme), 210 "_bucket", 211 data.getLabels(), 212 scheme, 213 "le", 214 buckets.getUpperBound(i)); 215 writeLong(writer, cumulativeCount); 216 writeScrapeTimestampAndNewline(writer, data); 217 } 218 if (!snapshot.isGaugeHistogram()) { 219 if (data.hasCount()) { 220 writeNameAndLabels( 221 writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); 222 writeLong(writer, data.getCount()); 223 writeScrapeTimestampAndNewline(writer, data); 224 } 225 if (data.hasSum()) { 226 writeNameAndLabels( 227 writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); 228 writeDouble(writer, data.getSum()); 229 writeScrapeTimestampAndNewline(writer, data); 230 } 231 } 232 } 233 if (snapshot.isGaugeHistogram()) { 234 writeGaugeCountSum(writer, snapshot, metadata, scheme); 235 } 236 } 237 238 private ClassicHistogramBuckets getClassicBuckets( 239 HistogramSnapshot.HistogramDataPointSnapshot data) { 240 if (data.getClassicBuckets().isEmpty()) { 241 return ClassicHistogramBuckets.of( 242 new double[] {Double.POSITIVE_INFINITY}, new long[] {data.getCount()}); 243 } else { 244 return data.getClassicBuckets(); 245 } 246 } 247 248 private void writeGaugeCountSum( 249 Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata, EscapingScheme scheme) 250 throws IOException { 251 // Prometheus text format does not support gaugehistogram's _gcount and _gsum. 252 // So we append _gcount and _gsum as gauge metrics. 253 boolean metadataWritten = false; 254 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 255 if (data.hasCount()) { 256 if (!metadataWritten) { 257 writeMetadata(writer, "_gcount", "gauge", metadata, scheme); 258 metadataWritten = true; 259 } 260 writeNameAndLabels( 261 writer, getMetadataName(metadata, scheme), "_gcount", data.getLabels(), scheme); 262 writeLong(writer, data.getCount()); 263 writeScrapeTimestampAndNewline(writer, data); 264 } 265 } 266 metadataWritten = false; 267 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 268 if (data.hasSum()) { 269 if (!metadataWritten) { 270 writeMetadata(writer, "_gsum", "gauge", metadata, scheme); 271 metadataWritten = true; 272 } 273 writeNameAndLabels( 274 writer, getMetadataName(metadata, scheme), "_gsum", data.getLabels(), scheme); 275 writeDouble(writer, data.getSum()); 276 writeScrapeTimestampAndNewline(writer, data); 277 } 278 } 279 } 280 281 private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) 282 throws IOException { 283 boolean metadataWritten = false; 284 MetricMetadata metadata = snapshot.getMetadata(); 285 for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { 286 if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) { 287 continue; 288 } 289 if (!metadataWritten) { 290 writeMetadata(writer, "", "summary", metadata, scheme); 291 metadataWritten = true; 292 } 293 for (Quantile quantile : data.getQuantiles()) { 294 writeNameAndLabels( 295 writer, 296 getMetadataName(metadata, scheme), 297 null, 298 data.getLabels(), 299 scheme, 300 "quantile", 301 quantile.getQuantile()); 302 writeDouble(writer, quantile.getValue()); 303 writeScrapeTimestampAndNewline(writer, data); 304 } 305 if (data.hasCount()) { 306 writeNameAndLabels( 307 writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); 308 writeLong(writer, data.getCount()); 309 writeScrapeTimestampAndNewline(writer, data); 310 } 311 if (data.hasSum()) { 312 writeNameAndLabels( 313 writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); 314 writeDouble(writer, data.getSum()); 315 writeScrapeTimestampAndNewline(writer, data); 316 } 317 } 318 } 319 320 private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme) 321 throws IOException { 322 MetricMetadata metadata = snapshot.getMetadata(); 323 writeMetadata(writer, "_info", "gauge", metadata, scheme); 324 for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { 325 writeNameAndLabels( 326 writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme); 327 writer.write("1"); 328 writeScrapeTimestampAndNewline(writer, data); 329 } 330 } 331 332 private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme) 333 throws IOException { 334 MetricMetadata metadata = snapshot.getMetadata(); 335 writeMetadata(writer, "", "gauge", metadata, scheme); 336 for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { 337 for (int i = 0; i < data.size(); i++) { 338 writer.write(getMetadataName(metadata, scheme)); 339 writer.write('{'); 340 for (int j = 0; j < data.getLabels().size(); j++) { 341 if (j > 0) { 342 writer.write(","); 343 } 344 writer.write(getSnapshotLabelName(data.getLabels(), j, scheme)); 345 writer.write("=\""); 346 writeEscapedString(writer, data.getLabels().getValue(j)); 347 writer.write("\""); 348 } 349 if (!data.getLabels().isEmpty()) { 350 writer.write(","); 351 } 352 writer.write(getMetadataName(metadata, scheme)); 353 writer.write("=\""); 354 writeEscapedString(writer, data.getName(i)); 355 writer.write("\"} "); 356 if (data.isTrue(i)) { 357 writer.write("1"); 358 } else { 359 writer.write("0"); 360 } 361 writeScrapeTimestampAndNewline(writer, data); 362 } 363 } 364 } 365 366 private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme) 367 throws IOException { 368 MetricMetadata metadata = snapshot.getMetadata(); 369 writeMetadata(writer, "", "untyped", metadata, scheme); 370 for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { 371 writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); 372 writeDouble(writer, data.getValue()); 373 writeScrapeTimestampAndNewline(writer, data); 374 } 375 } 376 377 private void writeNameAndLabels( 378 Writer writer, 379 String name, 380 @Nullable String suffix, 381 Labels labels, 382 EscapingScheme escapingScheme) 383 throws IOException { 384 writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0); 385 } 386 387 private void writeNameAndLabels( 388 Writer writer, 389 String name, 390 @Nullable String suffix, 391 Labels labels, 392 EscapingScheme scheme, 393 @Nullable String additionalLabelName, 394 double additionalLabelValue) 395 throws IOException { 396 boolean metricInsideBraces = false; 397 // If the name does not pass the legacy validity check, we must put the 398 // metric name inside the braces. 399 if (!PrometheusNaming.isValidLegacyLabelName(name)) { 400 metricInsideBraces = true; 401 writer.write('{'); 402 } 403 writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); 404 if (!labels.isEmpty() || additionalLabelName != null) { 405 writeLabels( 406 writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces, scheme); 407 } else if (metricInsideBraces) { 408 writer.write('}'); 409 } 410 writer.write(' '); 411 } 412 413 private void writeMetadata( 414 Writer writer, 415 @Nullable String suffix, 416 String typeString, 417 MetricMetadata metadata, 418 EscapingScheme scheme) 419 throws IOException { 420 String name = getMetadataName(metadata, scheme) + (suffix != null ? suffix : ""); 421 if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { 422 writer.write("# HELP "); 423 writeName(writer, name, NameType.Metric); 424 writer.write(' '); 425 writeEscapedHelp(writer, metadata.getHelp()); 426 writer.write('\n'); 427 } 428 writer.write("# TYPE "); 429 writeName(writer, name, NameType.Metric); 430 writer.write(' '); 431 writer.write(typeString); 432 writer.write('\n'); 433 } 434 435 private void writeEscapedHelp(Writer writer, String s) throws IOException { 436 for (int i = 0; i < s.length(); i++) { 437 char c = s.charAt(i); 438 switch (c) { 439 case '\\': 440 writer.append("\\\\"); 441 break; 442 case '\n': 443 writer.append("\\n"); 444 break; 445 default: 446 writer.append(c); 447 } 448 } 449 } 450 451 private void writeScrapeTimestampAndNewline(Writer writer, DataPointSnapshot data) 452 throws IOException { 453 if (data.hasScrapeTimestamp()) { 454 writer.write(' '); 455 writePrometheusTimestamp(writer, data.getScrapeTimestampMillis(), timestampsInMs); 456 } 457 writer.write('\n'); 458 } 459}