Skip to content

Commit c3baf0b

Browse files
JonasKunzelasticsearchmachine
andauthored
Implement max for exponential histograms (#133913)
--------- Co-authored-by: elasticsearchmachine <[email protected]>
1 parent 913cf47 commit c3baf0b

File tree

16 files changed

+309
-73
lines changed

16 files changed

+309
-73
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramMergeBench.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private ExponentialHistogram asCompressedHistogram(ExponentialHistogram histogra
130130
CompressedExponentialHistogram.writeHistogramBytes(histoBytes, histogram.scale(), negativeBuckets, positiveBuckets);
131131
CompressedExponentialHistogram result = new CompressedExponentialHistogram();
132132
BytesRef data = histoBytes.bytes().toBytesRef();
133-
result.reset(histogram.zeroBucket().zeroThreshold(), totalCount, histogram.sum(), histogram.min(), data);
133+
result.reset(histogram.zeroBucket().zeroThreshold(), totalCount, histogram.sum(), histogram.min(), histogram.max(), data);
134134
return result;
135135
} catch (IOException e) {
136136
throw new RuntimeException(e);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public double min() {
8787
return Double.NaN;
8888
}
8989

90+
@Override
91+
public double max() {
92+
return Double.NaN;
93+
}
94+
9095
@Override
9196
public long ramBytesUsed() {
9297
return 0;

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
*/
4848
public interface ExponentialHistogram extends Accountable {
4949

50-
// TODO(b/128622): support min/max storage and merging.
5150
// TODO(b/128622): Add special positive and negative infinity buckets
5251
// to allow representation of explicit bucket histograms with open boundaries.
5352

@@ -117,6 +116,13 @@ public interface ExponentialHistogram extends Accountable {
117116
*/
118117
double min();
119118

119+
/**
120+
* Returns maximum of all values represented by this histogram.
121+
*
122+
* @return the maximum, NaN for empty histograms
123+
*/
124+
double max();
125+
120126
/**
121127
* Represents a bucket range of an {@link ExponentialHistogram}, either the positive or the negative range.
122128
*/
@@ -154,6 +160,7 @@ static boolean equals(ExponentialHistogram a, ExponentialHistogram b) {
154160
return a.scale() == b.scale()
155161
&& a.sum() == b.sum()
156162
&& equalsIncludingNaN(a.min(), b.min())
163+
&& equalsIncludingNaN(a.max(), b.max())
157164
&& a.zeroBucket().equals(b.zeroBucket())
158165
&& bucketIteratorsEqual(a.negativeBuckets().iterator(), b.negativeBuckets().iterator())
159166
&& bucketIteratorsEqual(a.positiveBuckets().iterator(), b.positiveBuckets().iterator());
@@ -187,6 +194,7 @@ static int hashCode(ExponentialHistogram histogram) {
187194
hash = 31 * hash + Double.hashCode(histogram.sum());
188195
hash = 31 * hash + Long.hashCode(histogram.valueCount());
189196
hash = 31 * hash + Double.hashCode(histogram.min());
197+
hash = 31 * hash + Double.hashCode(histogram.max());
190198
hash = 31 * hash + histogram.zeroBucket().hashCode();
191199
// we intentionally don't include the hash of the buckets here, because that is likely expensive to compute
192200
// instead, we assume that the value count and sum are a good enough approximation in most cases to minimize collisions

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ private void mergeValuesToHistogram() {
126126
Aggregates aggregates = rawValuesAggregates();
127127
valueBuffer.setSum(aggregates.sum());
128128
valueBuffer.setMin(aggregates.min());
129+
valueBuffer.setMax(aggregates.max());
129130
int scale = valueBuffer.scale();
130131

131132
// Buckets must be provided with their indices in ascending order.
@@ -166,15 +167,17 @@ private void mergeValuesToHistogram() {
166167

167168
private Aggregates rawValuesAggregates() {
168169
if (valueCount == 0) {
169-
return new Aggregates(0, Double.NaN);
170+
return new Aggregates(0, Double.NaN, Double.NaN);
170171
}
171172
double sum = 0;
172173
double min = Double.MAX_VALUE;
174+
double max = -Double.MAX_VALUE;
173175
for (int i = 0; i < valueCount; i++) {
174176
sum += rawValueBuffer[i];
175177
min = Math.min(min, rawValueBuffer[i]);
178+
max = Math.max(max, rawValueBuffer[i]);
176179
}
177-
return new Aggregates(sum, min);
180+
return new Aggregates(sum, min, max);
178181
}
179182

180183
private static long estimateBaseSize(int numBuckets) {
@@ -198,5 +201,5 @@ public void close() {
198201
}
199202
}
200203

201-
private record Aggregates(double sum, double min) {}
204+
private record Aggregates(double sum, double min, double max) {}
202205
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.core.Releasable;
2828

2929
import java.util.OptionalLong;
30+
import java.util.function.DoubleBinaryOperator;
3031

3132
import static org.elasticsearch.exponentialhistogram.ExponentialScaleUtils.getMaximumScaleIncrease;
3233

@@ -151,7 +152,8 @@ public void add(ExponentialHistogram toAdd) {
151152
}
152153
buffer.setZeroBucket(zeroBucket);
153154
buffer.setSum(a.sum() + b.sum());
154-
buffer.setMin(nanAwareMin(a.min(), b.min()));
155+
buffer.setMin(nanAwareAggregate(a.min(), b.min(), Math::min));
156+
buffer.setMax(nanAwareAggregate(a.max(), b.max(), Math::max));
155157
// We attempt to bring everything to the scale of A.
156158
// This might involve increasing the scale for B, which would increase its indices.
157159
// We need to ensure that we do not exceed MAX_INDEX / MIN_INDEX in this case.
@@ -231,14 +233,14 @@ private static int putBuckets(
231233
return overflowCount;
232234
}
233235

234-
private static double nanAwareMin(double a, double b) {
236+
private static double nanAwareAggregate(double a, double b, DoubleBinaryOperator aggregator) {
235237
if (Double.isNaN(a)) {
236238
return b;
237239
}
238240
if (Double.isNaN(b)) {
239241
return a;
240242
}
241-
return Math.min(a, b);
243+
return aggregator.applyAsDouble(a, b);
242244
}
243245

244246
}

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static double estimateSum(BucketIterator negativeBuckets, BucketIterator
6767
* Estimates the minimum value of the histogram based on the populated buckets.
6868
* The returned value is guaranteed to be less than or equal to the exact minimum value of the histogram values.
6969
* If the histogram is empty, an empty Optional is returned.
70-
*
70+
* <p>
7171
* Note that this method can return +-Infinity if the histogram bucket boundaries are not representable in a double.
7272
*
7373
* @param zeroBucket the zero bucket of the histogram
@@ -102,4 +102,40 @@ public static OptionalDouble estimateMin(
102102
}
103103
return OptionalDouble.empty();
104104
}
105+
106+
/**
107+
* Estimates the maximum value of the histogram based on the populated buckets.
108+
* The returned value is guaranteed to be greater than or equal to the exact maximum value of the histogram values.
109+
* If the histogram is empty, an empty Optional is returned.
110+
* <p>
111+
* Note that this method can return +-Infinity if the histogram bucket boundaries are not representable in a double.
112+
*
113+
* @param zeroBucket the zero bucket of the histogram
114+
* @param negativeBuckets the negative buckets of the histogram
115+
* @param positiveBuckets the positive buckets of the histogram
116+
* @return the estimated minimum
117+
*/
118+
public static OptionalDouble estimateMax(
119+
ZeroBucket zeroBucket,
120+
ExponentialHistogram.Buckets negativeBuckets,
121+
ExponentialHistogram.Buckets positiveBuckets
122+
) {
123+
int scale = negativeBuckets.iterator().scale();
124+
assert scale == positiveBuckets.iterator().scale();
125+
126+
OptionalLong positiveMaxIndex = positiveBuckets.maxBucketIndex();
127+
if (positiveMaxIndex.isPresent()) {
128+
return OptionalDouble.of(ExponentialScaleUtils.getUpperBucketBoundary(positiveMaxIndex.getAsLong(), scale));
129+
}
130+
131+
if (zeroBucket.count() > 0) {
132+
return OptionalDouble.of(zeroBucket.zeroThreshold());
133+
}
134+
135+
BucketIterator negativeBucketsIt = negativeBuckets.iterator();
136+
if (negativeBucketsIt.hasNext()) {
137+
return OptionalDouble.of(-ExponentialScaleUtils.getLowerBucketBoundary(negativeBucketsIt.peekIndex(), scale));
138+
}
139+
return OptionalDouble.empty();
140+
}
105141
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class ExponentialHistogramXContent {
3333
public static final String SCALE_FIELD = "scale";
3434
public static final String SUM_FIELD = "sum";
3535
public static final String MIN_FIELD = "min";
36+
public static final String MAX_FIELD = "max";
3637
public static final String ZERO_FIELD = "zero";
3738
public static final String ZERO_COUNT_FIELD = "count";
3839
public static final String ZERO_THRESHOLD_FIELD = "threshold";
@@ -55,6 +56,9 @@ public static void serialize(XContentBuilder builder, ExponentialHistogram histo
5556
if (Double.isNaN(histogram.min()) == false) {
5657
builder.field(MIN_FIELD, histogram.min());
5758
}
59+
if (Double.isNaN(histogram.max()) == false) {
60+
builder.field(MAX_FIELD, histogram.max());
61+
}
5862
double zeroThreshold = histogram.zeroBucket().zeroThreshold();
5963
long zeroCount = histogram.zeroBucket().count();
6064

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ final class FixedCapacityExponentialHistogram extends AbstractExponentialHistogr
5555

5656
private double sum;
5757
private double min;
58+
private double max;
5859

5960
private final ExponentialHistogramCircuitBreaker circuitBreaker;
6061
private boolean closed = false;
@@ -83,6 +84,7 @@ private FixedCapacityExponentialHistogram(int bucketCapacity, ExponentialHistogr
8384
void reset() {
8485
sum = 0;
8586
min = Double.NaN;
87+
max = Double.NaN;
8688
setZeroBucket(ZeroBucket.minimalEmpty());
8789
resetBuckets(MAX_SCALE);
8890
}
@@ -133,6 +135,15 @@ void setMin(double min) {
133135
this.min = min;
134136
}
135137

138+
@Override
139+
public double max() {
140+
return max;
141+
}
142+
143+
void setMax(double max) {
144+
this.max = max;
145+
}
146+
136147
/**
137148
* Attempts to add a bucket to the positive or negative range of this histogram.
138149
* <br>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public enum Modification {
4141
SCALE,
4242
SUM,
4343
MIN,
44+
MAX,
4445
ZERO_THRESHOLD,
4546
ZERO_COUNT,
4647
POSITIVE_BUCKETS,
@@ -102,6 +103,11 @@ private ExponentialHistogram copyWithModification(ExponentialHistogram toCopy, M
102103
} else {
103104
copy.setMin(toCopy.min());
104105
}
106+
if (modification == Modification.MAX) {
107+
copy.setMax(randomDouble());
108+
} else {
109+
copy.setMax(toCopy.max());
110+
}
105111
long zeroCount = toCopy.zeroBucket().count();
106112
double zeroThreshold = toCopy.zeroBucket().zeroThreshold();
107113
if (modification == Modification.ZERO_COUNT) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public void testAggregatesCorrectness() {
112112
double[] secondValues = randomDoubles(50).map(val -> val * 2 - 1).toArray();
113113
double correctSum = Arrays.stream(firstValues).sum() + Arrays.stream(secondValues).sum();
114114
double correctMin = DoubleStream.concat(Arrays.stream(firstValues), Arrays.stream(secondValues)).min().getAsDouble();
115+
double correctMax = DoubleStream.concat(Arrays.stream(firstValues), Arrays.stream(secondValues)).max().getAsDouble();
115116
try (
116117
// Merge some empty histograms too to test that code path
117118
ReleasableExponentialHistogram merged = ExponentialHistogram.merge(
@@ -125,6 +126,7 @@ public void testAggregatesCorrectness() {
125126
) {
126127
assertThat(merged.sum(), closeTo(correctSum, 0.000001));
127128
assertThat(merged.min(), equalTo(correctMin));
129+
assertThat(merged.max(), equalTo(correctMax));
128130
}
129131
}
130132

0 commit comments

Comments
 (0)