Skip to content

Commit 0723de6

Browse files
committed
Add tests, fix implementation
1 parent f53d4db commit 0723de6

File tree

8 files changed

+249
-74
lines changed

8 files changed

+249
-74
lines changed

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/EmptyExponentialHistogram.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import java.util.OptionalLong;
2525

26-
class EmptyExponentialHistogram implements ReleasableExponentialHistogram {
26+
class EmptyExponentialHistogram extends ReleasableExponentialHistogram {
2727

2828
static final EmptyExponentialHistogram INSTANCE = new EmptyExponentialHistogram();
2929

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogram.java

Lines changed: 48 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@
2727
import java.util.List;
2828
import java.util.OptionalLong;
2929

30+
import static org.elasticsearch.exponentialhistogram.ExponentialHistogramUtils.bucketIteratorHash;
31+
import static org.elasticsearch.exponentialhistogram.ExponentialHistogramUtils.bucketIteratorsEqual;
32+
3033
/**
31-
* Interface for implementations of exponential histograms adhering to the
34+
* Base class for implementations of exponential histograms adhering to the
3235
* <a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram">OpenTelemetry definition</a>.
33-
* This interface supports sparse implementations, allowing iteration over buckets without requiring direct index access.<br>
36+
* This class supports sparse implementations, allowing iteration over buckets without requiring direct index access.<br>
3437
* The most important properties are:
3538
* <ul>
3639
* <li>The histogram has a scale parameter, which defines the accuracy. A higher scale implies a higher accuracy.
@@ -45,7 +48,7 @@
4548
* Additionally, all algorithms assume that samples within a bucket are located at a single point: the point of least relative error
4649
* (see {@link ExponentialScaleUtils#getPointOfLeastRelativeError(long, int)}).
4750
*/
48-
public interface ExponentialHistogram extends Accountable {
51+
public abstract class ExponentialHistogram implements Accountable {
4952

5053
// TODO(b/128622): support min/max storage and merging.
5154
// TODO(b/128622): Add special positive and negative infinity buckets
@@ -56,42 +59,42 @@ public interface ExponentialHistogram extends Accountable {
5659
// Theoretically, a MAX_SCALE of 51 would work and would still cover the entire range of double values.
5760
// For that to work, the math for converting from double to indices and back would need to be reworked.
5861
// One option would be to use "Quadruple": https://github.com/m-vokhm/Quadruple
59-
int MAX_SCALE = 38;
62+
public static final int MAX_SCALE = 38;
6063

6164
// At this scale, all double values fall into a single bucket.
62-
int MIN_SCALE = -11;
65+
public static final int MIN_SCALE = -11;
6366

6467
// Only use 62 bits (plus the sign bit) at max to allow computing the difference between the smallest and largest index without causing
6568
// an overflow.
6669
// The extra bit also provides room for compact storage tricks.
67-
int MAX_INDEX_BITS = 62;
68-
long MAX_INDEX = (1L << MAX_INDEX_BITS) - 1;
69-
long MIN_INDEX = -MAX_INDEX;
70+
public static final int MAX_INDEX_BITS = 62;
71+
public static final long MAX_INDEX = (1L << MAX_INDEX_BITS) - 1;
72+
public static final long MIN_INDEX = -MAX_INDEX;
7073

7174
/**
7275
* The scale of the histogram. Higher scales result in higher accuracy but potentially more buckets.
7376
* Must be less than or equal to {@link #MAX_SCALE} and greater than or equal to {@link #MIN_SCALE}.
7477
*
7578
* @return the scale of the histogram
7679
*/
77-
int scale();
80+
public abstract int scale();
7881

7982
/**
8083
* @return the {@link ZeroBucket} representing the number of zero (or close-to-zero) values and its threshold
8184
*/
82-
ZeroBucket zeroBucket();
85+
public abstract ZeroBucket zeroBucket();
8386

8487
/**
8588
* @return a {@link Buckets} instance for the populated buckets covering the positive value range of this histogram.
8689
* The {@link BucketIterator#scale()} of iterators obtained via {@link Buckets#iterator()} must be the same as {@link #scale()}.
8790
*/
88-
Buckets positiveBuckets();
91+
public abstract Buckets positiveBuckets();
8992

9093
/**
9194
* @return a {@link Buckets} instance for the populated buckets covering the negative value range of this histogram.
9295
* The {@link BucketIterator#scale()} of iterators obtained via {@link Buckets#iterator()} must be the same as {@link #scale()}.
9396
*/
94-
Buckets negativeBuckets();
97+
public abstract Buckets negativeBuckets();
9598

9699
/**
97100
* Returns the sum of all values represented by this histogram.
@@ -100,12 +103,12 @@ public interface ExponentialHistogram extends Accountable {
100103
*
101104
* @return the sum, guaranteed to be zero for empty histograms
102105
*/
103-
double sum();
106+
public abstract double sum();
104107

105108
/**
106109
* Represents a bucket range of an {@link ExponentialHistogram}, either the positive or the negative range.
107110
*/
108-
interface Buckets {
111+
public interface Buckets {
109112

110113
/**
111114
* @return a {@link BucketIterator} for the populated buckets of this bucket range.
@@ -125,60 +128,37 @@ interface Buckets {
125128

126129
}
127130

128-
static ExponentialHistogram empty() {
129-
return EmptyExponentialHistogram.INSTANCE;
130-
}
131+
@Override
132+
public boolean equals(Object other) {
133+
if (this == other) return true;
134+
if (other == null) return false;
131135

132-
static boolean equals(ExponentialHistogram a, ExponentialHistogram b) {
133-
if (a == b) {
134-
return true;
135-
}
136-
if (a == null || b == null) {
137-
return false;
138-
}
139-
if (a.scale() != b.scale()) {
140-
return false;
141-
}
142-
if (a.zeroBucket().count() != b.zeroBucket().count()) {
136+
if ((other instanceof ExponentialHistogram) == false) {
143137
return false;
144138
}
145-
if (Double.compare(a.zeroBucket().zeroThreshold(), b.zeroBucket().zeroThreshold()) != 0) {
146-
return false;
147-
}
148-
if (a.positiveBuckets().valueCount() != b.positiveBuckets().valueCount()) {
149-
return false;
150-
}
151-
if (a.negativeBuckets().valueCount() != b.negativeBuckets().valueCount()) {
152-
return false;
153-
}
154-
BucketIterator itA = a.positiveBuckets().iterator();
155-
BucketIterator itB = b.positiveBuckets().iterator();
156-
while (itA.hasNext() && itB.hasNext()) {
157-
if (itA.peekIndex() != itB.peekIndex()) {
158-
return false;
159-
}
160-
if (itA.peekCount() != itB.peekCount()) {
161-
return false;
162-
}
163-
itA.advance();
164-
itB.advance();
165-
}
166-
if (itA.hasNext() || itB.hasNext()) {
167-
return false;
168-
}
169-
itA = a.negativeBuckets().iterator();
170-
itB = b.negativeBuckets().iterator();
171-
while (itA.hasNext() && itB.hasNext()) {
172-
if (itA.peekIndex() != itB.peekIndex()) {
173-
return false;
174-
}
175-
if (itA.peekCount() != itB.peekCount()) {
176-
return false;
177-
}
178-
itA.advance();
179-
itB.advance();
180-
}
181-
return itA.hasNext() == false && itB.hasNext() == false;
139+
ExponentialHistogram that = (ExponentialHistogram) other;
140+
141+
if (scale() != that.scale()) return false;
142+
if (sum() != that.sum()) return false;
143+
if (zeroBucket().equals(that.zeroBucket()) == false) return false;
144+
if (bucketIteratorsEqual(negativeBuckets().iterator(), that.negativeBuckets().iterator()) == false) return false;
145+
if (bucketIteratorsEqual(positiveBuckets().iterator(), that.positiveBuckets().iterator()) == false) return false;
146+
147+
return true;
148+
}
149+
150+
@Override
151+
public int hashCode() {
152+
int hash = scale();
153+
hash = 31 * hash + Double.hashCode(sum());
154+
hash = 31 * hash + zeroBucket().hashCode();
155+
hash = 31 * hash + bucketIteratorHash(negativeBuckets().iterator());
156+
hash = 31 * hash + bucketIteratorHash(positiveBuckets().iterator());
157+
return hash;
158+
}
159+
160+
public static ExponentialHistogram empty() {
161+
return EmptyExponentialHistogram.INSTANCE;
182162
}
183163

184164
/**
@@ -191,7 +171,7 @@ static boolean equals(ExponentialHistogram a, ExponentialHistogram b) {
191171
* @param values the values to be added to the histogram
192172
* @return a new {@link ReleasableExponentialHistogram}
193173
*/
194-
static ReleasableExponentialHistogram create(int maxBucketCount, ExponentialHistogramCircuitBreaker breaker, double... values) {
174+
public static ReleasableExponentialHistogram create(int maxBucketCount, ExponentialHistogramCircuitBreaker breaker, double... values) {
195175
try (ExponentialHistogramGenerator generator = ExponentialHistogramGenerator.create(maxBucketCount, breaker)) {
196176
for (double val : values) {
197177
generator.add(val);
@@ -208,7 +188,7 @@ static ReleasableExponentialHistogram create(int maxBucketCount, ExponentialHist
208188
* @param histograms the histograms to merge
209189
* @return the merged histogram
210190
*/
211-
static ReleasableExponentialHistogram merge(
191+
public static ReleasableExponentialHistogram merge(
212192
int maxBucketCount,
213193
ExponentialHistogramCircuitBreaker breaker,
214194
Iterator<ExponentialHistogram> histograms
@@ -229,7 +209,7 @@ static ReleasableExponentialHistogram merge(
229209
* @param histograms the histograms to merge
230210
* @return the merged histogram
231211
*/
232-
static ReleasableExponentialHistogram merge(
212+
public static ReleasableExponentialHistogram merge(
233213
int maxBucketCount,
234214
ExponentialHistogramCircuitBreaker breaker,
235215
ExponentialHistogram... histograms

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,28 @@ public static double estimateSum(BucketIterator negativeBuckets, BucketIterator
5959
}
6060
return sum;
6161
}
62+
63+
static boolean bucketIteratorsEqual(BucketIterator a, BucketIterator b) {
64+
if (a.scale() != b.scale()) {
65+
return false;
66+
}
67+
while (a.hasNext() && b.hasNext()) {
68+
if (a.peekIndex() != b.peekIndex() || a.peekCount() != b.peekCount()) {
69+
return false;
70+
}
71+
a.advance();
72+
b.advance();
73+
}
74+
return a.hasNext() == b.hasNext();
75+
}
76+
77+
static int bucketIteratorHash(BucketIterator it) {
78+
int hash = 0;
79+
while (it.hasNext()) {
80+
hash = 31 * hash + Long.hashCode(it.peekIndex());
81+
hash = 31 * hash + Long.hashCode(it.peekCount());
82+
it.advance();
83+
}
84+
return hash;
85+
}
6286
}

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialScaleUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ public static int getMaximumScaleIncrease(long index) {
185185
return Long.numberOfLeadingZeros(index) - (64 - MAX_INDEX_BITS);
186186
}
187187

188-
189188
/**
190189
* Returns a scale at to which the given index can be scaled down without changing the exponentially scaled number it represents.
191190
* @param index the index of the number

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogram.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* Consumers must ensure that if the histogram is mutated, all previously acquired {@link BucketIterator}
3232
* instances are no longer used.
3333
*/
34-
final class FixedCapacityExponentialHistogram implements ReleasableExponentialHistogram {
34+
final class FixedCapacityExponentialHistogram extends ReleasableExponentialHistogram {
3535

3636
static final long BASE_SIZE = RamUsageEstimator.shallowSizeOfInstance(FixedCapacityExponentialHistogram.class) + ZeroBucket.SHALLOW_SIZE
3737
+ 2 * Buckets.SHALLOW_SIZE;

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ReleasableExponentialHistogram.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
/**
2727
* A histogram which participates in the {@link ExponentialHistogramCircuitBreaker} and therefore requires proper releasing.
2828
*/
29-
public interface ReleasableExponentialHistogram extends ExponentialHistogram, Releasable {
29+
public abstract class ReleasableExponentialHistogram extends ExponentialHistogram implements Releasable {
3030

3131
/**
3232
* @return an empty singleton, which does not allocate any memory and therefore {@link #close()} is a no-op.
3333
*/
34-
static ReleasableExponentialHistogram empty() {
34+
public static ReleasableExponentialHistogram empty() {
3535
return EmptyExponentialHistogram.INSTANCE;
3636
}
3737
}

0 commit comments

Comments
 (0)