001package io.prometheus.client; 002 003import io.prometheus.client.exemplars.Exemplar; 004import io.prometheus.client.exemplars.ExemplarConfig; 005import io.prometheus.client.exemplars.HistogramExemplarSampler; 006 007import java.io.Closeable; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.List; 011import java.util.Map; 012import java.util.concurrent.Callable; 013import java.util.concurrent.atomic.AtomicReference; 014 015import static java.lang.Boolean.FALSE; 016import static java.lang.Boolean.TRUE; 017 018/** 019 * Histogram metric, to track distributions of events. 020 * <p> 021 * Example of uses for Histograms include: 022 * <ul> 023 * <li>Response latency</li> 024 * <li>Request size</li> 025 * </ul> 026 * <p> 027 * <em>Note:</em> Each bucket is one timeseries. Many buckets and/or many dimensions with labels 028 * can produce large amount of time series, that may cause performance problems. 029 * 030 * <p> 031 * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. 032 * <p> 033 * Example Histograms: 034 * <pre> 035 * {@code 036 * class YourClass { 037 * static final Histogram requestLatency = Histogram.build() 038 * .name("requests_latency_seconds").help("Request latency in seconds.").register(); 039 * 040 * void processRequest(Request req) { 041 * Histogram.Timer requestTimer = requestLatency.startTimer(); 042 * try { 043 * // Your code here. 044 * } finally { 045 * requestTimer.observeDuration(); 046 * } 047 * } 048 * 049 * // Or if using Java 8 lambdas. 050 * void processRequestLambda(Request req) { 051 * requestLatency.time(() -> { 052 * // Your code here. 053 * }); 054 * } 055 * } 056 * } 057 * </pre> 058 * <p> 059 * You can choose your own buckets: 060 * <pre> 061 * {@code 062 * static final Histogram requestLatency = Histogram.build() 063 * .buckets(.01, .02, .03, .04) 064 * .name("requests_latency_seconds").help("Request latency in seconds.").register(); 065 * } 066 * </pre> 067 * {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and 068 * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets} 069 * offer easy ways to set common bucket patterns. 070 */ 071public class Histogram extends SimpleCollector<Histogram.Child> implements Collector.Describable { 072 private final double[] buckets; 073 private final Boolean exemplarsEnabled; // null means default from ExemplarConfig applies 074 private final HistogramExemplarSampler exemplarSampler; 075 076 Histogram(Builder b) { 077 super(b); 078 this.exemplarsEnabled = b.exemplarsEnabled; 079 this.exemplarSampler = b.exemplarSampler; 080 buckets = b.buckets; 081 initializeNoLabelsChild(); 082 } 083 084 public static class Builder extends SimpleCollector.Builder<Builder, Histogram> { 085 086 private Boolean exemplarsEnabled = null; 087 private HistogramExemplarSampler exemplarSampler = null; 088 private double[] buckets = new double[] { .005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10 }; 089 090 @Override 091 public Histogram create() { 092 for (int i = 0; i < buckets.length - 1; i++) { 093 if (buckets[i] >= buckets[i + 1]) { 094 throw new IllegalStateException("Histogram buckets must be in increasing order: " 095 + buckets[i] + " >= " + buckets[i + 1]); 096 } 097 } 098 if (buckets.length == 0) { 099 throw new IllegalStateException("Histogram must have at least one bucket."); 100 } 101 for (String label : labelNames) { 102 if (label.equals("le")) { 103 throw new IllegalStateException("Histogram cannot have a label named 'le'."); 104 } 105 } 106 107 // Append infinity bucket if it's not already there. 108 if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) { 109 double[] tmp = new double[buckets.length + 1]; 110 System.arraycopy(buckets, 0, tmp, 0, buckets.length); 111 tmp[buckets.length] = Double.POSITIVE_INFINITY; 112 buckets = tmp; 113 } 114 dontInitializeNoLabelsChild = true; 115 return new Histogram(this); 116 } 117 118 /** 119 * Set the upper bounds of buckets for the histogram. 120 */ 121 public Builder buckets(double... buckets) { 122 this.buckets = buckets; 123 return this; 124 } 125 126 /** 127 * Set the upper bounds of buckets for the histogram with a linear sequence. 128 */ 129 public Builder linearBuckets(double start, double width, int count) { 130 buckets = new double[count]; 131 for (int i = 0; i < count; i++) { 132 buckets[i] = start + i * width; 133 } 134 return this; 135 } 136 137 /** 138 * Set the upper bounds of buckets for the histogram with an exponential sequence. 139 */ 140 public Builder exponentialBuckets(double start, double factor, int count) { 141 buckets = new double[count]; 142 for (int i = 0; i < count; i++) { 143 buckets[i] = start * Math.pow(factor, i); 144 } 145 return this; 146 } 147 148 /** 149 * Enable exemplars and provide a custom {@link HistogramExemplarSampler}. 150 */ 151 public Builder withExemplarSampler(HistogramExemplarSampler exemplarSampler) { 152 if (exemplarSampler == null) { 153 throw new NullPointerException(); 154 } 155 this.exemplarSampler = exemplarSampler; 156 return withExemplars(); 157 } 158 159 /** 160 * Allow this histogram to load exemplars from a {@link HistogramExemplarSampler}. 161 * <p> 162 * If a specific exemplar sampler is configured for this histogram that exemplar sampler is used 163 * (see {@link #withExemplarSampler(HistogramExemplarSampler)}). 164 * Otherwise the default from {@link ExemplarConfig} is used. 165 */ 166 public Builder withExemplars() { 167 this.exemplarsEnabled = TRUE; 168 return this; 169 } 170 171 /** 172 * Prevent this histogram from loading exemplars from a {@link HistogramExemplarSampler}. 173 * <p> 174 * You can still provide exemplars for explicitly individual observations, e.g. using 175 * {@link #observeWithExemplar(double, String...)}. 176 */ 177 public Builder withoutExemplars() { 178 this.exemplarsEnabled = FALSE; 179 return this; 180 } 181 } 182 183 /** 184 * Return a Builder to allow configuration of a new Histogram. Ensures required fields are provided. 185 * 186 * @param name The name of the metric 187 * @param help The help string of the metric 188 */ 189 public static Builder build(String name, String help) { 190 return new Builder().name(name).help(help); 191 } 192 193 /** 194 * Return a Builder to allow configuration of a new Histogram. 195 */ 196 public static Builder build() { 197 return new Builder(); 198 } 199 200 @Override 201 protected Child newChild() { 202 return new Child(buckets, exemplarsEnabled, exemplarSampler); 203 } 204 205 /** 206 * Represents an event being timed. 207 */ 208 public static class Timer implements Closeable { 209 private final Child child; 210 private final long start; 211 212 private Timer(Child child, long start) { 213 this.child = child; 214 this.start = start; 215 } 216 217 /** 218 * Observe the amount of time in seconds since {@link Child#startTimer} was called. 219 * 220 * @return Measured duration in seconds since {@link Child#startTimer} was called. 221 */ 222 public double observeDuration() { 223 return observeDurationWithExemplar((String[]) null); 224 } 225 226 public double observeDurationWithExemplar(String... exemplarLabels) { 227 double elapsed = SimpleTimer.elapsedSecondsFromNanos(start, SimpleTimer.defaultTimeProvider.nanoTime()); 228 child.observeWithExemplar(elapsed, exemplarLabels); 229 return elapsed; 230 } 231 232 public double observeDurationWithExemplar(Map<String, String> exemplarLabels) { 233 return observeDurationWithExemplar(Exemplar.mapToArray(exemplarLabels)); 234 } 235 236 /** 237 * Equivalent to calling {@link #observeDuration()}. 238 */ 239 @Override 240 public void close() { 241 observeDuration(); 242 } 243 } 244 245 /** 246 * The value of a single Histogram. 247 * <p> 248 * <em>Warning:</em> References to a Child become invalid after using 249 * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. 250 */ 251 public static class Child { 252 253 /** 254 * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. 255 * 256 * @param timeable Code that is being timed 257 * @return Measured duration in seconds for timeable to complete. 258 */ 259 public double time(Runnable timeable) { 260 return timeWithExemplar(timeable, (String[]) null); 261 } 262 263 /** 264 * Like {@link #time(Runnable)}, but additionally create an exemplar. 265 * <p> 266 * See {@link #observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter. 267 */ 268 public double timeWithExemplar(Runnable timeable, String... exemplarLabels) { 269 Timer timer = startTimer(); 270 271 double elapsed; 272 try { 273 timeable.run(); 274 } finally { 275 elapsed = timer.observeDurationWithExemplar(exemplarLabels); 276 } 277 return elapsed; 278 } 279 280 /** 281 * Like {@link #time(Runnable)}, but additionally create an exemplar. 282 * <p> 283 * See {@link #observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter. 284 */ 285 public double timeWithExemplar(Runnable timeable, Map<String, String> exemplarLabels) { 286 return timeWithExemplar(timeable, Exemplar.mapToArray(exemplarLabels)); 287 } 288 289 /** 290 * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. 291 * 292 * @param timeable Code that is being timed 293 * @return Result returned by callable. 294 */ 295 public <E> E time(Callable<E> timeable) { 296 return timeWithExemplar(timeable, (String[]) null); 297 } 298 299 /** 300 * Like {@link #time(Callable)}, but additionally create an exemplar. 301 * <p> 302 * See {@link #observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter. 303 */ 304 public <E> E timeWithExemplar(Callable<E> timeable, String... exemplarLabels) { 305 Timer timer = startTimer(); 306 307 try { 308 return timeable.call(); 309 } catch (RuntimeException e) { 310 throw e; 311 } catch (Exception e) { 312 throw new RuntimeException(e); 313 } finally { 314 timer.observeDurationWithExemplar(exemplarLabels); 315 } 316 } 317 318 /** 319 * Like {@link #time(Callable)}, but additionally create an exemplar. 320 * <p> 321 * See {@link #observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter. 322 */ 323 public <E> E timeWithExemplar(Callable<E> timeable, Map<String, String> exemplarLabels) { 324 return timeWithExemplar(timeable, Exemplar.mapToArray(exemplarLabels)); 325 } 326 327 public static class Value { 328 public final double sum; 329 public final double[] buckets; 330 public final Exemplar[] exemplars; 331 public final long created; 332 333 public Value(double sum, double[] buckets, Exemplar[] exemplars, long created) { 334 this.sum = sum; 335 this.buckets = buckets; 336 this.exemplars = exemplars; 337 this.created = created; 338 } 339 } 340 341 private Child(double[] buckets, Boolean exemplarsEnabled, HistogramExemplarSampler exemplarSampler) { 342 upperBounds = buckets; 343 this.exemplarsEnabled = exemplarsEnabled; 344 this.exemplarSampler = exemplarSampler; 345 exemplars = new ArrayList<AtomicReference<Exemplar>>(buckets.length); 346 cumulativeCounts = new DoubleAdder[buckets.length]; 347 for (int i = 0; i < buckets.length; ++i) { 348 cumulativeCounts[i] = new DoubleAdder(); 349 exemplars.add(new AtomicReference<Exemplar>()); 350 } 351 } 352 353 private final ArrayList<AtomicReference<Exemplar>> exemplars; 354 private final Boolean exemplarsEnabled; 355 private final HistogramExemplarSampler exemplarSampler; 356 private final double[] upperBounds; 357 private final DoubleAdder[] cumulativeCounts; 358 private final DoubleAdder sum = new DoubleAdder(); 359 private final long created = System.currentTimeMillis(); 360 361 /** 362 * Observe the given amount. 363 * 364 * @param amt in most cases amt should be >= 0. Negative values are supported, but you should read 365 * <a href="https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations"> 366 * https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations</a> for 367 * implications and alternatives. 368 */ 369 public void observe(double amt) { 370 observeWithExemplar(amt, (String[]) null); 371 } 372 373 /** 374 * Like {@link #observe(double)}, but additionally creates an exemplar. 375 * <p> 376 * This exemplar takes precedence over any exemplar returned by the {@link HistogramExemplarSampler} configured 377 * in {@link ExemplarConfig}. 378 * <p> 379 * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp, 380 * and the specified labels. 381 * 382 * @param amt same as in {@link #observe(double)} (double)} 383 * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}. 384 * A commonly used name is {@code "trace_id"}. 385 * Calling {@code observeWithExemplar(amt)} means that an exemplar without labels is created. 386 * Calling {@code observeWithExemplar(amt, (String[]) null)} is equivalent 387 * to calling {@code observe(amt)}. 388 */ 389 public void observeWithExemplar(double amt, String... exemplarLabels) { 390 Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels); 391 for (int i = 0; i < upperBounds.length; ++i) { 392 // The last bucket is +Inf, so we always increment. 393 if (amt <= upperBounds[i]) { 394 cumulativeCounts[i].add(1); 395 updateExemplar(amt, i, exemplar); 396 break; 397 } 398 } 399 sum.add(amt); 400 } 401 402 /** 403 * Like {@link #observeWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}. 404 */ 405 public void observeWithExemplar(double amt, Map<String, String> exemplarLabels) { 406 observeWithExemplar(amt, Exemplar.mapToArray(exemplarLabels)); 407 } 408 409 private void updateExemplar(double amt, int i, Exemplar userProvidedExemplar) { 410 AtomicReference<Exemplar> exemplar = exemplars.get(i); 411 double bucketFrom = i == 0 ? Double.NEGATIVE_INFINITY : upperBounds[i - 1]; 412 double bucketTo = upperBounds[i]; 413 Exemplar prev, next; 414 do { 415 prev = exemplar.get(); 416 if (userProvidedExemplar != null) { 417 next = userProvidedExemplar; 418 } else { 419 next = sampleNextExemplar(amt, bucketFrom, bucketTo, prev); 420 } 421 if (next == null || next == prev) { 422 return; 423 } 424 } while (!exemplar.compareAndSet(prev, next)); 425 } 426 427 private Exemplar sampleNextExemplar(double amt, double bucketFrom, double bucketTo, Exemplar prev) { 428 if (FALSE.equals(exemplarsEnabled)) { 429 return null; 430 } 431 if (exemplarSampler != null) { 432 return exemplarSampler.sample(amt, bucketFrom, bucketTo, prev); 433 } 434 if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) { 435 HistogramExemplarSampler exemplarSampler = ExemplarConfig.getHistogramExemplarSampler(); 436 if (exemplarSampler != null) { 437 return exemplarSampler.sample(amt, bucketFrom, bucketTo, prev); 438 } 439 } 440 return null; 441 } 442 443 /** 444 * Start a timer to track a duration. 445 * <p> 446 * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. 447 */ 448 public Timer startTimer() { 449 return new Timer(this, SimpleTimer.defaultTimeProvider.nanoTime()); 450 } 451 452 /** 453 * Get the value of the Histogram. 454 * <p> 455 * <em>Warning:</em> The definition of {@link Value} is subject to change. 456 */ 457 public Value get() { 458 double[] buckets = new double[cumulativeCounts.length]; 459 Exemplar[] exemplars = new Exemplar[cumulativeCounts.length]; 460 double acc = 0; 461 for (int i = 0; i < cumulativeCounts.length; ++i) { 462 acc += cumulativeCounts[i].sum(); 463 buckets[i] = acc; 464 exemplars[i] = this.exemplars.get(i).get(); 465 } 466 return new Value(sum.sum(), buckets, exemplars, created); 467 } 468 } 469 470 // Convenience methods. 471 472 /** 473 * Observe the given amount on the histogram with no labels. 474 * 475 * @param amt in most cases amt should be >= 0. Negative values are supported, but you should read 476 * <a href="https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations"> 477 * https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations</a> for 478 * implications and alternatives. 479 */ 480 public void observe(double amt) { 481 noLabelsChild.observe(amt); 482 } 483 484 /** 485 * Like {@link Child#observeWithExemplar(double, String...)}, but for the histogram without labels. 486 */ 487 public void observeWithExemplar(double amt, String... exemplarLabels) { 488 noLabelsChild.observeWithExemplar(amt, exemplarLabels); 489 } 490 491 /** 492 * Like {@link Child#observeWithExemplar(double, Map)}, but for the histogram without labels. 493 */ 494 public void observeWithExemplar(double amt, Map<String, String> exemplarLabels) { 495 noLabelsChild.observeWithExemplar(amt, exemplarLabels); 496 } 497 498 /** 499 * Start a timer to track a duration on the histogram with no labels. 500 * <p> 501 * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. 502 */ 503 public Timer startTimer() { 504 return noLabelsChild.startTimer(); 505 } 506 507 /** 508 * Executes runnable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. 509 * 510 * @param timeable Code that is being timed 511 * @return Measured duration in seconds for timeable to complete. 512 */ 513 public double time(Runnable timeable) { 514 return noLabelsChild.time(timeable); 515 } 516 517 /** 518 * Like {@link #time(Runnable)}, but additionally create an exemplar. 519 * <p> 520 * See {@link Child#observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter. 521 */ 522 public double timeWithExemplar(Runnable timeable, String... exemplarLabels) { 523 return noLabelsChild.timeWithExemplar(timeable, exemplarLabels); 524 } 525 526 /** 527 * Like {@link #time(Runnable)}, but additionally create an exemplar. 528 * <p> 529 * See {@link Child#observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter. 530 */ 531 public double timeWithExemplar(Runnable timeable, Map<String, String> exemplarLabels) { 532 return noLabelsChild.timeWithExemplar(timeable, exemplarLabels); 533 } 534 535 /** 536 * Executes callable code (e.g. a Java 8 Lambda) and observes a duration of how long it took to run. 537 * 538 * @param timeable Code that is being timed 539 * @return Result returned by callable. 540 */ 541 public <E> E time(Callable<E> timeable) { 542 return noLabelsChild.time(timeable); 543 } 544 545 /** 546 * Like {@link #time(Callable)}, but additionally create an exemplar. 547 * <p> 548 * See {@link Child#observeWithExemplar(double, String...)} for documentation on the {@code exemplarLabels} parameter. 549 */ 550 public <E> E timeWithExemplar(Callable<E> timeable, String... exemplarLabels) { 551 return noLabelsChild.timeWithExemplar(timeable, exemplarLabels); 552 } 553 554 /** 555 * Like {@link #time(Callable)}, but additionally create an exemplar. 556 * <p> 557 * See {@link Child#observeWithExemplar(double, Map)} for documentation on the {@code exemplarLabels} parameter. 558 */ 559 public <E> E timeWithExemplar(Callable<E> timeable, Map<String, String> exemplarLabels) { 560 return noLabelsChild.timeWithExemplar(timeable, exemplarLabels); 561 } 562 563 @Override 564 public List<MetricFamilySamples> collect() { 565 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(); 566 for (Map.Entry<List<String>, Child> c : children.entrySet()) { 567 Child.Value v = c.getValue().get(); 568 List<String> labelNamesWithLe = new ArrayList<String>(labelNames); 569 labelNamesWithLe.add("le"); 570 for (int i = 0; i < v.buckets.length; ++i) { 571 List<String> labelValuesWithLe = new ArrayList<String>(c.getKey()); 572 labelValuesWithLe.add(doubleToGoString(buckets[i])); 573 samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i], v.exemplars[i])); 574 } 575 samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1])); 576 samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); 577 samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), v.created / 1000.0)); 578 } 579 580 return familySamplesList(Type.HISTOGRAM, samples); 581 } 582 583 @Override 584 public List<MetricFamilySamples> describe() { 585 return Collections.singletonList( 586 new MetricFamilySamples(fullname, Type.HISTOGRAM, help, Collections.<MetricFamilySamples.Sample>emptyList())); 587 } 588 589 double[] getBuckets() { 590 return buckets; 591 } 592}