Skip to content

Commit 03a227a

Browse files
Align DeltaHistogram in SignalFx registry with count and total (#3799)
Count and total for Step Meters have been made to roll over at the start of the step. SignalFx's DeltaHistogram however did not align to this, resulting in a histogram that does not match with the count/total values as it should. This makes those align and generally updates the implementation to align better with the behavior provided to StepMeterRegistry implementations. Closes gh-3774
1 parent 693ffa8 commit 03a227a

File tree

10 files changed

+260
-196
lines changed

10 files changed

+260
-196
lines changed

Diff for: implementations/micrometer-registry-signalfx/src/main/java/io/micrometer/signalfx/CumulativeHistogramConfigUtil.java

+17-8
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,38 @@
2828
*/
2929
final class CumulativeHistogramConfigUtil {
3030

31-
static DistributionStatisticConfig updateConfig(DistributionStatisticConfig distributionStatisticConfig) {
31+
private static final double[] EMPTY_SLO = new double[0];
32+
33+
static DistributionStatisticConfig updateConfig(DistributionStatisticConfig distributionStatisticConfig,
34+
boolean isDelta) {
3235
double[] sloBoundaries = distributionStatisticConfig.getServiceLevelObjectiveBoundaries();
3336
if (sloBoundaries == null || sloBoundaries.length == 0) {
3437
return distributionStatisticConfig;
3538
}
36-
double[] newSloBoundaries = sloBoundaries;
37-
// Add the +Inf bucket since the "count" resets every export.
38-
if (!isPositiveInf(sloBoundaries[sloBoundaries.length - 1])) {
39-
newSloBoundaries = Arrays.copyOf(sloBoundaries, sloBoundaries.length + 1);
40-
newSloBoundaries[newSloBoundaries.length - 1] = Double.MAX_VALUE;
41-
}
4239

4340
return DistributionStatisticConfig.builder()
4441
// Set the expiration duration for the histogram counts to be effectively
4542
// infinite.
4643
// Without this, the counts are reset every expiry duration.
4744
.expiry(Duration.ofNanos(Long.MAX_VALUE)) // effectively infinite
4845
.bufferLength(1)
49-
.serviceLevelObjectives(newSloBoundaries)
46+
// If delta Histograms are enabled, empty the slo's and use
47+
// StepBucketHistogram.
48+
.serviceLevelObjectives(isDelta ? EMPTY_SLO : addPositiveInfBucket(sloBoundaries))
5049
.build()
5150
.merge(distributionStatisticConfig);
5251
}
5352

53+
static double[] addPositiveInfBucket(double[] sloBoundaries) {
54+
double[] newSloBoundaries = sloBoundaries;
55+
// Add the +Inf bucket since the "count" resets every export.
56+
if (!isPositiveInf(sloBoundaries[sloBoundaries.length - 1])) {
57+
newSloBoundaries = Arrays.copyOf(sloBoundaries, sloBoundaries.length + 1);
58+
newSloBoundaries[newSloBoundaries.length - 1] = Double.MAX_VALUE;
59+
}
60+
return newSloBoundaries;
61+
}
62+
5463
private static boolean isPositiveInf(double bucket) {
5564
return bucket == Double.POSITIVE_INFINITY || bucket == Double.MAX_VALUE || (long) bucket == Long.MAX_VALUE;
5665
}

Diff for: implementations/micrometer-registry-signalfx/src/main/java/io/micrometer/signalfx/DeltaHistogramCounts.java

-41
This file was deleted.

Diff for: implementations/micrometer-registry-signalfx/src/main/java/io/micrometer/signalfx/SignalfxDistributionSummary.java

+34-50
Original file line numberDiff line numberDiff line change
@@ -16,88 +16,72 @@
1616
package io.micrometer.signalfx;
1717

1818
import io.micrometer.common.lang.Nullable;
19-
import io.micrometer.core.instrument.AbstractDistributionSummary;
2019
import io.micrometer.core.instrument.Clock;
2120
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
2221
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
23-
import io.micrometer.core.instrument.distribution.TimeWindowMax;
24-
import io.micrometer.core.instrument.step.StepTuple2;
25-
26-
import java.util.concurrent.atomic.DoubleAdder;
27-
import java.util.concurrent.atomic.LongAdder;
22+
import io.micrometer.core.instrument.distribution.StepBucketHistogram;
23+
import io.micrometer.core.instrument.step.StepDistributionSummary;
2824

2925
/**
30-
* This class is mostly the same as
31-
* {@link io.micrometer.core.instrument.step.StepDistributionSummary}, with one notable
32-
* difference: the {@link DistributionStatisticConfig} is modified before being passed to
33-
* the super class constructor - that forces the histogram generated by this meter to be
34-
* cumulative.
26+
* A StepDistributionSummary which provides support for multiple flavours of Histograms to
27+
* be recorded based on {@link SignalFxConfig#publishCumulativeHistogram()} and
28+
* {@link SignalFxConfig#publishDeltaHistogram()}.
3529
*
3630
* @author Bogdan Drutu
3731
* @author Mateusz Rzeszutek
32+
* @author Lenin Jaganathan
3833
*/
39-
final class SignalfxDistributionSummary extends AbstractDistributionSummary {
40-
41-
private final LongAdder count = new LongAdder();
42-
43-
private final DoubleAdder total = new DoubleAdder();
44-
45-
private final StepTuple2<Long, Double> countTotal;
46-
47-
private final TimeWindowMax max;
34+
final class SignalfxDistributionSummary extends StepDistributionSummary {
4835

4936
@Nullable
50-
private final DeltaHistogramCounts deltaHistogramCounts;
37+
private final StepBucketHistogram stepBucketHistogram;
5138

5239
SignalfxDistributionSummary(Id id, Clock clock, DistributionStatisticConfig distributionStatisticConfig,
5340
double scale, long stepMillis, boolean isDelta) {
54-
super(id, clock, CumulativeHistogramConfigUtil.updateConfig(distributionStatisticConfig), scale, false);
55-
this.countTotal = new StepTuple2<>(clock, stepMillis, 0L, 0.0, count::sumThenReset, total::sumThenReset);
56-
max = new TimeWindowMax(clock, distributionStatisticConfig);
57-
if (distributionStatisticConfig.isPublishingHistogram() && isDelta) {
58-
deltaHistogramCounts = new DeltaHistogramCounts();
41+
super(id, clock, distributionStatisticConfig, scale, stepMillis, defaultHistogram(clock,
42+
CumulativeHistogramConfigUtil.updateConfig(distributionStatisticConfig, isDelta), false));
43+
44+
double[] slo = distributionStatisticConfig.getServiceLevelObjectiveBoundaries();
45+
if (slo != null && slo.length > 0 && isDelta) {
46+
stepBucketHistogram = new StepBucketHistogram(clock, stepMillis,
47+
DistributionStatisticConfig.builder()
48+
.serviceLevelObjectives(CumulativeHistogramConfigUtil.addPositiveInfBucket(slo))
49+
.build()
50+
.merge(distributionStatisticConfig),
51+
false, true);
5952
}
6053
else {
61-
deltaHistogramCounts = null;
54+
stepBucketHistogram = null;
6255
}
6356
}
6457

6558
@Override
6659
protected void recordNonNegative(double amount) {
67-
count.increment();
68-
total.add(amount);
69-
max.record(amount);
60+
if (stepBucketHistogram != null) {
61+
stepBucketHistogram.recordDouble(amount);
62+
}
63+
super.recordNonNegative(amount);
7064
}
7165

7266
@Override
7367
public long count() {
74-
return countTotal.poll1();
75-
}
76-
77-
@Override
78-
public double totalAmount() {
79-
return countTotal.poll2();
80-
}
81-
82-
@Override
83-
public double max() {
84-
return max.poll();
68+
if (stepBucketHistogram != null) {
69+
// Force the stepBucketHistogram to be aligned to step by calling count. This
70+
// ensures that all
71+
// values exported by the Timer are measured for the same step.
72+
stepBucketHistogram.poll();
73+
}
74+
return super.count();
8575
}
8676

8777
@Override
8878
public HistogramSnapshot takeSnapshot() {
8979
HistogramSnapshot currentSnapshot = super.takeSnapshot();
90-
if (deltaHistogramCounts == null) {
80+
if (stepBucketHistogram == null) {
9181
return currentSnapshot;
9282
}
93-
return new HistogramSnapshot(currentSnapshot.count(), // Already delta in sfx
94-
// implementation.
95-
currentSnapshot.total(), // Already delta in sfx implementation.
96-
currentSnapshot.max(), // Max cannot be calculated as delta, keep the
97-
// current.
98-
currentSnapshot.percentileValues(), // No changes to the percentile
99-
// values.
100-
deltaHistogramCounts.calculate(currentSnapshot.histogramCounts()), currentSnapshot::outputSummary);
83+
return new HistogramSnapshot(currentSnapshot.count(), currentSnapshot.total(), currentSnapshot.max(),
84+
currentSnapshot.percentileValues(), stepBucketHistogram.poll(), currentSnapshot::outputSummary);
10185
}
10286

10387
}

Diff for: implementations/micrometer-registry-signalfx/src/main/java/io/micrometer/signalfx/SignalfxTimer.java

+34-50
Original file line numberDiff line numberDiff line change
@@ -16,91 +16,75 @@
1616
package io.micrometer.signalfx;
1717

1818
import io.micrometer.common.lang.Nullable;
19-
import io.micrometer.core.instrument.AbstractTimer;
2019
import io.micrometer.core.instrument.Clock;
2120
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
2221
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
23-
import io.micrometer.core.instrument.distribution.TimeWindowMax;
22+
import io.micrometer.core.instrument.distribution.StepBucketHistogram;
2423
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
25-
import io.micrometer.core.instrument.step.StepTuple2;
26-
import io.micrometer.core.instrument.util.TimeUtils;
24+
import io.micrometer.core.instrument.step.StepTimer;
2725

2826
import java.util.concurrent.TimeUnit;
29-
import java.util.concurrent.atomic.LongAdder;
3027

3128
/**
32-
* This class is mostly the same as {@link io.micrometer.core.instrument.step.StepTimer},
33-
* with one notable difference: the {@link DistributionStatisticConfig} is modified before
34-
* being passed to the super class constructor - that forces the histogram generated by
35-
* this meter to be cumulative.
29+
* A StepTimer which provides support for multiple flavours of Histograms to be recorded
30+
* based on {@link SignalFxConfig#publishCumulativeHistogram()} and
31+
* {@link SignalFxConfig#publishDeltaHistogram()}.
3632
*
3733
* @author Bogdan Drutu
3834
* @author Mateusz Rzeszutek
35+
* @author Lenin Jaganathan
3936
*/
40-
final class SignalfxTimer extends AbstractTimer {
41-
42-
private final LongAdder count = new LongAdder();
43-
44-
private final LongAdder total = new LongAdder();
45-
46-
private final StepTuple2<Long, Long> countTotal;
47-
48-
private final TimeWindowMax max;
37+
final class SignalfxTimer extends StepTimer {
4938

5039
@Nullable
51-
private final DeltaHistogramCounts deltaHistogramCounts;
40+
private final StepBucketHistogram stepBucketHistogram;
5241

5342
SignalfxTimer(Id id, Clock clock, DistributionStatisticConfig distributionStatisticConfig,
5443
PauseDetector pauseDetector, TimeUnit baseTimeUnit, long stepMillis, boolean isDelta) {
55-
super(id, clock, CumulativeHistogramConfigUtil.updateConfig(distributionStatisticConfig), pauseDetector,
56-
baseTimeUnit, false);
57-
countTotal = new StepTuple2<>(clock, stepMillis, 0L, 0L, count::sumThenReset, total::sumThenReset);
58-
max = new TimeWindowMax(clock, distributionStatisticConfig);
59-
if (distributionStatisticConfig.isPublishingHistogram() && isDelta) {
60-
deltaHistogramCounts = new DeltaHistogramCounts();
44+
super(id, clock, distributionStatisticConfig, pauseDetector, baseTimeUnit, stepMillis, defaultHistogram(clock,
45+
CumulativeHistogramConfigUtil.updateConfig(distributionStatisticConfig, isDelta), false));
46+
47+
double[] slo = distributionStatisticConfig.getServiceLevelObjectiveBoundaries();
48+
if (slo != null && slo.length > 0 && isDelta) {
49+
stepBucketHistogram = new StepBucketHistogram(clock, stepMillis,
50+
DistributionStatisticConfig.builder()
51+
.serviceLevelObjectives(CumulativeHistogramConfigUtil.addPositiveInfBucket(slo))
52+
.build()
53+
.merge(distributionStatisticConfig),
54+
false, true);
6155
}
6256
else {
63-
deltaHistogramCounts = null;
57+
stepBucketHistogram = null;
6458
}
6559
}
6660

6761
@Override
6862
protected void recordNonNegative(long amount, TimeUnit unit) {
69-
final long nanoAmount = (long) TimeUtils.convert(amount, unit, TimeUnit.NANOSECONDS);
70-
count.increment();
71-
total.add(nanoAmount);
72-
max.record(amount, unit);
63+
if (stepBucketHistogram != null) {
64+
stepBucketHistogram.recordLong(TimeUnit.NANOSECONDS.convert(amount, unit));
65+
}
66+
super.recordNonNegative(amount, unit);
7367
}
7468

7569
@Override
7670
public long count() {
77-
return countTotal.poll1();
78-
}
79-
80-
@Override
81-
public double totalTime(TimeUnit unit) {
82-
return TimeUtils.nanosToUnit(countTotal.poll2(), unit);
83-
}
84-
85-
@Override
86-
public double max(TimeUnit unit) {
87-
return max.poll(unit);
71+
if (stepBucketHistogram != null) {
72+
// Force the stepBucketHistogram to be aligned to step by calling count. This
73+
// ensures that all
74+
// values exported by the Timer are measured for the same step.
75+
stepBucketHistogram.poll();
76+
}
77+
return super.count();
8878
}
8979

9080
@Override
9181
public HistogramSnapshot takeSnapshot() {
9282
HistogramSnapshot currentSnapshot = super.takeSnapshot();
93-
if (deltaHistogramCounts == null) {
83+
if (stepBucketHistogram == null) {
9484
return currentSnapshot;
9585
}
96-
return new HistogramSnapshot(currentSnapshot.count(), // Already delta in sfx
97-
// implementation
98-
currentSnapshot.total(), // Already delta in sfx implementation
99-
currentSnapshot.max(), // Max cannot be calculated as delta, keep the
100-
// current.
101-
currentSnapshot.percentileValues(), // No changes to the percentile
102-
// values.
103-
deltaHistogramCounts.calculate(currentSnapshot.histogramCounts()), currentSnapshot::outputSummary);
86+
return new HistogramSnapshot(currentSnapshot.count(), currentSnapshot.total(), currentSnapshot.max(),
87+
currentSnapshot.percentileValues(), stepBucketHistogram.poll(), currentSnapshot::outputSummary);
10488
}
10589

10690
}

Diff for: implementations/micrometer-registry-signalfx/src/test/java/io/micrometer/signalfx/DeltaHistogramCountsTest.java

-45
This file was deleted.

0 commit comments

Comments
 (0)