diff --git a/dd-trace-api/src/main/java/datadog/trace/api/Config.java b/dd-trace-api/src/main/java/datadog/trace/api/Config.java index 90eb9f358a..e70d979c70 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/Config.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/Config.java @@ -63,6 +63,10 @@ public class Config { public static final String TRACE_EXECUTORS = "trace.executors"; public static final String TRACE_METHODS = "trace.methods"; public static final String TRACE_CLASSES_EXCLUDE = "trace.classes.exclude"; + public static final String TRACE_SAMPLING_SERVICE_RULES = "trace.sampling.service.rules"; + public static final String TRACE_SAMPLING_OPERATION_RULES = "trace.sampling.operation.rules"; + public static final String TRACE_SAMPLING_DEFAULT_RATE = "trace.sampling.default.rate"; + public static final String TRACE_SAMPLING_RATE_LIMIT = "trace.sampling.rate.limit"; public static final String TRACE_REPORT_HOSTNAME = "trace.report-hostname"; public static final String HEADER_TAGS = "trace.header.tags"; public static final String HTTP_SERVER_ERROR_STATUSES = "http.server.error.statuses"; @@ -145,6 +149,7 @@ public class Config { private static final String DEFAULT_TRACE_METHODS = null; public static final boolean DEFAULT_TRACE_ANALYTICS_ENABLED = false; public static final float DEFAULT_ANALYTICS_SAMPLE_RATE = 1.0f; + public static final double DEFAULT_TRACE_SAMPLING_RATE_LIMIT = 100; public enum PropagationStyle { DATADOG, @@ -215,6 +220,11 @@ public class Config { @Getter private final boolean traceAnalyticsEnabled; + @Getter private final Map traceSamplingServiceRules; + @Getter private final Map traceSamplingOperationRules; + @Getter private final Double traceSamplingDefaultRate; + @Getter private final Double traceSamplingRateLimit; + // Values from an optionally provided properties file private static Properties propertiesFromConfigFile; @@ -336,6 +346,14 @@ public class Config { traceAnalyticsEnabled = getBooleanSettingFromEnvironment(TRACE_ANALYTICS_ENABLED, DEFAULT_TRACE_ANALYTICS_ENABLED); + traceSamplingServiceRules = getMapSettingFromEnvironment(TRACE_SAMPLING_SERVICE_RULES, null); + traceSamplingOperationRules = + getMapSettingFromEnvironment(TRACE_SAMPLING_OPERATION_RULES, null); + traceSamplingDefaultRate = getDoubleSettingFromEnvironment(TRACE_SAMPLING_DEFAULT_RATE, null); + traceSamplingRateLimit = + getDoubleSettingFromEnvironment( + TRACE_SAMPLING_RATE_LIMIT, DEFAULT_TRACE_SAMPLING_RATE_LIMIT); + log.debug("New instance: {}", this); } @@ -460,6 +478,19 @@ public class Config { traceAnalyticsEnabled = getPropertyBooleanValue(properties, TRACE_ANALYTICS_ENABLED, parent.traceAnalyticsEnabled); + traceSamplingServiceRules = + getPropertyMapValue( + properties, TRACE_SAMPLING_SERVICE_RULES, parent.traceSamplingServiceRules); + traceSamplingOperationRules = + getPropertyMapValue( + properties, TRACE_SAMPLING_OPERATION_RULES, parent.traceSamplingOperationRules); + traceSamplingDefaultRate = + getPropertyDoubleValue( + properties, TRACE_SAMPLING_DEFAULT_RATE, parent.traceSamplingDefaultRate); + traceSamplingRateLimit = + getPropertyDoubleValue( + properties, TRACE_SAMPLING_RATE_LIMIT, parent.traceSamplingRateLimit); + log.debug("New instance: {}", this); } @@ -697,6 +728,22 @@ public class Config { } } + /** + * Calls {@link #getSettingFromEnvironment(String, String)} and converts the result to a Double. + * + * @deprecated This method should only be used internally. Use the explicit getter instead. + */ + public static Double getDoubleSettingFromEnvironment( + final String name, final Double defaultValue) { + final String value = getSettingFromEnvironment(name, null); + try { + return value == null ? defaultValue : Double.valueOf(value); + } catch (final NumberFormatException e) { + log.warn("Invalid configuration for " + name, e); + return defaultValue; + } + } + /** * Calls {@link #getSettingFromEnvironment(String, String)} and converts the result to a Integer. */ @@ -795,6 +842,18 @@ public class Config { return value == null || value.trim().isEmpty() ? defaultValue : Integer.valueOf(value); } + private static Float getPropertyFloatValue( + final Properties properties, final String name, final Float defaultValue) { + final String value = properties.getProperty(name); + return value == null || value.trim().isEmpty() ? defaultValue : Float.valueOf(value); + } + + private static Double getPropertyDoubleValue( + final Properties properties, final String name, final Double defaultValue) { + final String value = properties.getProperty(name); + return value == null || value.trim().isEmpty() ? defaultValue : Double.valueOf(value); + } + private static > Set getPropertySetValue( final Properties properties, final String name, final Class clazz) { final String value = properties.getProperty(name); diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RuleBasedSampler.java b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RuleBasedSampler.java index 559d209dc8..d99d3685a4 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RuleBasedSampler.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/sampling/RuleBasedSampler.java @@ -32,6 +32,47 @@ public class RuleBasedSampler implements Sampler, PrioritySampler { this.rateLimit = rateLimit; } + public static RuleBasedSampler build( + final Map serviceRules, + final Map operationRules, + final Double defaultRate, + final double rateLimit) { + + final List samplingRules = new ArrayList<>(); + + if (serviceRules != null) { + for (final Entry entry : serviceRules.entrySet()) { + try { + final double rateForEntry = Double.parseDouble(entry.getValue()); + final SamplingRule samplingRule = + new ServiceSamplingRule(entry.getKey(), new KnuthSampler(rateForEntry)); + samplingRules.add(samplingRule); + } catch (final NumberFormatException e) { + log.error("Unable to parse rate for service: {}", entry, e); + } + } + } + + if (operationRules != null) { + for (final Entry entry : operationRules.entrySet()) { + try { + final double rateForEntry = Double.parseDouble(entry.getValue()); + final SamplingRule samplingRule = + new OperationSamplingRule(entry.getKey(), new KnuthSampler(rateForEntry)); + samplingRules.add(samplingRule); + } catch (final NumberFormatException e) { + log.error("Unable to parse rate for operation: {}", entry, e); + } + } + } + + if (defaultRate != null) { + final SamplingRule samplingRule = new AlwaysMatchesSamplingRule(new KnuthSampler(defaultRate)); + samplingRules.add(samplingRule); + } + + return new RuleBasedSampler(samplingRules, rateLimit, new RateByServiceSampler()); + } @Override public boolean sample(final DDSpan span) { 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 072a05f361..2fbf654bc5 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 @@ -2,7 +2,9 @@ package datadog.trace.common.sampling; import datadog.opentracing.DDSpan; import datadog.trace.api.Config; +import java.util.Map; import java.util.Properties; +import lombok.extern.slf4j.Slf4j; /** Main interface to sample a collection of traces. */ public interface Sampler { @@ -15,11 +17,30 @@ public interface Sampler { */ boolean sample(DDSpan span); + @Slf4j final class Builder { public static Sampler forConfig(final Config config) { - final Sampler sampler; + Sampler sampler; if (config != null) { - if (config.isPrioritySamplingEnabled()) { + final Map serviceRules = config.getTraceSamplingServiceRules(); + final Map operationRules = config.getTraceSamplingOperationRules(); + + if ((serviceRules != null && !serviceRules.isEmpty()) + || (operationRules != null && !operationRules.isEmpty()) + || config.getTraceSamplingDefaultRate() != null) { + + try { + sampler = + RuleBasedSampler.build( + serviceRules, + operationRules, + config.getTraceSamplingDefaultRate(), + config.getTraceSamplingRateLimit()); + } catch (final IllegalArgumentException e) { + log.error("Invalid sampler configuration. Using AllSampler", e); + sampler = new AllSampler(); + } + } else if (config.isPrioritySamplingEnabled()) { sampler = new RateByServiceSampler(); } else { sampler = new AllSampler();