1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.metrics.impl;
19
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import java.util.concurrent.atomic.AtomicLong;
22
23 import org.apache.hadoop.hbase.classification.InterfaceAudience;
24 import org.apache.hadoop.hbase.classification.InterfaceStability;
25 import org.apache.hadoop.hbase.metrics.Snapshot;
26 import org.apache.hadoop.hbase.util.AtomicUtils;
27 import org.apache.hadoop.hbase.util.LongAdder;
28
29
30
31
32
33 @InterfaceAudience.Private
34 @InterfaceStability.Evolving
35 public class FastLongHistogram {
36
37
38
39
40 public static final int DEFAULT_NBINS = 255;
41
42 public static final double[] DEFAULT_QUANTILES =
43 new double[]{0.25, 0.5, 0.75, 0.90, 0.95, 0.98, 0.99, 0.999};
44
45
46
47
48 private static class Bins {
49 private final LongAdder[] counts;
50
51 private final long binsMin;
52
53 private final long binsMax;
54 private final long bins10XMax;
55 private final AtomicLong min = new AtomicLong(Long.MAX_VALUE);
56 private final AtomicLong max = new AtomicLong(0L);
57
58 private final LongAdder count = new LongAdder();
59 private final LongAdder total = new LongAdder();
60
61
62
63 private final AtomicBoolean hasData = new AtomicBoolean(false);
64
65
66
67
68 public Bins(int numBins) {
69 counts = createCounters(numBins + 3);
70 this.binsMin = 1L;
71
72
73
74 this.binsMax = 1000;
75 this.bins10XMax = binsMax * 10;
76 }
77
78
79
80
81 public Bins(Bins last, int numOfBins, double minQ, double maxQ) {
82 long[] values = last.getQuantiles(new double[] { minQ, maxQ });
83 long wd = values[1] - values[0] + 1;
84
85 this.binsMin = Math.max(0L, (long) (values[0] - wd * minQ));
86 long binsMax = (long) (values[1] + wd * (1 - maxQ)) + 1;
87
88 this.binsMax = Math.max(binsMax, this.binsMin + numOfBins);
89 this.bins10XMax = Math.max((long) (values[1] + (binsMax - 1) * 9), this.binsMax + 1);
90
91 this.counts = createCounters(numOfBins + 3);
92 }
93
94 private LongAdder[] createCounters(int num) {
95 LongAdder[] counters = new LongAdder[num];
96 for (int i = 0; i < num; i++) {
97 counters[i] = new LongAdder();
98 }
99 return counters;
100 }
101
102 private int getIndex(long value) {
103 if (value < this.binsMin) {
104 return 0;
105 } else if (value > this.bins10XMax) {
106 return this.counts.length - 1;
107 } else if (value >= this.binsMax) {
108 return this.counts.length - 2;
109 }
110
111 return 1 + (int) ((value - this.binsMin) * (this.counts.length - 3) /
112 (this.binsMax - this.binsMin));
113
114 }
115
116
117
118
119 public void add(long value, long count) {
120 if (value < 0) {
121
122
123
124
125
126 return;
127 }
128 AtomicUtils.updateMin(min, value);
129 AtomicUtils.updateMax(max, value);
130
131 this.count.add(count);
132 this.total.add(value * count);
133
134 int pos = getIndex(value);
135 this.counts[pos].add(count);
136
137
138 this.hasData.set(true);
139 }
140
141
142
143
144 public long[] getQuantiles(double[] quantiles) {
145 if (!this.hasData.get()) {
146
147 return new long[quantiles.length];
148 }
149
150
151
152
153 long[] counts = new long[this.counts.length];
154 long total = 0L;
155 for (int i = 0; i < this.counts.length; i++) {
156 counts[i] = this.counts[i].sum();
157 total += counts[i];
158 }
159
160 int rIndex = 0;
161 double qCount = total * quantiles[0];
162 long cum = 0L;
163
164 long[] res = new long[quantiles.length];
165 countsLoop: for (int i = 0; i < counts.length; i++) {
166
167 long mn, mx;
168 if (i == 0) {
169 mn = this.min.get();
170 mx = this.binsMin;
171 } else if (i == counts.length - 1) {
172 mn = this.bins10XMax;
173 mx = this.max.get();
174 } else if (i == counts.length - 2) {
175 mn = this.binsMax;
176 mx = this.bins10XMax;
177 } else {
178 mn = this.binsMin + (i - 1) * (this.binsMax - this.binsMin) / (this.counts.length - 3);
179 mx = this.binsMin + i * (this.binsMax - this.binsMin) / (this.counts.length - 3);
180 }
181
182 if (mx < this.min.get()) {
183 continue;
184 }
185 if (mn > this.max.get()) {
186 break;
187 }
188 mn = Math.max(mn, this.min.get());
189 mx = Math.min(mx, this.max.get());
190
191
192 double lastCum = cum;
193 cum += counts[i];
194
195
196 while (qCount <= cum) {
197 if (cum == lastCum) {
198 res[rIndex] = mn;
199 } else {
200 res[rIndex] = (long) ((qCount - lastCum) * (mx - mn) / (cum - lastCum) + mn);
201 }
202
203
204 rIndex++;
205 if (rIndex >= quantiles.length) {
206 break countsLoop;
207 }
208 qCount = total * quantiles[rIndex];
209 }
210 }
211
212 for (; rIndex < quantiles.length; rIndex++) {
213 res[rIndex] = this.max.get();
214 }
215
216 return res;
217 }
218
219
220 long getNumAtOrBelow(long val) {
221 final int targetIndex = getIndex(val);
222 long totalToCurrentIndex = 0;
223 for (int i = 0; i <= targetIndex; i++) {
224 totalToCurrentIndex += this.counts[i].sum();
225 }
226 return totalToCurrentIndex;
227 }
228
229 public long getMin() {
230 long min = this.min.get();
231 return min == Long.MAX_VALUE ? 0 : min;
232 }
233
234 public long getMean() {
235 long count = this.count.sum();
236 long total = this.total.sum();
237 if (count == 0) {
238 return 0;
239 }
240 return total / count;
241 }
242 }
243
244
245 private volatile Bins bins;
246
247
248
249
250 public FastLongHistogram() {
251 this(DEFAULT_NBINS);
252 }
253
254
255
256
257
258
259 public FastLongHistogram(int numOfBins) {
260 this.bins = new Bins(numOfBins);
261 }
262
263
264
265
266
267
268
269
270 public FastLongHistogram(int numOfBins, long min, long max) {
271 this(numOfBins);
272 Bins bins = new Bins(numOfBins);
273 bins.add(min, 1);
274 bins.add(max, 1);
275 this.bins = new Bins(bins, numOfBins, 0.01, 0.999);
276 }
277
278 private FastLongHistogram(Bins bins) {
279 this.bins = bins;
280 }
281
282
283
284
285 public void add(long value, long count) {
286 this.bins.add(value, count);
287 }
288
289
290
291
292 public long[] getQuantiles(double[] quantiles) {
293 return this.bins.getQuantiles(quantiles);
294 }
295
296 public long[] getQuantiles() {
297 return this.bins.getQuantiles(DEFAULT_QUANTILES);
298 }
299
300 public long getMin() {
301 return this.bins.getMin();
302 }
303
304 public long getMax() {
305 return this.bins.max.get();
306 }
307
308 public long getCount() {
309 return this.bins.count.sum();
310 }
311
312 public long getMean() {
313 return this.bins.getMean();
314 }
315
316 public long getNumAtOrBelow(long value) {
317 return this.bins.getNumAtOrBelow(value);
318 }
319
320
321
322
323 public Snapshot snapshotAndReset() {
324 final Bins oldBins = this.bins;
325 this.bins = new Bins(this.bins, this.bins.counts.length - 3, 0.01, 0.99);
326 final long[] percentiles = oldBins.getQuantiles(DEFAULT_QUANTILES);
327 final long count = oldBins.count.sum();
328
329 return new Snapshot() {
330 @Override
331 public long[] getQuantiles(double[] quantiles) {
332 return oldBins.getQuantiles(quantiles);
333 }
334
335 @Override
336 public long[] getQuantiles() {
337 return percentiles;
338 }
339
340 @Override
341 public long getCount() {
342 return count;
343 }
344
345 @Override
346 public long getCountAtOrBelow(long val) {
347 return oldBins.getNumAtOrBelow(val);
348 }
349
350 @Override
351 public long get25thPercentile() {
352 return percentiles[0];
353 }
354
355 @Override
356 public long get75thPercentile() {
357 return percentiles[2];
358 }
359
360 @Override
361 public long get90thPercentile() {
362 return percentiles[3];
363 }
364
365 @Override
366 public long get95thPercentile() {
367 return percentiles[4];
368 }
369
370 @Override
371 public long get98thPercentile() {
372 return percentiles[5];
373 }
374
375 @Override
376 public long get99thPercentile() {
377 return percentiles[6];
378 }
379
380 @Override
381 public long get999thPercentile() {
382 return percentiles[7];
383 }
384
385 @Override
386 public long getMedian() {
387 return percentiles[1];
388 }
389
390 @Override
391 public long getMax() {
392 return oldBins.max.get();
393 }
394
395 @Override
396 public long getMean() {
397 return oldBins.getMean();
398 }
399
400 @Override
401 public long getMin() {
402 return oldBins.getMin();
403 }
404 };
405 }
406 }