Skip to content

Commit 0b13192

Browse files
committed
reduce flakyness
1 parent c288708 commit 0b13192

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@ public class ExemplarSampler {
4040
// to be overwritten by automatic exemplar sampling. exemplars.length == customExemplars.length
4141
private final AtomicBoolean acceptingNewExemplars = new AtomicBoolean(true);
4242
private final AtomicBoolean acceptingNewCustomExemplars = new AtomicBoolean(true);
43+
private final LongSupplier currentTimeMillis;
4344

4445
@Nullable
4546
private final SpanContext
4647
spanContext; // may be null, in that case SpanContextSupplier.getSpanContext() is used.
4748

4849
public ExemplarSampler(ExemplarSamplerConfig config) {
49-
this(config, null);
50+
this(config, null, System::currentTimeMillis);
5051
}
5152

5253
/**
@@ -58,15 +59,32 @@ public ExemplarSampler(ExemplarSamplerConfig config) {
5859
* SpanContextSupplier.getSpanContext()} is called to find a span context.
5960
*/
6061
public ExemplarSampler(ExemplarSamplerConfig config, @Nullable SpanContext spanContext) {
62+
this(config, spanContext, System::currentTimeMillis);
63+
}
64+
65+
/**
66+
* Constructor with an additional {code currentTimeMillis} argument for testing. This allows
67+
* injecting a custom time source to make tests deterministic and avoid flaky tests caused by
68+
* timing issues.
69+
*
70+
* @param config the exemplar sampler configuration
71+
* @param spanContext the span context, may be null
72+
* @param currentTimeMillis time source function that returns current time in milliseconds
73+
*/
74+
public ExemplarSampler(
75+
ExemplarSamplerConfig config,
76+
@Nullable SpanContext spanContext,
77+
LongSupplier currentTimeMillis) {
6178
this.config = config;
6279
this.exemplars = new Exemplar[config.getNumberOfExemplars()];
6380
this.customExemplars = new Exemplar[exemplars.length];
6481
this.spanContext = spanContext;
82+
this.currentTimeMillis = currentTimeMillis;
6583
}
6684

6785
public Exemplars collect() {
6886
// this may run in parallel with observe()
69-
long now = System.currentTimeMillis();
87+
long now = currentTimeMillis.getAsLong();
7088
List<Exemplar> result = new ArrayList<>(exemplars.length);
7189
for (int i = 0; i < customExemplars.length; i++) {
7290
Exemplar exemplar = customExemplars[i];
@@ -129,7 +147,7 @@ private long doObserve(double value) {
129147
}
130148

131149
private long doObserveSingleExemplar(double value) {
132-
long now = System.currentTimeMillis();
150+
long now = currentTimeMillis.getAsLong();
133151
Exemplar current = exemplars[0];
134152
if (current == null
135153
|| now - current.getTimestampMillis() > config.getMinRetentionPeriodMillis()) {
@@ -139,7 +157,7 @@ private long doObserveSingleExemplar(double value) {
139157
}
140158

141159
private long doObserveSingleExemplar(double amount, Labels labels) {
142-
long now = System.currentTimeMillis();
160+
long now = currentTimeMillis.getAsLong();
143161
Exemplar current = customExemplars[0];
144162
if (current == null
145163
|| now - current.getTimestampMillis() > config.getMinRetentionPeriodMillis()) {
@@ -149,7 +167,7 @@ private long doObserveSingleExemplar(double amount, Labels labels) {
149167
}
150168

151169
private long doObserveWithUpperBounds(double value, double[] classicUpperBounds) {
152-
long now = System.currentTimeMillis();
170+
long now = currentTimeMillis.getAsLong();
153171
for (int i = 0; i < classicUpperBounds.length; i++) {
154172
if (value <= classicUpperBounds[i]) {
155173
Exemplar previous = exemplars[i];
@@ -165,7 +183,7 @@ private long doObserveWithUpperBounds(double value, double[] classicUpperBounds)
165183
}
166184

167185
private long doObserveWithoutUpperBounds(double value) {
168-
final long now = System.currentTimeMillis();
186+
final long now = currentTimeMillis.getAsLong();
169187
Exemplar smallest = null;
170188
int smallestIndex = -1;
171189
Exemplar largest = null;
@@ -234,7 +252,7 @@ private long doObserveWithExemplar(double amount, Labels labels) {
234252

235253
private long doObserveWithExemplarWithUpperBounds(
236254
double value, Labels labels, double[] classicUpperBounds) {
237-
long now = System.currentTimeMillis();
255+
long now = currentTimeMillis.getAsLong();
238256
for (int i = 0; i < classicUpperBounds.length; i++) {
239257
if (value <= classicUpperBounds[i]) {
240258
Exemplar previous = customExemplars[i];
@@ -250,7 +268,7 @@ private long doObserveWithExemplarWithUpperBounds(
250268
}
251269

252270
private long doObserveWithExemplarWithoutUpperBounds(double amount, Labels labels) {
253-
final long now = System.currentTimeMillis();
271+
final long now = currentTimeMillis.getAsLong();
254272
int nullPos = -1;
255273
int oldestPos = -1;
256274
Exemplar oldest = null;

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.atomic.AtomicBoolean;
2525
import java.util.concurrent.atomic.DoubleAdder;
2626
import java.util.concurrent.atomic.LongAdder;
27+
import java.util.function.LongSupplier;
2728
import javax.annotation.Nullable;
2829

2930
/**
@@ -71,6 +72,9 @@ public class Histogram extends StatefulMetric<DistributionDataPoint, Histogram.D
7172

7273
@Nullable private final ExemplarSamplerConfig exemplarSamplerConfig;
7374

75+
// For testing: allows injecting a custom time source for exemplar sampling
76+
@Nullable LongSupplier exemplarSamplerTimeSource = null;
77+
7478
// Upper bounds for the classic histogram buckets. Contains at least +Inf.
7579
// An empty array indicates that this is a native histogram only.
7680
private final double[] classicUpperBounds;
@@ -199,7 +203,12 @@ public class DataPoint implements DistributionDataPoint {
199203

200204
private DataPoint() {
201205
if (exemplarSamplerConfig != null) {
202-
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
206+
if (exemplarSamplerTimeSource != null) {
207+
exemplarSampler =
208+
new ExemplarSampler(exemplarSamplerConfig, null, exemplarSamplerTimeSource);
209+
} else {
210+
exemplarSampler = new ExemplarSampler(exemplarSamplerConfig);
211+
}
203212
} else {
204213
exemplarSampler = null;
205214
}

prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerConfigTestUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.prometheus.metrics.core.exemplars;
22

33
import java.lang.reflect.Field;
4+
import java.util.function.LongSupplier;
45

56
public class ExemplarSamplerConfigTestUtil {
67

@@ -29,4 +30,11 @@ public static void setSampleIntervalMillis(Object metric, long value)
2930
ExemplarSamplerConfig config = getConfig(metric, "exemplarSamplerConfig");
3031
setRetentionPeriod(config, "sampleIntervalMillis", value);
3132
}
33+
34+
public static void setExemplarSamplerTimeSource(Object metric, LongSupplier timeSource)
35+
throws NoSuchFieldException, IllegalAccessException {
36+
Field timeSourceField = metric.getClass().getDeclaredField("exemplarSamplerTimeSource");
37+
timeSourceField.setAccessible(true);
38+
timeSourceField.set(metric, timeSource);
39+
}
3240
}

prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.concurrent.Future;
3838
import java.util.concurrent.TimeUnit;
3939
import java.util.concurrent.TimeoutException;
40+
import java.util.function.LongSupplier;
4041
import java.util.stream.Collectors;
4142
import org.junit.jupiter.api.AfterEach;
4243
import org.junit.jupiter.api.BeforeEach;
@@ -956,6 +957,21 @@ public void testDefaults() throws IOException {
956957

957958
@Test
958959
public void testExemplarsClassicHistogram() throws Exception {
960+
class MockTimeSource implements LongSupplier {
961+
private long currentTimeMillis = 0;
962+
963+
@Override
964+
public long getAsLong() {
965+
return currentTimeMillis;
966+
}
967+
968+
void advance(long millis) {
969+
currentTimeMillis += millis;
970+
}
971+
}
972+
973+
MockTimeSource mockTime = new MockTimeSource();
974+
959975
SpanContext spanContext =
960976
new SpanContext() {
961977
int callCount = 0;
@@ -990,6 +1006,7 @@ public void markCurrentSpanAsExemplar() {}
9901006

9911007
long sampleIntervalMillis = 10;
9921008
ExemplarSamplerConfigTestUtil.setSampleIntervalMillis(histogram, sampleIntervalMillis);
1009+
ExemplarSamplerConfigTestUtil.setExemplarSamplerTimeSource(histogram, mockTime);
9931010
SpanContextSupplier.setSpanContext(spanContext);
9941011

9951012
Exemplar ex1a = Exemplar.builder().value(0.5).spanId("spanId-1").traceId("traceId-1").build();
@@ -1020,7 +1037,9 @@ public void markCurrentSpanAsExemplar() {}
10201037
assertThat(getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/hello")).isNull();
10211038
assertThat(getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/world")).isNull();
10221039

1023-
Thread.sleep(sampleIntervalMillis + 1);
1040+
mockTime.advance(sampleIntervalMillis + 1);
1041+
// Small wait to let the scheduler re-enable accepting new exemplars
1042+
Thread.sleep(50);
10241043
histogram.labelValues("/hello").observe(4.5);
10251044
histogram.labelValues("/world").observe(4.5);
10261045

@@ -1036,13 +1055,16 @@ public void markCurrentSpanAsExemplar() {}
10361055
assertExemplarEquals(ex2a, getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/hello"));
10371056
assertExemplarEquals(ex2b, getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/world"));
10381057

1039-
Thread.sleep(sampleIntervalMillis + 1);
1058+
mockTime.advance(sampleIntervalMillis + 1);
1059+
Thread.sleep(50);
10401060
histogram.labelValues("/hello").observe(1.5);
10411061
histogram.labelValues("/world").observe(1.5);
1042-
Thread.sleep(sampleIntervalMillis + 1);
1062+
mockTime.advance(sampleIntervalMillis + 1);
1063+
Thread.sleep(50);
10431064
histogram.labelValues("/hello").observe(2.5);
10441065
histogram.labelValues("/world").observe(2.5);
1045-
Thread.sleep(sampleIntervalMillis + 1);
1066+
mockTime.advance(sampleIntervalMillis + 1);
1067+
Thread.sleep(50);
10461068
histogram.labelValues("/hello").observe(3.5);
10471069
histogram.labelValues("/world").observe(3.5);
10481070

@@ -1072,7 +1094,8 @@ public void markCurrentSpanAsExemplar() {}
10721094
"span_id",
10731095
"spanId-11"))
10741096
.build();
1075-
Thread.sleep(sampleIntervalMillis + 1);
1097+
mockTime.advance(sampleIntervalMillis + 1);
1098+
Thread.sleep(50);
10761099
histogram
10771100
.labelValues("/hello")
10781101
.observeWithExemplar(3.4, Labels.of("key1", "value1", "key2", "value2"));

0 commit comments

Comments
 (0)