20
20
import com .arpnetworking .tsdcore .model .CalculatedValue ;
21
21
import com .arpnetworking .tsdcore .model .Quantity ;
22
22
import com .arpnetworking .tsdcore .model .Unit ;
23
+ import it .unimi .dsi .fastutil .doubles .Double2LongAVLTreeMap ;
24
+ import it .unimi .dsi .fastutil .doubles .Double2LongMap ;
25
+ import it .unimi .dsi .fastutil .doubles .Double2LongSortedMap ;
26
+ import it .unimi .dsi .fastutil .objects .ObjectSortedSet ;
23
27
import net .sf .oval .constraint .NotNull ;
24
28
25
29
import java .util .List ;
26
30
import java .util .Map ;
27
31
import java .util .Optional ;
28
- import java .util .Set ;
29
- import java .util .TreeMap ;
30
32
31
33
/**
32
34
* Histogram statistic. This is a supporting statistic and does not produce
@@ -59,6 +61,7 @@ public Quantity calculateAggregations(final List<AggregatedData> aggregations) {
59
61
60
62
private HistogramStatistic () { }
61
63
64
+ private static final int DEFAULT_PRECISION = 7 ;
62
65
private static final long serialVersionUID = 7060886488604176233L ;
63
66
64
67
/**
@@ -183,9 +186,9 @@ public HistogramSnapshot getHistogramSnapshot() {
183
186
public HistogramSupportingData toUnit (final Unit newUnit ) {
184
187
if (_unit .isPresent ()) {
185
188
final Histogram newHistogram = new Histogram ();
186
- for (final Map .Entry < Double , Integer > entry : _histogramSnapshot .getValues ()) {
187
- final Double newBucket = newUnit .convert (entry .getKey (), _unit .get ());
188
- newHistogram .recordValue (newBucket , entry .getValue ());
189
+ for (final Double2LongMap .Entry entry : _histogramSnapshot .getValues ()) {
190
+ final double newBucket = newUnit .convert (entry .getDoubleKey (), _unit .get ());
191
+ newHistogram .recordValue (newBucket , entry .getLongValue ());
189
192
}
190
193
return new HistogramSupportingData .Builder ()
191
194
.setHistogramSnapshot (newHistogram .getSnapshot ())
@@ -249,14 +252,15 @@ public Builder setUnit(final Optional<Unit> value) {
249
252
*/
250
253
public static final class Histogram {
251
254
255
+
252
256
/**
253
257
* Records a value into the histogram.
254
258
*
255
259
* @param value The value of the entry.
256
260
* @param count The number of entries at this value.
257
261
*/
258
- public void recordValue (final double value , final int count ) {
259
- _data .merge (truncate (value ), count , ( i , j ) -> i + j );
262
+ public void recordValue (final double value , final long count ) {
263
+ _data .merge (truncateToDouble (value ), count , Long :: sum );
260
264
_entriesCount += count ;
261
265
}
262
266
@@ -269,29 +273,83 @@ public void recordValue(final double value) {
269
273
recordValue (value , 1 );
270
274
}
271
275
276
+ /**
277
+ * Records a packed value into the histogram. The packed values must be at the
278
+ * precision that this {@link Histogram} was declared with!
279
+ *
280
+ * @param packed The packed bucket key.
281
+ * @param count The number of entries at this value.
282
+ */
283
+ public void recordPacked (final long packed , final long count ) {
284
+ recordValue (unpack (packed ), count );
285
+ }
286
+
272
287
/**
273
288
* Adds a histogram snapshot to this one.
274
289
*
275
290
* @param histogramSnapshot The histogram snapshot to add to this one.
276
291
*/
277
292
public void add (final HistogramSnapshot histogramSnapshot ) {
278
- for (final Map .Entry < Double , Integer > entry : histogramSnapshot ._data .entrySet ()) {
279
- _data .merge (entry .getKey (), entry .getValue (), ( i , j ) -> i + j );
293
+ for (final Double2LongMap .Entry entry : histogramSnapshot ._data .double2LongEntrySet ()) {
294
+ _data .merge (entry .getDoubleKey (), entry .getLongValue (), Long :: sum );
280
295
}
281
296
_entriesCount += histogramSnapshot ._entriesCount ;
282
297
}
283
298
284
299
public HistogramSnapshot getSnapshot () {
285
- return new HistogramSnapshot (_data , _entriesCount );
300
+ return new HistogramSnapshot (_data , _entriesCount , _precision );
301
+ }
302
+
303
+ long truncateToLong (final double val ) {
304
+ return Double .doubleToRawLongBits (val ) & _truncateMask ;
305
+ }
306
+
307
+ double truncateToDouble (final double val ) {
308
+ return Double .longBitsToDouble (truncateToLong (val ));
309
+ }
310
+
311
+ long pack (final double val ) {
312
+ final long truncated = truncateToLong (val );
313
+ final long shifted = truncated >> (MANTISSA_BITS - _precision );
314
+ return shifted & _packMask ;
315
+ }
316
+
317
+ double unpack (final long packed ) {
318
+ return Double .longBitsToDouble (packed << (MANTISSA_BITS - _precision ));
286
319
}
287
320
288
- private static double truncate (final double val ) {
289
- final long mask = 0xffffe00000000000L ;
290
- return Double .longBitsToDouble (Double .doubleToRawLongBits (val ) & mask );
321
+ /**
322
+ * Public constructor.
323
+ */
324
+ public Histogram () {
325
+ this (DEFAULT_PRECISION );
326
+ }
327
+
328
+ /**
329
+ * Public constructor.
330
+ *
331
+ * @param precision the bits of mantissa precision in the bucket key
332
+ */
333
+ public Histogram (final int precision ) {
334
+ // TODO(ville): Support variable precision histograms end-to-end.
335
+ if (precision != DEFAULT_PRECISION ) {
336
+ throw new IllegalArgumentException ("The stack does not fully support variable precision histograms." );
337
+ }
338
+
339
+ _precision = precision ;
340
+ _truncateMask = BASE_MASK >> _precision ;
341
+ _packMask = (1 << (_precision + EXPONENT_BITS + 1 )) - 1 ;
291
342
}
292
343
293
344
private int _entriesCount = 0 ;
294
- private final TreeMap <Double , Integer > _data = new TreeMap <>();
345
+ private final Double2LongSortedMap _data = new Double2LongAVLTreeMap ();
346
+ private final int _precision ;
347
+ private final long _truncateMask ;
348
+ private final int _packMask ;
349
+
350
+ private static final int MANTISSA_BITS = 52 ;
351
+ private static final int EXPONENT_BITS = 11 ;
352
+ private static final long BASE_MASK = (1L << (MANTISSA_BITS + EXPONENT_BITS )) >> EXPONENT_BITS ;
295
353
}
296
354
297
355
/**
@@ -300,7 +358,8 @@ private static double truncate(final double val) {
300
358
* @author Brandon Arp (brandon dot arp at inscopemetrics dot com)
301
359
*/
302
360
public static final class HistogramSnapshot {
303
- private HistogramSnapshot (final TreeMap <Double , Integer > data , final int entriesCount ) {
361
+ private HistogramSnapshot (final Double2LongSortedMap data , final long entriesCount , final int precision ) {
362
+ _precision = precision ;
304
363
_entriesCount = entriesCount ;
305
364
_data .putAll (data );
306
365
}
@@ -316,24 +375,30 @@ public Double getValueAtPercentile(final double percentile) {
316
375
// The Math.min is for the case where the computation may be just
317
376
// slightly larger than the _entriesCount and prevents an index out of range.
318
377
final int target = (int ) Math .min (Math .ceil (_entriesCount * percentile / 100.0D ), _entriesCount );
319
- int accumulated = 0 ;
320
- for (final Map .Entry < Double , Integer > next : _data .entrySet ()) {
321
- accumulated += next .getValue ();
378
+ long accumulated = 0 ;
379
+ for (final Double2LongMap .Entry next : _data .double2LongEntrySet ()) {
380
+ accumulated += next .getLongValue ();
322
381
if (accumulated >= target ) {
323
- return next .getKey ();
382
+ return next .getDoubleKey ();
324
383
}
325
384
}
326
385
return 0D ;
327
386
}
328
387
329
- public int getEntriesCount () {
388
+ public int getPrecision () {
389
+ return _precision ;
390
+ }
391
+
392
+ public long getEntriesCount () {
330
393
return _entriesCount ;
331
394
}
332
395
333
- public Set < Map .Entry < Double , Integer > > getValues () {
334
- return _data .entrySet ();
396
+ public ObjectSortedSet < Double2LongMap .Entry > getValues () {
397
+ return _data .double2LongEntrySet ();
335
398
}
336
- private int _entriesCount = 0 ;
337
- private final TreeMap <Double , Integer > _data = new TreeMap <>();
399
+
400
+ private long _entriesCount = 0 ;
401
+ private final Double2LongSortedMap _data = new Double2LongAVLTreeMap ();
402
+ private final int _precision ;
338
403
}
339
404
}
0 commit comments