23
23
24
24
import org .apache .lucene .util .RamUsageEstimator ;
25
25
26
+ import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MAX_INDEX ;
26
27
import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MAX_SCALE ;
27
28
import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MIN_INDEX ;
28
29
import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MIN_SCALE ;
36
37
* To allow efficient comparison with bucket boundaries, this class internally
37
38
* represents the zero threshold as a exponential histogram bucket index with a scale,
38
39
* computed via {@link ExponentialScaleUtils#computeIndex(double, int)}.
39
- *
40
- * @param index The index used with the scale to determine the zero threshold.
41
- * @param scale The scale used with the index to determine the zero threshold.
42
- * @param count The number of values in the zero bucket.
43
40
*/
44
- public record ZeroBucket ( long index , int scale , long count ) {
41
+ public final class ZeroBucket {
45
42
46
43
public static final long SHALLOW_SIZE = RamUsageEstimator .shallowSizeOfInstance (ZeroBucket .class );
47
44
45
+ /**
46
+ * The exponential histogram scale used for {@link #index}
47
+ */
48
+ private final int scale ;
49
+
50
+ /**
51
+ * The exponential histogram bucket index whose upper boundary corresponds to the zero threshold.
52
+ * Might be computed lazily from {@link #realThreshold}, uses {@link Long#MAX_VALUE} as placeholder in this case.
53
+ */
54
+ private long index ;
55
+
56
+ /**
57
+ * Might be computed lazily from {@link #realThreshold}, uses {@link Double#NaN} as placeholder in this case.
58
+ */
59
+ private double realThreshold ;
60
+
61
+ private final long count ;
48
62
// A singleton for an empty zero bucket with the smallest possible threshold.
49
63
private static final ZeroBucket MINIMAL_EMPTY = new ZeroBucket (MIN_INDEX , MIN_SCALE , 0 );
50
64
@@ -55,7 +69,27 @@ public record ZeroBucket(long index, int scale, long count) {
55
69
* @param count The number of values in the bucket.
56
70
*/
57
71
public ZeroBucket (double zeroThreshold , long count ) {
58
- this (computeIndex (zeroThreshold , MAX_SCALE ) + 1 , MAX_SCALE , count );
72
+ assert zeroThreshold >= 0.0 : "zeroThreshold must not be negative" ;
73
+ this .index = Long .MAX_VALUE ; // compute lazily when needed
74
+ this .scale = MAX_SCALE ;
75
+ this .realThreshold = zeroThreshold ;
76
+ this .count = count ;
77
+ }
78
+
79
+ private ZeroBucket (long index , int scale , long count ) {
80
+ assert index >= MIN_INDEX && index <= MAX_INDEX : "index must be in range [" + MIN_INDEX + ", " + MAX_INDEX + "]" ;
81
+ assert scale >= MIN_SCALE && scale <= MAX_SCALE : "scale must be in range [" + MIN_SCALE + ", " + MAX_SCALE + "]" ;
82
+ this .index = index ;
83
+ this .scale = scale ;
84
+ this .realThreshold = Double .NaN ; // compute lazily when needed
85
+ this .count = count ;
86
+ }
87
+
88
+ private ZeroBucket (double realThreshold , long index , int scale , long count ) {
89
+ this .realThreshold = realThreshold ;
90
+ this .index = index ;
91
+ this .scale = scale ;
92
+ this .count = count ;
59
93
}
60
94
61
95
/**
@@ -75,8 +109,33 @@ public static ZeroBucket minimalWithCount(long count) {
75
109
if (count == 0 ) {
76
110
return MINIMAL_EMPTY ;
77
111
} else {
78
- return new ZeroBucket (MINIMAL_EMPTY .index , MINIMAL_EMPTY .scale (), count );
112
+ return new ZeroBucket (MINIMAL_EMPTY .zeroThreshold (), MINIMAL_EMPTY .index (), MINIMAL_EMPTY .scale (), count );
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @return The value of the zero threshold.
118
+ */
119
+ public double zeroThreshold () {
120
+ if (Double .isNaN (realThreshold )) {
121
+ realThreshold = exponentiallyScaledToDoubleValue (index (), scale ());
79
122
}
123
+ return realThreshold ;
124
+ }
125
+
126
+ public long index () {
127
+ if (index == Long .MAX_VALUE ) {
128
+ index = computeIndex (zeroThreshold (), scale ()) + 1 ;
129
+ }
130
+ return index ;
131
+ }
132
+
133
+ public int scale () {
134
+ return scale ;
135
+ }
136
+
137
+ public long count () {
138
+ return count ;
80
139
}
81
140
82
141
/**
@@ -99,9 +158,9 @@ public ZeroBucket merge(ZeroBucket other) {
99
158
long totalCount = count + other .count ;
100
159
// Both are populated, so we need to use the higher zero-threshold.
101
160
if (this .compareZeroThreshold (other ) >= 0 ) {
102
- return new ZeroBucket (index , scale , totalCount );
161
+ return new ZeroBucket (realThreshold , index , scale , totalCount );
103
162
} else {
104
- return new ZeroBucket (other .index , other .scale , totalCount );
163
+ return new ZeroBucket (other .realThreshold , other . index , other .scale , totalCount );
105
164
}
106
165
}
107
166
}
@@ -133,14 +192,7 @@ public ZeroBucket collapseOverlappingBucketsForAll(BucketIterator... bucketItera
133
192
* equal to, or greater than the other's.
134
193
*/
135
194
public int compareZeroThreshold (ZeroBucket other ) {
136
- return compareExponentiallyScaledValues (index , scale , other .index , other .scale );
137
- }
138
-
139
- /**
140
- * @return The value of the zero threshold.
141
- */
142
- public double zeroThreshold () {
143
- return exponentiallyScaledToDoubleValue (index , scale );
195
+ return compareExponentiallyScaledValues (index (), scale (), other .index (), other .scale ());
144
196
}
145
197
146
198
/**
@@ -154,7 +206,7 @@ public ZeroBucket collapseOverlappingBuckets(BucketIterator buckets) {
154
206
155
207
long collapsedCount = 0 ;
156
208
long highestCollapsedIndex = 0 ;
157
- while (buckets .hasNext () && compareExponentiallyScaledValues (buckets .peekIndex (), buckets .scale (), index , scale ) < 0 ) {
209
+ while (buckets .hasNext () && compareExponentiallyScaledValues (buckets .peekIndex (), buckets .scale (), index () , scale () ) < 0 ) {
158
210
highestCollapsedIndex = buckets .peekIndex ();
159
211
collapsedCount += buckets .peekCount ();
160
212
buckets .advance ();
@@ -165,9 +217,9 @@ public ZeroBucket collapseOverlappingBuckets(BucketIterator buckets) {
165
217
long newZeroCount = count + collapsedCount ;
166
218
// +1 because we need to adjust the zero threshold to the upper boundary of the collapsed bucket
167
219
long collapsedUpperBoundIndex = highestCollapsedIndex + 1 ;
168
- if (compareExponentiallyScaledValues (index , scale , collapsedUpperBoundIndex , buckets .scale ()) >= 0 ) {
220
+ if (compareExponentiallyScaledValues (index () , scale () , collapsedUpperBoundIndex , buckets .scale ()) >= 0 ) {
169
221
// Our current zero-threshold is larger than the upper boundary of the largest collapsed bucket, so we keep it.
170
- return new ZeroBucket (index , scale , newZeroCount );
222
+ return new ZeroBucket (realThreshold , index , scale , newZeroCount );
171
223
} else {
172
224
return new ZeroBucket (collapsedUpperBoundIndex , buckets .scale (), newZeroCount );
173
225
}
0 commit comments