From fc447f8861a48040ad4b956cf3e5af86f99985e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20St=C3=A4ber?= Date: Thu, 21 Mar 2024 20:11:43 +0100 Subject: [PATCH] Add optional SpanContext parameter to ExemplarSampler (#929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fabian Stäber --- .../metrics/config/ExemplarsProperties.java | 2 +- .../core/exemplars/ExemplarSampler.java | 14 ++- .../core/exemplars/ExemplarSamplerTest.java | 19 +--- .../exemplars/SpanContextSupplierTest.java | 103 ++++++++++++++++++ .../metrics/tracer/common/SpanContext.java | 4 +- 5 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java index 258e7f45d..ff955bc50 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java @@ -107,7 +107,7 @@ public Builder sampleIntervalMilliseconds(int sampleIntervalMilliseconds) { return this; } - public ExemplarsProperties builder() { + public ExemplarsProperties build() { return new ExemplarsProperties(minRetentionPeriodSeconds, maxRetentionPeriodSeconds, sampleIntervalMilliseconds); } } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java index 9472b237f..af898724d 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java @@ -37,11 +37,23 @@ public class ExemplarSampler { // to be overwritten by automatic exemplar sampling. exemplars.lengt == customExemplars.length private final AtomicBoolean acceptingNewExemplars = new AtomicBoolean(true); private final AtomicBoolean acceptingNewCustomExemplars = new AtomicBoolean(true); + private final SpanContext spanContext; // may be null, in that case SpanContextSupplier.getSpanContext() is used. public ExemplarSampler(ExemplarSamplerConfig config) { + this(config, null); + } + + /** + * Constructor with an additional {code spanContext} argument. + * This is useful for testing, but may also be useful in some production scenarios. + * If {@code spanContext != null} that spanContext is used and {@link SpanContextSupplier} is not used. + * If {@code spanContext == null} the {@link SpanContextSupplier#getSpanContext()} is called to find a span context. + */ + public ExemplarSampler(ExemplarSamplerConfig config, SpanContext spanContext) { this.config = config; this.exemplars = new Exemplar[config.getNumberOfExemplars()]; this.customExemplars = new Exemplar[exemplars.length]; + this.spanContext = spanContext; } public Exemplars collect() { @@ -307,8 +319,8 @@ private long updateExemplar(int index, double value, long now) { } private Labels doSampleExemplar() { + SpanContext spanContext = this.spanContext != null ? this.spanContext : SpanContextSupplier.getSpanContext(); try { - SpanContext spanContext = SpanContextSupplier.getSpanContext(); if (spanContext != null) { if (spanContext.isCurrentSpanSampled()) { String spanId = spanContext.getCurrentSpanId(); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java index 24882065e..1af5da42b 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java @@ -76,8 +76,7 @@ public void tearDown() { public void testIsSampled() throws Exception { SpanContext context = new SpanContext(); context.isSampled = false; - SpanContextSupplier.setSpanContext(context); - ExemplarSampler sampler = new ExemplarSampler(makeConfig()); + ExemplarSampler sampler = new ExemplarSampler(makeConfig(), context); Thread.sleep(tick); // t = 1 tick sampler.observe(0.3); // no sampled, because isSampled() returns false assertExemplars(sampler); // empty @@ -85,9 +84,7 @@ public void testIsSampled() throws Exception { @Test public void testDefaultConfigHasFourExemplars() throws Exception { - SpanContext context = new SpanContext(); - SpanContextSupplier.setSpanContext(context); - ExemplarSampler sampler = new ExemplarSampler(makeConfig()); + ExemplarSampler sampler = new ExemplarSampler(makeConfig(), new SpanContext()); Thread.sleep(tick); // t = 1 tick sampler.observe(0.3); Thread.sleep(sampleInterval + tick); // t = 12 tick @@ -104,9 +101,7 @@ public void testDefaultConfigHasFourExemplars() throws Exception { @Test public void testEmptyBuckets() throws Exception { - SpanContext context = new SpanContext(); - SpanContextSupplier.setSpanContext(context); - ExemplarSampler sampler = new ExemplarSampler(makeConfig(Double.POSITIVE_INFINITY)); + ExemplarSampler sampler = new ExemplarSampler(makeConfig(Double.POSITIVE_INFINITY), new SpanContext()); Thread.sleep(tick); // t = 1 tick sampler.observe(0.8); // observed in the +Inf bucket Thread.sleep(sampleInterval + tick); // t = 12 tick @@ -117,9 +112,7 @@ public void testEmptyBuckets() throws Exception { @Test public void testDefaultExemplarsBuckets() throws Exception { - SpanContext context = new SpanContext(); - SpanContextSupplier.setSpanContext(context); - ExemplarSampler sampler = new ExemplarSampler(makeConfig(0.2, 0.4, 0.6, 0.8, 1.0, Double.POSITIVE_INFINITY)); + ExemplarSampler sampler = new ExemplarSampler(makeConfig(0.2, 0.4, 0.6, 0.8, 1.0, Double.POSITIVE_INFINITY), new SpanContext()); Scheduler.awaitInitialization(); Thread.sleep(tick); // t = 1 tick sampler.observe(0.3); @@ -150,9 +143,7 @@ public void testCustomExemplarsNoBuckets() throws Exception { @Test public void testDefaultExemplarsNoBuckets() throws Exception { - SpanContext context = new SpanContext(); - SpanContextSupplier.setSpanContext(context); - ExemplarSampler sampler = new ExemplarSampler(makeConfig()); + ExemplarSampler sampler = new ExemplarSampler(makeConfig(), new SpanContext()); Scheduler.awaitInitialization(); Thread.sleep(tick); // t = 1 tick sampler.observe(1); // observed diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java new file mode 100644 index 000000000..033420950 --- /dev/null +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java @@ -0,0 +1,103 @@ +package io.prometheus.metrics.core.exemplars; + +import io.prometheus.metrics.config.ExemplarsProperties; +import io.prometheus.metrics.model.snapshots.Exemplar; +import io.prometheus.metrics.model.snapshots.Exemplars; +import io.prometheus.metrics.tracer.common.SpanContext; +import io.prometheus.metrics.tracer.initializer.SpanContextSupplier; +import org.junit.*; + +import static io.prometheus.metrics.model.snapshots.Exemplar.TRACE_ID; + +public class SpanContextSupplierTest { + + public SpanContext makeSpanContext(String traceId, String spanId) { + + return new SpanContext() { + @Override + public String getCurrentTraceId() { + return traceId; + } + + @Override + public String getCurrentSpanId() { + return spanId; + } + + @Override + public boolean isCurrentSpanSampled() { + return true; + } + + @Override + public void markCurrentSpanAsExemplar() { + } + }; + } + + SpanContext spanContextA = makeSpanContext("A", "a"); + SpanContext spanContextB = makeSpanContext("B", "b"); + SpanContext origSpanContext; + + ExemplarSamplerConfig config = new ExemplarSamplerConfig( + 10, // min retention period in milliseconds + 20, // max retention period in milliseconds + 5, // sample interval in millisecnods + 1, // number of exemplars + null // histogram upper bounds + ); + + @Before + public void setUp() { + origSpanContext = SpanContextSupplier.getSpanContext(); + } + + @After + public void tearDown() { + SpanContextSupplier.setSpanContext(origSpanContext); + } + + /** + * Test: When a {@link SpanContext} is provided as a constructor argument to the {@link ExemplarSampler}, + * then that {@link SpanContext} is used, not the one from the {@link SpanContextSupplier}. + */ + @Test + public void testConstructorInjection() { + ExemplarsProperties properties = ExemplarsProperties.builder().build(); + ExemplarSamplerConfig config = new ExemplarSamplerConfig(properties, 1); + ExemplarSampler exemplarSampler = new ExemplarSampler(config, spanContextA); + + SpanContextSupplier.setSpanContext(spanContextB); + exemplarSampler.observe(1.0); + Exemplars exemplars = exemplarSampler.collect(); + Assert.assertEquals(1, exemplars.size()); + Exemplar exemplar = exemplars.get(0); + Assert.assertEquals("A", exemplar.getLabels().get(TRACE_ID)); + } + + /** + * When the global {@link SpanContext} is updated via {@link SpanContextSupplier#setSpanContext(SpanContext)}, + * the {@link ExemplarSampler} recognizes the update (unless a {@link ExemplarSampler} was provided as + * constructor argument to {@link ExemplarSampler}). + */ + @Test + public void testUpdateSpanContext() throws InterruptedException { + ExemplarSampler exemplarSampler = new ExemplarSampler(config); + + SpanContextSupplier.setSpanContext(spanContextB); + exemplarSampler.observe(1.0); + Exemplars exemplars = exemplarSampler.collect(); + Assert.assertEquals(1, exemplars.size()); + Exemplar exemplar = exemplars.get(0); + Assert.assertEquals("B", exemplar.getLabels().get(TRACE_ID)); + + Thread.sleep(15); // more than the minimum retention period defined in config above. + + SpanContextSupplier.setSpanContext(spanContextA); + exemplarSampler.observe(1.0); + exemplars = exemplarSampler.collect(); + Assert.assertEquals(1, exemplars.size()); + exemplar = exemplars.get(0); + Assert.assertEquals("A", exemplar.getLabels().get(TRACE_ID)); + } +} diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java index b53fe4d20..e71cef98e 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java @@ -2,8 +2,8 @@ public interface SpanContext { - public static final String EXEMPLAR_ATTRIBUTE_NAME = "exemplar"; - public static final String EXEMPLAR_ATTRIBUTE_VALUE = "true"; + String EXEMPLAR_ATTRIBUTE_NAME = "exemplar"; + String EXEMPLAR_ATTRIBUTE_VALUE = "true"; /** * @return the current trace id, or {@code null} if this call is not happening within a span context.