Skip to content

Commit 3287ced

Browse files
authored
Introduce separate HistogramWithSum for situations where we want an accurate sum (#10895)
Introduce separate HistogramWithSum for situations where we want an accurate sum and don't mind the additional fields, while avoiding this overhead for situations where we don't need the sum. Also fixes a bug where the histogram sum wasn't reset on 'clear'. Co-authored-by: stuart.mcculloch <stuart.mcculloch@datadoghq.com>
1 parent 018d1ea commit 3287ced

File tree

11 files changed

+134
-65
lines changed

11 files changed

+134
-65
lines changed

dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/data/OtelHistogramSketch.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
package datadog.trace.bootstrap.otel.metrics.data;
22

33
import datadog.metrics.api.Histogram;
4+
import datadog.metrics.api.HistogramWithSum;
45
import java.util.List;
56

67
final class OtelHistogramSketch extends OtelAggregator {
7-
private final Histogram histogram;
8-
private volatile double totalSum;
8+
private final HistogramWithSum histogram;
99

1010
OtelHistogramSketch(List<Double> bucketBoundaries) {
11-
this.histogram = Histogram.newHistogram(bucketBoundaries);
11+
this.histogram = Histogram.newHistogramWithSum(bucketBoundaries);
1212
}
1313

1414
@Override
1515
void doRecordDouble(double value) {
1616
synchronized (histogram) {
1717
histogram.accept(value);
18-
totalSum += value;
1918
}
2019
}
2120

@@ -34,10 +33,9 @@ OtelPoint doCollect(boolean reset) {
3433
count = histogram.getCount();
3534
binBoundaries = histogram.getBinBoundaries();
3635
binCounts = histogram.getBinCounts();
37-
sum = totalSum;
36+
sum = histogram.getSum();
3837
if (reset) {
3938
histogram.clear();
40-
totalSum = 0;
4139
}
4240
}
4341
return new OtelHistogramPoint(count, binBoundaries, binCounts, sum);

dd-java-agent/instrumentation/spark/spark-common/src/main/java/datadog/trace/instrumentation/spark/SparkAggregatedTaskMetrics.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.core.JsonGenerator;
44
import datadog.metrics.api.Histogram;
5+
import datadog.metrics.api.HistogramWithSum;
56
import datadog.trace.api.Config;
67
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
78
import java.io.IOException;
@@ -67,7 +68,7 @@ class SparkAggregatedTaskMetrics {
6768
private Histogram diskBytesSpilledHistogram;
6869

6970
// Used for Spark SQL Plan metrics ONLY, don't put in regular span for now
70-
private Map<Long, Histogram> externalAccumulableHistograms;
71+
private Map<Long, HistogramWithSum> externalAccumulableHistograms;
7172

7273
public SparkAggregatedTaskMetrics() {}
7374

@@ -148,10 +149,11 @@ public void addTaskMetrics(
148149

149150
externalAccumulators.forEach(
150151
acc -> {
151-
Histogram hist = externalAccumulableHistograms.get(acc.id());
152+
HistogramWithSum hist = externalAccumulableHistograms.get(acc.id());
152153
if (hist == null) {
153154
hist =
154-
Histogram.newHistogram(HISTOGRAM_RELATIVE_ACCURACY, HISTOGRAM_MAX_NUM_BINS);
155+
Histogram.newHistogramWithSum(
156+
HISTOGRAM_RELATIVE_ACCURACY, HISTOGRAM_MAX_NUM_BINS);
155157
}
156158

157159
try {
@@ -315,7 +317,7 @@ private Histogram lazyHistogramAccept(Histogram hist, double value) {
315317
// Used to put external accum metrics to JSON for Spark SQL plans
316318
public void externalAccumToJson(JsonGenerator generator, SQLMetricInfo info) throws IOException {
317319
if (externalAccumulableHistograms != null) {
318-
Histogram hist = externalAccumulableHistograms.get(info.accumulatorId());
320+
HistogramWithSum hist = externalAccumulableHistograms.get(info.accumulatorId());
319321
String name = info.name();
320322

321323
if (name != null && hist != null) {

products/metrics/metrics-api/src/main/java/datadog/metrics/api/Histogram.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
public interface Histogram {
77

8-
double getSum();
9-
108
double getCount();
119

1210
boolean isEmpty();
@@ -41,7 +39,11 @@ static Histogram newHistogram(double relativeAccuracy, int maxNumBins) {
4139
return Histograms.factory.newHistogram(relativeAccuracy, maxNumBins);
4240
}
4341

44-
static Histogram newHistogram(List<Double> binBoundaries) {
45-
return Histograms.factory.newHistogram(binBoundaries);
42+
static HistogramWithSum newHistogramWithSum(double relativeAccuracy, int maxNumBins) {
43+
return Histograms.factory.newHistogramWithSum(relativeAccuracy, maxNumBins);
44+
}
45+
46+
static HistogramWithSum newHistogramWithSum(List<Double> binBoundaries) {
47+
return Histograms.factory.newHistogramWithSum(binBoundaries);
4648
}
4749
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package datadog.metrics.api;
2+
3+
/** Adds exact summary statistics to the histogram */
4+
public interface HistogramWithSum extends Histogram {
5+
double getSum();
6+
}

products/metrics/metrics-api/src/main/java/datadog/metrics/api/Histograms.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface Factory {
2323

2424
Histogram newHistogram(double relativeAccuracy, int maxNumBins);
2525

26-
Histogram newHistogram(List<Double> binBoundaries);
26+
HistogramWithSum newHistogramWithSum(double relativeAccuracy, int maxNumBins);
27+
28+
HistogramWithSum newHistogramWithSum(List<Double> binBoundaries);
2729
}
2830
}

products/metrics/metrics-api/src/main/java/datadog/metrics/api/NoOpHistogram.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import java.util.Collections;
55
import java.util.List;
66

7-
class NoOpHistogram implements Histogram {
8-
public static final Histogram INSTANCE = new NoOpHistogram();
7+
class NoOpHistogram implements HistogramWithSum {
8+
public static final HistogramWithSum INSTANCE = new NoOpHistogram();
99

1010
@Override
1111
public double getCount() {

products/metrics/metrics-api/src/main/java/datadog/metrics/api/NoOpHistogramsFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ public Histogram newHistogram(double relativeAccuracy, int maxNumBins) {
1919
}
2020

2121
@Override
22-
public Histogram newHistogram(List<Double> binBoundaries) {
22+
public HistogramWithSum newHistogramWithSum(double relativeAccuracy, int maxNumBins) {
23+
return NoOpHistogram.INSTANCE;
24+
}
25+
26+
@Override
27+
public HistogramWithSum newHistogramWithSum(List<Double> binBoundaries) {
2328
return NoOpHistogram.INSTANCE;
2429
}
2530
}

products/metrics/metrics-lib/src/main/java/datadog/metrics/impl/DDSketchHistogram.java

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,71 +12,48 @@
1212
/** Wrapper around the DDSketch library so that it can be used in an instrumentation */
1313
public class DDSketchHistogram implements Histogram {
1414
private final DDSketch sketch;
15-
private double sum;
16-
// We use a compensated sum to avoid accumulating rounding errors.
17-
// See https://en.wikipedia.org/wiki/Kahan_summation_algorithm.
18-
private double sumCompensation; // Low order bits of sum
19-
private double simpleSum; // Used to compute right sum for non-finite inputs
2015

2116
public DDSketchHistogram(DDSketch sketch) {
2217
this.sketch = sketch;
23-
this.sum = 0;
24-
this.simpleSum = 0;
25-
this.sumCompensation = 0;
2618
}
2719

2820
@Override
29-
public double getCount() {
21+
public final double getCount() {
3022
return sketch.getCount();
3123
}
3224

3325
@Override
34-
public double getSum() {
35-
// Better error bounds to add both terms as the final sum
36-
final double tmp = sum + sumCompensation;
37-
if (Double.isNaN(tmp) && Double.isInfinite(simpleSum)) {
38-
// If the compensated sum is spuriously NaN from accumulating one or more same-signed infinite
39-
// values, return the correctly-signed infinity stored in simpleSum.
40-
return simpleSum;
41-
} else {
42-
return tmp;
43-
}
44-
}
45-
46-
@Override
47-
public boolean isEmpty() {
26+
public final boolean isEmpty() {
4827
return sketch.isEmpty();
4928
}
5029

5130
@Override
5231
public void accept(double value) {
5332
sketch.accept(value);
54-
updateSum(value);
5533
}
5634

5735
@Override
5836
public void accept(double value, double count) {
5937
sketch.accept(value, count);
60-
updateSum(value * count);
6138
}
6239

6340
@Override
64-
public double getValueAtQuantile(double quantile) {
41+
public final double getValueAtQuantile(double quantile) {
6542
return sketch.getValueAtQuantile(quantile);
6643
}
6744

6845
@Override
69-
public double getMinValue() {
46+
public final double getMinValue() {
7047
return sketch.getMinValue();
7148
}
7249

7350
@Override
74-
public double getMaxValue() {
51+
public final double getMaxValue() {
7552
return sketch.getMaxValue();
7653
}
7754

7855
@Override
79-
public List<Double> getBinBoundaries() {
56+
public final List<Double> getBinBoundaries() {
8057
List<Double> boundaries = new ArrayList<>();
8158
// Bin boundaries are defined as a sequence of upper bounds. When our sketch contains gaps we
8259
// must introduce extra bounds to represent the lower bound of any bins after a gap; otherwise
@@ -98,7 +75,7 @@ public List<Double> getBinBoundaries() {
9875
}
9976

10077
@Override
101-
public List<Double> getBinCounts() {
78+
public final List<Double> getBinCounts() {
10279
List<Double> counts = new ArrayList<>();
10380
// to maintain alignment with getBoundaries we must introduce zero counts for
10481
// boundaries inserted to represent the lower bound of any bins after a gap.
@@ -123,19 +100,7 @@ public void clear() {
123100
}
124101

125102
@Override
126-
public ByteBuffer serialize() {
103+
public final ByteBuffer serialize() {
127104
return sketch.serialize();
128105
}
129-
130-
private void updateSum(double value) {
131-
simpleSum += value;
132-
sumWithCompensation(value);
133-
}
134-
135-
private void sumWithCompensation(double value) {
136-
final double tmp = value - sumCompensation;
137-
final double velvel = sum + tmp; // Little wolf of rounding error
138-
sumCompensation = (velvel - sum) - tmp;
139-
sum = velvel;
140-
}
141106
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package datadog.metrics.impl;
2+
3+
import com.datadoghq.sketch.ddsketch.DDSketch;
4+
import datadog.metrics.api.HistogramWithSum;
5+
6+
/** Adds exact summary statistics to the DDSketch wrapper */
7+
public final class DDSketchHistogramWithSum extends DDSketchHistogram implements HistogramWithSum {
8+
private double sum;
9+
// We use a compensated sum to avoid accumulating rounding errors.
10+
// See https://en.wikipedia.org/wiki/Kahan_summation_algorithm.
11+
private double sumCompensation; // Low order bits of sum
12+
private double simpleSum; // Used to compute right sum for non-finite inputs
13+
14+
public DDSketchHistogramWithSum(DDSketch sketch) {
15+
super(sketch);
16+
}
17+
18+
@Override
19+
public double getSum() {
20+
// Better error bounds to add both terms as the final sum
21+
final double tmp = sum + sumCompensation;
22+
if (Double.isNaN(tmp) && Double.isInfinite(simpleSum)) {
23+
// If the compensated sum is spuriously NaN from accumulating one or more same-signed infinite
24+
// values, return the correctly-signed infinity stored in simpleSum.
25+
return simpleSum;
26+
} else {
27+
return tmp;
28+
}
29+
}
30+
31+
@Override
32+
public void accept(double value) {
33+
super.accept(value);
34+
updateSum(value);
35+
}
36+
37+
@Override
38+
public void accept(double value, double count) {
39+
super.accept(value, count);
40+
updateSum(value * count);
41+
}
42+
43+
@Override
44+
public void clear() {
45+
super.clear();
46+
sum = 0;
47+
simpleSum = 0;
48+
sumCompensation = 0;
49+
}
50+
51+
private void updateSum(double value) {
52+
simpleSum += value;
53+
sumWithCompensation(value);
54+
}
55+
56+
private void sumWithCompensation(double value) {
57+
final double tmp = value - sumCompensation;
58+
final double velvel = sum + tmp; // Little wolf of rounding error
59+
sumCompensation = (velvel - sum) - tmp;
60+
sum = velvel;
61+
}
62+
}

products/metrics/metrics-lib/src/main/java/datadog/metrics/impl/DDSketchHistograms.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.datadoghq.sketch.ddsketch.mapping.LogarithmicMapping;
1212
import com.datadoghq.sketch.ddsketch.store.CollapsingLowestDenseStore;
1313
import datadog.metrics.api.Histogram;
14+
import datadog.metrics.api.HistogramWithSum;
1415
import datadog.metrics.api.Histograms;
1516
import java.util.List;
1617

@@ -44,15 +45,21 @@ public Histogram newHistogram(double relativeAccuracy, int maxNumBins) {
4445
}
4546

4647
@Override
47-
public Histogram newHistogram(List<Double> binBoundaries) {
48+
public HistogramWithSum newHistogramWithSum(double relativeAccuracy, int maxNumBins) {
49+
DDSketch sketch = DDSketches.logarithmicCollapsingLowestDense(relativeAccuracy, maxNumBins);
50+
return new DDSketchHistogramWithSum(sketch);
51+
}
52+
53+
@Override
54+
public HistogramWithSum newHistogramWithSum(List<Double> binBoundaries) {
4855
validateBoundaries(binBoundaries);
4956
DDSketch sketch =
5057
new DDSketch(
5158
new ExplicitBoundaries(binBoundaries),
5259
() -> new CollapsingLowestDenseStore(0), // negative store not used
5360
() -> new CollapsingLowestDenseStore(binBoundaries.size() + 1),
5461
Double.NEGATIVE_INFINITY); // assign all negative/zero values to first bin
55-
return new DDSketchHistogram(sketch);
62+
return new DDSketchHistogramWithSum(sketch);
5663
}
5764

5865
static void validateBoundaries(List<Double> boundaries) {

0 commit comments

Comments
 (0)