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