diff --git a/README.md b/README.md
index bf1e3da0d1..8ac12d0847 100644
--- a/README.md
+++ b/README.md
@@ -49,14 +49,13 @@ The Java Agent—once passed to your application—automatically traces requests
#### Configuration
-| Config | System Property | Environment Variable | Default |
-| ------------- | ---------------- | -------------------- | ------------------ |
-| service.name | dd.service.name | DD_SERVICE_NAME | `unnamed-java-app` |
-| writer.type | dd.writer.type | DD_WRITER_TYPE | `DDAgentWriter` |
-| agent.host | dd.agent.host | DD_AGENT_HOST | `localhost` |
-| agent.port | dd.agent.port | DD_AGENT_PORT | `8126` |
-| sampler.type | dd.sampler.type | DD_SAMPLER_TYPE | `AllSampler` |
-| sampler.rate | dd.sampler.rate | DD_SAMPLER_RATE | `1.0` |
+| Config | System Property | Environment Variable | Default |
+| ------------------ | --------------------- | ------------------------- | ------------------ |
+| service.name | dd.service.name | DD_SERVICE_NAME | `unnamed-java-app` |
+| writer.type | dd.writer.type | DD_WRITER_TYPE | `DDAgentWriter` |
+| agent.host | dd.agent.host | DD_AGENT_HOST | `localhost` |
+| agent.port | dd.agent.port | DD_AGENT_PORT | `8126` |
+| priority.sampling | dd.priority.sampling | DD_PRIORITY_SAMPLING | `false` |
#### Application Servers
diff --git a/dd-trace-ot/dd-trace-ot.gradle b/dd-trace-ot/dd-trace-ot.gradle
index fa14e46701..05d4dfaeab 100644
--- a/dd-trace-ot/dd-trace-ot.gradle
+++ b/dd-trace-ot/dd-trace-ot.gradle
@@ -14,6 +14,7 @@ whitelistedInstructionClasses += whitelistedBranchClasses += [
'datadog.trace.common.writer.ListWriter',
'datadog.trace.common.util.Clock',
'datadog.trace.api.DDTags',
+ 'datadog.trace.common.sampling.PrioritySampling'
]
dependencies {
diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
index a8400fd7ac..16eb8d3714 100644
--- a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
+++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
@@ -2,7 +2,10 @@ package datadog.opentracing;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import datadog.trace.api.DDTags;
+import datadog.trace.common.sampling.PrioritySampling;
import datadog.trace.common.util.Clock;
import io.opentracing.Span;
import java.io.PrintWriter;
@@ -94,7 +97,8 @@ public class DDSpan implements Span {
*
* @return true if root, false otherwise
*/
- protected final boolean isRootSpan() {
+ @JsonIgnore
+ public final boolean isRootSpan() {
if (context().getTrace().isEmpty()) {
return false;
@@ -236,6 +240,16 @@ public class DDSpan implements Span {
return this;
}
+ /**
+ * Set the sampling priority of the span.
+ *
+ *
Has no effect if the span priority has been propagated (injected or extracted).
+ */
+ public final DDSpan setSamplingPriority(int newPriority) {
+ this.context().setSamplingPriority(newPriority);
+ return this;
+ }
+
public final DDSpan setSpanType(final String type) {
this.context().setSpanType(type);
return this;
@@ -300,6 +314,17 @@ public class DDSpan implements Span {
return context.getOperationName();
}
+ @JsonGetter("sampling_priority")
+ @JsonInclude(Include.NON_NULL)
+ public Integer getSamplingPriority() {
+ final int samplingPriority = context.getSamplingPriority();
+ if (samplingPriority == PrioritySampling.UNSET) {
+ return null;
+ } else {
+ return samplingPriority;
+ }
+ }
+
@JsonIgnore
public Map getTags() {
return this.context().getTags();
diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java
index b353955050..b46b0d10fd 100644
--- a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java
+++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Maps;
import datadog.opentracing.decorators.AbstractDecorator;
import datadog.trace.api.DDTags;
+import datadog.trace.common.sampling.PrioritySampling;
import io.opentracing.tag.Tags;
import java.util.Collections;
import java.util.HashMap;
@@ -49,6 +50,10 @@ public class DDSpanContext implements io.opentracing.SpanContext {
private String spanType;
/** Each span have an operation name describing the current span */
private String operationName;
+ /** The sampling priority of the trace */
+ private volatile int samplingPriority = PrioritySampling.UNSET;
+ /** When true, the samplingPriority cannot be changed. */
+ private volatile boolean samplingPriorityLocked = false;
// Others attributes
/** Tags are associated to the current span, they will not propagate to the children span */
private Map tags;
@@ -60,6 +65,7 @@ public class DDSpanContext implements io.opentracing.SpanContext {
final String serviceName,
final String operationName,
final String resourceName,
+ final int samplingPriority,
final Map baggageItems,
final boolean errorFlag,
final String spanType,
@@ -80,6 +86,7 @@ public class DDSpanContext implements io.opentracing.SpanContext {
this.serviceName = serviceName;
this.operationName = operationName;
this.resourceName = resourceName;
+ this.samplingPriority = samplingPriority;
this.errorFlag = errorFlag;
this.spanType = spanType;
@@ -141,6 +148,46 @@ public class DDSpanContext implements io.opentracing.SpanContext {
this.spanType = spanType;
}
+ public void setSamplingPriority(int newPriority) {
+ if (samplingPriorityLocked) {
+ log.warn(
+ "samplingPriority locked at {}. Refusing to set to {}", samplingPriority, newPriority);
+ } else {
+ synchronized (this) {
+ // sync with lockSamplingPriority
+ this.samplingPriority = newPriority;
+ }
+ }
+ }
+
+ public int getSamplingPriority() {
+ return samplingPriority;
+ }
+
+ /**
+ * Prevent future changes to the context's sampling priority.
+ *
+ * Used when a span is extracted or injected for propagation.
+ *
+ *
Has no effect if the sampling priority is unset.
+ *
+ * @return true if the sampling priority was locked.
+ */
+ public boolean lockSamplingPriority() {
+ if (!samplingPriorityLocked) {
+ synchronized (this) {
+ // sync with setSamplingPriority
+ if (samplingPriority == PrioritySampling.UNSET) {
+ log.debug("{} : refusing to lock unset samplingPriority", this);
+ } else {
+ this.samplingPriorityLocked = true;
+ log.debug("{} : locked samplingPriority to {}", this, this.samplingPriority);
+ }
+ }
+ }
+ return samplingPriorityLocked;
+ }
+
public void setBaggageItem(final String key, final String value) {
if (this.baggageItems.isEmpty()) {
this.baggageItems = new HashMap<>();
@@ -248,6 +295,9 @@ public class DDSpanContext implements io.opentracing.SpanContext {
.append(getOperationName())
.append("/")
.append(getResourceName());
+ if (getSamplingPriority() != PrioritySampling.UNSET) {
+ s.append(" samplingPriority=").append(getSamplingPriority());
+ }
if (errorFlag) {
s.append(" *errored*");
}
diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
index 38fdfd0a63..446f32ea2f 100644
--- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
+++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
@@ -9,7 +9,12 @@ import datadog.trace.api.DDTags;
import datadog.trace.common.DDTraceConfig;
import datadog.trace.common.Service;
import datadog.trace.common.sampling.AllSampler;
+import datadog.trace.common.sampling.PrioritySampling;
+import datadog.trace.common.sampling.RateByServiceSampler;
import datadog.trace.common.sampling.Sampler;
+import datadog.trace.common.writer.DDAgentWriter;
+import datadog.trace.common.writer.DDApi;
+import datadog.trace.common.writer.DDApi.ResponseListener;
import datadog.trace.common.writer.Writer;
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
@@ -78,6 +83,10 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
registry = new CodecRegistry();
registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec());
registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec());
+ if (this.writer instanceof DDAgentWriter && sampler instanceof DDApi.ResponseListener) {
+ final DDApi api = ((DDAgentWriter) this.writer).getApi();
+ api.addResponseListener((DDApi.ResponseListener) this.sampler);
+ }
log.info("New instance: {}", this);
}
@@ -139,7 +148,6 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
@Override
public SpanContext extract(final Format format, final T carrier) {
-
final Codec codec = registry.get(format);
if (codec == null) {
log.warn("Unsupported format for propagation - {}", format.getClass().getName());
@@ -250,7 +258,11 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
}
private DDSpan startSpan() {
- return new DDSpan(this.timestamp, buildSpanContext());
+ final DDSpan span = new DDSpan(this.timestamp, buildSpanContext());
+ if (DDTracer.this.sampler instanceof RateByServiceSampler) {
+ ((RateByServiceSampler) DDTracer.this.sampler).initializeSamplingPriority(span);
+ }
+ return span;
}
@Override
@@ -373,6 +385,7 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
final long parentSpanId;
final Map baggage;
final Queue parentTrace;
+ final int samplingPriority;
final DDSpanContext context;
SpanContext parentContext = this.parent;
@@ -388,6 +401,7 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
parentSpanId = ddsc.getSpanId();
baggage = ddsc.getBaggageItems();
parentTrace = ddsc.getTrace();
+ samplingPriority = ddsc.getSamplingPriority();
if (this.serviceName == null) this.serviceName = ddsc.getServiceName();
if (this.spanType == null) this.spanType = ddsc.getSpanType();
@@ -396,6 +410,7 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
parentSpanId = 0L;
baggage = null;
parentTrace = null;
+ samplingPriority = PrioritySampling.UNSET;
}
if (serviceName == null) {
@@ -416,6 +431,7 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing.
serviceName,
operationName,
this.resourceName,
+ samplingPriority,
baggage,
errorFlag,
spanType,
diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java
index c8b2693c53..a7a76e92d4 100644
--- a/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java
+++ b/dd-trace-ot/src/main/java/datadog/opentracing/propagation/HTTPCodec.java
@@ -1,6 +1,7 @@
package datadog.opentracing.propagation;
import datadog.opentracing.DDSpanContext;
+import datadog.trace.common.sampling.PrioritySampling;
import io.opentracing.propagation.TextMap;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -17,12 +18,15 @@ public class HTTPCodec implements Codec {
private static final String OT_BAGGAGE_PREFIX = "ot-baggage-";
private static final String TRACE_ID_KEY = "x-datadog-trace-id";
private static final String SPAN_ID_KEY = "x-datadog-parent-id";
+ private static final String SAMPLING_PRIORITY_KEY = "x-datadog-sampling-priority";
@Override
public void inject(final DDSpanContext context, final TextMap carrier) {
-
carrier.put(TRACE_ID_KEY, String.valueOf(context.getTraceId()));
carrier.put(SPAN_ID_KEY, String.valueOf(context.getSpanId()));
+ if (context.lockSamplingPriority()) {
+ carrier.put(SAMPLING_PRIORITY_KEY, String.valueOf(context.getSamplingPriority()));
+ }
for (final Map.Entry entry : context.baggageItems()) {
carrier.put(OT_BAGGAGE_PREFIX + entry.getKey(), encode(entry.getValue()));
@@ -35,9 +39,9 @@ public class HTTPCodec implements Codec {
Map baggage = Collections.emptyMap();
Long traceId = 0L;
Long spanId = 0L;
+ int samplingPriority = PrioritySampling.UNSET;
for (final Map.Entry entry : carrier) {
-
final String key = entry.getKey().toLowerCase();
if (key.equalsIgnoreCase(TRACE_ID_KEY)) {
traceId = Long.parseLong(entry.getValue());
@@ -48,14 +52,28 @@ public class HTTPCodec implements Codec {
baggage = new HashMap<>();
}
baggage.put(key.replace(OT_BAGGAGE_PREFIX, ""), decode(entry.getValue()));
+ } else if (key.equalsIgnoreCase(SAMPLING_PRIORITY_KEY)) {
+ samplingPriority = Integer.parseInt(entry.getValue());
}
}
DDSpanContext context = null;
if (traceId != 0L) {
-
context =
new DDSpanContext(
- traceId, spanId, 0L, null, null, null, baggage, false, null, null, null, null);
+ traceId,
+ spanId,
+ 0L,
+ null,
+ null,
+ null,
+ samplingPriority,
+ baggage,
+ false,
+ null,
+ null,
+ null,
+ null);
+ context.lockSamplingPriority();
log.debug("{} - Parent context extracted", context);
}
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java
index 1ba0ecae85..e24ebb13c4 100644
--- a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java
@@ -1,7 +1,6 @@
package datadog.trace.common;
import datadog.opentracing.DDTracer;
-import datadog.trace.common.sampling.Sampler;
import datadog.trace.common.writer.DDAgentWriter;
import datadog.trace.common.writer.Writer;
import java.util.Properties;
@@ -23,15 +22,13 @@ public class DDTraceConfig extends Properties {
public static final String WRITER_TYPE = "writer.type";
public static final String AGENT_HOST = "agent.host";
public static final String AGENT_PORT = "agent.port";
- public static final String SAMPLER_TYPE = "sampler.type";
- public static final String SAMPLER_RATE = "sampler.rate";
+ public static final String PRIORITY_SAMPLING = "priority.sampling";
private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME);
private final String writerType = getPropOrEnv(PREFIX + WRITER_TYPE);
private final String agentHost = getPropOrEnv(PREFIX + AGENT_HOST);
private final String agentPort = getPropOrEnv(PREFIX + AGENT_PORT);
- private final String samplerType = getPropOrEnv(PREFIX + SAMPLER_TYPE);
- private final String samplerRate = getPropOrEnv(PREFIX + SAMPLER_RATE);
+ private final String prioritySampling = getPropOrEnv(PREFIX + PRIORITY_SAMPLING);
public DDTraceConfig() {
super();
@@ -41,16 +38,13 @@ public class DDTraceConfig extends Properties {
defaults.setProperty(WRITER_TYPE, Writer.DD_AGENT_WRITER_TYPE);
defaults.setProperty(AGENT_HOST, DDAgentWriter.DEFAULT_HOSTNAME);
defaults.setProperty(AGENT_PORT, String.valueOf(DDAgentWriter.DEFAULT_PORT));
- defaults.setProperty(SAMPLER_TYPE, Sampler.ALL_SAMPLER_TYPE);
- defaults.setProperty(SAMPLER_RATE, "1.0");
super.defaults = defaults;
setIfNotNull(SERVICE_NAME, serviceName);
setIfNotNull(WRITER_TYPE, writerType);
setIfNotNull(AGENT_HOST, agentHost);
setIfNotNull(AGENT_PORT, agentPort);
- setIfNotNull(SAMPLER_TYPE, samplerType);
- setIfNotNull(SAMPLER_RATE, samplerRate);
+ setIfNotNull(PRIORITY_SAMPLING, prioritySampling);
}
public DDTraceConfig(final String serviceName) {
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/PrioritySampling.java b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/PrioritySampling.java
new file mode 100644
index 0000000000..aca9c67852
--- /dev/null
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/PrioritySampling.java
@@ -0,0 +1,20 @@
+package datadog.trace.common.sampling;
+
+public class PrioritySampling {
+ /**
+ * Implementation detail of the client. will not be sent to the agent or propagated.
+ *
+ * Internal value used when the priority sampling flag has not been set on the span context.
+ */
+ public static final int UNSET = Integer.MIN_VALUE;
+ /** The sampler has decided to drop the trace. */
+ public static final int SAMPLER_DROP = 0;
+ /** The sampler has decided to keep the trace. */
+ public static final int SAMPLER_KEEP = 1;
+ /** The user has decided to drop the trace. */
+ public static final int USER_DROP = -1;
+ /** The user has decided to keep the trace. */
+ public static final int USER_KEEP = 2;
+
+ private PrioritySampling() {}
+}
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateByServiceSampler.java b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateByServiceSampler.java
new file mode 100644
index 0000000000..0287d8d56a
--- /dev/null
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateByServiceSampler.java
@@ -0,0 +1,140 @@
+
+package datadog.trace.common.sampling;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import datadog.opentracing.DDSpan;
+import datadog.trace.common.writer.DDApi.ResponseListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A rate sampler which maintains different sample rates per service+env name.
+ *
+ *
The configuration of (serviceName,env)->rate is configured by the core agent.
+ */
+@Slf4j
+public class RateByServiceSampler implements Sampler, ResponseListener {
+ /** Key for setting the baseline rate */
+ private static final String BASE_KEY = "service:,env:";
+ /** Sampler to use if service+env is not in the map */
+ private RateSampler baseSampler = new RateSampler(1.0);
+
+ private final Map serviceRates = new HashMap();
+
+ @Override
+ public synchronized boolean sample(DDSpan span) {
+ // Priority sampling sends all traces to the core agent, including traces marked dropped.
+ // This allows the core agent to collect stats on all traces.
+ return true;
+ }
+
+ /** If span is a root span, set the span context samplingPriority to keep or drop */
+ public void initializeSamplingPriority(DDSpan span) {
+ if (span.isRootSpan()) {
+ // Run the priority sampler on the new span
+ setSamplingPriorityOnSpanContext(span);
+ } else if (span.getSamplingPriority() == null) {
+ // Edge case: If the parent context did not set the priority, run the priority sampler.
+ // Happens when extracted http context did not send the priority header.
+ setSamplingPriorityOnSpanContext(span);
+ }
+ }
+
+ private synchronized void setSamplingPriorityOnSpanContext(DDSpan span) {
+ final String serviceName = span.getServiceName();
+ final String env = getSpanEnv(span);
+ final String key = "service:" + serviceName + ",env:" + env;
+ boolean agentSample;
+ if (serviceRates.containsKey(key)) {
+ agentSample = serviceRates.get(key).sample(span);
+ } else {
+ agentSample = baseSampler.sample(span);
+ }
+ if (agentSample) {
+ span.setSamplingPriority(PrioritySampling.SAMPLER_KEEP);
+ } else {
+ span.setSamplingPriority(PrioritySampling.SAMPLER_DROP);
+ }
+ }
+
+ private static String getSpanEnv(DDSpan span) {
+ return null == span.getTags().get("env") ? "" : String.valueOf(span.getTags().get("env"));
+ }
+
+ @Override
+ public void onResponse(String endpoint, JsonNode responseJson) {
+ JsonNode newServiceRates = responseJson.get("rate_by_service");
+ if (null != newServiceRates) {
+ log.debug("Update service sampler rates: {} -> {}", endpoint, responseJson);
+ synchronized (this) {
+ serviceRates.clear();
+ Iterator itr = newServiceRates.fieldNames();
+ while (itr.hasNext()) {
+ final String key = itr.next();
+ try {
+ final float val = Float.parseFloat(newServiceRates.get(key).toString());
+ if (BASE_KEY.equals(key)) {
+ baseSampler = new RateSampler(val);
+ } else {
+ serviceRates.put(key, new RateSampler(val));
+ }
+ } catch (NumberFormatException nfe) {
+ log.debug("Unable to parse new service rate {} -> {}", key, newServiceRates.get(key));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This sampler sample the traces at a predefined rate.
+ *
+ * Keep (100 * `sample_rate`)% of the traces. It samples randomly, its main purpose is to
+ * reduce the integration footprint.
+ */
+ private static class RateSampler extends AbstractSampler {
+
+ /** The sample rate used */
+ private final double sampleRate;
+
+ public RateSampler(final String sampleRate) {
+ this(sampleRate == null ? 1 : Double.valueOf(sampleRate));
+ }
+
+ /**
+ * Build an instance of the sampler. The Sample rate is fixed for each instance.
+ *
+ * @param sampleRate a number [0,1] representing the rate ratio.
+ */
+ public RateSampler(double sampleRate) {
+
+ if (sampleRate <= 0) {
+ sampleRate = 1;
+ log.error("SampleRate is negative or null, disabling the sampler");
+ } else if (sampleRate > 1) {
+ sampleRate = 1;
+ }
+
+ this.sampleRate = sampleRate;
+ log.debug("Initializing the RateSampler, sampleRate: {} %", this.sampleRate * 100);
+ }
+
+ @Override
+ public boolean doSample(final DDSpan span) {
+ final boolean sample = Math.random() <= this.sampleRate;
+ log.debug("{} - Span is sampled: {}", span, sample);
+ return sample;
+ }
+
+ public double getSampleRate() {
+ return this.sampleRate;
+ }
+
+ @Override
+ public String toString() {
+ return "RateSampler { sampleRate=" + sampleRate + " }";
+ }
+ }
+}
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateSampler.java b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateSampler.java
deleted file mode 100644
index bf7e807673..0000000000
--- a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RateSampler.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package datadog.trace.common.sampling;
-
-import com.google.auto.service.AutoService;
-import datadog.opentracing.DDSpan;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * This sampler sample the traces at a predefined rate.
- *
- *
Keep (100 * `sample_rate`)% of the traces. It samples randomly, its main purpose is to reduce
- * the integration footprint.
- */
-@Slf4j
-@AutoService(Sampler.class)
-public class RateSampler extends AbstractSampler {
-
- /** The sample rate used */
- private final double sampleRate;
-
- public RateSampler(final String sampleRate) {
- this(sampleRate == null ? 1 : Double.valueOf(sampleRate));
- }
-
- /**
- * Build an instance of the sampler. The Sample rate is fixed for each instance.
- *
- * @param sampleRate a number [0,1] representing the rate ratio.
- */
- public RateSampler(double sampleRate) {
-
- if (sampleRate <= 0) {
- sampleRate = 1;
- log.error("SampleRate is negative or null, disabling the sampler");
- } else if (sampleRate > 1) {
- sampleRate = 1;
- }
-
- this.sampleRate = sampleRate;
- log.debug("Initializing the RateSampler, sampleRate: {} %", this.sampleRate * 100);
- }
-
- @Override
- public boolean doSample(final DDSpan span) {
- final boolean sample = Math.random() <= this.sampleRate;
- log.debug("{} - Span is sampled: {}", span, sample);
- return sample;
- }
-
- public double getSampleRate() {
- return this.sampleRate;
- }
-
- @Override
- public String toString() {
- return "RateSampler { sampleRate=" + sampleRate + " }";
- }
-}
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/Sampler.java b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/Sampler.java
index 8059bbc871..5d27359389 100644
--- a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/Sampler.java
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/Sampler.java
@@ -3,12 +3,10 @@ package datadog.trace.common.sampling;
import datadog.opentracing.DDSpan;
import datadog.trace.common.DDTraceConfig;
import java.util.Properties;
-import lombok.extern.slf4j.Slf4j;
/** Main interface to sample a collection of traces. */
public interface Sampler {
static final String ALL_SAMPLER_TYPE = AllSampler.class.getSimpleName();
- static final String RATE_SAMPLER_TYPE = RateSampler.class.getSimpleName();
/**
* Sample a collection of traces based on the parent span
@@ -18,26 +16,18 @@ public interface Sampler {
*/
boolean sample(DDSpan span);
- @Slf4j
final class Builder {
public static Sampler forConfig(final Properties config) {
final Sampler sampler;
-
if (config != null) {
- final String configuredType = config.getProperty(DDTraceConfig.SAMPLER_TYPE);
- if (RATE_SAMPLER_TYPE.equals(configuredType)) {
- sampler = new RateSampler(config.getProperty(DDTraceConfig.SAMPLER_RATE));
- } else if (ALL_SAMPLER_TYPE.equals(configuredType)) {
- sampler = new AllSampler();
+ final boolean prioritySamplingEnabled =
+ Boolean.parseBoolean(config.getProperty(DDTraceConfig.PRIORITY_SAMPLING));
+ if (prioritySamplingEnabled) {
+ sampler = new RateByServiceSampler();
} else {
- log.warn(
- "Sampler type not configured correctly: Type {} not recognized. Defaulting to AllSampler.",
- configuredType);
sampler = new AllSampler();
}
} else {
- log.warn(
- "Sampler type not configured correctly: No config provided! Defaulting to AllSampler.");
sampler = new AllSampler();
}
return sampler;
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java
index b304d12285..c428ce0916 100644
--- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java
@@ -147,6 +147,10 @@ public class DDAgentWriter implements Writer {
return "DDAgentWriter { api=" + api + " }";
}
+ public DDApi getApi() {
+ return api;
+ }
+
/** Infinite tasks blocking until some spans come in the blocking queue. */
class TracesSendingTask implements Runnable {
diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java
index 558915a1f8..815cb92c9b 100644
--- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java
+++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java
@@ -1,14 +1,19 @@
package datadog.trace.common.writer;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.RateLimiter;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDTraceOTInfo;
import datadog.trace.common.Service;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -19,12 +24,15 @@ import org.msgpack.jackson.dataformat.MessagePackFactory;
@Slf4j
public class DDApi {
- private static final String TRACES_ENDPOINT = "/v0.3/traces";
- private static final String SERVICES_ENDPOINT = "/v0.3/services";
+ private static final String TRACES_ENDPOINT_V3 = "/v0.3/traces";
+ private static final String SERVICES_ENDPOINT_V3 = "/v0.3/services";
+ private static final String TRACES_ENDPOINT_V4 = "/v0.4/traces";
+ private static final String SERVICES_ENDPOINT_V4 = "/v0.4/services";
private static final long SECONDS_BETWEEN_ERROR_LOG = TimeUnit.MINUTES.toSeconds(5);
private final String tracesEndpoint;
private final String servicesEndpoint;
+ private final List responseListeners = new ArrayList();
private final RateLimiter loggingRateLimiter =
RateLimiter.create(1.0 / SECONDS_BETWEEN_ERROR_LOG);
@@ -32,8 +40,21 @@ public class DDApi {
private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
public DDApi(final String host, final int port) {
- this.tracesEndpoint = "http://" + host + ":" + port + TRACES_ENDPOINT;
- this.servicesEndpoint = "http://" + host + ":" + port + SERVICES_ENDPOINT;
+ if (endpointAvailable("http://" + host + ":" + port + TRACES_ENDPOINT_V4)
+ && endpointAvailable("http://" + host + ":" + port + SERVICES_ENDPOINT_V4)) {
+ this.tracesEndpoint = "http://" + host + ":" + port + TRACES_ENDPOINT_V4;
+ this.servicesEndpoint = "http://" + host + ":" + port + SERVICES_ENDPOINT_V4;
+ } else {
+ log.debug("API v0.4 endpoints not available. Downgrading to v0.3");
+ this.tracesEndpoint = "http://" + host + ":" + port + TRACES_ENDPOINT_V3;
+ this.servicesEndpoint = "http://" + host + ":" + port + SERVICES_ENDPOINT_V3;
+ }
+ }
+
+ public void addResponseListener(ResponseListener listener) {
+ if (!responseListeners.contains(listener)) {
+ responseListeners.add(listener);
+ }
}
/**
@@ -74,6 +95,21 @@ public class DDApi {
out.flush();
out.close();
+ String responseString = null;
+ {
+ final BufferedReader responseReader =
+ new BufferedReader(new InputStreamReader(httpCon.getInputStream()));
+ final StringBuilder sb = new StringBuilder();
+
+ String line = null;
+ while ((line = responseReader.readLine()) != null) {
+ sb.append(line);
+ }
+ responseReader.close();
+
+ responseString = sb.toString();
+ }
+
final int responseCode = httpCon.getResponseCode();
if (responseCode != 200) {
if (log.isDebugEnabled()) {
@@ -96,6 +132,19 @@ public class DDApi {
}
log.debug("Succesfully sent {} {} to the DD agent.", size, type);
+
+ try {
+ if (null != responseString
+ && !"".equals(responseString.trim())
+ && !"OK".equalsIgnoreCase(responseString.trim())) {
+ JsonNode response = objectMapper.readTree(responseString);
+ for (ResponseListener listener : responseListeners) {
+ listener.onResponse(endpoint, response);
+ }
+ }
+ } catch (IOException e) {
+ log.debug("failed to parse DD agent response: " + responseString, e);
+ }
return true;
} catch (final IOException e) {
@@ -114,11 +163,24 @@ public class DDApi {
}
}
+ private boolean endpointAvailable(final String endpoint) {
+ try {
+ final HttpURLConnection httpCon = getHttpURLConnection(endpoint);
+ OutputStreamWriter out = new OutputStreamWriter(httpCon.getOutputStream());
+ out.flush();
+ out.close();
+ return httpCon.getResponseCode() == 200;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
private HttpURLConnection getHttpURLConnection(final String endpoint) throws IOException {
final HttpURLConnection httpCon;
final URL url = new URL(endpoint);
httpCon = (HttpURLConnection) url.openConnection();
httpCon.setDoOutput(true);
+ httpCon.setDoInput(true);
httpCon.setRequestMethod("PUT");
httpCon.setRequestProperty("Content-Type", "application/msgpack");
httpCon.setRequestProperty("Datadog-Meta-Lang", "java");
@@ -132,4 +194,9 @@ public class DDApi {
public String toString() {
return "DDApi { tracesEndpoint=" + tracesEndpoint + " }";
}
+
+ public static interface ResponseListener {
+ /** Invoked after the api receives a response from the core agent. */
+ void onResponse(String endpoint, JsonNode responseJson);
+ }
}
diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanSerializationTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanSerializationTest.groovy
new file mode 100644
index 0000000000..0ab8b4acc9
--- /dev/null
+++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanSerializationTest.groovy
@@ -0,0 +1,67 @@
+package datadog.opentracing
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.google.common.collect.Maps
+import datadog.trace.api.DDTags
+import datadog.trace.common.sampling.PrioritySampling
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DDSpanSerializationTest extends Specification {
+
+ @Unroll
+ def "serialize spans"() throws Exception {
+ setup:
+ final Map baggage = new HashMap<>()
+ baggage.put("a-baggage", "value")
+ final Map tags = new HashMap<>()
+ baggage.put("k1", "v1")
+
+ Map expected = Maps.newHashMap()
+ expected.put("meta", baggage)
+ expected.put("service", "service")
+ expected.put("error", 0)
+ expected.put("type", "type")
+ expected.put("name", "operation")
+ expected.put("duration", 33000)
+ expected.put("resource", "operation")
+ if (samplingPriority != PrioritySampling.UNSET) {
+ expected.put("sampling_priority", samplingPriority)
+ }
+ expected.put("start", 100000)
+ expected.put("span_id", 2l)
+ expected.put("parent_id", 0l)
+ expected.put("trace_id", 1l)
+
+ final DDSpanContext context =
+ new DDSpanContext(
+ 1L,
+ 2L,
+ 0L,
+ "service",
+ "operation",
+ null,
+ samplingPriority,
+ new HashMap<>(baggage),
+ false,
+ "type",
+ tags,
+ null,
+ null)
+
+ baggage.put(DDTags.THREAD_NAME, Thread.currentThread().getName())
+ baggage.put(DDTags.THREAD_ID, String.valueOf(Thread.currentThread().getId()))
+
+ DDSpan span = new DDSpan(100L, context)
+ span.finish(133L)
+ ObjectMapper serializer = new ObjectMapper()
+
+ expect:
+ serializer.readTree(serializer.writeValueAsString(span)) == serializer.readTree(serializer.writeValueAsString(expected))
+
+ where:
+ samplingPriority | _
+ PrioritySampling.SAMPLER_KEEP | _
+ PrioritySampling.UNSET | _
+ }
+}
diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
new file mode 100644
index 0000000000..189d632fa6
--- /dev/null
+++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
@@ -0,0 +1,88 @@
+package datadog.opentracing
+
+import datadog.trace.common.sampling.PrioritySampling
+import spock.lang.Specification
+
+class DDSpanTest extends Specification {
+
+ def "getters and setters"() {
+ setup:
+ final DDSpanContext context =
+ new DDSpanContext(
+ 1L,
+ 1L,
+ 0L,
+ "fakeService",
+ "fakeOperation",
+ "fakeResource",
+ PrioritySampling.UNSET,
+ Collections.emptyMap(),
+ false,
+ "fakeType",
+ null,
+ null,
+ null)
+
+ final DDSpan span = new DDSpan(1L, context)
+
+ when:
+ span.setServiceName("service")
+ then:
+ span.getServiceName() == "service"
+
+ when:
+ span.setOperationName("operation")
+ then:
+ span.getOperationName() == "operation"
+
+ when:
+ span.setResourceName("resource")
+ then:
+ span.getResourceName() == "resource"
+
+ when:
+ span.setSpanType("type")
+ then:
+ span.getType() == "type"
+
+ when:
+ span.setSamplingPriority(PrioritySampling.UNSET)
+ then:
+ span.getSamplingPriority() == null
+
+ when:
+ span.setSamplingPriority(PrioritySampling.SAMPLER_KEEP)
+ then:
+ span.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP
+
+ when:
+ context.lockSamplingPriority()
+ span.setSamplingPriority(PrioritySampling.USER_KEEP)
+ then:
+ span.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP
+ }
+
+ def "resource name equals operation name if null"() {
+ setup:
+ final String opName = "operationName"
+ DDSpan span
+
+ when:
+ span = new DDTracer().buildSpan(opName).startManual()
+ then:
+ span.getResourceName() == opName
+ span.getServiceName() == DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME
+
+ when:
+ final String resourceName = "fake"
+ final String serviceName = "myService"
+ span = new DDTracer()
+ .buildSpan(opName)
+ .withResourceName(resourceName)
+ .withServiceName(serviceName)
+ .startManual()
+ then:
+ span.getResourceName() == resourceName
+ span.getServiceName() == serviceName
+ }
+}
diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy
new file mode 100644
index 0000000000..e79fabdfdc
--- /dev/null
+++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HTTPCodecTest.groovy
@@ -0,0 +1,95 @@
+package datadog.opentracing.propagation
+
+import datadog.opentracing.DDSpanContext
+import datadog.trace.common.sampling.PrioritySampling
+import io.opentracing.propagation.TextMapExtractAdapter
+import io.opentracing.propagation.TextMapInjectAdapter
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class HTTPCodecTest extends Specification {
+ @Shared
+ private static final String OT_BAGGAGE_PREFIX = "ot-baggage-"
+ @Shared
+ private static final String TRACE_ID_KEY = "x-datadog-trace-id"
+ @Shared
+ private static final String SPAN_ID_KEY = "x-datadog-parent-id"
+ @Shared
+ private static final String SAMPLING_PRIORITY_KEY = "x-datadog-sampling-priority"
+
+ @Unroll
+ def "inject http headers"() {
+ setup:
+ final DDSpanContext mockedContext =
+ new DDSpanContext(
+ 1L,
+ 2L,
+ 0L,
+ "fakeService",
+ "fakeOperation",
+ "fakeResource",
+ samplingPriority,
+ new HashMap() {
+ {
+ put("k1", "v1")
+ put("k2", "v2")
+ }
+ },
+ false,
+ "fakeType",
+ null,
+ null,
+ null)
+
+ final Map carrier = new HashMap<>()
+
+ final HTTPCodec codec = new HTTPCodec()
+ codec.inject(mockedContext, new TextMapInjectAdapter(carrier))
+
+ expect:
+ carrier.get(TRACE_ID_KEY) == "1"
+ carrier.get(SPAN_ID_KEY) == "2"
+ carrier.get(SAMPLING_PRIORITY_KEY) == (samplingPriority == PrioritySampling.UNSET ? null : String.valueOf(samplingPriority))
+ carrier.get(OT_BAGGAGE_PREFIX + "k1") == "v1"
+ carrier.get(OT_BAGGAGE_PREFIX + "k2") == "v2"
+
+ where:
+ samplingPriority | _
+ PrioritySampling.UNSET | _
+ PrioritySampling.SAMPLER_KEEP | _
+ }
+
+ @Unroll
+ def "extract http headers"() {
+ setup:
+ final Map actual =
+ new HashMap() {
+ {
+ put(TRACE_ID_KEY.toUpperCase(), "1")
+ put(SPAN_ID_KEY.toUpperCase(), "2")
+ put(OT_BAGGAGE_PREFIX.toUpperCase() + "k1", "v1")
+ put(OT_BAGGAGE_PREFIX.toUpperCase() + "k2", "v2")
+ }
+ }
+
+ if (samplingPriority != PrioritySampling.UNSET) {
+ actual.put(SAMPLING_PRIORITY_KEY, String.valueOf(samplingPriority))
+ }
+
+ final HTTPCodec codec = new HTTPCodec()
+ final DDSpanContext context = codec.extract(new TextMapExtractAdapter(actual))
+
+ expect:
+ context.getTraceId() == 1l
+ context.getSpanId() == 2l
+ context.getBaggageItem("k1") == "v1"
+ context.getBaggageItem("k2") == "v2"
+ context.getSamplingPriority() == samplingPriority
+
+ where:
+ samplingPriority | _
+ PrioritySampling.UNSET | _
+ PrioritySampling.SAMPLER_KEEP | _
+ }
+}
diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy
index 6353c2fdb4..b708fd41ad 100644
--- a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy
+++ b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy
@@ -3,7 +3,6 @@ package datadog.trace
import datadog.opentracing.DDTracer
import datadog.trace.common.DDTraceConfig
import datadog.trace.common.sampling.AllSampler
-import datadog.trace.common.sampling.RateSampler
import datadog.trace.common.writer.DDAgentWriter
import datadog.trace.common.writer.ListWriter
import datadog.trace.common.writer.LoggingWriter
@@ -52,8 +51,6 @@ class DDTraceConfigTest extends Specification {
System.clearProperty(PREFIX + WRITER_TYPE)
System.clearProperty(PREFIX + AGENT_HOST)
System.clearProperty(PREFIX + AGENT_PORT)
- System.clearProperty(PREFIX + SAMPLER_TYPE)
- System.clearProperty(PREFIX + SAMPLER_RATE)
}
def "verify env override"() {
@@ -73,8 +70,6 @@ class DDTraceConfigTest extends Specification {
config.getProperty(WRITER_TYPE) == "DDAgentWriter"
config.getProperty(AGENT_HOST) == "localhost"
config.getProperty(AGENT_PORT) == "8126"
- config.getProperty(SAMPLER_TYPE) == "AllSampler"
- config.getProperty(SAMPLER_RATE) == "1.0"
when:
config = new DDTraceConfig("A different service name")
@@ -84,56 +79,45 @@ class DDTraceConfigTest extends Specification {
config.getProperty(WRITER_TYPE) == "DDAgentWriter"
config.getProperty(AGENT_HOST) == "localhost"
config.getProperty(AGENT_PORT) == "8126"
- config.getProperty(SAMPLER_TYPE) == "AllSampler"
- config.getProperty(SAMPLER_RATE) == "1.0"
}
def "specify overrides via system properties"() {
when:
System.setProperty(PREFIX + SERVICE_NAME, "something else")
System.setProperty(PREFIX + WRITER_TYPE, LoggingWriter.simpleName)
- System.setProperty(PREFIX + SAMPLER_TYPE, RateSampler.simpleName)
- System.setProperty(PREFIX + SAMPLER_RATE, ".5")
def tracer = new DDTracer()
then:
tracer.serviceName == "something else"
tracer.writer instanceof LoggingWriter
- tracer.sampler.toString() == "RateSampler { sampleRate=0.5 }"
}
def "specify overrides via env vars"() {
when:
overrideEnvMap.put(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
overrideEnvMap.put(propToEnvName(PREFIX + WRITER_TYPE), LoggingWriter.simpleName)
- overrideEnvMap.put(propToEnvName(PREFIX + SAMPLER_TYPE), AllSampler.simpleName)
def tracer = new DDTracer()
then:
tracer.serviceName == "still something else"
tracer.writer instanceof LoggingWriter
- tracer.sampler instanceof AllSampler
}
def "sys props override env vars"() {
when:
overrideEnvMap.put(propToEnvName(PREFIX + SERVICE_NAME), "still something else")
overrideEnvMap.put(propToEnvName(PREFIX + WRITER_TYPE), ListWriter.simpleName)
- overrideEnvMap.put(propToEnvName(PREFIX + SAMPLER_TYPE), AllSampler.simpleName)
System.setProperty(PREFIX + SERVICE_NAME, "what we actually want")
System.setProperty(PREFIX + WRITER_TYPE, DDAgentWriter.simpleName)
System.setProperty(PREFIX + AGENT_HOST, "somewhere")
System.setProperty(PREFIX + AGENT_PORT, "9999")
- System.setProperty(PREFIX + SAMPLER_TYPE, RateSampler.simpleName)
- System.setProperty(PREFIX + SAMPLER_RATE, ".9")
def tracer = new DDTracer()
then:
tracer.serviceName == "what we actually want"
tracer.writer.toString() == "DDAgentWriter { api=DDApi { tracesEndpoint=http://somewhere:9999/v0.3/traces } }"
- tracer.sampler.toString() == "RateSampler { sampleRate=0.9 }"
}
def "verify defaults on tracer"() {
@@ -164,8 +148,5 @@ class DDTraceConfigTest extends Specification {
"writer" | "writer.type" | "LoggingWriter" | "LoggingWriter { }"
"writer" | "agent.host" | "somethingelse" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://somethingelse:8126/v0.3/traces } }"
"writer" | "agent.port" | "9999" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:9999/v0.3/traces } }"
- "sampler" | "default" | "default" | "AllSampler { sample=true }"
- "sampler" | "sampler.type" | "RateSampler" | "RateSampler { sampleRate=1.0 }"
- "sampler" | "sampler.rate" | "100" | "AllSampler { sample=true }"
}
}
diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/SpanFactory.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/SpanFactory.groovy
index 10e7c4ed22..9f901536f7 100644
--- a/dd-trace-ot/src/test/groovy/datadog/trace/SpanFactory.groovy
+++ b/dd-trace-ot/src/test/groovy/datadog/trace/SpanFactory.groovy
@@ -3,6 +3,7 @@ package datadog.trace
import datadog.opentracing.DDSpan
import datadog.opentracing.DDSpanContext
import datadog.opentracing.DDTracer
+import datadog.trace.common.sampling.PrioritySampling
class SpanFactory {
static newSpanOf(long timestampMicro) {
@@ -13,6 +14,7 @@ class SpanFactory {
"fakeService",
"fakeOperation",
"fakeResource",
+ PrioritySampling.UNSET,
Collections.emptyMap(),
false,
"fakeType",
@@ -30,6 +32,7 @@ class SpanFactory {
"fakeService",
"fakeOperation",
"fakeResource",
+ PrioritySampling.UNSET,
Collections.emptyMap(),
false,
"fakeType",
diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/api/sampling/RateByServiceSamplerTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/api/sampling/RateByServiceSamplerTest.groovy
new file mode 100644
index 0000000000..1f254e79f2
--- /dev/null
+++ b/dd-trace-ot/src/test/groovy/datadog/trace/api/sampling/RateByServiceSamplerTest.groovy
@@ -0,0 +1,71 @@
+package datadog.trace.api.sampling
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import datadog.opentracing.DDSpan
+import datadog.opentracing.DDSpanContext
+import datadog.opentracing.DDTracer
+import datadog.trace.common.sampling.PrioritySampling
+import datadog.trace.common.sampling.RateByServiceSampler
+import spock.lang.Specification
+
+class RateByServiceSamplerTest extends Specification {
+
+ def "rate by service name"() {
+ setup:
+ RateByServiceSampler serviceSampler = new RateByServiceSampler()
+ ObjectMapper serializer = new ObjectMapper()
+
+ when:
+ String response = '{"rate_by_service": {"service:,env:":1.0, "service:spock,env:test":0.000001}}'
+ serviceSampler.onResponse("traces", serializer.readTree(response))
+ DDSpan span1 = makeTrace("foo", "bar")
+ serviceSampler.initializeSamplingPriority(span1)
+ then:
+ span1.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP
+ serviceSampler.sample(span1)
+ // !serviceSampler.sample(makeTrace("spock", "test"))
+
+ when:
+ response = '{"rate_by_service": {"service:,env:":0.000001, "service:spock,env:test":1.0}}'
+ serviceSampler.onResponse("traces", serializer.readTree(response))
+ DDSpan span2 = makeTrace("spock", "test")
+ serviceSampler.initializeSamplingPriority(span2)
+ then:
+ // !serviceSampler.sample(makeTrace("foo", "bar"))
+ span2.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP
+ serviceSampler.sample(span2)
+ }
+
+ def "sampling priority set on context"() {
+ setup:
+ RateByServiceSampler serviceSampler = new RateByServiceSampler()
+ ObjectMapper serializer = new ObjectMapper()
+ String response = '{"rate_by_service": {"service:,env:":1.0}}'
+ serviceSampler.onResponse("traces", serializer.readTree(response))
+
+ DDSpan span = makeTrace("foo", "bar")
+ serviceSampler.initializeSamplingPriority(span)
+ expect:
+ // sets correctly on root span
+ span.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP
+ }
+
+ private DDSpan makeTrace(String serviceName, String envName) {
+ def context = new DDSpanContext(
+ 1L,
+ 1L,
+ 0L,
+ serviceName,
+ "fakeOperation",
+ "fakeResource",
+ PrioritySampling.UNSET,
+ Collections.emptyMap(),
+ false,
+ "fakeType",
+ Collections.emptyMap(),
+ null,
+ new DDTracer())
+ context.setTag("env", envName)
+ return new DDSpan(0l, context)
+ }
+}
diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy
index 2987248721..9029f62dc5 100644
--- a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy
+++ b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy
@@ -1,10 +1,12 @@
package datadog.trace.api.writer
import com.fasterxml.jackson.core.type.TypeReference
+import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import datadog.trace.SpanFactory
import datadog.trace.common.Service
import datadog.trace.common.writer.DDApi
+import datadog.trace.common.writer.DDApi.ResponseListener
import org.msgpack.jackson.dataformat.MessagePackFactory
import ratpack.http.Headers
import ratpack.http.MediaType
@@ -21,7 +23,10 @@ class DDApiTest extends Specification {
setup:
def agent = ratpack {
handlers {
- put("v0.3/traces") {
+ put("v0.4/traces") {
+ response.status(200).send()
+ }
+ put("v0.4/services") {
response.status(200).send()
}
}
@@ -39,9 +44,12 @@ class DDApiTest extends Specification {
setup:
def agent = ratpack {
handlers {
- put("v0.3/traces") {
+ put("v0.4/traces") {
response.status(404).send()
}
+ put("v0.4/services") {
+ response.status(200).send()
+ }
}
}
def client = new DDApi("localhost", agent.address.port)
@@ -60,7 +68,7 @@ class DDApiTest extends Specification {
def requestBody = new AtomicReference()
def agent = ratpack {
handlers {
- put("v0.3/traces") {
+ put("v0.4/traces") {
requestContentType.set(request.contentType)
requestHeaders.set(request.headers)
request.body.then {
@@ -68,6 +76,9 @@ class DDApiTest extends Specification {
response.send()
}
}
+ put("v0.4/services") {
+ response.status(200).send()
+ }
}
}
def client = new DDApi("localhost", agent.address.port)
@@ -120,7 +131,10 @@ class DDApiTest extends Specification {
setup:
def agent = ratpack {
handlers {
- put("v0.3/services") {
+ put("v0.4/traces") {
+ response.status(200).send()
+ }
+ put("v0.4/services") {
response.status(200).send()
}
}
@@ -138,7 +152,10 @@ class DDApiTest extends Specification {
setup:
def agent = ratpack {
handlers {
- put("v0.3/services") {
+ put("v0.4/traces") {
+ response.status(200).send()
+ }
+ put("v0.4/services") {
response.status(404).send()
}
}
@@ -159,7 +176,10 @@ class DDApiTest extends Specification {
def requestBody = new AtomicReference()
def agent = ratpack {
handlers {
- put("v0.3/services") {
+ put("v0.4/traces") {
+ response.status(200).send()
+ }
+ put("v0.4/services") {
requestContentType.set(request.contentType)
requestHeaders.set(request.headers)
request.body.then {
@@ -192,6 +212,74 @@ class DDApiTest extends Specification {
]
}
+ def "Api ResponseListeners see 200 responses"() {
+ setup:
+ def agentResponse = new AtomicReference(null)
+ ResponseListener responseListener = new ResponseListener() {
+ @Override
+ void onResponse(String endpoint, JsonNode responseJson) {
+ agentResponse.set(responseJson.toString())
+ }
+ }
+ boolean servicesAvailable = true
+ def agent = ratpack {
+ handlers {
+ put("v0.4/traces") {
+ response.status(200).send('{"hello":"test"}')
+ }
+ put("v0.4/services") {
+ if (servicesAvailable) {
+ response.status(200).send('{"service-response":"from-test"}')
+ } else {
+ response.status(404).send('{"service-response":"from-test"}')
+ }
+ }
+ }
+ }
+ def client = new DDApi("localhost", agent.address.port)
+ client.addResponseListener(responseListener)
+ def services = ["my-service-name": new Service("my-service-name", "my-app-name", Service.AppType.CUSTOM)]
+
+ when:
+ client.sendTraces([])
+ then:
+ agentResponse.get() == '{"hello":"test"}'
+
+ when:
+ servicesAvailable = false
+ agentResponse.set('not-set')
+ client.sendServices(services)
+ then:
+ // response not seen because of non-200 status
+ agentResponse.get() == 'not-set'
+
+
+ cleanup:
+ agent.close()
+ }
+
+ def "Api Downgrades to v3"() {
+ setup:
+ def v3Agent = ratpack {
+ handlers {
+ put("v0.3/traces") {
+ response.status(200).send()
+ }
+ put("v0.3/services") {
+ response.status(200).send()
+ }
+ }
+ }
+ def client = new DDApi("localhost", v3Agent.address.port)
+
+ expect:
+ client.sendTraces([])
+ client.sendServices()
+
+ cleanup:
+ v3Agent.close()
+ }
+
static List> convertList(byte[] bytes) {
return mapper.readValue(bytes, new TypeReference>>() {})
}
diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanSerializationTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanSerializationTest.java
deleted file mode 100644
index 3731020141..0000000000
--- a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanSerializationTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package datadog.opentracing;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.Maps;
-import datadog.trace.api.DDTags;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-
-public class DDSpanSerializationTest {
-
- ObjectMapper serializer;
- DDSpan span;
- Map expected = Maps.newHashMap();
-
- @Before
- public void setUp() throws Exception {
-
- final Map baggage = new HashMap<>();
- baggage.put("a-baggage", "value");
- final Map tags = new HashMap<>();
- baggage.put("k1", "v1");
-
- expected.put("meta", baggage);
- expected.put("service", "service");
- expected.put("error", 0);
- expected.put("type", "type");
- expected.put("name", "operation");
- expected.put("duration", 33000);
- expected.put("resource", "operation");
- expected.put("start", 100000);
- expected.put("span_id", 2l);
- expected.put("parent_id", 0l);
- expected.put("trace_id", 1l);
-
- final DDSpanContext context =
- new DDSpanContext(
- 1L,
- 2L,
- 0L,
- "service",
- "operation",
- null,
- new HashMap<>(baggage),
- false,
- "type",
- tags,
- null,
- null);
-
- baggage.put(DDTags.THREAD_NAME, Thread.currentThread().getName());
- baggage.put(DDTags.THREAD_ID, String.valueOf(Thread.currentThread().getId()));
-
- span = new DDSpan(100L, context);
- span.finish(133L);
- serializer = new ObjectMapper();
- }
-
- @Test
- public void test() throws Exception {
- assertThat(serializer.readTree(serializer.writeValueAsString(span)))
- .isEqualTo(serializer.readTree(serializer.writeValueAsString(expected)));
- }
-}
diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanTest.java
deleted file mode 100644
index 0462fe986e..0000000000
--- a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package datadog.opentracing;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.Collections;
-import org.junit.Test;
-
-public class DDSpanTest {
-
- @Test
- public void testGetterSetter() {
-
- final DDSpanContext context =
- new DDSpanContext(
- 1L,
- 1L,
- 0L,
- "fakeService",
- "fakeOperation",
- "fakeResource",
- Collections.emptyMap(),
- false,
- "fakeType",
- null,
- null,
- null);
-
- String expected;
- final DDSpan span = new DDSpan(1L, context);
-
- expected = "service";
- span.setServiceName(expected);
- assertThat(span.getServiceName()).isEqualTo(expected);
-
- expected = "operation";
- span.setOperationName(expected);
- assertThat(span.getOperationName()).isEqualTo(expected);
-
- expected = "resource";
- span.setResourceName(expected);
- assertThat(span.getResourceName()).isEqualTo(expected);
-
- expected = "type";
- span.setSpanType(expected);
- assertThat(span.getType()).isEqualTo(expected);
- }
-
- @Test
- public void shouldResourceNameEqualsOperationNameIfNull() {
-
- final String expectedName = "operationName";
-
- DDSpan span = new DDTracer().buildSpan(expectedName).startManual();
- // ResourceName = expectedName
- assertThat(span.getResourceName()).isEqualTo(expectedName);
- assertThat(span.getServiceName()).isEqualTo(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME);
-
- // ResourceName = expectedResourceName
- final String expectedResourceName = "fake";
- span =
- new DDTracer()
- .buildSpan(expectedName)
- .withResourceName(expectedResourceName)
- .withServiceName("foo")
- .startManual();
-
- assertThat(span.getResourceName()).isEqualTo(expectedResourceName);
- assertThat(span.getServiceName()).isEqualTo("foo");
- }
-}
diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java
deleted file mode 100644
index 993b4e141e..0000000000
--- a/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package datadog.opentracing;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import datadog.trace.common.sampling.RateSampler;
-import datadog.trace.common.writer.Writer;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.Queue;
-import org.junit.Test;
-
-public class DDTracerTest {
-
- @Test
- public void write() throws Exception {
-
- final Writer writer = mock(Writer.class);
- final RateSampler sampler = mock(RateSampler.class);
- final DDSpan span = mock(DDSpan.class);
-
- // Rate 0.5
- when(sampler.sample(any(DDSpan.class))).thenReturn(true).thenReturn(false);
-
- final Queue spans = new LinkedList<>();
- spans.add(span);
- spans.add(span);
- spans.add(span);
-
- final DDTracer tracer = new DDTracer(DDTracer.UNASSIGNED_DEFAULT_SERVICE_NAME, writer, sampler);
-
- tracer.write(spans);
- tracer.write(spans);
-
- verify(sampler, times(2)).sample(span);
- verify(writer, times(1)).write(new ArrayList<>(spans));
- }
-}
diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/propagation/HTTPCodecTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/propagation/HTTPCodecTest.java
deleted file mode 100644
index 1030798759..0000000000
--- a/dd-trace-ot/src/test/java/datadog/opentracing/propagation/HTTPCodecTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package datadog.opentracing.propagation;
-
-import static org.assertj.core.api.Java6Assertions.assertThat;
-
-import datadog.opentracing.DDSpanContext;
-import io.opentracing.propagation.TextMapExtractAdapter;
-import io.opentracing.propagation.TextMapInjectAdapter;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Test;
-
-/** Created by gpolaert on 6/23/17. */
-public class HTTPCodecTest {
-
- private static final String OT_BAGGAGE_PREFIX = "ot-baggage-";
- private static final String TRACE_ID_KEY = "x-datadog-trace-id";
- private static final String SPAN_ID_KEY = "x-datadog-parent-id";
-
- @Test
- public void shoudAddHttpHeaders() {
-
- final DDSpanContext mockedContext =
- new DDSpanContext(
- 1L,
- 2L,
- 0L,
- "fakeService",
- "fakeOperation",
- "fakeResource",
- new HashMap() {
- {
- put("k1", "v1");
- put("k2", "v2");
- }
- },
- false,
- "fakeType",
- null,
- null,
- null);
-
- final Map carrier = new HashMap<>();
-
- final HTTPCodec codec = new HTTPCodec();
- codec.inject(mockedContext, new TextMapInjectAdapter(carrier));
-
- assertThat(carrier.get(TRACE_ID_KEY)).isEqualTo("1");
- assertThat(carrier.get(SPAN_ID_KEY)).isEqualTo("2");
- assertThat(carrier.get(OT_BAGGAGE_PREFIX + "k1")).isEqualTo("v1");
- assertThat(carrier.get(OT_BAGGAGE_PREFIX + "k2")).isEqualTo("v2");
- }
-
- @Test
- public void shoudReadHttpHeaders() {
-
- final Map actual =
- new HashMap() {
- {
- put(TRACE_ID_KEY.toUpperCase(), "1");
- put(SPAN_ID_KEY.toUpperCase(), "2");
- put(OT_BAGGAGE_PREFIX.toUpperCase() + "k1", "v1");
- put(OT_BAGGAGE_PREFIX.toUpperCase() + "k2", "v2");
- }
- };
-
- final HTTPCodec codec = new HTTPCodec();
- final DDSpanContext context = codec.extract(new TextMapExtractAdapter(actual));
-
- assertThat(context.getTraceId()).isEqualTo(1l);
- assertThat(context.getSpanId()).isEqualTo(2l);
- assertThat(context.getBaggageItem("k1")).isEqualTo("v1");
- assertThat(context.getBaggageItem("k2")).isEqualTo("v2");
- }
-}
diff --git a/dd-trace-ot/src/test/java/datadog/trace/api/sampling/RateSamplerTest.java b/dd-trace-ot/src/test/java/datadog/trace/api/sampling/RateSamplerTest.java
deleted file mode 100644
index eb4bdfceee..0000000000
--- a/dd-trace-ot/src/test/java/datadog/trace/api/sampling/RateSamplerTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package datadog.trace.api.sampling;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-import datadog.opentracing.DDSpan;
-import datadog.trace.common.sampling.RateSampler;
-import datadog.trace.common.sampling.Sampler;
-import org.junit.Test;
-
-public class RateSamplerTest {
-
- @Test
- public void testRateSampler() {
-
- final DDSpan mockSpan = mock(DDSpan.class);
-
- final double sampleRate = 0.35;
- final int iterations = 1000;
- final Sampler sampler = new RateSampler(sampleRate);
-
- int kept = 0;
-
- for (int i = 0; i < iterations; i++) {
- if (sampler.sample(mockSpan)) {
- kept++;
- }
- }
- // FIXME test has to be more predictable
- // assertThat(((double) kept / iterations)).isBetween(sampleRate - 0.02, sampleRate + 0.02);
-
- }
-
- @Test
- public void testRateBoundaries() {
-
- RateSampler sampler = new RateSampler(1000);
- assertThat(sampler.getSampleRate()).isEqualTo(1);
-
- sampler = new RateSampler(-1000);
- assertThat(sampler.getSampleRate()).isEqualTo(1);
-
- sampler = new RateSampler(0.337);
- assertThat(sampler.getSampleRate()).isEqualTo(0.337);
- }
-}