From 873086bfd33d4ada3f32ff8f56485198d56de079 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 20 Mar 2018 07:18:11 +0100
Subject: [PATCH 1/2] Implement sampling
(closes #3)
Signed-off-by: Felix Barnsteiner
---
.../apm/impl/AbstractReporterBenchmark.java | 3 +-
.../apm/configuration/CoreConfiguration.java | 14 +++-
.../co/elastic/apm/impl/ElasticApmTracer.java | 21 ++++--
.../apm/impl/sampling/ConstantSampler.java | 29 +++++++++
.../apm/impl/sampling/ProbabilitySampler.java | 30 +++++++++
.../co/elastic/apm/impl/sampling/Sampler.java | 28 ++++++++
.../co/elastic/apm/impl/transaction/Span.java | 1 +
.../apm/impl/transaction/Transaction.java | 5 +-
.../apm/impl/transaction/TransactionId.java | 5 ++
.../apm/configuration/SpyConfiguration.java | 4 +-
.../apm/impl/ElasticApmTracerTest.java | 15 +++++
.../TransactionPayloadJsonSchemaTest.java | 3 +-
.../impl/sampling/ProbabilitySamplerTest.java | 64 +++++++++++++++++++
13 files changed, 211 insertions(+), 11 deletions(-)
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java
diff --git a/apm-agent-benchmarks/src/main/java/co/elastic/apm/impl/AbstractReporterBenchmark.java b/apm-agent-benchmarks/src/main/java/co/elastic/apm/impl/AbstractReporterBenchmark.java
index 7eea5cad1b..d830032093 100644
--- a/apm-agent-benchmarks/src/main/java/co/elastic/apm/impl/AbstractReporterBenchmark.java
+++ b/apm-agent-benchmarks/src/main/java/co/elastic/apm/impl/AbstractReporterBenchmark.java
@@ -10,6 +10,7 @@
import co.elastic.apm.impl.payload.Service;
import co.elastic.apm.impl.payload.SystemInfo;
import co.elastic.apm.impl.payload.TransactionPayload;
+import co.elastic.apm.impl.sampling.ConstantSampler;
import co.elastic.apm.impl.stacktrace.StacktraceFactory;
import co.elastic.apm.impl.transaction.Span;
import co.elastic.apm.impl.transaction.Transaction;
@@ -69,7 +70,7 @@ public void setUp() throws Exception {
payload = new TransactionPayload(process, service, system);
for (int i = 0; i < reporterConfiguration.getMaxQueueSize(); i++) {
Transaction t = new Transaction();
- t.start(tracer, 0, true);
+ t.start(tracer, 0, ConstantSampler.of(true));
fillTransaction(t);
payload.getTransactions().add(t);
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/configuration/CoreConfiguration.java
index 16936844fb..1bbb9e71ec 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/configuration/CoreConfiguration.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/configuration/CoreConfiguration.java
@@ -57,6 +57,16 @@ public class CoreConfiguration extends ConfigurationOptionProvider {
"To reduce overhead and storage requirements, you can set the sample rate to a value between 0.0 and 1.0. " +
"We still record overall time and the result for unsampled transactions, but no context information, tags, or spans.")
.dynamic(true)
+ .addValidator(new ConfigurationOption.Validator() {
+ @Override
+ public void assertValid(Double value) {
+ if (value != null) {
+ if (value < 0 || value > 1) {
+ throw new IllegalArgumentException("The sample rate must be between 0 and 1");
+ }
+ }
+ }
+ })
.buildWithDefault(1.0);
private final ConfigurationOption transactionMaxSpans = ConfigurationOption.integerOption()
@@ -87,8 +97,8 @@ public String getEnvironment() {
return environment.get();
}
- public double getSampleRate() {
- return sampleRate.get();
+ public ConfigurationOption getSampleRate() {
+ return sampleRate;
}
public int getTransactionMaxSpans() {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java
index cfd168af76..89ab2d78ef 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/ElasticApmTracer.java
@@ -6,6 +6,9 @@
import co.elastic.apm.configuration.CoreConfiguration;
import co.elastic.apm.configuration.PrefixingConfigurationSourceWrapper;
import co.elastic.apm.impl.error.ErrorCapture;
+import co.elastic.apm.impl.sampling.ConstantSampler;
+import co.elastic.apm.impl.sampling.ProbabilitySampler;
+import co.elastic.apm.impl.sampling.Sampler;
import co.elastic.apm.impl.stacktrace.Stacktrace;
import co.elastic.apm.impl.stacktrace.StacktraceConfiguration;
import co.elastic.apm.impl.stacktrace.StacktraceFactory;
@@ -21,6 +24,7 @@
import com.blogspot.mydailyjava.weaklockfree.DetachedThreadLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.stagemonitor.configuration.ConfigurationOption;
import org.stagemonitor.configuration.ConfigurationOptionProvider;
import org.stagemonitor.configuration.ConfigurationRegistry;
import org.stagemonitor.configuration.source.EnvironmentVariableConfigurationSource;
@@ -28,6 +32,7 @@
import org.stagemonitor.configuration.source.SimpleSource;
import org.stagemonitor.configuration.source.SystemPropertyConfigurationSource;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
@@ -56,6 +61,7 @@ public class ElasticApmTracer implements Tracer {
private final CoreConfiguration coreConfiguration;
private final Transaction noopTransaction;
private final Span noopSpan;
+ private Sampler sampler;
ElasticApmTracer(ConfigurationRegistry configurationRegistry, Reporter reporter, StacktraceFactory stacktraceFactory) {
this.configurationRegistry = configurationRegistry;
@@ -91,9 +97,15 @@ public Stacktrace createInstance() {
}
});
coreConfiguration = configurationRegistry.getConfig(CoreConfiguration.class);
- noopTransaction = new Transaction().withName("noop").withType("noop").start(this, 0, false);
+ noopTransaction = new Transaction().withName("noop").withType("noop").start(this, 0, ConstantSampler.of(false));
noopSpan = new Span().withName("noop").withType("noop").start(this, noopTransaction, null, 0, true);
-
+ sampler = ProbabilitySampler.of(coreConfiguration.getSampleRate().get());
+ coreConfiguration.getSampleRate().addChangeListener(new ConfigurationOption.ChangeListener() {
+ @Override
+ public void onChange(ConfigurationOption> configurationOption, Double oldValue, Double newValue) {
+ sampler = ProbabilitySampler.of(newValue);
+ }
+ });
}
public static Builder builder() {
@@ -121,13 +133,14 @@ ElasticApmTracer register() {
return this;
}
+ @Nonnull
@Override
public Transaction startTransaction() {
Transaction transaction;
if (!coreConfiguration.isActive()) {
transaction = noopTransaction;
} else {
- transaction = transactionPool.createInstance().start(this, System.nanoTime(), true);
+ transaction = transactionPool.createInstance().start(this, System.nanoTime(), sampler);
}
currentTransaction.set(transaction);
return transaction;
@@ -143,6 +156,7 @@ public Span currentSpan() {
return currentSpan.get();
}
+ @Nonnull
@Override
public Span startSpan() {
Transaction transaction = currentTransaction();
@@ -167,7 +181,6 @@ private Span createRealSpan(Transaction transaction) {
transaction.getSpanCount().getDropped().increment();
} else {
dropped = false;
- transaction.addSpan(span);
}
span.start(this, transaction, currentSpan(), System.nanoTime(), dropped);
return span;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
new file mode 100644
index 0000000000..6264dd9165
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
@@ -0,0 +1,29 @@
+package co.elastic.apm.impl.sampling;
+
+import co.elastic.apm.impl.transaction.TransactionId;
+
+public class ConstantSampler implements Sampler {
+
+ private static final Sampler TRUE = new ConstantSampler(true);
+ private static final Sampler FALSE = new ConstantSampler(false);
+
+ private final boolean decision;
+
+ private ConstantSampler(boolean decision) {
+ this.decision = decision;
+ }
+
+ public static Sampler of(boolean decision) {
+ if (decision) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ @Override
+
+ public boolean isSampled(TransactionId transactionId) {
+ return decision;
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
new file mode 100644
index 0000000000..be4174bbb4
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
@@ -0,0 +1,30 @@
+package co.elastic.apm.impl.sampling;
+
+import co.elastic.apm.impl.transaction.TransactionId;
+
+public class ProbabilitySampler implements Sampler {
+
+ private final long lowerBound;
+ private final long higherBound;
+
+ public static Sampler of(double samplingRate) {
+ if (samplingRate == 1) {
+ return ConstantSampler.of(true);
+ }
+ if (samplingRate == 0) {
+ return ConstantSampler.of(false);
+ }
+ return new ProbabilitySampler(samplingRate);
+ }
+
+ private ProbabilitySampler(double samplingRate) {
+ higherBound = (long) (Long.MAX_VALUE * samplingRate);
+ lowerBound = -higherBound;
+ }
+
+ @Override
+ public boolean isSampled(TransactionId transactionId) {
+ final long mostSignificantBits = transactionId.getMostSignificantBits();
+ return mostSignificantBits > lowerBound && mostSignificantBits < higherBound;
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
new file mode 100644
index 0000000000..ae8d9ad0ae
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
@@ -0,0 +1,28 @@
+package co.elastic.apm.impl.sampling;
+
+import co.elastic.apm.impl.transaction.TransactionId;
+
+/**
+ * A sampler is responsible for determining whether a {@link co.elastic.apm.api.Transaction} should be sampled.
+ *
+ * In contrast other tracing systems,
+ * in Elastic APM,
+ * non-sampled {@link co.elastic.apm.api.Transaction}s do get reported to the APM server.
+ * However,
+ * to keep the size at a minimum,
+ * the reported {@link co.elastic.apm.api.Transaction} only contains the transaction name,
+ * the duration and the id.
+ * Also,
+ * {@link co.elastic.apm.api.Span}s of non sampled {@link co.elastic.apm.api.Transaction}s are not reported.
+ *
+ */
+public interface Sampler {
+
+ /**
+ * Determines whether the given transaction should be sampled.
+ *
+ * @param transactionId The id of the transaction.
+ * @return The sampling decision.
+ */
+ boolean isSampled(TransactionId transactionId);
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Span.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Span.java
index d15ffd963f..1252d8f2eb 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Span.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Span.java
@@ -79,6 +79,7 @@ public Span start(ElasticApmTracer tracer, Transaction transaction, @Nullable Sp
if (sampled) {
start = (nanoTime - transaction.getDuration()) / MS_IN_NANOS;
duration = nanoTime;
+ transaction.addSpan(this);
}
return this;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Transaction.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Transaction.java
index 7d48ec6390..cfc0357fd1 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Transaction.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/Transaction.java
@@ -2,6 +2,7 @@
import co.elastic.apm.impl.ElasticApmTracer;
import co.elastic.apm.impl.context.Context;
+import co.elastic.apm.impl.sampling.Sampler;
import co.elastic.apm.objectpool.Recyclable;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -86,12 +87,12 @@ public class Transaction implements Recyclable, co.elastic.apm.api.Transaction {
@JsonProperty("sampled")
private boolean sampled;
- public Transaction start(ElasticApmTracer tracer, long startTimestampNanos, boolean sampled) {
+ public Transaction start(ElasticApmTracer tracer, long startTimestampNanos, Sampler sampler) {
this.tracer = tracer;
this.duration = startTimestampNanos;
- this.sampled = sampled;
this.timestamp.setTime(System.currentTimeMillis());
this.id.setToRandomValue();
+ this.sampled = sampler.isSampled(id);
return this;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TransactionId.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TransactionId.java
index a96e44db29..eab0851bbd 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TransactionId.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/transaction/TransactionId.java
@@ -6,6 +6,7 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
@@ -29,6 +30,10 @@ public void setToRandomValue(Random random) {
random.nextBytes(data);
}
+ public void setValue(long mostSignificantBits, long leastSignificantBits) {
+ ByteBuffer.wrap(data).putLong(mostSignificantBits).putLong(leastSignificantBits);
+ }
+
@Override
public void resetState() {
for (int i = 0; i < data.length; i++) {
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/configuration/SpyConfiguration.java b/apm-agent-core/src/test/java/co/elastic/apm/configuration/SpyConfiguration.java
index 1ca6e9c04c..f54a56bc3e 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/configuration/SpyConfiguration.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/configuration/SpyConfiguration.java
@@ -10,6 +10,8 @@
public class SpyConfiguration {
+ public static final String CONFIG_SOURCE_NAME = "test config source";
+
/**
* Creates a configuration registry where all {@link ConfigurationOptionProvider}s are wrapped with
* {@link org.mockito.Mockito#spy(Object)}
@@ -24,7 +26,7 @@ public static ConfigurationRegistry createSpyConfig() {
builder.addOptionProvider(spy(options));
}
return builder
- .addConfigSource(SimpleSource.forTest("service_name", "elastic-apm-test"))
+ .addConfigSource(new SimpleSource(CONFIG_SOURCE_NAME).add("service_name", "elastic-apm-test"))
.build();
}
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/ElasticApmTracerTest.java
index 832b8805e1..3393a95e37 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/impl/ElasticApmTracerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/ElasticApmTracerTest.java
@@ -14,6 +14,8 @@
import org.junit.jupiter.api.Test;
import org.stagemonitor.configuration.ConfigurationRegistry;
+import java.io.IOException;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@@ -195,4 +197,17 @@ void testDisableMidTransaction() {
assertThat(reporter.getFirstTransaction()).isSameAs(transaction);
}
+
+ @Test
+ void testSamplingNone() throws IOException {
+ config.getConfig(CoreConfiguration.class).getSampleRate().update(0.0, SpyConfiguration.CONFIG_SOURCE_NAME);
+ try (Transaction transaction = tracerImpl.startTransaction()) {
+ transaction.setUser("1", "jon.doe@example.com", "jondoe");
+ try (Span span = tracerImpl.startSpan()) {
+ }
+ }
+ assertThat(reporter.getTransactions()).hasSize(1);
+ assertThat(reporter.getFirstTransaction().getSpans()).hasSize(0);
+ assertThat(reporter.getFirstTransaction().getContext().getUser().getEmail()).isNull();
+ }
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/payload/TransactionPayloadJsonSchemaTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/payload/TransactionPayloadJsonSchemaTest.java
index 505448393b..28a59e6ecb 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/impl/payload/TransactionPayloadJsonSchemaTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/payload/TransactionPayloadJsonSchemaTest.java
@@ -1,6 +1,7 @@
package co.elastic.apm.impl.payload;
import co.elastic.apm.impl.ElasticApmTracer;
+import co.elastic.apm.impl.sampling.ConstantSampler;
import co.elastic.apm.impl.transaction.Span;
import co.elastic.apm.impl.transaction.Transaction;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -35,7 +36,7 @@ private TransactionPayload createPayloadWithRequiredValues() {
private Transaction createTransactionWithRequiredValues() {
Transaction t = new Transaction();
- t.start(mock(ElasticApmTracer.class), 0, true);
+ t.start(mock(ElasticApmTracer.class), 0, ConstantSampler.of(true));
t.setType("type");
t.getContext().getRequest().withMethod("GET");
Span s = new Span();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java
new file mode 100644
index 0000000000..ee7b1e4b9b
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/impl/sampling/ProbabilitySamplerTest.java
@@ -0,0 +1,64 @@
+package co.elastic.apm.impl.sampling;
+
+import co.elastic.apm.impl.transaction.TransactionId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ProbabilitySamplerTest {
+
+ public static final int ITERATIONS = 1_000_000;
+ public static final int DELTA = (int) (ITERATIONS * 0.01);
+ public static final double SAMPLING_RATE = 0.5;
+ private Sampler sampler;
+
+ @BeforeEach
+ void setUp() {
+ sampler = ProbabilitySampler.of(SAMPLING_RATE);
+ }
+
+ @Test
+ void isSampledEmpiricalTest() {
+ int sampledTransactions = 0;
+ TransactionId id = new TransactionId();
+ for (int i = 0; i < ITERATIONS; i++) {
+ id.setToRandomValue();
+ if (sampler.isSampled(id)) {
+ sampledTransactions++;
+ }
+ }
+ assertThat(sampledTransactions).isBetween((int) (SAMPLING_RATE * ITERATIONS - DELTA), (int) (SAMPLING_RATE * ITERATIONS + DELTA));
+ }
+
+ @Test
+ void testSamplingUpperBoundary() {
+ long upperBound = Long.MAX_VALUE / 2;
+ final TransactionId transactionId = new TransactionId();
+
+ transactionId.setValue(upperBound - 1, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue();
+
+ transactionId.setValue(upperBound, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue();
+
+ transactionId.setValue(upperBound + 1, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isFalse();
+ }
+
+ @Test
+ void testSamplingLowerBoundary() {
+ long lowerBound = -Long.MAX_VALUE / 2;
+ final TransactionId transactionId = new TransactionId();
+
+ transactionId.setValue(lowerBound + 1, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue();
+
+ transactionId.setValue(lowerBound, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isTrue();
+
+ transactionId.setValue(lowerBound - 1, 0);
+ assertThat(ProbabilitySampler.of(0.5).isSampled(transactionId)).isFalse();
+ }
+
+}
From 3ac211ab2ecbc24a2c20664a1a79806be9d27fef Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Wed, 21 Mar 2018 14:51:07 +0100
Subject: [PATCH 2/2] add docs
---
.../apm/impl/sampling/ConstantSampler.java | 4 +++-
.../apm/impl/sampling/ProbabilitySampler.java | 24 +++++++++++++++++++
.../co/elastic/apm/impl/sampling/Sampler.java | 9 +++----
3 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
index 6264dd9165..8446895291 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ConstantSampler.java
@@ -2,6 +2,9 @@
import co.elastic.apm.impl.transaction.TransactionId;
+/**
+ * This is a implementation of {@link Sampler} which always returns the same sampling decision.
+ */
public class ConstantSampler implements Sampler {
private static final Sampler TRUE = new ConstantSampler(true);
@@ -22,7 +25,6 @@ public static Sampler of(boolean decision) {
}
@Override
-
public boolean isSampled(TransactionId transactionId) {
return decision;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
index be4174bbb4..e168a2a232 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/ProbabilitySampler.java
@@ -2,6 +2,30 @@
import co.elastic.apm.impl.transaction.TransactionId;
+/**
+ * This implementation of {@link Sampler} samples based on a sampling probability (or sampling rate) between 0.0 and 1.0.
+ *
+ * A sampling rate of 0.5 means that 50% of all transactions should be {@linkplain Sampler sampled}.
+ *
+ *
+ * Implementation notes:
+ *
+ *
+ * We are taking advantage of the fact, that the {@link TransactionId} is randomly generated.
+ * So instead of generating another random number,
+ * we just see if the long value returned by {@link TransactionId#getMostSignificantBits()}
+ * falls into the range between the lowerBound and the higherBound.
+ * This is a visual representation of the mechanism with a sampling rate of 0.5 (=50%):
+ *
+ * Long.MIN_VALUE 0 Long.MAX_VALUE
+ * v v v
+ * [----------[----------|----------]----------]
+ * ^ ^
+ * lowerBound higherBound = Long.MAX_VALUE * samplingRate
+ * = Long.MAX_VALUE * samplingRate * -1
+ *
+ *
+ */
public class ProbabilitySampler implements Sampler {
private final long lowerBound;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
index ae8d9ad0ae..b57785efd6 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/impl/sampling/Sampler.java
@@ -1,19 +1,20 @@
package co.elastic.apm.impl.sampling;
+import co.elastic.apm.api.Transaction;
import co.elastic.apm.impl.transaction.TransactionId;
/**
- * A sampler is responsible for determining whether a {@link co.elastic.apm.api.Transaction} should be sampled.
+ * A sampler is responsible for determining whether a {@link Transaction} should be sampled.
*
* In contrast other tracing systems,
* in Elastic APM,
- * non-sampled {@link co.elastic.apm.api.Transaction}s do get reported to the APM server.
+ * non-sampled {@link Transaction}s do get reported to the APM server.
* However,
* to keep the size at a minimum,
- * the reported {@link co.elastic.apm.api.Transaction} only contains the transaction name,
+ * the reported {@link Transaction} only contains the transaction name,
* the duration and the id.
* Also,
- * {@link co.elastic.apm.api.Span}s of non sampled {@link co.elastic.apm.api.Transaction}s are not reported.
+ * {@link co.elastic.apm.api.Span}s of non sampled {@link Transaction}s are not reported.
*
*/
public interface Sampler {