Skip to content

Commit 41e8876

Browse files
committed
Fix HdrHistogram range issues by computing ratio from min/max expected values
The fix computes an appropriate `highestToLowestValueRatio` from the configured `minimumExpectedValue` and `maximumExpectedValue` in `DistributionStatisticConfig`, allowing HdrHistogram to cover the expected range without constant resizing. ## Problem The current implementation hardcodes a ratio of 2: ```java new DoubleHistogram(percentilePrecision(distributionStatisticConfig)); // This constructor internally calls: this(2, numberOfSignificantValueDigits, ...) ``` This means the histogram can only handle values that differ by a factor of 2 (e.g., 1ms to 2ms). When recording typical operation times that span microseconds to seconds, HdrHistogram constantly attempts to resize, frequently throwing `ArrayIndexOutOfBoundsException` during the resize operations. In production environments, this can result in hundreds of thousands of exceptions being thrown, causing significant performance overhead. ## Solution This PR modifies `TimeWindowPercentileHistogram` to: 1. **Compute the ratio from configuration**: When `minimumExpectedValue` and `maximumExpectedValue` are provided, calculate an appropriate ratio 2. **Use the computed ratio**: Pass this ratio to HdrHistogram constructors instead of relying on the default 3. **Maintain backward compatibility**: When min/max values are not configured, fall back to the original behavior (ratio=2) ## Performance Impact ### Before - Frequent `ArrayIndexOutOfBoundsException` as values exceed the narrow range - Initial `HdrHistogram#counts` has length of 127 ### After - No exceptions when values are within the configured min/max range - Initial `HdrHistogram#counts` has length of 272 when using defaults (1ms - 30s) This last bullet could be a problem and require a different solution. ## Issues - Fixes #4327 Signed-off-by: Knut Wannheden <[email protected]>
1 parent 79e3c5a commit 41e8876

File tree

1 file changed

+29
-3
lines changed

1 file changed

+29
-3
lines changed

micrometer-core/src/main/java/io/micrometer/core/instrument/distribution/TimeWindowPercentileHistogram.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ public TimeWindowPercentileHistogram(Clock clock, DistributionStatisticConfig di
6464
protected TimeWindowPercentileHistogram(Clock clock, DistributionStatisticConfig distributionStatisticConfig,
6565
boolean supportsAggregablePercentiles, boolean isCumulativeBucketCounts, boolean includeInfinityBucket) {
6666
super(clock, distributionStatisticConfig, DoubleRecorder.class);
67-
intervalHistogram = new DoubleHistogram(percentilePrecision(distributionStatisticConfig));
67+
intervalHistogram = new DoubleHistogram(computeHighestToLowestValueRatio(distributionStatisticConfig),
68+
percentilePrecision(distributionStatisticConfig));
69+
intervalHistogram.setAutoResize(true);
70+
6871
this.isCumulativeBucketCounts = isCumulativeBucketCounts;
6972

7073
Set<Double> monitoredBuckets = distributionStatisticConfig.getHistogramBuckets(supportsAggregablePercentiles);
@@ -80,7 +83,8 @@ protected TimeWindowPercentileHistogram(Clock clock, DistributionStatisticConfig
8083

8184
@Override
8285
DoubleRecorder newBucket() {
83-
return new DoubleRecorder(percentilePrecision(distributionStatisticConfig));
86+
return new DoubleRecorder(computeHighestToLowestValueRatio(distributionStatisticConfig),
87+
percentilePrecision(distributionStatisticConfig));
8488
}
8589

8690
@Override
@@ -100,7 +104,8 @@ void resetBucket(DoubleRecorder bucket) {
100104

101105
@Override
102106
DoubleHistogram newAccumulatedHistogram(DoubleRecorder[] ringBuffer) {
103-
return new DoubleHistogram(percentilePrecision(distributionStatisticConfig));
107+
return new DoubleHistogram(computeHighestToLowestValueRatio(distributionStatisticConfig),
108+
percentilePrecision(distributionStatisticConfig));
104109
}
105110

106111
@Override
@@ -143,6 +148,27 @@ private int percentilePrecision(DistributionStatisticConfig config) {
143148
return config.getPercentilePrecision() == null ? 1 : config.getPercentilePrecision();
144149
}
145150

151+
/**
152+
* Compute the highestToLowestValueRatio based on the configured min/max expected values.
153+
* This allows HdrHistogram to cover the expected range without frequent resizing.
154+
*
155+
* @param config The distribution statistic configuration
156+
* @return The computed ratio, or 2 if not enough information is available
157+
*/
158+
private long computeHighestToLowestValueRatio(DistributionStatisticConfig config) {
159+
Double min = config.getMinimumExpectedValueAsDouble();
160+
Double max = config.getMaximumExpectedValueAsDouble();
161+
162+
// Only compute ratio if both min and max are explicitly set and finite
163+
if (min != null && max != null && min > 0 && max > min && !Double.isInfinite(max)) {
164+
// Compute the ratio, ensuring it's at least 2
165+
long ratio = (long) Math.ceil(max / min);
166+
return Math.max(ratio, 2L);
167+
}
168+
169+
return 2L;
170+
}
171+
146172
@Override
147173
void outputSummary(PrintStream out, double bucketScaling) {
148174
accumulatedHistogram().outputPercentileDistribution(out, bucketScaling);

0 commit comments

Comments
 (0)