Skip to content

Commit ac0d061

Browse files
authored
ExponentialHistograms: refactor tests to use builder, add copy method (#134037)
1 parent 4705af4 commit ac0d061

11 files changed

+173
-192
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ static ExponentialHistogramBuilder builder(int scale, ExponentialHistogramCircui
216216
return new ExponentialHistogramBuilder(scale, breaker);
217217
}
218218

219+
/**
220+
* Create a builder for an exponential histogram, which is initialized to copy the given histogram.
221+
* @param toCopy the histogram to copy
222+
* @param breaker the circuit breaker to use
223+
* @return a new builder
224+
*/
225+
static ExponentialHistogramBuilder builder(ExponentialHistogram toCopy, ExponentialHistogramCircuitBreaker breaker) {
226+
return new ExponentialHistogramBuilder(toCopy, breaker);
227+
}
228+
219229
/**
220230
* Creates a histogram representing the distribution of the given values with at most the given number of buckets.
221231
* If the given {@code maxBucketCount} is greater than or equal to the number of values, the resulting histogram will have a

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

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727

2828
/**
2929
* A builder for building a {@link ReleasableExponentialHistogram} directly from buckets.
30-
* Note that this class is not optimized regarding memory allocations, so it is not intended for high-throughput usage.
30+
* Note that this class is not optimized regarding memory allocations or performance, so it is not intended for high-throughput usage.
3131
*/
3232
public class ExponentialHistogramBuilder {
3333

3434
private final ExponentialHistogramCircuitBreaker breaker;
3535

36-
private final int scale;
36+
private int scale;
3737
private ZeroBucket zeroBucket = ZeroBucket.minimalEmpty();
3838
private Double sum;
3939
private Double min;
@@ -47,6 +47,29 @@ public class ExponentialHistogramBuilder {
4747
this.scale = scale;
4848
}
4949

50+
ExponentialHistogramBuilder(ExponentialHistogram toCopy, ExponentialHistogramCircuitBreaker breaker) {
51+
this(toCopy.scale(), breaker);
52+
zeroBucket(toCopy.zeroBucket());
53+
sum(toCopy.sum());
54+
min(toCopy.min());
55+
max(toCopy.max());
56+
BucketIterator negBuckets = toCopy.negativeBuckets().iterator();
57+
while (negBuckets.hasNext()) {
58+
setNegativeBucket(negBuckets.peekIndex(), negBuckets.peekCount());
59+
negBuckets.advance();
60+
}
61+
BucketIterator posBuckets = toCopy.positiveBuckets().iterator();
62+
while (posBuckets.hasNext()) {
63+
setPositiveBucket(posBuckets.peekIndex(), posBuckets.peekCount());
64+
posBuckets.advance();
65+
}
66+
}
67+
68+
public ExponentialHistogramBuilder scale(int scale) {
69+
this.scale = scale;
70+
return this;
71+
}
72+
5073
public ExponentialHistogramBuilder zeroBucket(ZeroBucket zeroBucket) {
5174
this.zeroBucket = zeroBucket;
5275
return this;
@@ -83,39 +106,33 @@ public ExponentialHistogramBuilder max(double max) {
83106
}
84107

85108
/**
86-
* Adds the given bucket to the positive buckets.
87-
* Buckets may be added in arbitrary order, but each bucket can only be added once.
109+
* Sets the given bucket of the positive buckets.
110+
* Buckets may be set in arbitrary order. If the bucket already exists, it will be replaced.
88111
*
89112
* @param index the index of the bucket
90113
* @param count the count of the bucket, must be at least 1
91114
* @return the builder
92115
*/
93-
public ExponentialHistogramBuilder addPositiveBucket(long index, long count) {
116+
public ExponentialHistogramBuilder setPositiveBucket(long index, long count) {
94117
if (count < 1) {
95118
throw new IllegalArgumentException("Bucket count must be at least 1");
96119
}
97-
if (positiveBuckets.containsKey(index)) {
98-
throw new IllegalArgumentException("Positive bucket already exists: " + index);
99-
}
100120
positiveBuckets.put(index, count);
101121
return this;
102122
}
103123

104124
/**
105-
* Adds the given bucket to the negative buckets.
106-
* Buckets may be added in arbitrary order, but each bucket can only be added once.
125+
* Sets the given bucket of the negative buckets.
126+
* Buckets may be set in arbitrary order. If the bucket already exists, it will be replaced.
107127
*
108128
* @param index the index of the bucket
109129
* @param count the count of the bucket, must be at least 1
110130
* @return the builder
111131
*/
112-
public ExponentialHistogramBuilder addNegativeBucket(long index, long count) {
132+
public ExponentialHistogramBuilder setNegativeBucket(long index, long count) {
113133
if (count < 1) {
114134
throw new IllegalArgumentException("Bucket count must be at least 1");
115135
}
116-
if (negativeBuckets.containsKey(index)) {
117-
throw new IllegalArgumentException("Negative bucket already exists: " + index);
118-
}
119136
negativeBuckets.put(index, count);
120137
return this;
121138
}
@@ -152,8 +169,6 @@ public ReleasableExponentialHistogram build() {
152169
Releasables.close(result);
153170
}
154171
}
155-
156-
// Create histogram
157172
return result;
158173
}
159174
}

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramBuilderTests.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@
2727

2828
public class ExponentialHistogramBuilderTests extends ExponentialHistogramTestCase {
2929

30-
public void testBuildWithAllFieldsSet() {
30+
public void testBuildAndCopyWithAllFieldsSet() {
3131
ZeroBucket zeroBucket = ZeroBucket.create(1, 2);
3232
ExponentialHistogramBuilder builder = ExponentialHistogram.builder(3, breaker())
3333
.zeroBucket(zeroBucket)
3434
.sum(100.0)
3535
.min(1.0)
3636
.max(50.0)
37-
.addPositiveBucket(2, 10)
38-
.addPositiveBucket(0, 1)
39-
.addPositiveBucket(5, 2)
40-
.addNegativeBucket(-2, 5)
41-
.addNegativeBucket(1, 2);
37+
.setPositiveBucket(2, 10)
38+
.setPositiveBucket(0, 1)
39+
.setPositiveBucket(5, 2)
40+
.setNegativeBucket(-2, 5)
41+
.setNegativeBucket(1, 2);
4242

4343
try (ReleasableExponentialHistogram histogram = builder.build()) {
4444
assertThat(histogram.scale(), equalTo(3));
@@ -48,14 +48,19 @@ public void testBuildWithAllFieldsSet() {
4848
assertThat(histogram.max(), equalTo(50.0));
4949
assertBuckets(histogram.positiveBuckets(), List.of(0L, 2L, 5L), List.of(1L, 10L, 2L));
5050
assertBuckets(histogram.negativeBuckets(), List.of(-2L, 1L), List.of(5L, 2L));
51+
52+
try (ReleasableExponentialHistogram copy = ExponentialHistogram.builder(histogram, breaker()).build()) {
53+
assertThat(copy, equalTo(histogram));
54+
}
5155
}
56+
5257
}
5358

5459
public void testBuildWithEstimation() {
5560
ExponentialHistogramBuilder builder = ExponentialHistogram.builder(0, breaker())
56-
.addPositiveBucket(0, 1)
57-
.addPositiveBucket(1, 1)
58-
.addNegativeBucket(0, 4);
61+
.setPositiveBucket(0, 1)
62+
.setPositiveBucket(1, 1)
63+
.setNegativeBucket(0, 4);
5964

6065
try (ReleasableExponentialHistogram histogram = builder.build()) {
6166
assertThat(histogram.scale(), equalTo(0));
@@ -68,16 +73,16 @@ public void testBuildWithEstimation() {
6873
}
6974
}
7075

71-
public void testAddDuplicatePositiveBucketThrows() {
76+
public void testDuplicateBucketOverrides() {
7277
ExponentialHistogramBuilder builder = ExponentialHistogram.builder(0, breaker());
73-
builder.addPositiveBucket(1, 10);
74-
expectThrows(IllegalArgumentException.class, () -> builder.addPositiveBucket(1, 5));
75-
}
76-
77-
public void testAddDuplicateNegativeBucketThrows() {
78-
ExponentialHistogramBuilder builder = ExponentialHistogram.builder(0, breaker());
79-
builder.addNegativeBucket(-1, 10);
80-
expectThrows(IllegalArgumentException.class, () -> builder.addNegativeBucket(-1, 5));
78+
builder.setPositiveBucket(1, 10);
79+
builder.setNegativeBucket(2, 1);
80+
builder.setPositiveBucket(1, 100);
81+
builder.setNegativeBucket(2, 123);
82+
try (ReleasableExponentialHistogram histogram = builder.build()) {
83+
assertBuckets(histogram.positiveBuckets(), List.of(1L), List.of(100L));
84+
assertBuckets(histogram.negativeBuckets(), List.of(2L), List.of(123L));
85+
}
8186
}
8287

8388
private static void assertBuckets(ExponentialHistogram.Buckets buckets, List<Long> indices, List<Long> counts) {

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramEqualityTests.java

Lines changed: 42 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import java.util.ArrayList;
2727
import java.util.Arrays;
28-
import java.util.Collections;
2928
import java.util.List;
3029

3130
import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MAX_INDEX;
@@ -61,7 +60,8 @@ public ExponentialHistogramEqualityTests(Modification modification) {
6160

6261
public void testEquality() {
6362
ExponentialHistogram histo = randomHistogram();
64-
ExponentialHistogram copy = copyWithModification(histo, null);
63+
ReleasableExponentialHistogram copy = ExponentialHistogram.builder(histo, breaker()).build();
64+
autoReleaseOnTestEnd(copy);
6565
ExponentialHistogram modifiedCopy = copyWithModification(histo, modification);
6666

6767
assertThat(histo, equalTo(copy));
@@ -86,46 +86,31 @@ private ExponentialHistogram randomHistogram() {
8686
}
8787

8888
private ExponentialHistogram copyWithModification(ExponentialHistogram toCopy, Modification modification) {
89-
int totalBucketCount = getBucketCount(toCopy.positiveBuckets(), toCopy.negativeBuckets());
90-
FixedCapacityExponentialHistogram copy = createAutoReleasedHistogram(totalBucketCount + 2);
91-
if (modification == Modification.SCALE) {
92-
copy.resetBuckets((int) createRandomLongBetweenOtherThan(MIN_SCALE, MAX_SCALE, toCopy.scale()));
93-
} else {
94-
copy.resetBuckets(toCopy.scale());
95-
}
96-
if (modification == Modification.SUM) {
97-
copy.setSum(randomDouble());
98-
} else {
99-
copy.setSum(toCopy.sum());
100-
}
101-
if (modification == Modification.MIN) {
102-
copy.setMin(randomDouble());
103-
} else {
104-
copy.setMin(toCopy.min());
89+
ExponentialHistogramBuilder copyBuilder = ExponentialHistogram.builder(toCopy, breaker());
90+
switch (modification) {
91+
case SCALE -> copyBuilder.scale((int) createRandomLongBetweenOtherThan(MIN_SCALE, MAX_SCALE, toCopy.scale()));
92+
case SUM -> copyBuilder.sum(randomDouble());
93+
case MIN -> copyBuilder.min(randomDouble());
94+
case MAX -> copyBuilder.max(randomDouble());
95+
case ZERO_THRESHOLD -> copyBuilder.zeroBucket(ZeroBucket.create(randomDouble(), toCopy.zeroBucket().count()));
96+
case ZERO_COUNT -> copyBuilder.zeroBucket(
97+
ZeroBucket.create(
98+
toCopy.zeroBucket().zeroThreshold(),
99+
createRandomLongBetweenOtherThan(0, Long.MAX_VALUE, toCopy.zeroBucket().count())
100+
)
101+
);
102+
case POSITIVE_BUCKETS -> modifyBuckets(copyBuilder, toCopy.positiveBuckets(), true);
103+
case NEGATIVE_BUCKETS -> modifyBuckets(copyBuilder, toCopy.negativeBuckets(), false);
105104
}
106-
if (modification == Modification.MAX) {
107-
copy.setMax(randomDouble());
108-
} else {
109-
copy.setMax(toCopy.max());
110-
}
111-
long zeroCount = toCopy.zeroBucket().count();
112-
double zeroThreshold = toCopy.zeroBucket().zeroThreshold();
113-
if (modification == Modification.ZERO_COUNT) {
114-
zeroCount = createRandomLongBetweenOtherThan(0, Long.MAX_VALUE, zeroCount);
115-
} else if (modification == Modification.ZERO_THRESHOLD) {
116-
zeroThreshold = randomDouble();
117-
}
118-
copy.setZeroBucket(ZeroBucket.create(zeroThreshold, zeroCount));
119-
copyBuckets(copy, toCopy.negativeBuckets(), modification == Modification.NEGATIVE_BUCKETS, false);
120-
copyBuckets(copy, toCopy.positiveBuckets(), modification == Modification.POSITIVE_BUCKETS, true);
121105

122-
return copy;
106+
ReleasableExponentialHistogram result = copyBuilder.build();
107+
autoReleaseOnTestEnd(result);
108+
return result;
123109
}
124110

125-
private void copyBuckets(
126-
FixedCapacityExponentialHistogram into,
111+
private ExponentialHistogramBuilder modifyBuckets(
112+
ExponentialHistogramBuilder builder,
127113
ExponentialHistogram.Buckets buckets,
128-
boolean modify,
129114
boolean isPositive
130115
) {
131116
List<Long> indices = new ArrayList<>();
@@ -136,29 +121,28 @@ private void copyBuckets(
136121
counts.add(it.peekCount());
137122
it.advance();
138123
}
139-
if (modify) {
140-
if (counts.isEmpty() == false && randomBoolean()) {
141-
int toModify = randomIntBetween(0, indices.size() - 1);
142-
counts.set(toModify, createRandomLongBetweenOtherThan(1, Long.MAX_VALUE, counts.get(toModify)));
143-
} else {
144-
insertRandomBucket(indices, counts);
145-
}
124+
long toModifyIndex;
125+
long toModifyCount;
126+
if (counts.isEmpty() == false && randomBoolean()) {
127+
// Modify existing bucket
128+
int position = randomIntBetween(0, indices.size() - 1);
129+
toModifyIndex = indices.get(position);
130+
toModifyCount = createRandomLongBetweenOtherThan(1, Long.MAX_VALUE, counts.get(position));
131+
} else {
132+
// Add new bucket
133+
long minIndex = indices.stream().mapToLong(Long::longValue).min().orElse(MIN_INDEX);
134+
long maxIndex = indices.stream().mapToLong(Long::longValue).min().orElse(MAX_INDEX);
135+
do {
136+
toModifyIndex = randomLongBetween(Math.max(MIN_INDEX, minIndex - 10), Math.min(MAX_INDEX, maxIndex + 10));
137+
} while (indices.contains(toModifyIndex));
138+
toModifyCount = randomLongBetween(1, Long.MAX_VALUE);
146139
}
147-
for (int i = 0; i < indices.size(); i++) {
148-
into.tryAddBucket(indices.get(i), counts.get(i), isPositive);
140+
if (isPositive) {
141+
builder.setPositiveBucket(toModifyIndex, toModifyCount);
142+
} else {
143+
builder.setNegativeBucket(toModifyIndex, toModifyCount);
149144
}
150-
}
151-
152-
private void insertRandomBucket(List<Long> indices, List<Long> counts) {
153-
long minIndex = indices.stream().mapToLong(Long::longValue).min().orElse(MIN_INDEX);
154-
long maxIndex = indices.stream().mapToLong(Long::longValue).min().orElse(MAX_INDEX);
155-
long newIndex;
156-
do {
157-
newIndex = randomLongBetween(Math.max(MIN_INDEX, minIndex - 10), Math.min(MAX_INDEX, maxIndex + 10));
158-
} while (indices.contains(newIndex));
159-
int position = -(Collections.binarySearch(indices, newIndex) + 1);
160-
indices.add(position, newIndex);
161-
counts.add(position, randomLongBetween(1, Long.MAX_VALUE));
145+
return builder;
162146
}
163147

164148
private static long createRandomLongBetweenOtherThan(long min, long max, long notAllowedValue) {
@@ -169,16 +153,4 @@ private static long createRandomLongBetweenOtherThan(long min, long max, long no
169153
} while (result == notAllowedValue);
170154
return result;
171155
}
172-
173-
private static int getBucketCount(ExponentialHistogram.Buckets... buckets) {
174-
int count = 0;
175-
for (ExponentialHistogram.Buckets val : buckets) {
176-
BucketIterator it = val.iterator();
177-
while (it.hasNext()) {
178-
count++;
179-
it.advance();
180-
}
181-
}
182-
return count;
183-
}
184156
}

0 commit comments

Comments
 (0)