001package io.prometheus.metrics.simpleclient.bridge; 002 003import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; 004 005import io.prometheus.client.Collector; 006import io.prometheus.client.CollectorRegistry; 007import io.prometheus.metrics.config.PrometheusProperties; 008import io.prometheus.metrics.model.registry.MultiCollector; 009import io.prometheus.metrics.model.registry.PrometheusRegistry; 010import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; 011import io.prometheus.metrics.model.snapshots.CounterSnapshot; 012import io.prometheus.metrics.model.snapshots.Exemplar; 013import io.prometheus.metrics.model.snapshots.Exemplars; 014import io.prometheus.metrics.model.snapshots.GaugeSnapshot; 015import io.prometheus.metrics.model.snapshots.HistogramSnapshot; 016import io.prometheus.metrics.model.snapshots.InfoSnapshot; 017import io.prometheus.metrics.model.snapshots.Labels; 018import io.prometheus.metrics.model.snapshots.MetricSnapshot; 019import io.prometheus.metrics.model.snapshots.MetricSnapshots; 020import io.prometheus.metrics.model.snapshots.Quantile; 021import io.prometheus.metrics.model.snapshots.Quantiles; 022import io.prometheus.metrics.model.snapshots.StateSetSnapshot; 023import io.prometheus.metrics.model.snapshots.SummarySnapshot; 024import io.prometheus.metrics.model.snapshots.Unit; 025import io.prometheus.metrics.model.snapshots.UnknownSnapshot; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Enumeration; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032 033/** 034 * Bridge from {@code simpleclient} (version 0.16.0 and older) to the new {@code prometheus-metrics} 035 * (version 1.0.0 and newer). 036 * 037 * <p>Usage: The following line will register all metrics from a {@code simpleclient} {@link 038 * CollectorRegistry#defaultRegistry} to a {@code prometheus-metrics} {@link 039 * PrometheusRegistry#defaultRegistry}: 040 * 041 * <pre>{@code 042 * SimpleclientCollector.builder().register(); 043 * }</pre> 044 * 045 * <p>If you have custom registries (not the default registries), use the following snippet: 046 * 047 * <pre>{@code 048 * CollectorRegistry simpleclientRegistry = ...; 049 * PrometheusRegistry prometheusRegistry = ...; 050 * SimpleclientCollector.builder() 051 * .collectorRegistry(simpleclientRegistry) 052 * .register(prometheusRegistry); 053 * }</pre> 054 */ 055public class SimpleclientCollector implements MultiCollector { 056 057 private final CollectorRegistry simpleclientRegistry; 058 059 private SimpleclientCollector(CollectorRegistry simpleclientRegistry) { 060 this.simpleclientRegistry = simpleclientRegistry; 061 } 062 063 @Override 064 public MetricSnapshots collect() { 065 return convert(simpleclientRegistry.metricFamilySamples()); 066 } 067 068 private MetricSnapshots convert(Enumeration<Collector.MetricFamilySamples> samples) { 069 MetricSnapshots.Builder result = MetricSnapshots.builder(); 070 while (samples.hasMoreElements()) { 071 Collector.MetricFamilySamples sample = samples.nextElement(); 072 switch (sample.type) { 073 case COUNTER: 074 result.metricSnapshot(convertCounter(sample)); 075 break; 076 case GAUGE: 077 result.metricSnapshot(convertGauge(sample)); 078 break; 079 case HISTOGRAM: 080 result.metricSnapshot(convertHistogram(sample, false)); 081 break; 082 case GAUGE_HISTOGRAM: 083 result.metricSnapshot(convertHistogram(sample, true)); 084 break; 085 case SUMMARY: 086 result.metricSnapshot(convertSummary(sample)); 087 break; 088 case INFO: 089 result.metricSnapshot(convertInfo(sample)); 090 break; 091 case STATE_SET: 092 result.metricSnapshot(convertStateSet(sample)); 093 break; 094 case UNKNOWN: 095 result.metricSnapshot(convertUnknown(sample)); 096 break; 097 default: 098 throw new IllegalStateException(sample.type + ": Unexpected metric type"); 099 } 100 } 101 return result.build(); 102 } 103 104 private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) { 105 CounterSnapshot.Builder counter = 106 CounterSnapshot.builder() 107 .name(sanitizeMetricName(samples.name)) 108 .help(samples.help) 109 .unit(convertUnit(samples)); 110 Map<Labels, CounterSnapshot.CounterDataPointSnapshot.Builder> dataPoints = new HashMap<>(); 111 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 112 Labels labels = Labels.of(sample.labelNames, sample.labelValues); 113 CounterSnapshot.CounterDataPointSnapshot.Builder dataPoint = 114 dataPoints.computeIfAbsent( 115 labels, l -> CounterSnapshot.CounterDataPointSnapshot.builder().labels(labels)); 116 if (sample.name.endsWith("_created")) { 117 dataPoint.createdTimestampMillis((long) Unit.secondsToMillis(sample.value)); 118 } else { 119 dataPoint.value(sample.value).exemplar(convertExemplar(sample.exemplar)); 120 if (sample.timestampMs != null) { 121 dataPoint.scrapeTimestampMillis(sample.timestampMs); 122 } 123 } 124 } 125 for (CounterSnapshot.CounterDataPointSnapshot.Builder dataPoint : dataPoints.values()) { 126 counter.dataPoint(dataPoint.build()); 127 } 128 return counter.build(); 129 } 130 131 private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) { 132 GaugeSnapshot.Builder gauge = 133 GaugeSnapshot.builder() 134 .name(sanitizeMetricName(samples.name)) 135 .help(samples.help) 136 .unit(convertUnit(samples)); 137 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 138 GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPoint = 139 GaugeSnapshot.GaugeDataPointSnapshot.builder() 140 .value(sample.value) 141 .labels(Labels.of(sample.labelNames, sample.labelValues)) 142 .exemplar(convertExemplar(sample.exemplar)); 143 if (sample.timestampMs != null) { 144 dataPoint.scrapeTimestampMillis(sample.timestampMs); 145 } 146 gauge.dataPoint(dataPoint.build()); 147 } 148 return gauge.build(); 149 } 150 151 private MetricSnapshot convertHistogram( 152 Collector.MetricFamilySamples samples, boolean isGaugeHistogram) { 153 HistogramSnapshot.Builder histogram = 154 HistogramSnapshot.builder() 155 .name(sanitizeMetricName(samples.name)) 156 .help(samples.help) 157 .unit(convertUnit(samples)) 158 .gaugeHistogram(isGaugeHistogram); 159 Map<Labels, HistogramSnapshot.HistogramDataPointSnapshot.Builder> dataPoints = new HashMap<>(); 160 Map<Labels, Map<Double, Long>> cumulativeBuckets = new HashMap<>(); 161 Map<Labels, Exemplars.Builder> exemplars = new HashMap<>(); 162 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 163 Labels labels = labelsWithout(sample, "le"); 164 dataPoints.computeIfAbsent( 165 labels, l -> HistogramSnapshot.HistogramDataPointSnapshot.builder().labels(labels)); 166 cumulativeBuckets.computeIfAbsent(labels, l -> new HashMap<>()); 167 exemplars.computeIfAbsent(labels, l -> Exemplars.builder()); 168 if (sample.name.endsWith("_sum")) { 169 dataPoints.get(labels).sum(sample.value); 170 } 171 if (sample.name.endsWith("_bucket")) { 172 addBucket(cumulativeBuckets.get(labels), sample); 173 } 174 if (sample.name.endsWith("_created")) { 175 dataPoints.get(labels).createdTimestampMillis((long) Unit.secondsToMillis(sample.value)); 176 } 177 if (sample.exemplar != null) { 178 exemplars.get(labels).exemplar(convertExemplar(sample.exemplar)); 179 } 180 if (sample.timestampMs != null) { 181 dataPoints.get(labels).scrapeTimestampMillis(sample.timestampMs); 182 } 183 } 184 for (Labels labels : dataPoints.keySet()) { 185 histogram.dataPoint( 186 dataPoints 187 .get(labels) 188 .classicHistogramBuckets(makeBuckets(cumulativeBuckets.get(labels))) 189 .exemplars(exemplars.get(labels).build()) 190 .build()); 191 } 192 return histogram.build(); 193 } 194 195 private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) { 196 SummarySnapshot.Builder summary = 197 SummarySnapshot.builder() 198 .name(sanitizeMetricName(samples.name)) 199 .help(samples.help) 200 .unit(convertUnit(samples)); 201 Map<Labels, SummarySnapshot.SummaryDataPointSnapshot.Builder> dataPoints = new HashMap<>(); 202 Map<Labels, Quantiles.Builder> quantiles = new HashMap<>(); 203 Map<Labels, Exemplars.Builder> exemplars = new HashMap<>(); 204 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 205 Labels labels = labelsWithout(sample, "quantile"); 206 dataPoints.computeIfAbsent( 207 labels, l -> SummarySnapshot.SummaryDataPointSnapshot.builder().labels(labels)); 208 quantiles.computeIfAbsent(labels, l -> Quantiles.builder()); 209 exemplars.computeIfAbsent(labels, l -> Exemplars.builder()); 210 if (sample.name.endsWith("_sum")) { 211 dataPoints.get(labels).sum(sample.value); 212 } else if (sample.name.endsWith("_count")) { 213 dataPoints.get(labels).count((long) sample.value); 214 } else if (sample.name.endsWith("_created")) { 215 dataPoints.get(labels).createdTimestampMillis((long) Unit.secondsToMillis(sample.value)); 216 } else { 217 for (int i = 0; i < sample.labelNames.size(); i++) { 218 if (sample.labelNames.get(i).equals("quantile")) { 219 quantiles 220 .get(labels) 221 .quantile( 222 new Quantile(Double.parseDouble(sample.labelValues.get(i)), sample.value)); 223 break; 224 } 225 } 226 } 227 if (sample.exemplar != null) { 228 exemplars.get(labels).exemplar(convertExemplar(sample.exemplar)); 229 } 230 if (sample.timestampMs != null) { 231 dataPoints.get(labels).scrapeTimestampMillis(sample.timestampMs); 232 } 233 } 234 for (Labels labels : dataPoints.keySet()) { 235 summary.dataPoint( 236 dataPoints 237 .get(labels) 238 .quantiles(quantiles.get(labels).build()) 239 .exemplars(exemplars.get(labels).build()) 240 .build()); 241 } 242 return summary.build(); 243 } 244 245 private MetricSnapshot convertStateSet(Collector.MetricFamilySamples samples) { 246 StateSetSnapshot.Builder stateSet = 247 StateSetSnapshot.builder().name(sanitizeMetricName(samples.name)).help(samples.help); 248 Map<Labels, StateSetSnapshot.StateSetDataPointSnapshot.Builder> dataPoints = new HashMap<>(); 249 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 250 Labels labels = labelsWithout(sample, sample.name); 251 dataPoints.computeIfAbsent( 252 labels, l -> StateSetSnapshot.StateSetDataPointSnapshot.builder().labels(labels)); 253 String stateName = null; 254 for (int i = 0; i < sample.labelNames.size(); i++) { 255 if (sample.labelNames.get(i).equals(sample.name)) { 256 stateName = sample.labelValues.get(i); 257 break; 258 } 259 } 260 if (stateName == null) { 261 throw new IllegalStateException("Invalid StateSet metric: No state name found."); 262 } 263 dataPoints.get(labels).state(stateName, sample.value == 1.0); 264 if (sample.timestampMs != null) { 265 dataPoints.get(labels).scrapeTimestampMillis(sample.timestampMs); 266 } 267 } 268 for (StateSetSnapshot.StateSetDataPointSnapshot.Builder dataPoint : dataPoints.values()) { 269 stateSet.dataPoint(dataPoint.build()); 270 } 271 return stateSet.build(); 272 } 273 274 private MetricSnapshot convertUnknown(Collector.MetricFamilySamples samples) { 275 UnknownSnapshot.Builder unknown = 276 UnknownSnapshot.builder() 277 .name(sanitizeMetricName(samples.name)) 278 .help(samples.help) 279 .unit(convertUnit(samples)); 280 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 281 UnknownSnapshot.UnknownDataPointSnapshot.Builder dataPoint = 282 UnknownSnapshot.UnknownDataPointSnapshot.builder() 283 .value(sample.value) 284 .labels(Labels.of(sample.labelNames, sample.labelValues)) 285 .exemplar(convertExemplar(sample.exemplar)); 286 if (sample.timestampMs != null) { 287 dataPoint.scrapeTimestampMillis(sample.timestampMs); 288 } 289 unknown.dataPoint(dataPoint.build()); 290 } 291 return unknown.build(); 292 } 293 294 private Unit convertUnit(Collector.MetricFamilySamples samples) { 295 if (samples.unit != null && !samples.unit.isEmpty()) { 296 return new Unit(samples.unit); 297 } else { 298 return null; 299 } 300 } 301 302 private ClassicHistogramBuckets makeBuckets(Map<Double, Long> cumulativeBuckets) { 303 List<Double> upperBounds = new ArrayList<>(cumulativeBuckets.size()); 304 upperBounds.addAll(cumulativeBuckets.keySet()); 305 Collections.sort(upperBounds); 306 ClassicHistogramBuckets.Builder result = ClassicHistogramBuckets.builder(); 307 long previousCount = 0L; 308 for (Double upperBound : upperBounds) { 309 long cumulativeCount = cumulativeBuckets.get(upperBound); 310 result.bucket(upperBound, cumulativeCount - previousCount); 311 previousCount = cumulativeCount; 312 } 313 return result.build(); 314 } 315 316 private void addBucket(Map<Double, Long> buckets, Collector.MetricFamilySamples.Sample sample) { 317 for (int i = 0; i < sample.labelNames.size(); i++) { 318 if (sample.labelNames.get(i).equals("le")) { 319 double upperBound; 320 switch (sample.labelValues.get(i)) { 321 case "+Inf": 322 upperBound = Double.POSITIVE_INFINITY; 323 break; 324 case "-Inf": // Doesn't make sense as count would always be zero. Catch this anyway. 325 upperBound = Double.NEGATIVE_INFINITY; 326 break; 327 default: 328 upperBound = Double.parseDouble(sample.labelValues.get(i)); 329 } 330 buckets.put(upperBound, (long) sample.value); 331 return; 332 } 333 } 334 throw new IllegalStateException(sample.name + " does not have a le label."); 335 } 336 337 private Labels labelsWithout( 338 Collector.MetricFamilySamples.Sample sample, String excludedLabelName) { 339 Labels.Builder labels = Labels.builder(); 340 for (int i = 0; i < sample.labelNames.size(); i++) { 341 if (!sample.labelNames.get(i).equals(excludedLabelName)) { 342 labels.label(sample.labelNames.get(i), sample.labelValues.get(i)); 343 } 344 } 345 return labels.build(); 346 } 347 348 private MetricSnapshot convertInfo(Collector.MetricFamilySamples samples) { 349 InfoSnapshot.Builder info = 350 InfoSnapshot.builder().name(sanitizeMetricName(samples.name)).help(samples.help); 351 for (Collector.MetricFamilySamples.Sample sample : samples.samples) { 352 info.dataPoint( 353 InfoSnapshot.InfoDataPointSnapshot.builder() 354 .labels(Labels.of(sample.labelNames, sample.labelValues)) 355 .build()); 356 } 357 return info.build(); 358 } 359 360 private Exemplar convertExemplar(io.prometheus.client.exemplars.Exemplar exemplar) { 361 if (exemplar == null) { 362 return null; 363 } 364 Exemplar.Builder result = Exemplar.builder().value(exemplar.getValue()); 365 if (exemplar.getTimestampMs() != null) { 366 result.timestampMillis(exemplar.getTimestampMs()); 367 } 368 Labels.Builder labels = Labels.builder(); 369 for (int i = 0; i < exemplar.getNumberOfLabels(); i++) { 370 labels.label(exemplar.getLabelName(i), exemplar.getLabelValue(i)); 371 } 372 return result.labels(labels.build()).build(); 373 } 374 375 /** 376 * Currently there are no configuration options for the SimpleclientCollector. However, we want to 377 * follow the pattern to pass the config everywhere so that we can introduce config options later 378 * without the need for API changes. 379 */ 380 @SuppressWarnings("unused") 381 public static Builder builder(PrometheusProperties config) { 382 return new Builder(); 383 } 384 385 public static Builder builder() { 386 return builder(PrometheusProperties.get()); 387 } 388 389 public static class Builder { 390 391 private CollectorRegistry collectorRegistry; 392 393 private Builder() {} 394 395 public Builder collectorRegistry(CollectorRegistry registry) { 396 this.collectorRegistry = registry; 397 return this; 398 } 399 400 public SimpleclientCollector build() { 401 return collectorRegistry != null 402 ? new SimpleclientCollector(collectorRegistry) 403 : new SimpleclientCollector(CollectorRegistry.defaultRegistry); 404 } 405 406 public SimpleclientCollector register() { 407 return register(PrometheusRegistry.defaultRegistry); 408 } 409 410 public SimpleclientCollector register(PrometheusRegistry registry) { 411 SimpleclientCollector result = build(); 412 registry.register(result); 413 return result; 414 } 415 } 416}