001package io.prometheus.client; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Map; 006 007/** 008 * Histogram metric, to track distributions of events. 009 * <p> 010 * Example of uses for Histograms include: 011 * <ul> 012 * <li>Response latency</li> 013 * <li>Request size</li> 014 * </ul> 015 * <p> 016 * <em>Note:</em> Each bucket is one timeseries. Many buckets and/or many dimensions with labels 017 * can produce large amount of time series, that may cause performance problems. 018 * 019 * <p> 020 * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. 021 * <p> 022 * Example Histograms: 023 * <pre> 024 * {@code 025 * class YourClass { 026 * static final Histogram requestLatency = Histogram.build() 027 * .name("requests_latency_seconds").help("Request latency in seconds.").register(); 028 * 029 * void processRequest(Request req) { 030 * Histogram.Timer requestTimer = requestLatency.startTimer(); 031 * try { 032 * // Your code here. 033 * } finally { 034 * requestTimer.observeDuration(); 035 * } 036 * } 037 * } 038 * } 039 * </pre> 040 * <p> 041 * You can choose your own buckets: 042 * <pre> 043 * {@code 044 * static final Histogram requestLatency = Histogram.build() 045 * .buckets(.01, .02, .03, .04) 046 * .name("requests_latency_seconds").help("Request latency in seconds.").register(); 047 * } 048 * </pre> 049 * {@link Histogram.Builder#linearBuckets(double, double, int) linearBuckets} and 050 * {@link Histogram.Builder#exponentialBuckets(double, double, int) exponentialBuckets} 051 * offer easy ways to set common bucket patterns. 052 */ 053public class Histogram extends SimpleCollector<Histogram.Child> { 054 private final double[] buckets; 055 056 Histogram(Builder b) { 057 super(b); 058 buckets = b.buckets; 059 initializeNoLabelsChild(); 060 } 061 062 public static class Builder extends SimpleCollector.Builder<Builder, Histogram> { 063 private double[] buckets = new double[]{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}; 064 065 @Override 066 public Histogram create() { 067 for (int i = 0; i < buckets.length - 1; i++) { 068 if (buckets[i] >= buckets[i + 1]) { 069 throw new IllegalStateException("Histogram buckets must be in increasing order: " 070 + buckets[i] + " >= " + buckets[i + 1]); 071 } 072 } 073 if (buckets.length == 0) { 074 throw new IllegalStateException("Histogram must have at least one bucket."); 075 } 076 for (String label: labelNames) { 077 if (label.equals("le")) { 078 throw new IllegalStateException("Histogram cannot have a label named 'le'."); 079 } 080 } 081 082 // Append infinity bucket if it's not already there. 083 if (buckets[buckets.length - 1] != Double.POSITIVE_INFINITY) { 084 double[] tmp = new double[buckets.length + 1]; 085 System.arraycopy(buckets, 0, tmp, 0, buckets.length); 086 tmp[buckets.length] = Double.POSITIVE_INFINITY; 087 buckets = tmp; 088 } 089 dontInitializeNoLabelsChild = true; 090 return new Histogram(this); 091 } 092 093 /** 094 * Set the upper bounds of buckets for the histogram. 095 */ 096 public Builder buckets(double... buckets) { 097 this.buckets = buckets; 098 return this; 099 } 100 101 /** 102 * Set the upper bounds of buckets for the histogram with a linear sequence. 103 */ 104 public Builder linearBuckets(double start, double width, int count) { 105 buckets = new double[count]; 106 for (int i = 0; i < count; i++){ 107 buckets[i] = start + i*width; 108 } 109 return this; 110 } 111 /** 112 * Set the upper bounds of buckets for the histogram with an exponential sequence. 113 */ 114 public Builder exponentialBuckets(double start, double factor, int count) { 115 buckets = new double[count]; 116 for (int i = 0; i < count; i++) { 117 buckets[i] = start * Math.pow(factor, i); 118 } 119 return this; 120 } 121 122 } 123 124 /** 125 * Return a Builder to allow configuration of a new Histogram. 126 */ 127 public static Builder build() { 128 return new Builder(); 129 } 130 131 @Override 132 protected Child newChild() { 133 return new Child(buckets); 134 } 135 136 /** 137 * Represents an event being timed. 138 */ 139 public static class Timer { 140 private final Child child; 141 private final long start; 142 private Timer(Child child) { 143 this.child = child; 144 start = Child.timeProvider.nanoTime(); 145 } 146 /** 147 * Observe the amount of time in seconds since {@link Child#startTimer} was called. 148 * @return Measured duration in seconds since {@link Child#startTimer} was called. 149 */ 150 public double observeDuration() { 151 double elapsed = (Child.timeProvider.nanoTime() - start) / NANOSECONDS_PER_SECOND; 152 child.observe(elapsed); 153 return elapsed; 154 } 155 } 156 157 /** 158 * The value of a single Histogram. 159 * <p> 160 * <em>Warning:</em> References to a Child become invalid after using 161 * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. 162 */ 163 public static class Child { 164 public static class Value { 165 public final double sum; 166 public final double[] buckets; 167 168 public Value(double sum, double[] buckets) { 169 this.sum = sum; 170 this.buckets = buckets; 171 } 172 } 173 174 private Child(double[] buckets) { 175 upperBounds = buckets; 176 cumulativeCounts = new DoubleAdder[buckets.length]; 177 for (int i = 0; i < buckets.length; ++i) { 178 cumulativeCounts[i] = new DoubleAdder(); 179 } 180 } 181 private final double[] upperBounds; 182 private final DoubleAdder[] cumulativeCounts; 183 private final DoubleAdder sum = new DoubleAdder(); 184 185 static TimeProvider timeProvider = new TimeProvider(); 186 /** 187 * Observe the given amount. 188 */ 189 public void observe(double amt) { 190 for (int i = 0; i < upperBounds.length; ++i) { 191 // The last bucket is +Inf, so we always increment. 192 if (amt <= upperBounds[i]) { 193 cumulativeCounts[i].add(1); 194 break; 195 } 196 } 197 sum.add(amt); 198 } 199 /** 200 * Start a timer to track a duration. 201 * <p> 202 * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. 203 */ 204 public Timer startTimer() { 205 return new Timer(this); 206 } 207 /** 208 * Get the value of the Histogram. 209 * <p> 210 * <em>Warning:</em> The definition of {@link Value} is subject to change. 211 */ 212 public Value get() { 213 double[] buckets = new double[cumulativeCounts.length]; 214 double acc = 0; 215 for (int i = 0; i < cumulativeCounts.length; ++i) { 216 acc += cumulativeCounts[i].sum(); 217 buckets[i] = acc; 218 } 219 return new Value(sum.sum(), buckets); 220 } 221 } 222 223 // Convenience methods. 224 /** 225 * Observe the given amount on the histogram with no labels. 226 */ 227 public void observe(double amt) { 228 noLabelsChild.observe(amt); 229 } 230 /** 231 * Start a timer to track a duration on the histogram with no labels. 232 * <p> 233 * Call {@link Timer#observeDuration} at the end of what you want to measure the duration of. 234 */ 235 public Timer startTimer() { 236 return noLabelsChild.startTimer(); 237 } 238 239 @Override 240 public List<MetricFamilySamples> collect() { 241 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(); 242 for(Map.Entry<List<String>, Child> c: children.entrySet()) { 243 Child.Value v = c.getValue().get(); 244 List<String> labelNamesWithLe = new ArrayList<String>(labelNames); 245 labelNamesWithLe.add("le"); 246 for (int i = 0; i < v.buckets.length; ++i) { 247 List<String> labelValuesWithLe = new ArrayList<String>(c.getKey()); 248 labelValuesWithLe.add(doubleToGoString(buckets[i])); 249 samples.add(new MetricFamilySamples.Sample(fullname + "_bucket", labelNamesWithLe, labelValuesWithLe, v.buckets[i])); 250 } 251 samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1])); 252 samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); 253 } 254 255 MetricFamilySamples mfs = new MetricFamilySamples(fullname, Type.HISTOGRAM, help, samples); 256 List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>(); 257 mfsList.add(mfs); 258 return mfsList; 259 } 260 261 double[] getBuckets() { 262 return buckets; 263 } 264 265 static class TimeProvider { 266 long nanoTime() { 267 return System.nanoTime(); 268 } 269 } 270}