diff --git a/agent-interfaces/src/main/java/com/newrelic/agent/interfaces/SamplingPriorityQueue.java b/agent-interfaces/src/main/java/com/newrelic/agent/interfaces/SamplingPriorityQueue.java index 64e690ea70..8c9d43b237 100644 --- a/agent-interfaces/src/main/java/com/newrelic/agent/interfaces/SamplingPriorityQueue.java +++ b/agent-interfaces/src/main/java/com/newrelic/agent/interfaces/SamplingPriorityQueue.java @@ -34,15 +34,11 @@ public interface SamplingPriorityQueue { String getServiceName(); - int getSampled(); - - int getDecided(); - - int getTarget(); - - int getDecidedLast(); + int getTotalSampledPriorityEvents(); int size(); void clear(); + + void logReservoirStats(); } diff --git a/agent-model/src/main/java/com/newrelic/agent/model/AnalyticsEvent.java b/agent-model/src/main/java/com/newrelic/agent/model/AnalyticsEvent.java index 5227f4f2ff..26320bcbd8 100644 --- a/agent-model/src/main/java/com/newrelic/agent/model/AnalyticsEvent.java +++ b/agent-model/src/main/java/com/newrelic/agent/model/AnalyticsEvent.java @@ -68,11 +68,6 @@ public long getTimestamp() { return timestamp; } - @Override - public boolean decider() { - return false; - } - @Override public float getPriority() { return priority; diff --git a/agent-model/src/main/java/com/newrelic/agent/model/PriorityAware.java b/agent-model/src/main/java/com/newrelic/agent/model/PriorityAware.java index f125b6b1d4..2e880af46b 100644 --- a/agent-model/src/main/java/com/newrelic/agent/model/PriorityAware.java +++ b/agent-model/src/main/java/com/newrelic/agent/model/PriorityAware.java @@ -8,12 +8,10 @@ package com.newrelic.agent.model; /** - * Simple interface to grab a priority (float) value from an object and to determine if this app was the "decider". + * Simple interface to grab a priority (float) value from an object. */ public interface PriorityAware { - boolean decider(); - float getPriority(); } diff --git a/agent-model/src/main/java/com/newrelic/agent/model/SpanEvent.java b/agent-model/src/main/java/com/newrelic/agent/model/SpanEvent.java index 03b2fd9a86..439981fde8 100644 --- a/agent-model/src/main/java/com/newrelic/agent/model/SpanEvent.java +++ b/agent-model/src/main/java/com/newrelic/agent/model/SpanEvent.java @@ -25,13 +25,11 @@ public class SpanEvent extends AnalyticsEvent implements JSONStreamAware { private final String appName; private final Map intrinsics; private final Map agentAttributes; - private final boolean decider; private SpanEvent(Builder builder) { super(SPAN, builder.timestamp, builder.priority, builder.userAttributes); this.appName = builder.appName; this.agentAttributes = builder.agentAttributes; - this.decider = builder.decider; this.intrinsics = builder.intrinsics; } @@ -51,11 +49,6 @@ public Map getAgentAttributes() { return agentAttributes; } - @Override - public boolean decider() { - return decider; - } - @Override public void writeJSONString(Writer out) throws IOException { JSONArray.writeJSONString(Arrays.asList(intrinsics, getMutableUserAttributes(), getAgentAttributes()), out); @@ -98,8 +91,7 @@ public boolean equals(Object o) { return false; } SpanEvent spanEvent = (SpanEvent) o; - return decider == spanEvent.decider && - Objects.equals(appName, spanEvent.appName) && + return Objects.equals(appName, spanEvent.appName) && Objects.equals(intrinsics, spanEvent.intrinsics) && Objects.equals(agentAttributes, spanEvent.agentAttributes) && super.equals(o); @@ -107,7 +99,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(appName, intrinsics, agentAttributes, decider); + return Objects.hash(appName, intrinsics, agentAttributes); } public static class Builder { @@ -116,7 +108,6 @@ public static class Builder { private final Map userAttributes = new HashMap<>(); private String appName; private float priority; - private boolean decider; private long timestamp; private Object spanKind; @@ -191,11 +182,6 @@ public Object getSpanKindFromUserAttributes() { return result == null ? CLIENT_SPAN_KIND : result; } - public Builder decider(boolean decider) { - this.decider = decider; - return this; - } - public Builder timestamp(long timestamp) { this.timestamp = timestamp; return this; diff --git a/agent-model/src/test/java/com/newrelic/agent/model/AnalyticsEventTest.java b/agent-model/src/test/java/com/newrelic/agent/model/AnalyticsEventTest.java index 6b26841240..d687bda6c8 100644 --- a/agent-model/src/test/java/com/newrelic/agent/model/AnalyticsEventTest.java +++ b/agent-model/src/test/java/com/newrelic/agent/model/AnalyticsEventTest.java @@ -46,7 +46,6 @@ public void allGetters_returnProperly() { Assert.assertEquals(0.9F, event.getPriority(), .1); Assert.assertEquals("type", event.getType()); Assert.assertTrue(event.isValid()); - Assert.assertFalse(event.decider()); } @Test diff --git a/agent-model/src/test/java/com/newrelic/agent/model/SpanEventTest.java b/agent-model/src/test/java/com/newrelic/agent/model/SpanEventTest.java index 7798c71bc2..cf9a35e6fa 100644 --- a/agent-model/src/test/java/com/newrelic/agent/model/SpanEventTest.java +++ b/agent-model/src/test/java/com/newrelic/agent/model/SpanEventTest.java @@ -53,10 +53,6 @@ public void testEquals() { assertNotEquals(span1, span5); assertNotEquals(span2, span5); - SpanEvent span6 = baseBuilder(now).decider(false).build(); - assertNotEquals(span1, span6); - assertNotEquals(span2, span6); - SpanEvent span7 = baseBuilder(now).appName("somethingDifferent").build(); assertNotEquals(span1, span7); assertNotEquals(span2, span7); @@ -128,7 +124,6 @@ public void spanEvent_getsAll_Attributes() { assertEquals("thud", spanEvent.getName()); assertEquals("8675zzz", spanEvent.getTransactionId()); assertEquals("wally", spanEvent.getAppName()); - assertEquals(true, spanEvent.decider()); } private SpanEvent.Builder baseBuilderExtraUser(long now, String extraUserAttr, String value) { @@ -155,7 +150,6 @@ private SpanEvent.Builder baseBuilder(long now) { .putAllUserAttributes(userAttributes) .putAllAgentAttributes(agentAttributes) .putAllIntrinsics(intrinsics) - .decider(true) .appName("wally") .priority(21.7f) .timestamp(now); diff --git a/instrumentation-test/src/test/java/com/newrelic/agent/introspec/internal/SpanEventImplTest.java b/instrumentation-test/src/test/java/com/newrelic/agent/introspec/internal/SpanEventImplTest.java index a8e5e36a71..abf6207275 100644 --- a/instrumentation-test/src/test/java/com/newrelic/agent/introspec/internal/SpanEventImplTest.java +++ b/instrumentation-test/src/test/java/com/newrelic/agent/introspec/internal/SpanEventImplTest.java @@ -13,7 +13,7 @@ public class SpanEventImplTest { @Test public void getters_returnUnderlyingSpanEventData() throws IOException { SpanEvent.Builder spanEventBuilder = SpanEvent.builder(); - SpanEvent spanEvent = spanEventBuilder.appName("appname").decider(true).priority(.1f).timestamp(100000).putIntrinsic("category", "http") + SpanEvent spanEvent = spanEventBuilder.appName("appname").priority(.1f).timestamp(100000).putIntrinsic("category", "http") .putIntrinsic("transactionId", "transactionid").putIntrinsic("duration", 0.1f) .putIntrinsic("name", "name").putIntrinsic("traceId", "traceid").putIntrinsic("guid", "guid") .putIntrinsic("parentId", "parentid").putAgentAttribute("http.url", "url").putAgentAttribute("http.statusCode", 200) diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/HeadersUtil.java b/newrelic-agent/src/main/java/com/newrelic/agent/HeadersUtil.java index 1266a1e59a..8da4213ba1 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/HeadersUtil.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/HeadersUtil.java @@ -8,6 +8,7 @@ package com.newrelic.agent; import com.google.common.collect.ImmutableSet; +import com.newrelic.agent.tracing.Sampled; import com.newrelic.api.agent.Headers; import com.newrelic.agent.config.DistributedTracingConfig; import com.newrelic.agent.tracers.DefaultTracer; @@ -265,7 +266,7 @@ public static void parseAndAcceptDistributedTraceHeaders(Transaction tx, Inbound } if (w3CTracePayload.getTraceParent() != null) { tx.getSpanProxy().setInitiatingW3CTraceParent(w3CTracePayload.getTraceParent()); - tx.applyDistributedTracingSamplerConfig(w3CTracePayload.getTraceParent()); + tx.assignPriorityFromRemoteParent(w3CTracePayload.getTraceParent().sampled()); } if (w3CTracePayload.getTraceState() != null) { tx.getSpanProxy().setInitiatingW3CTraceState(w3CTracePayload.getTraceState()); @@ -276,6 +277,11 @@ public static void parseAndAcceptDistributedTraceHeaders(Transaction tx, Inbound String tracePayload = HeadersUtil.getNewRelicTraceHeader(inboundHeaders); if (tracePayload != null) { tx.acceptDistributedTracePayload(tracePayload); + DistributedTracePayloadImpl newRelicPayload = tx.getSpanProxy().getInboundDistributedTracePayload(); + boolean samplingDecisionExists = newRelicPayload.sampled != Sampled.UNKNOWN; + if (samplingDecisionExists) { + tx.assignPriorityFromRemoteParent(newRelicPayload.sampled.booleanValue()); + } } } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/RPMService.java b/newrelic-agent/src/main/java/com/newrelic/agent/RPMService.java index ea183900c8..0192f3011c 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/RPMService.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/RPMService.java @@ -14,6 +14,7 @@ import com.newrelic.agent.config.AgentJarHelper; import com.newrelic.agent.config.BrowserMonitoringConfig; import com.newrelic.agent.config.BrowserMonitoringConfigImpl; +import com.newrelic.agent.config.DistributedTracingConfig; import com.newrelic.agent.config.Hostname; import com.newrelic.agent.config.SystemPropertyFactory; import com.newrelic.agent.environment.AgentIdentity; @@ -238,9 +239,6 @@ private Map getSettings(boolean sendEnvironmentInfo) { } settings.put("services", ServiceFactory.getServicesConfiguration()); - // the sysprops and envvars above an in unmodifiable collections, so just change it here - updateSamplingTargetSettings(settings); - return settings; } @@ -973,51 +971,6 @@ private void handle503Error(Exception e) { } } - private void updateSamplingTargetSettings (Map settings) { - // the new distributed_tracing.sampler.adaptive_sampling_target is meant to be per minute - // but, the harvest cycle is 12 times per minute right now, - // so we need to divide this number by 12 until that changes - // there are 2 spots we need to do this: - // - settings.distributed_tracing_sampler_adaptive_sampling_target - // - settings.distributed_tracing.sampler.adaptive_sampling_target - if (settings == null) return; - try { - // local settings - Map dtConfig = (Map) settings.get("distributed_tracing"); - if (dtConfig != null) { - Map samplerConfig = (Map) dtConfig.get("sampler"); - if (samplerConfig != null) { - Integer adaptiveSamplingTarget = toInt(samplerConfig.get("adaptive_sampling_target")); - if (adaptiveSamplingTarget != null) { - Integer newAdaptiveSamplingTarget = (int) Math.ceil(adaptiveSamplingTarget.doubleValue() / 12.0); - NewRelic.getAgent() - .getLogger() - .log(Level.FINE, "Updating local setting adaptive_sampling_target from " + adaptiveSamplingTarget + " to " + - newAdaptiveSamplingTarget); - samplerConfig.put("adaptive_sampling_target", newAdaptiveSamplingTarget); - } - } - } - } catch (Exception e) { - NewRelic.getAgent().getLogger().log(Level.WARNING, "Unable to parse local setting adaptive_sampling_target setting: {0}", e); - } - - try { - // env var - if (settings.containsKey("distributed_tracing_sampler_adaptive_sampling_target")) { - Integer adaptiveSamplingTarget = toInt(settings.get("distributed_tracing_sampler_adaptive_sampling_target")); - Integer newAdaptiveSamplingTarget = (int) Math.ceil(adaptiveSamplingTarget.doubleValue() / 12.0); - NewRelic.getAgent() - .getLogger() - .log(Level.FINE, "Updating environment variable adaptive_sampling_target from " + adaptiveSamplingTarget + " to " + - newAdaptiveSamplingTarget); - settings.put("distributed_tracing_sampler_adaptive_sampling_target", newAdaptiveSamplingTarget); - } - } catch (Exception e) { - NewRelic.getAgent().getLogger().log(Level.WARNING, "Unable to parse environment variable adaptive_sampling_target setting: {0}", e); - } - } - private static Integer toInt(Object o) { if (o == null) return null; if (o instanceof Number) { diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java b/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java index 3f04ac038d..62178e5d43 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java @@ -40,8 +40,6 @@ import com.newrelic.agent.normalization.Normalizer; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.service.ServiceUtils; -import com.newrelic.agent.service.analytics.DistributedSamplingPriorityQueue; -import com.newrelic.agent.service.analytics.TransactionEvent; import com.newrelic.agent.sql.SlowQueryListener; import com.newrelic.agent.stats.AbstractMetricAggregator; import com.newrelic.agent.stats.StatsWorks; @@ -59,8 +57,8 @@ import com.newrelic.agent.tracing.DistributedTracePayloadImpl; import com.newrelic.agent.tracing.DistributedTraceService; import com.newrelic.agent.tracing.DistributedTraceServiceImpl; +import com.newrelic.agent.tracing.Sampled; import com.newrelic.agent.tracing.SpanProxy; -import com.newrelic.agent.tracing.W3CTraceParent; import com.newrelic.agent.transaction.PriorityTransactionName; import com.newrelic.agent.transaction.TransactionCache; import com.newrelic.agent.transaction.TransactionCounts; @@ -283,6 +281,7 @@ public long getTransportDurationInMillis() { // WARNING: Mutates this instance by mutating the span proxy public DistributedTracePayloadImpl createDistributedTracePayload(String spanId) { + assignPriorityRootIfNotSet(); SpanProxy spanProxy = this.spanProxy.get(); long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - this.getTransactionTimer().getStartTimeInNanos()); long txnStartTimeSinceEpochInMillis = System.currentTimeMillis() - elapsedMillis; @@ -333,39 +332,75 @@ public boolean acceptDistributedTracePayload(DistributedTracePayload payload) { } } - public void applyDistributedTracingSamplerConfig(W3CTraceParent parent) { - if (parent != null) { - DistributedTracingConfig dtConfig = getAgentConfig().getDistributedTracingConfig(); - if (parent.sampled()) { // traceparent exists and sampled is 1 - if (DistributedTracingConfig.SAMPLE_ALWAYS_ON.equals(dtConfig.getRemoteParentSampled())) { - this.setPriorityIfNotNull(2.0f); - } else if (DistributedTracingConfig.SAMPLE_ALWAYS_OFF.equals(dtConfig.getRemoteParentSampled())) { - this.setPriorityIfNotNull(0.0f); - } // else leave it as it was - } else { // traceparent exists and sampled is 0 - if (DistributedTracingConfig.SAMPLE_ALWAYS_ON.equals(dtConfig.getRemoteParentNotSampled())) { - this.setPriorityIfNotNull(2.0f); - } else if (DistributedTracingConfig.SAMPLE_ALWAYS_OFF.equals(dtConfig.getRemoteParentNotSampled())) { - this.setPriorityIfNotNull(0.0f); - } // else leave it as it was + /** + * Assigns priority to this transaction using sampling and priority data from a remote parent. + * + * There are two kinds of parent data that are used: + * - The remote parent sampled flag, which indicates whether the remote parent is sampled or not. This + * determines which parent sampler (remote_parent_sampled or remote_parent_not_sampled) to use when making the priority assignment. + * - Inbound priority data, which is taken from the span proxy's inbound payload if available. This + * will be used by the adaptive sampler if configured, and ignored by all other sampler types. + * + * @param remoteParentSampled whether the remote parent was sampled or not + */ + public void assignPriorityFromRemoteParent(boolean remoteParentSampled) { + DistributedTraceService dtService = ServiceFactory.getDistributedTraceService(); + float priority = dtService.calculatePriorityRemoteParent(remoteParentSampled, getPriorityFromInboundSamplingDecision()); + this.priority.set(priority); + } + + /** + * Assigns priority to this transaction (unless previously assigned) without any information from a remote parent. + * + * If Distributed Tracing is enabled, and no priority has been set on this transaction, the configured root sampler + * will be used to obtain a priority for this transaction. No inbound priority data is read (because if an inbound + * payload was processed, it should have made a priority assignment earlier). + * + * If a priority assignment has already been made, this call is ignored. This is a required check to avoid + * overwriting any priority decision that was made earlier, either because a remote parent was processed or a previous + * call to this method was made earlier in the txn's lifecycle. It is also required to avoid accidentally running the + * adaptive sampler twice on the same transaction. + * + * If Distributed Tracing is not enabled, or this is a synthetic transaction, a random priority in [0,1) is assigned. + */ + public void assignPriorityRootIfNotSet(){ + if (getAgentConfig().getDistributedTracingConfig().isEnabled()){ + if (priority.get() == null){ + //The "if" check above is required even though we do compareAndSet(null) below. + //Its purpose is to avoid running the sampler more than once for the same txn. + Float samplerPriority = ServiceFactory.getDistributedTraceService().calculatePriorityRoot(); + priority.compareAndSet(null, samplerPriority); } + } else { + priority.compareAndSet(null, DistributedTraceServiceImpl.nextTruncatedFloat()); } } - private void checkAndSetPriority() { - if (getAgentConfig().getDistributedTracingConfig().isEnabled()) { - DistributedTraceService distributedTraceService = ServiceFactory.getDistributedTraceService(); - - DistributedTracePayloadImpl inboundPayload = spanProxy.get().getInboundDistributedTracePayload(); - Float inboundPriority = inboundPayload != null ? inboundPayload.priority : null; - - DistributedSamplingPriorityQueue reservoir = ServiceFactory.getTransactionEventsService() - .getOrCreateDistributedSamplingReservoir(getApplicationName()); + /*** + * Retrieve priority from the inbound payload (using both sampling and priority-related information). + * + * First, check to see if there is a sampling decision available on the inbound payload. + * - If there is a sampling decision, use the inbound priority if it exists or compute a new priority. + * - If there is no sampling decision, return null. + * + * In the case of W3C headers, this is distinct from the remoteParentSampled decision we get from the trace parent header. + * The sampling and priority values in the payload come from the trace state header (and they may be missing, even if we got a sampled + * flag on the trace parent header). + * + * @return a float in [0, 2) if priority-related information was found, or null if a new decision needs to be made + */ - priority.compareAndSet(null, distributedTraceService.calculatePriority(inboundPriority, reservoir)); - } else { - priority.compareAndSet(null, DistributedTraceServiceImpl.nextTruncatedFloat()); + @VisibleForTesting + protected Float getPriorityFromInboundSamplingDecision(){ + DistributedTracePayloadImpl payload = spanProxy.get().getInboundDistributedTracePayload(); + if (payload != null && payload.sampled != Sampled.UNKNOWN) { + if (payload.priority != null) { + return payload.priority; + } else { + return (payload.sampled.booleanValue() ? 1.0f : 0.0f) + DistributedTraceServiceImpl.nextTruncatedFloat(); + } } + return null; } public TransportType getTransportType() { @@ -496,7 +531,6 @@ protected Transaction() { // registered. private void postConstruct() { this.initialActivity = TransactionActivity.create(this, nextActivityId.getAndIncrement());; - checkAndSetPriority(); } private static long getGCTime() { @@ -1012,7 +1046,7 @@ private void finishTransaction() { synchronized (lock) { // this may have the side-effect of ignoring the transaction freezeTransactionName(); - + assignPriorityRootIfNotSet(); if (ignore) { Agent.LOG.log(Level.FINER, "Transaction {0} was cancelled: ignored. This is not an error condition.", this); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java index d43267ce20..8564237793 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java @@ -55,6 +55,10 @@ public interface AgentConfig extends com.newrelic.api.agent.Config, DataSenderCo */ boolean isAutoTransactionNamingEnabled(); + int getAdaptiveSamplingTarget(); + + int getAdaptiveSamplingPeriodSeconds(); + /** * Get the ApdexT value sent by New Relic, or the default value. */ diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigFactory.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigFactory.java index 3cc4618ee0..a4e3a912f1 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigFactory.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigFactory.java @@ -66,6 +66,8 @@ public class AgentConfigFactory { ApplicationLoggingConfigImpl.LOCAL_DECORATING + DOT_SEPARATOR + ApplicationLoggingForwardingConfig.ENABLED; public static final String APPLICATION_LOGGING_METRICS_ENABLED = AgentConfigImpl.APPLICATION_LOGGING + DOT_SEPARATOR + ApplicationLoggingConfigImpl.METRICS + DOT_SEPARATOR + ApplicationLoggingForwardingConfig.ENABLED; + public static final String ADAPTIVE_SAMPLER_SAMPLING_PERIOD = AgentConfigImpl.ADAPTIVE_SAMPLER_SAMPLING_PERIOD; + public static final String ADAPTIVE_SAMPLER_SAMPLING_TARGET = AgentConfigImpl.ADAPTIVE_SAMPLER_SAMPLING_TARGET; @Deprecated public static final String SLOW_QUERY_WHITELIST = TRANSACTION_TRACER_PREFIX + TransactionTracerConfigImpl.SLOW_QUERY_WHITELIST; public static final String COLLECT_SLOW_QUERIES_FROM = TRANSACTION_TRACER_PREFIX + TransactionTracerConfigImpl.COLLECT_SLOW_QUERIES_FROM; @@ -217,6 +219,9 @@ public static void mergeServerData(Map settings, Map appNames; @@ -376,6 +380,11 @@ private AgentConfigImpl(Map props) { slowTransactionsConfig = initSlowTransactionsConfig(); obfuscateJvmPropsConfig = initObfuscateJvmPropsConfig(); agentControlIntegrationConfig = initAgentControlHealthCheckConfig(); + //This setting should use the locally configured distributed_tracing.sampler.adaptive_sampling_target setting + //until it is later merged with sampling_target from the server. + //If nothing is configured, it will use the local default, which is 120. + adaptiveSamplingTarget = getProperty(ADAPTIVE_SAMPLER_SAMPLING_TARGET, distributedTracingConfig.getAdaptiveSamplingTarget()); + adaptiveSamplingPeriodSeconds = getProperty(ADAPTIVE_SAMPLER_SAMPLING_PERIOD, DistributedTracingConfig.DEFAULT_ADAPTIVE_SAMPLING_PERIOD); Map flattenedProps = new HashMap<>(); flatten("", props, flattenedProps); @@ -846,6 +855,16 @@ private AgentControlIntegrationConfig initAgentControlHealthCheckConfig() { return new AgentControlIntegrationConfigImpl(nestedProps(AgentControlIntegrationConfigImpl.ROOT)); } + @Override + public int getAdaptiveSamplingTarget(){ + return adaptiveSamplingTarget; + } + + @Override + public int getAdaptiveSamplingPeriodSeconds(){ + return adaptiveSamplingPeriodSeconds; + } + @Override public long getApdexTInMillis() { return apdexTInMillis; diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/ConfigServiceImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/ConfigServiceImpl.java index f6f710f189..64c8daaade 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/ConfigServiceImpl.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/ConfigServiceImpl.java @@ -18,6 +18,7 @@ import com.newrelic.agent.service.AbstractService; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.stats.StatsEngine; +import com.newrelic.api.agent.NewRelic; import org.json.simple.JSONObject; import java.io.File; @@ -139,6 +140,8 @@ public Map getSanitizedLocalSettings() { if (settings.containsKey(AgentConfigImpl.PROXY_PASS)) { settings.put(AgentConfigImpl.PROXY_PASS, SANITIZED_SETTING); } + //if there is no adaptive_sampling_target set, set it here. + addAdaptiveSamplerDefaultIfNotSet(settings); return settings; } @@ -213,6 +216,18 @@ private void checkConfigFile() throws Exception { replaceServerConfig(defaultAppName, fileSettings, savedServerData, laspPolicies); } + private void addAdaptiveSamplerDefaultIfNotSet(Map settings) { + try { + settings.putIfAbsent("distributed_tracing", new HashMap<>()); + Map dtSettings = (Map) settings.get("distributed_tracing"); + dtSettings.putIfAbsent("sampler", new HashMap<>()); + Map samplerSettings = (Map) dtSettings.get("sampler"); + samplerSettings.putIfAbsent(DistributedTracingConfig.ADAPTIVE_SAMPLING_TARGET, DistributedTracingConfig.DEFAULT_ADAPTIVE_SAMPLING_TARGET); + } catch (Exception e){ + NewRelic.getAgent().getLogger().log(Level.WARNING, "Error adding default adaptive sampling target settings to agent config."); + } + } + private void updateDynamicAuditAndLogConfig(AgentConfig localAgentConfig, Map settings) { AuditModeConfig auditModeConfig = localAgentConfig.getAuditModeConfig(); if (auditModeConfig != null) { diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/DistributedTracingConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/DistributedTracingConfig.java index d6568154e7..4d0cc503aa 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/DistributedTracingConfig.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/DistributedTracingConfig.java @@ -15,9 +15,9 @@ public class DistributedTracingConfig extends BaseConfig { private static final boolean DEFAULT_DISTRIBUTED_TRACING = true; - private static final Integer DEFAULT_ADAPTIVE_SAMPLING_TARGET = 120; private static final String SYSTEM_PROPERTY_ROOT = "newrelic.config.distributed_tracing."; + //public setting names public static final String ENABLED = "enabled"; public static final String TRUSTED_ACCOUNT_KEY = "trusted_account_key"; public static final String ACCOUNT_ID = "account_id"; @@ -26,17 +26,19 @@ public class DistributedTracingConfig extends BaseConfig { public static final String ENABLED_ENV_KEY = "NEW_RELIC_DISTRIBUTED_TRACING_ENABLED"; public static final String EXCLUDE_NEWRELIC_HEADER = "exclude_newrelic_header"; public static final String SAMPLER = "sampler"; + public static final String ADAPTIVE_SAMPLING_TARGET = "adaptive_sampling_target"; // see below public static final String REMOTE_PARENT_SAMPLED = "remote_parent_sampled"; public static final String REMOTE_PARENT_NOT_SAMPLED = "remote_parent_not_sampled"; - public static final String SAMPLE_ALWAYS_ON = "always_on"; - public static final String SAMPLE_ALWAYS_OFF = "always_off"; + //public setting values + public static final Integer DEFAULT_ADAPTIVE_SAMPLING_TARGET = 120; + public static final Integer DEFAULT_ADAPTIVE_SAMPLING_PERIOD = 60; // note: there is no special logic for these yet, as they are just the fallback if the other values don't exist // if we have to add special logic, we should treat one as an alias of the other public static final String SAMPLE_DEFAULT = "default"; // same as 'adaptive_sampling' - public static final String SAMPLE_ADAPTIVE_SAMPLING = "adaptive_sampling"; // same as 'default' - - public static final String ADAPTIVE_SAMPLING_TARGET = "adaptive_sampling_target"; // see below + public static final String SAMPLE_ADAPTIVE_SAMPLING = "adaptive_sampling"; + public static final String SAMPLE_ALWAYS_ON = "always_on"; + public static final String SAMPLE_ALWAYS_OFF = "always_off";// same as 'default' private final boolean enabled; private final String trustedAccountKey; @@ -92,4 +94,8 @@ public String getRemoteParentSampled() { public String getRemoteParentNotSampled() { return remoteParentNotSampled; } + + public Integer getAdaptiveSamplingTarget() { + return adaptiveSamplingTarget; + } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/AdaptiveSampling.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/AdaptiveSampling.java deleted file mode 100644 index 8ee2ed1de9..0000000000 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/AdaptiveSampling.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * - * * Copyright 2020 New Relic Corporation. All rights reserved. - * * SPDX-License-Identifier: Apache-2.0 - * - */ - -package com.newrelic.agent.service.analytics; - -import com.newrelic.agent.Agent; -import com.newrelic.agent.interfaces.SamplingPriorityQueue; - -import java.util.logging.Level; - -class AdaptiveSampling { - - static int decidedLast(SamplingPriorityQueue reservoir, int target) { - if (reservoir == null) { - return 0; - } - - Agent.LOG.log(Level.FINER, "Application \"{0}\" decided {1} event(s) for {2}, sampled {3} of them with a target of {4}, decided {5} last time", - reservoir.getAppName(), reservoir.getDecided(), reservoir.getServiceName(), reservoir.getSampled(), target, reservoir.getDecidedLast()); - - return reservoir.getDecided(); - } - -} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/CollectorSpanEventReservoirManager.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/CollectorSpanEventReservoirManager.java index 48d796f163..b2a720b179 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/CollectorSpanEventReservoirManager.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/CollectorSpanEventReservoirManager.java @@ -36,7 +36,7 @@ public CollectorSpanEventReservoirManager(ConfigService configService) { public SamplingPriorityQueue getOrCreateReservoir(String appName) { SamplingPriorityQueue reservoir = spanReservoirsForApp.get(appName); if (reservoir == null) { - reservoir = spanReservoirsForApp.putIfAbsent(appName, createDistributedSamplingReservoir(appName, 0)); + reservoir = spanReservoirsForApp.putIfAbsent(appName, createDistributedSamplingReservoir(appName)); if (reservoir == null) { reservoir = spanReservoirsForApp.get(appName); } @@ -44,10 +44,8 @@ public SamplingPriorityQueue getOrCreateReservoir(String appName) { return reservoir; } - private SamplingPriorityQueue createDistributedSamplingReservoir(String appName, int decidedLast) { - SpanEventsConfig spanEventsConfig = configService.getDefaultAgentConfig().getSpanEventsConfig(); - int target = spanEventsConfig.getTargetSamplesStored(); - return new DistributedSamplingPriorityQueue<>(appName, "Span Event Service", maxSamplesStored, decidedLast, target, SPAN_EVENT_COMPARATOR); + private SamplingPriorityQueue createDistributedSamplingReservoir(String appName) { + return new DistributedSamplingPriorityQueue<>(appName, "Span Event Service", maxSamplesStored, SPAN_EVENT_COMPARATOR); } @Override @@ -63,11 +61,15 @@ public HarvestResult attemptToSendReservoir(final String appName, EventSender toSend = spanReservoirsForApp.get(appName); - spanReservoirsForApp.put(appName, createDistributedSamplingReservoir(appName, decidedLast)); + + if (toSend != null){ + toSend.logReservoirStats(); + } + + spanReservoirsForApp.put(appName, createDistributedSamplingReservoir(appName)); if (toSend == null || toSend.size() <= 0) { return null; @@ -107,7 +109,7 @@ public int getMaxSamplesStored() { public void setMaxSamplesStored(int newMax) { maxSamplesStored = newMax; ConcurrentHashMap> newMaxSpanReservoirs = new ConcurrentHashMap<>(); - spanReservoirsForApp.forEach((appName,reservoir ) -> newMaxSpanReservoirs.putIfAbsent(appName, createDistributedSamplingReservoir(appName, 0))); + spanReservoirsForApp.forEach((appName,reservoir ) -> newMaxSpanReservoirs.putIfAbsent(appName, createDistributedSamplingReservoir(appName))); spanReservoirsForApp = newMaxSpanReservoirs; } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueue.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueue.java index fd43805732..f2d6ca08c3 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueue.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueue.java @@ -7,72 +7,54 @@ package com.newrelic.agent.service.analytics; -import com.google.common.collect.MinMaxPriorityQueue; -import com.google.common.collect.Queues; import com.newrelic.agent.interfaces.SamplingPriorityQueue; import com.newrelic.agent.model.PriorityAware; import com.newrelic.agent.tracing.DistributedTraceUtil; +import com.newrelic.agent.util.MinAwareQueue; import com.newrelic.agent.util.NoOpQueue; +import com.newrelic.agent.util.SynchronizedMinAwareQueue; +import com.newrelic.api.agent.NewRelic; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; public class DistributedSamplingPriorityQueue implements SamplingPriorityQueue { private final String appName; private final String serviceName; - private final Queue data; + private final MinAwareQueue data; private final AtomicInteger numberOfTries = new AtomicInteger(); - private final AtomicInteger recorded; - // the number of times the decider was used on an event on this application. That meaning, the number of - // events that started on this application that did not accept a payload. - private final AtomicInteger decided; - - private final int decidedLast; - private final int target; + private final AtomicInteger sampled; private final Comparator comparator; private final int maximumSize; public DistributedSamplingPriorityQueue(int reservoirSize) { - this("", "", reservoirSize, 0, 0, null); + this("", "", reservoirSize, null); } public DistributedSamplingPriorityQueue(String appName, String serviceName, int reservoirSize) { - this(appName, serviceName, reservoirSize, 0, 0, null); - } - - public DistributedSamplingPriorityQueue(int reservoirSize, int decidedLast, int target) { - this("", "", reservoirSize, decidedLast, target, null); - } - - public DistributedSamplingPriorityQueue(String appName, String serviceName, int reservoirSize, int decidedLast, int target) { - this(appName, serviceName, reservoirSize, decidedLast, target, null); + this(appName, serviceName, reservoirSize, null); } - public DistributedSamplingPriorityQueue(int reservoirSize, int decidedLast, int target, Comparator comparator) { - this("", "", reservoirSize, decidedLast, target, comparator); + public DistributedSamplingPriorityQueue(int reservoirSize, Comparator comparator) { + this("", "", reservoirSize, comparator); } - public DistributedSamplingPriorityQueue(String appName, String serviceName, int reservoirSize, int decidedLast, int target, Comparator comparator) { + public DistributedSamplingPriorityQueue(String appName, String serviceName, int reservoirSize, Comparator comparator) { this.appName = appName; this.serviceName = serviceName; this.comparator = comparator == null ? (left, right) -> Float.compare(right.getPriority(), left.getPriority()) : comparator; this.data = createQueue(reservoirSize, this.comparator); - this.recorded = new AtomicInteger(0); - this.decidedLast = decidedLast; - this.target = target; - this.decided = new AtomicInteger(0); + this.sampled = new AtomicInteger(0); this.maximumSize = reservoirSize; } - private Queue createQueue(int reservoirSize, Comparator comparator) { + private MinAwareQueue createQueue(int reservoirSize, Comparator comparator) { if (reservoirSize <= 0) { return new NoOpQueue<>(); } else { - return Queues.synchronizedQueue(MinMaxPriorityQueue - .orderedBy(comparator) - .maximumSize(reservoirSize) - .create()); + return new SynchronizedMinAwareQueue<>(reservoirSize, comparator); } } @@ -98,7 +80,7 @@ public boolean isFull() { @Override public float getMinPriority() { - return data.isEmpty() ? 0.0f : data.peek().getPriority(); + return data.isEmpty() ? 0.0f : data.peekLast().getPriority(); } @Override @@ -115,11 +97,8 @@ public void incrementNumberOfTries() { public boolean add(E element) { incrementNumberOfTries(); boolean added = data.offer(element); - if (added && element.decider()) { - decided.incrementAndGet(); - if (DistributedTraceUtil.isSampledPriority(element.getPriority())) { - recorded.incrementAndGet(); - } + if (added && DistributedTraceUtil.isSampledPriority(element.getPriority())) { + sampled.incrementAndGet(); } return added; } @@ -155,23 +134,8 @@ public String getServiceName() { } @Override - public int getSampled() { - return recorded.get(); - } - - @Override - public int getDecided() { - return decided.get(); - } - - @Override - public int getTarget() { - return target; - } - - @Override - public int getDecidedLast() { - return decidedLast; + public int getTotalSampledPriorityEvents(){ + return sampled.get(); } @Override @@ -184,4 +148,9 @@ public void clear() { data.clear(); } + public void logReservoirStats() { + NewRelic.getAgent().getLogger().log(Level.FINER, "Application {0} saw {1} events for {2}, added {3} of sampling priority.", + appName, getNumberOfTries(), serviceName, getTotalSampledPriorityEvents()); + } + } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java index 73368f8921..ef51fa7d92 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java @@ -353,11 +353,6 @@ private String truncateWithEllipsis(String value, int maxLengthWithEllipsis) { return value; } - public SpanEventFactory setDecider(boolean decider) { - builder.decider(decider); - return this; - } - private void setErrorClass(Class throwableClass, Integer errorStatus) { if (filter.shouldIncludeAgentAttribute(appName, "error.class")) { if (throwableClass != null) { diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TracerToSpanEvent.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TracerToSpanEvent.java index c48b44abf7..d9bc6658ff 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TracerToSpanEvent.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TracerToSpanEvent.java @@ -80,7 +80,6 @@ public TracerToSpanEvent(Map errorBuilderForApp, Envir public SpanEvent createSpanEvent(Tracer tracer, TransactionData transactionData, TransactionStats transactionStats, boolean isRoot, boolean crossProcessOnly) { SpanProxy spanProxy = transactionData.getSpanProxy(); - DistributedTracePayloadImpl inboundPayload = spanProxy.getInboundDistributedTracePayload(); SpanEventFactory builder = new SpanEventFactory(transactionData.getApplicationName(), filter, timestampSupplier) .setGuid(tracer.getGuid()) @@ -96,8 +95,7 @@ public SpanEvent createSpanEvent(Tracer tracer, TransactionData transactionData, .setExternalParameterAttributes(tracer.getExternalParameters()) .setAgentAttributesMarkedForSpans(tracer.getAgentAttributeNamesForSpans(), tracer.getAgentAttributes()) .setStackTraceAttributes(tracer.getAgentAttributes()) - .setIsRootSpanEvent(isRoot) - .setDecider(inboundPayload == null || inboundPayload.priority == null); + .setIsRootSpanEvent(isRoot); builder = maybeSetError(tracer, transactionData, isRoot, builder); builder = maybeSetGraphQLAttributes(tracer, builder); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEvent.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEvent.java index 71601c1673..9462b0a51d 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEvent.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEvent.java @@ -48,7 +48,6 @@ public class TransactionEvent extends AnalyticsEvent implements JSONStreamAware private final int port; private final boolean error; - private final boolean decider; /** * Required. Full metric name of the transaction */ @@ -71,7 +70,7 @@ public class TransactionEvent extends AnalyticsEvent implements JSONStreamAware public TransactionEvent(String appName, Map userAttributes, long timestamp, String name, TransactionTiming timing, String guid, String referringGuid, Integer port, String tripId, PathHashes pathHashes, ApdexPerfZone apdexPerfZone, SyntheticsIds syntheticsIds, SyntheticsInfo syntheticsInfo, boolean error, TimeoutCause timeoutCause, - float priority, Map distributedTraceIntrinsics, boolean decider) { + float priority, Map distributedTraceIntrinsics) { super(TYPE, timestamp, priority, userAttributes); if (pathHashes == null) throw new NullPointerException("pathHashes must not be null"); if (syntheticsIds == null) throw new NullPointerException("syntheticsIds must not be null"); @@ -90,7 +89,6 @@ public TransactionEvent(String appName, Map userAttributes, long this.syntheticsInfo = syntheticsInfo; this.error = error; this.timeoutCause = timeoutCause; - this.decider = decider; this.distributedTraceIntrinsics = distributedTraceIntrinsics; } @@ -326,11 +324,6 @@ public boolean isValid() { return true; } - @Override - public boolean decider() { - return decider; - } - @VisibleForTesting public Map getAgentAttributesCopy() { return new HashMap<>(agentAttributes); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventBuilder.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventBuilder.java index 37c80f4642..4dd490237a 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventBuilder.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventBuilder.java @@ -38,7 +38,6 @@ public class TransactionEventBuilder { private float priority; private final Map userAttributes = new HashMap<>(); private Map distributedTraceIntrinsics; - private boolean decider; private float timeToFirstByte = UNASSIGNED_FLOAT; private float timeToLastByte = UNASSIGNED_FLOAT; private PathHashes pathHashes; @@ -115,11 +114,6 @@ public TransactionEventBuilder setPriority(float priority) { return this; } - public TransactionEventBuilder setDecider(boolean decider) { - this.decider = decider; - return this; - } - public TransactionEventBuilder putAllUserAttributes(Map additionalAttributes) { this.userAttributes.putAll(additionalAttributes); return this; @@ -198,6 +192,6 @@ public TransactionEvent build() { return new TransactionEvent(appName, userAttributes, timestamp, name, timing, guid, referringGuid, port, tripId, pathHashes, apdexPerfZone, syntheticsIds, syntheticsInfo, error, timeoutCause, - priority, distributedTraceIntrinsics, decider); + priority, distributedTraceIntrinsics); } } \ No newline at end of file diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventsService.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventsService.java index f026f5e384..627c62f6ec 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventsService.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/TransactionEventsService.java @@ -186,13 +186,14 @@ public void harvestEvents(final String appName) { long startTimeInNanos = System.nanoTime(); beforeHarvestSynthetics(appName); - int targetStored = config.getTargetSamplesStored(); DistributedSamplingPriorityQueue currentReservoir = reservoirForApp.get(appName); - int decidedLast = AdaptiveSampling.decidedLast(currentReservoir, targetStored); + if (currentReservoir != null){ + currentReservoir.logReservoirStats(); + } // Now the reservoir for per-transaction analytic events from ordinary non-synthetic transactions final DistributedSamplingPriorityQueue reservoirToSend = reservoirForApp.put(appName, - new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored, decidedLast, targetStored)); + new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored)); if (reservoirToSend != null && reservoirToSend.size() > 0) { try { @@ -258,11 +259,11 @@ private void recordSupportabilityMetrics(StatsEngine statsEngine, long durationI private void beforeHarvestSynthetics(String appName) { DistributedSamplingPriorityQueue currentReservoir = syntheticsListForApp.get(appName); - int decidedLast = AdaptiveSampling.decidedLast(currentReservoir, config.getTargetSamplesStored()); - + if (currentReservoir != null){ + currentReservoir.logReservoirStats(); + } DistributedSamplingPriorityQueue current = syntheticsListForApp.put(appName, - new DistributedSamplingPriorityQueue(appName, "Synthetics Event Service", MAX_SYNTHETIC_EVENTS_PER_APP, decidedLast, - config.getTargetSamplesStored())); + new DistributedSamplingPriorityQueue(appName, "Synthetics Event Service", MAX_SYNTHETIC_EVENTS_PER_APP)); if (current != null && current.size() > 0) { if (pendingSyntheticsHeaps.size() < MAX_UNSENT_SYNTHETICS_HOLDERS) { pendingSyntheticsHeaps.add(current); @@ -324,13 +325,12 @@ public void dispatcherTransactionFinished(TransactionData transactionData, Trans boolean persisted = false; - int target = config.getTargetSamplesStored(); if (transactionData.isSyntheticTransaction()) { DistributedSamplingPriorityQueue currentSyntheticsList = syntheticsListForApp.get(appName); while (currentSyntheticsList == null) { // I don't think this loop can actually execute more than once, but it's prudent to assume it can. syntheticsListForApp.putIfAbsent(appName, - new DistributedSamplingPriorityQueue(appName, "Synthetics Event Service", MAX_SYNTHETIC_EVENTS_PER_APP, 0, target)); + new DistributedSamplingPriorityQueue(appName, "Synthetics Event Service", MAX_SYNTHETIC_EVENTS_PER_APP)); currentSyntheticsList = syntheticsListForApp.get(appName); } @@ -344,7 +344,7 @@ public void dispatcherTransactionFinished(TransactionData transactionData, Trans while (currentReservoir == null) { // I don't think this loop can actually execute more than once, but it's prudent to assume it can. reservoirForApp.putIfAbsent(appName, - new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored, 0, target)); + new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored)); currentReservoir = reservoirForApp.get(appName); } if (!currentReservoir.isFull() || currentReservoir.getMinPriority() < transactionData.getPriority()) { @@ -399,10 +399,6 @@ public TransactionEvent createEvent(TransactionData transactionData, Transaction .setPriority(transactionData.getPriority()); if (distributedTracingEnabled) { - DistributedTracePayloadImpl inboundDistributedTracePayload = transactionData.getInboundDistributedTracePayload(); - eventBuilder = eventBuilder - .setDecider(inboundDistributedTracePayload == null || inboundDistributedTracePayload.priority == null); - Map distributedTraceServiceIntrinsics = transactionDataToDistributedTraceIntrinsics.buildDistributedTracingIntrinsics(transactionData, true); eventBuilder = eventBuilder.setDistributedTraceIntrinsics(distributedTraceServiceIntrinsics); @@ -529,9 +525,8 @@ public DistributedSamplingPriorityQueue getDistributedSampling public DistributedSamplingPriorityQueue getOrCreateDistributedSamplingReservoir(String appName) { DistributedSamplingPriorityQueue reservoir = reservoirForApp.get(appName); if (reservoir == null) { - int target = config.getTargetSamplesStored(); reservoir = reservoirForApp.putIfAbsent(appName, - new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored, 0, target)); + new DistributedSamplingPriorityQueue(appName, "Transaction Event Service", maxSamplesStored)); if (reservoir == null) { reservoir = reservoirForApp.get(appName); } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceService.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceService.java index f1e723954c..af7b89a2f3 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceService.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceService.java @@ -27,7 +27,10 @@ public interface DistributedTraceService { String getApplicationId(); - float calculatePriority(Float priority, SamplingPriorityQueue reservoir); + + float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority); + + float calculatePriorityRoot(); Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, String traceId, TransportType transportType, long parentTransportDuration, long largestTransportDuration, String parentId, String parentSpanId, float priority); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceServiceImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceServiceImpl.java index a46eb242e3..c930ee62d3 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceServiceImpl.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/DistributedTraceServiceImpl.java @@ -16,12 +16,12 @@ import com.newrelic.agent.Transaction; import com.newrelic.agent.TransactionData; import com.newrelic.agent.bridge.NoOpDistributedTracePayload; +import com.newrelic.agent.tracing.samplers.AdaptiveSampler; +import com.newrelic.agent.tracing.samplers.Sampler; import com.newrelic.api.agent.TransportType; import com.newrelic.agent.config.AgentConfig; import com.newrelic.agent.config.AgentConfigListener; import com.newrelic.agent.config.DistributedTracingConfig; -import com.newrelic.agent.interfaces.SamplingPriorityQueue; -import com.newrelic.agent.model.PriorityAware; import com.newrelic.agent.service.AbstractService; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.stats.StatsEngine; @@ -55,6 +55,11 @@ public class DistributedTraceServiceImpl extends AbstractService implements Dist private DistributedTracingConfig distributedTraceConfig; + private Sampler sampler; + private Sampler remoteParentSampledSampler; + private Sampler remoteParentNotSampledSampler; + + // Instantiate a new DecimalFormat instance as it is not thread safe: // http://jonamiller.com/2015/12/21/decimalformat-is-not-thread-safe/ private static final ThreadLocal FORMATTER = @@ -76,6 +81,11 @@ public DistributedTraceServiceImpl() { super(DistributedTraceServiceImpl.class.getSimpleName()); distributedTraceConfig = ServiceFactory.getConfigService().getDefaultAgentConfig().getDistributedTracingConfig(); ServiceFactory.getConfigService().addIAgentConfigListener(this); + //Initially, set up the samplers based on local config. + //The adaptive sampler (SAMPLE_DEFAULT) will have its target overridden when we receive the connect response later. + this.sampler = Sampler.getSamplerForType(DistributedTracingConfig.SAMPLE_DEFAULT); + this.remoteParentSampledSampler = Sampler.getSamplerForType(distributedTraceConfig.getRemoteParentSampled()); + this.remoteParentNotSampledSampler = Sampler.getSamplerForType(distributedTraceConfig.getRemoteParentNotSampled()); } @Override @@ -128,6 +138,9 @@ public void connected(IRPMService rpmService, AgentConfig agentConfig) { // Fallback in case none of the previous attempts set the application ID applicationId.compareAndSet(null, "0"); + + //The connect response includes a server-only config, sampling_target, that MUST be used to configure the adaptive sampler shared instance. + AdaptiveSampler.setSharedTarget(agentConfig.getAdaptiveSamplingTarget()); } @Override @@ -175,43 +188,17 @@ public String getApplicationId() { } @Override - public float calculatePriority(Float priority, SamplingPriorityQueue reservoir) { - if (priority == null) { - if (reservoir == null) { - return nextTruncatedFloat(); - } - - // No inbound "priority" flag set so we need to calculate priority/sampling - int seen = reservoir.getNumberOfTries(); - if (firstHarvest.get() || seen == 0) { - if (seen <= reservoir.getTarget()) { - // Make sure we record the first 'target' events in the system - return nextTruncatedFloat() + 1.0f; - } - return nextTruncatedFloat(); - } - - int seenLast = reservoir.getDecidedLast(); - int sampled = reservoir.getSampled(); - int target = reservoir.getTarget(); - - boolean shouldSample; - if (sampled < target) { - shouldSample = (seenLast <= 0 ? 0 : ThreadLocalRandom.current().nextInt(seenLast)) < target; - } else if (sampled > (target * 2)) { - // As soon as we hit 2x the number of "target" events sampled, we need to stop - shouldSample = false; - } else { - int expTarget = (int) (Math.pow((float) target, (float) target / sampled) - Math.pow((float) target, 0.5)); - shouldSample = ThreadLocalRandom.current().nextInt(seen) < expTarget; - } - - // Add 1.0f to the priority number if we should sample based on previous throughput - return nextTruncatedFloat() + (shouldSample ? 1.0f : 0.0f); - } else { - // There was already a priority set on the inbound payload, don't truncate it - return priority; + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority){ + Sampler parentSampler = remoteParentSampled ? remoteParentSampledSampler : remoteParentNotSampledSampler; + if (parentSampler.getType().equals(Sampler.ADAPTIVE) && inboundPriority != null) { + return inboundPriority; } + return parentSampler.calculatePriority(); + } + + @Override + public float calculatePriorityRoot(){ + return sampler.calculatePriority(); } @Override @@ -364,6 +351,7 @@ public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { // Override guid if this trace is sampled and spans are enabled // guid will be the guid of the span that is creating this payload boolean spansEnabled = ServiceFactory.getConfigService().getDefaultAgentConfig().getSpanEventsConfig().isEnabled(); + tx.assignPriorityRootIfNotSet(); boolean sampled = DistributedTraceUtil.isSampledPriority(tx.getPriority()); if (sampled && spansEnabled) { // Need to do this in case the span that created this is a @Trace(excludeFromTransactionTrace=true) @@ -390,4 +378,30 @@ public void configChanged(String appName, AgentConfig agentConfig) { !distributedTraceConfig.isIncludeNewRelicHeader())); } } + + // These setters are NOT thread-safe. + // They should NOT be used outside of testing. + void setRemoteParentSampledSampler(Sampler sampler) { + this.remoteParentSampledSampler = sampler; + } + + void setRemoteParentNotSampledSampler(Sampler sampler) { + this.remoteParentNotSampledSampler = sampler; + } + + void setRootSampler(Sampler sampler) { + this.sampler = sampler; + } + + Sampler getRootSampler(){ + return sampler; + } + + Sampler getRemoteParentSampledSampler(){ + return remoteParentSampledSampler; + } + + Sampler getRemoteParentNotSampledSampler(){ + return remoteParentNotSampledSampler; + } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AdaptiveSampler.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AdaptiveSampler.java new file mode 100644 index 0000000000..273ee564c8 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AdaptiveSampler.java @@ -0,0 +1,139 @@ +package com.newrelic.agent.tracing.samplers; + +import com.google.common.annotations.VisibleForTesting; +import com.newrelic.agent.config.AgentConfig; +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.agent.tracing.DistributedTraceServiceImpl; +import com.newrelic.api.agent.NewRelic; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; + +public class AdaptiveSampler implements Sampler { + //Configured values + private final long reportPeriodMillis; + private int target; + + //Instance stats - thread safety managed by synchronized methods + private long startTimeMillis; + private int seen; + private int seenLast; + private int sampledCount; + private int sampledCountLast; + private boolean firstPeriod; + + private static AdaptiveSampler SAMPLER_SHARED_INSTANCE; + + protected AdaptiveSampler(int target, int reportPeriodSeconds){ + this.target = target; + this.reportPeriodMillis = reportPeriodSeconds * 1000L; + this.startTimeMillis = System.currentTimeMillis(); + this.seen = 0; + this.seenLast = 0; + this.sampledCount = 0; + this.sampledCountLast = 0; + this.firstPeriod = true; + NewRelic.getAgent().getLogger().log(Level.FINE, "Started Adaptive Sampler with sampling target " + this.target + " and report period " + + reportPeriodSeconds + " seconds."); + } + + /** + * Factory method for getting a shared instance of the adaptive sampler. + * This is the instance used when a top-level sampling target only is specified. + * Its state may be shared across multiple contexts using adaptive sampling, which is why + * it is a singleton. + * + * Lazy-instantiated. + * Currently managed via synchronized as it should only be accessed a few times, + * when DistributedTraceImpl class is initialized. + * + * @return The AdaptiveSampler instance. + */ + public static synchronized AdaptiveSampler getSharedInstance(){ + if (SAMPLER_SHARED_INSTANCE == null) { + AgentConfig config = ServiceFactory.getConfigService().getDefaultAgentConfig(); + SAMPLER_SHARED_INSTANCE = new AdaptiveSampler(config.getAdaptiveSamplingTarget(), config.getAdaptiveSamplingPeriodSeconds()); + } + return SAMPLER_SHARED_INSTANCE; + } + + /** + * Updates the SHARED_SAMPLER_INSTANCE to use a new target. + * If the SHARED_SAMPLER_INSTANCE isn't already running, this method is a no-op. + * @param newTarget the new target value the shared sampler instance should use + */ + public static synchronized void setSharedTarget(int newTarget) { + if (SAMPLER_SHARED_INSTANCE != null) { + NewRelic.getAgent().getLogger().log(Level.FINE, "Updating shared Adaptive Sampler sampling target to " + newTarget); + getSharedInstance().setTarget(newTarget); + } + } + + /** + * Calculate priority given the current state of the sampler. + * @return A float in [0.0f, 2.0f] + */ + @Override + public synchronized float calculatePriority(){ + resetPeriodIfElapsed(); + return (computeSampled() ? 1.0f : 0.0f) + DistributedTraceServiceImpl.nextTruncatedFloat(); + } + + @Override + public String getType(){ + return Sampler.ADAPTIVE; + } + + private void resetPeriodIfElapsed(){ + long now = System.currentTimeMillis(); + if (now - startTimeMillis >= reportPeriodMillis) { + NewRelic.getAgent().getLogger().log(Level.FINE, "Resetting sampler period. Seen: " + seen + ", Sampled: " + sampledCount); + //Calculate elapsed periods so that the start time is consistently incremented + //in multiples of the report period. + int elapsedPeriods = (int) ((now - startTimeMillis)/ reportPeriodMillis); + startTimeMillis += elapsedPeriods * reportPeriodMillis; + seenLast = seen; + seen = 0; + sampledCountLast = sampledCount; + sampledCount = 0; + firstPeriod = false; + } + } + + @VisibleForTesting + protected boolean computeSampled(){ + boolean sampled; + if (firstPeriod) { + sampled = sampledCount < target; + } else if (sampledCount < target) { + sampled = (seenLast <= 0 ? 0 : ThreadLocalRandom.current().nextInt(seenLast)) < target; + } else if (sampledCount >= (target * 2)) { + sampled = false; + } else { + int expTarget = (int) (Math.pow((float) target, (float) target / sampledCount) - Math.pow((float) target, 0.5)); + //seen should never be zero here. This is an added safety guard to prevent an exception from .nextInt. + sampled = (seen <= 0 ? 0 : ThreadLocalRandom.current().nextInt(seen)) < expTarget; + } + seen++; + if (sampled){ + sampledCount++; + } + return sampled; + } + + private synchronized void setTarget(int newTarget){ + this.target = newTarget; + } + + //These methods are for testing only. they are not thread-safe. + @VisibleForTesting + int getSampledCountLastPeriod(){ + return sampledCountLast; + } + + @VisibleForTesting + public int getTarget() { + return target; + } + +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOffSampler.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOffSampler.java new file mode 100644 index 0000000000..8794c8f82b --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOffSampler.java @@ -0,0 +1,11 @@ +package com.newrelic.agent.tracing.samplers; + +public class AlwaysOffSampler implements Sampler{ + public float calculatePriority(){ + return 0.0f; + } + + public String getType(){ + return Sampler.ALWAYS_OFF; + } +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOnSampler.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOnSampler.java new file mode 100644 index 0000000000..52b1177774 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/AlwaysOnSampler.java @@ -0,0 +1,13 @@ +package com.newrelic.agent.tracing.samplers; + +public class AlwaysOnSampler implements Sampler{ + + public float calculatePriority(){ + return 2.0f; + } + + public String getType(){ + return Sampler.ALWAYS_ON; + } + +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/Sampler.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/Sampler.java new file mode 100644 index 0000000000..b374280ffa --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracing/samplers/Sampler.java @@ -0,0 +1,27 @@ +package com.newrelic.agent.tracing.samplers; + +public interface Sampler { + //static members + String ADAPTIVE = "adaptive"; + String ALWAYS_OFF = "always_off"; + String ALWAYS_ON = "always_on"; + + static Sampler getSamplerForType(String samplerType){ + Sampler sampler; + switch (samplerType){ + case ALWAYS_OFF: + sampler = new AlwaysOffSampler(); + break; + case ALWAYS_ON: + sampler = new AlwaysOnSampler(); + break; + default: + sampler = AdaptiveSampler.getSharedInstance(); + } + return sampler; + } + + //instance methods + float calculatePriority(); + String getType(); +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/util/MinAwareQueue.java b/newrelic-agent/src/main/java/com/newrelic/agent/util/MinAwareQueue.java new file mode 100644 index 0000000000..0ffa50d804 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/util/MinAwareQueue.java @@ -0,0 +1,18 @@ +package com.newrelic.agent.util; + +import com.newrelic.agent.model.PriorityAware; + +import java.util.Queue; + +/*** + * Simple interface extending Queue with a peekLast method to get the tail (minimum priority) element of the queue. + * + * The standard Queue interface only gives access to the head element, which for us is the element of maximum priority. + * Read access to the minimum priority element can be used to avoid performing expensive object allocations + * for low-priority events that would get rejected by the queue anyway. + * + * @param + */ +public interface MinAwareQueue extends Queue { + E peekLast(); +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/util/NoOpQueue.java b/newrelic-agent/src/main/java/com/newrelic/agent/util/NoOpQueue.java index 1526ce23d6..482594b3b9 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/util/NoOpQueue.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/util/NoOpQueue.java @@ -15,7 +15,7 @@ import java.util.Iterator; import java.util.Queue; -public final class NoOpQueue implements Queue { +public final class NoOpQueue implements MinAwareQueue { private static final Queue INSTANCE = new NoOpQueue<>(); public static Queue getInstance() { @@ -110,4 +110,9 @@ public boolean retainAll(Collection c) { @Override public void clear() { } + + @Override + public E peekLast(){ + return null; + } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/util/SynchronizedMinAwareQueue.java b/newrelic-agent/src/main/java/com/newrelic/agent/util/SynchronizedMinAwareQueue.java new file mode 100644 index 0000000000..dd09f33364 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/util/SynchronizedMinAwareQueue.java @@ -0,0 +1,122 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.util; + +import com.google.common.collect.MinMaxPriorityQueue; +import com.newrelic.agent.model.PriorityAware; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Queue; + +public final class SynchronizedMinAwareQueue implements MinAwareQueue { + private final MinMaxPriorityQueue delegate; + + public SynchronizedMinAwareQueue(int reservoirSize, Comparator comparator) { + this.delegate = MinMaxPriorityQueue.orderedBy(comparator).maximumSize(reservoirSize).create(); + } + + //Used for reservoir + @Override + public synchronized boolean offer(E e) { + return delegate.offer(e); + } + + @Override + public synchronized E poll() { + return delegate.poll(); + } + + @Override + public synchronized E peek() { + return delegate.peek(); + } + + @Override + public synchronized int size() { + return delegate.size(); + } + + @Override + public synchronized boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public synchronized void clear() { + delegate.clear(); + } + + @Override + public synchronized E peekLast(){ + return delegate.peekLast(); + } + + //Not used for reservoir + @Override + public synchronized boolean add(E e) { + return delegate.add(e); + } + + @Override + public synchronized E remove() { + return delegate.remove(); + } + + @Override + public synchronized E element() { + return delegate.element(); + } + + @Override + public synchronized boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public synchronized Iterator iterator() { + return delegate.iterator(); + } + + @Override + public synchronized Object[] toArray() { + return delegate.toArray(); + } + + @Override + public synchronized T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public synchronized boolean remove(Object o) { + return delegate.remove(o); + } + + @Override + public synchronized boolean containsAll(Collection c) { + return delegate.containsAll(c); + } + + @Override + public synchronized boolean addAll(Collection c) { + return delegate.addAll(c); + } + + @Override + public synchronized boolean removeAll(Collection c) { + return delegate.removeAll(c); + } + + @Override + public synchronized boolean retainAll(Collection c) { + return delegate.retainAll(c); + } +} diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/MockSpanEventReservoirManager.java b/newrelic-agent/src/test/java/com/newrelic/agent/MockSpanEventReservoirManager.java index 6fce728051..f2bbc0ce2f 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/MockSpanEventReservoirManager.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/MockSpanEventReservoirManager.java @@ -33,7 +33,7 @@ public MockSpanEventReservoirManager(ConfigService configService) { public SamplingPriorityQueue getOrCreateReservoir(String appName) { SamplingPriorityQueue reservoir = spanReservoirsForApp.get(appName); if (reservoir == null) { - reservoir = spanReservoirsForApp.putIfAbsent(appName, createDistributedSamplingReservoir(appName, 0)); + reservoir = spanReservoirsForApp.putIfAbsent(appName, createDistributedSamplingReservoir(appName)); if (reservoir == null) { reservoir = spanReservoirsForApp.get(appName); } @@ -41,10 +41,9 @@ public SamplingPriorityQueue getOrCreateReservoir(String appName) { return reservoir; } - private SamplingPriorityQueue createDistributedSamplingReservoir(String appName, int decidedLast) { + private SamplingPriorityQueue createDistributedSamplingReservoir(String appName) { SpanEventsConfig spanEventsConfig = configService.getDefaultAgentConfig().getSpanEventsConfig(); - int target = spanEventsConfig.getTargetSamplesStored(); - return new DistributedSamplingPriorityQueue<>(appName, "Span Event Service", maxSamplesStored, decidedLast, target, SPAN_EVENT_COMPARATOR); + return new DistributedSamplingPriorityQueue<>(appName, "Span Event Service", maxSamplesStored, SPAN_EVENT_COMPARATOR); } @@ -67,7 +66,7 @@ public int getMaxSamplesStored() { public void setMaxSamplesStored(int newMax) { maxSamplesStored = newMax; ConcurrentHashMap> newMaxSpanReservoirs = new ConcurrentHashMap<>(); - spanReservoirsForApp.forEach((appName,reservoir ) -> newMaxSpanReservoirs.putIfAbsent(appName, createDistributedSamplingReservoir(appName, 0))); + spanReservoirsForApp.forEach((appName,reservoir ) -> newMaxSpanReservoirs.putIfAbsent(appName, createDistributedSamplingReservoir(appName))); spanReservoirsForApp = newMaxSpanReservoirs; } diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/TransactionTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/TransactionTest.java index a6bad5990a..395d049e9b 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/TransactionTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/TransactionTest.java @@ -16,9 +16,7 @@ import com.newrelic.agent.environment.EnvironmentService; import com.newrelic.agent.environment.EnvironmentServiceImpl; import com.newrelic.agent.instrumentation.InstrumentationImpl; -import com.newrelic.agent.interfaces.SamplingPriorityQueue; import com.newrelic.agent.model.AnalyticsEvent; -import com.newrelic.agent.model.PriorityAware; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.service.analytics.TransactionDataToDistributedTraceIntrinsics; import com.newrelic.agent.service.analytics.TransactionEventsService; @@ -186,11 +184,6 @@ public String getApplicationId() { return "1234"; } - @Override - public float calculatePriority(Float priority, SamplingPriorityQueue reservoir) { - return 1.0f; - } - @Override public Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, String traceId, TransportType transportType, long parentTransportDuration, @@ -207,6 +200,16 @@ public String getTrustKey() { public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { return null; } + + @Override + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority) { + return 1.1f; + } + + @Override + public float calculatePriorityRoot(){ + return 1.2f; + } }; } @@ -1238,11 +1241,6 @@ public String getApplicationId() { return "1234"; } - @Override - public float calculatePriority(Float priority, SamplingPriorityQueue reservoir) { - return 0.678f; - } - @Override public Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, String traceId, TransportType transportType, long parentTransportDuration, @@ -1259,6 +1257,16 @@ public String getTrustKey() { public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { return null; } + + @Override + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority) { + return 0.333f; + } + + @Override + public float calculatePriorityRoot(){ + return 0.678f; + } }); Transaction.clearTransaction(); @@ -1323,6 +1331,7 @@ public void dispatcherTransactionFinished(TransactionData transactionData, Trans transaction.getTransactionActivity().tracerStarted(dispatcherTracer); transaction.setTransactionName(com.newrelic.api.agent.TransactionNamePriority.CUSTOM_HIGH, true, "Test", "createBeforeAcceptTxn"); transaction.createDistributedTracePayload("spanId31238ou"); + float priorityAfterCreate = transaction.getPriority(); String inboundPayload = "{" + @@ -1339,10 +1348,13 @@ public void dispatcherTransactionFinished(TransactionData transactionData, Trans " }" + "}"; transaction.acceptDistributedTracePayload(inboundPayload); + float priorityAfterAccept = transaction.getPriority(); dispatcherTracer.finish(Opcodes.ARETURN, null); latch.await(); + //should not reset priority + assertEquals(priorityAfterCreate, priorityAfterAccept, 0.0f); assertTrue(1 <= transaction.getTransactionActivity() .getTransactionStats() .getUnscopedStats() @@ -1403,6 +1415,239 @@ public void testSampleFlag() throws Exception { assertTrue(DistributedTraceUtil.isSampledPriority(transaction.getPriority())); } + @Test + public void assignPriorityFromRemoteParentShouldOverwritePriority() throws Exception{ + //setup: configure the mock DT service to use always-on for the remote parent. + useAlwaysOnRemoteParent(); + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + serviceManager.setDistributedTraceService(mockDistributedTraceService); + + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + transaction.setPriorityIfNotNull(1.232f); + assertEquals(1.232f, transaction.getPriority(), 0.0f); + + transaction.assignPriorityFromRemoteParent(true); //remote parent was sampled + assertEquals(2.0f, transaction.getPriority(), 0.0f); + + finishTransaction(transaction, dispatcherTracer); + } + + @Test + public void assignPriorityRootIfNotSetShouldNotOverwritePriority() throws Exception{ + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + serviceManager.setDistributedTraceService(mockDistributedTraceService); + + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + transaction.setPriorityIfNotNull(1.232f); + assertEquals(1.232f, transaction.getPriority(), 0.0f); + + transaction.assignPriorityRootIfNotSet(); + assertEquals(1.232f, transaction.getPriority(), 0.0f); + + finishTransaction(transaction, dispatcherTracer); + } + + @Test + public void createDtPayloadAssignsPriorityWhenNotSet() throws Exception { + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + + serviceManager.setDistributedTraceService(mockDistributedTraceService); + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + assertEquals(0.0f, transaction.getPriority(), 0.0f); //the priority getter return 0.0f if the Float is null + transaction.createDistributedTracePayload("27856f70d3d314b7"); + assertEquals(1.2f, transaction.getPriority(), 0.0f); + + finishTransaction(transaction, dispatcherTracer); + } + + @Test + public void transactionFinishedAssignsPriorityIfUnset() throws Exception { + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + serviceManager.setDistributedTraceService(mockDistributedTraceService); + + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + assertEquals(0.0f, transaction.getPriority(), 0.0f); + + finishTransaction(transaction, dispatcherTracer); + assertEquals(1.2f, transaction.getPriority(), 0.0f); + } + + @Test + public void priorityAssigningActionsShouldOnlyCalculateRootPriorityOnce() throws Exception{ + //need to mock this was to use the mockito verifier + mockDistributedTraceService = Mockito.mock(DistributedTraceService.class); + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + serviceManager.setDistributedTraceService(mockDistributedTraceService); + + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + //all three of the actions below are capable of asking to calculate root priority. + //it is important we only actually ask for priority once, to avoid running the adaptive sampler twice + //and messing up its stats. + transaction.createDistributedTracePayload("27856f70d3d314b7"); + float priority1 = transaction.getPriority(); + transaction.assignPriorityRootIfNotSet(); + float priority2 = transaction.getPriority(); + finishTransaction(transaction, dispatcherTracer); + float priority3 = transaction.getPriority(); + + Mockito.verify(mockDistributedTraceService, Mockito.times(1)).calculatePriorityRoot(); + assertEquals(priority1, priority2, 0.0f); + assertEquals(priority1, priority3, 0.0f); + } + + @Test + public void testGetPriorityFromInboundSamplingDecision() throws Exception{ + //I don't want to make a million different tests so I'm going to test a bunch of behavior here. + //The assertions in this test are permutations of what we might expect from the sampled and priority flags + //on an inbound payload. + Map configMap = createConfigMap(); + configMap.put(AgentConfigImpl.DISTRIBUTED_TRACING, ImmutableMap.of("enabled", Boolean.TRUE)); + createServiceManager(configMap); + serviceManager.setDistributedTraceService(mockDistributedTraceService); + + //Case 1: a transaction with no inbound payload + Transaction.clearTransaction(); + BasicRequestRootTracer dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + Transaction transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + assertNull(transaction.getPriorityFromInboundSamplingDecision()); + finishTransaction(transaction, dispatcherTracer); + + //Case 2: Accept a Payload with inbound sampling and priority information on it. + Transaction.clearTransaction(); + dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + String inboundPayload = + "{" + + " \"v\": [0,2]," + + " \"d\": {" + + " \"ty\": \"Mobile\"," + + " \"ac\": \"9123\"," + + " \"tk\": \"67890\"," + + " \"ap\": \"51424\"" + + " \"id\": \"27856f70d3d314b7\"," + + " \"tr\": \"3221bf09aa0bcf0d\"," + + " \"pr\": 0.567," + + " \"sa\": true," + + " \"ti\": 1482959525577," + + " }" + + "}"; + + transaction.acceptDistributedTracePayload(inboundPayload); + assertEquals(0.567f, transaction.getPriorityFromInboundSamplingDecision(), 0.0f); + finishTransaction(transaction, dispatcherTracer); + + //Case 3: a payload that is missing inbound priority but has a sampled decision + Transaction.clearTransaction(); + dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + inboundPayload = + "{" + + " \"v\": [0,2]," + + " \"d\": {" + + " \"ty\": \"Mobile\"," + + " \"ac\": \"9123\"," + + " \"tk\": \"67890\"," + + " \"ap\": \"51424\"" + + " \"id\": \"27856f70d3d314b7\"," + + " \"tr\": \"3221bf09aa0bcf0d\"," + + " \"sa\": false," + + " \"ti\": 1482959525577," + + " }" + + "}"; + + transaction.acceptDistributedTracePayload(inboundPayload); + Float priority = transaction.getPriorityFromInboundSamplingDecision(); + //sampled is false, we should have generated a random priority less than 1 without the use of the sampler. + assertTrue(priority > 0.0f); + assertTrue(priority < 1.0f); + finishTransaction(transaction, dispatcherTracer); + + //Case 4: a payload that is missing a sampled decision but has an inbound priority. + //In this case, a new sampling decision needs to be made (regardless of the priority value). + Transaction.clearTransaction(); + dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + inboundPayload = + "{" + + " \"v\": [0,2]," + + " \"d\": {" + + " \"ty\": \"Mobile\"," + + " \"ac\": \"9123\"," + + " \"tk\": \"67890\"," + + " \"ap\": \"51424\"" + + " \"id\": \"27856f70d3d314b7\"," + + " \"tr\": \"3221bf09aa0bcf0d\"," + + " \"ti\": 1482959525577," + + " \"pr\": 0.567," + + " }" + + "}"; + + transaction.acceptDistributedTracePayload(inboundPayload); + assertNull(transaction.getPriorityFromInboundSamplingDecision()); + finishTransaction(transaction, dispatcherTracer); + + //Case 5: a payload that is missing both an inbound priority and a sampling decision + Transaction.clearTransaction(); + dispatcherTracer = (BasicRequestRootTracer) createDispatcherTracer(true); + transaction = dispatcherTracer.getTransactionActivity().getTransaction(); + transaction.getTransactionActivity().tracerStarted(dispatcherTracer); + + inboundPayload = + "{" + + " \"v\": [0,2]," + + " \"d\": {" + + " \"ty\": \"Mobile\"," + + " \"ac\": \"9123\"," + + " \"tk\": \"67890\"," + + " \"ap\": \"51424\"" + + " \"id\": \"27856f70d3d314b7\"," + + " \"tr\": \"3221bf09aa0bcf0d\"," + + " \"ti\": 1482959525577," + + " }" + + "}"; + + transaction.acceptDistributedTracePayload(inboundPayload); + assertNull(transaction.getPriorityFromInboundSamplingDecision()); + finishTransaction(transaction, dispatcherTracer); + } + @Test public void testTransportDuration() throws Exception { Map configMap = createNRDTConfigMap(false); @@ -1675,4 +1920,120 @@ private DefaultTracer createBasicTracer(String id) { return new DefaultTracer(tx, sig, this, new SimpleMetricNameFormat("Custom/myname" + id)); } + private void useAlwaysOnRemoteParent(){ + mockDistributedTraceService = new DistributedTraceService() { + @Override + public boolean isEnabled() { + return true; + } + + @Override + public int getMajorSupportedCatVersion() { + return 1; + } + + @Override + public int getMinorSupportedCatVersion() { + return 0; + } + + @Override + public String getAccountId() { + return "9123"; + } + + @Override + public String getApplicationId() { + return "1234"; + } + + @Override + public Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, + String traceId, TransportType transportType, long parentTransportDuration, + long largestTransportDuration, String parentId, String parentSpanId, float priority) { + return null; + } + + @Override + public String getTrustKey() { + return "67890"; + } + + @Override + public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { + return null; + } + + @Override + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority) { + return 2.0f; + } + + @Override + public float calculatePriorityRoot(){ + return 1.0f; + } + }; + } + + private void useDefaultRemoteParent(){ + mockDistributedTraceService = new DistributedTraceService() { + @Override + public boolean isEnabled() { + return true; + } + + @Override + public int getMajorSupportedCatVersion() { + return 1; + } + + @Override + public int getMinorSupportedCatVersion() { + return 0; + } + + @Override + public String getAccountId() { + return "9123"; + } + + @Override + public String getApplicationId() { + return "1234"; + } + + @Override + public Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, + String traceId, TransportType transportType, long parentTransportDuration, + long largestTransportDuration, String parentId, String parentSpanId, float priority) { + return null; + } + + @Override + public String getTrustKey() { + return "67890"; + } + + @Override + public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { + return null; + } + + @Override + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority) { + if (inboundPriority == null){ + return 1.0f; + } else { + return inboundPriority; + } + } + + @Override + public float calculatePriorityRoot(){ + return 1.0f; + } + }; + } + } diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/config/ConfigServiceTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/config/ConfigServiceTest.java index 3e1e3d01b0..63da507b24 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/config/ConfigServiceTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/config/ConfigServiceTest.java @@ -237,6 +237,70 @@ public void sanitizedSettings() throws Exception { assertEquals(sanitizedSettings.get(AgentConfigImpl.PROXY_HOST), "****"); } + @Test + public void sanitizerShouldAddAdaptiveSamplingTargetIfNotSet() throws Exception{ + //First, check if distributed_tracing.sampler.adaptive_sampling_target is added when nothing is yet there. + Map settings = AgentConfigFactoryTest.createStagingMap(); + createServiceManager(settings); + ConfigService configService = ServiceFactory.getConfigService(); + Map sanitizedSettings = configService.getSanitizedLocalSettings(); + + Map dtSettings = (Map) sanitizedSettings.get("distributed_tracing"); + Map samplerSettings = (Map) dtSettings.get("sampler"); + assertEquals(DistributedTracingConfig.DEFAULT_ADAPTIVE_SAMPLING_TARGET, samplerSettings.get(DistributedTracingConfig.ADAPTIVE_SAMPLING_TARGET)); + + //Second, put something in the dt config + Map specifyDtSettings = new HashMap<>(); + settings.put("distributed_tracing", specifyDtSettings); + specifyDtSettings.put("enabled", true); + createServiceManager(settings); + configService = ServiceFactory.getConfigService(); + sanitizedSettings = configService.getSanitizedLocalSettings(); + + dtSettings = (Map) sanitizedSettings.get("distributed_tracing"); + assertEquals(true, dtSettings.get("enabled")); + samplerSettings = (Map) dtSettings.get("sampler"); + assertEquals(DistributedTracingConfig.DEFAULT_ADAPTIVE_SAMPLING_TARGET, samplerSettings.get(DistributedTracingConfig.ADAPTIVE_SAMPLING_TARGET)); + + //Third, put something in the sampler config (not adaptive_sampling_target) + Map specifySamplerSettings = new HashMap<>(); + specifyDtSettings.put("sampler", specifySamplerSettings); + specifySamplerSettings.put("remoteParentSampled", "always_on"); + createServiceManager(settings); + configService = ServiceFactory.getConfigService(); + sanitizedSettings = configService.getSanitizedLocalSettings(); + + //assert it all looks good, all the originally specified values are there. + dtSettings = (Map) sanitizedSettings.get("distributed_tracing"); + assertEquals(true, dtSettings.get("enabled")); + samplerSettings = (Map) dtSettings.get("sampler"); + assertEquals("always_on", samplerSettings.get("remoteParentSampled")); + assertEquals(DistributedTracingConfig.DEFAULT_ADAPTIVE_SAMPLING_TARGET, samplerSettings.get(DistributedTracingConfig.ADAPTIVE_SAMPLING_TARGET)); + } + + @Test + public void sanitizerShouldNotAlterAdaptiveSamplingTargetIfAlreadySet() throws Exception{ + Map settings = AgentConfigFactoryTest.createStagingMap(); + Map specifyDtSettings = new HashMap<>(); + Map specifySamplerSettings = new HashMap<>(); + settings.put("distributed_tracing", specifyDtSettings); + specifyDtSettings.put("enabled", true); + specifyDtSettings.put("sampler", specifySamplerSettings); + specifySamplerSettings.put("remoteParentSampled", "always_on"); + specifySamplerSettings.put("adaptive_sampling_target", 133); + + createServiceManager(settings); + ConfigService configService = ServiceFactory.getConfigService(); + Map sanitizedSettings = configService.getSanitizedLocalSettings(); + + //assert it all looks good, all the originally specified values are there. + Map dtSettings = (Map) sanitizedSettings.get("distributed_tracing"); + assertEquals(true, dtSettings.get("enabled")); + Map samplerSettings = (Map) dtSettings.get("sampler"); + assertEquals("always_on", samplerSettings.get("remoteParentSampled")); + assertEquals(133, samplerSettings.get(DistributedTracingConfig.ADAPTIVE_SAMPLING_TARGET)); + } + @Test public void noUsernamePasswordProxy() throws Exception { Map configMap = AgentConfigFactoryTest.createStagingMap(); diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/metrics/TransactionTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/metrics/TransactionTest.java index 25800d110d..d002da6abb 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/metrics/TransactionTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/metrics/TransactionTest.java @@ -12,11 +12,13 @@ import com.newrelic.agent.attributes.CrossAgentInput; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.service.ServiceManager; +import com.newrelic.test.marker.RequiresFork; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -26,6 +28,7 @@ import java.util.List; @RunWith(Parameterized.class) +@Category(RequiresFork.class) public class TransactionTest { @Parameterized.Parameters(name = "{0}") diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueueTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueueTest.java index 46cb1daec3..5cfa6511d5 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueueTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/DistributedSamplingPriorityQueueTest.java @@ -24,19 +24,12 @@ public class DistributedSamplingPriorityQueueTest { static class SimplePriorityAware implements PriorityAware { - private final boolean decider; private final float priority; - public SimplePriorityAware(boolean decider, float priority) { - this.decider = decider; + public SimplePriorityAware(float priority) { this.priority = priority; } - @Override - public boolean decider() { - return decider; - } - @Override public float getPriority() { return priority; @@ -53,16 +46,16 @@ public int compare(SimplePriorityAware o1, SimplePriorityAware o2) { @Test public void resultsAreSameForBothRetryAll() { - DistributedSamplingPriorityQueue target = new DistributedSamplingPriorityQueue<>(5, 0, 5, SimplePriorityAware.COMPARATOR); + DistributedSamplingPriorityQueue target = new DistributedSamplingPriorityQueue<>(5, SimplePriorityAware.COMPARATOR); - target.add(new SimplePriorityAware(false, 1.2f)); - target.add(new SimplePriorityAware(false, 0.1f)); - target.add(new SimplePriorityAware(false, 1.3f)); - target.add(new SimplePriorityAware(false, 1.4f)); - target.add(new SimplePriorityAware(false, 1.5f)); - target.add(new SimplePriorityAware(false, 1.6f)); - target.add(new SimplePriorityAware(false, 0.7f)); - target.add(new SimplePriorityAware(false, 2.3f)); + target.add(new SimplePriorityAware( 1.2f)); + target.add(new SimplePriorityAware(0.1f)); + target.add(new SimplePriorityAware(1.3f)); + target.add(new SimplePriorityAware(1.4f)); + target.add(new SimplePriorityAware(1.5f)); + target.add(new SimplePriorityAware(1.6f)); + target.add(new SimplePriorityAware(0.7f)); + target.add(new SimplePriorityAware(2.3f)); assertEquals(5, target.size()); @@ -96,10 +89,10 @@ public void validateRetryTargetContents(DistributedSamplingPriorityQueue getRetryTarget() { - DistributedSamplingPriorityQueue retryTarget = new DistributedSamplingPriorityQueue<>(5, 0, 5, SimplePriorityAware.COMPARATOR); - retryTarget.add(new SimplePriorityAware(false, 1.1f)); - retryTarget.add(new SimplePriorityAware(false, 0.9f)); - retryTarget.add(new SimplePriorityAware(false, 1.7f)); + DistributedSamplingPriorityQueue retryTarget = new DistributedSamplingPriorityQueue<>(5, SimplePriorityAware.COMPARATOR); + retryTarget.add(new SimplePriorityAware(1.1f)); + retryTarget.add(new SimplePriorityAware(0.9f)); + retryTarget.add(new SimplePriorityAware(1.7f)); assertEquals(3, retryTarget.size()); return retryTarget; } @@ -164,7 +157,7 @@ public void testDefaultSort() { @Test public void testPriorityExternalSort() { - DistributedSamplingPriorityQueue eventPool = new DistributedSamplingPriorityQueue<>(10, 0, 0, CUSTOM_COMPARATOR); + DistributedSamplingPriorityQueue eventPool = new DistributedSamplingPriorityQueue<>(10, CUSTOM_COMPARATOR); seedEventPool(eventPool); List spanEvents = eventPool.asList(); @@ -193,6 +186,29 @@ public void testQueueSize() { assertEquals(0, sizeZeroQueue.size()); } + @Test + public void testGetMinPriority() { + DistributedSamplingPriorityQueue queue = new DistributedSamplingPriorityQueue<>(5); + + queue.add(new SimplePriorityAware(1.6f)); + queue.add(new SimplePriorityAware(1.8f)); + queue.add(new SimplePriorityAware(1.7f)); + queue.add(new SimplePriorityAware(0.55f)); + queue.add(new SimplePriorityAware(0.1f)); + queue.add(new SimplePriorityAware(0.2f)); + queue.add(new SimplePriorityAware(1.65f)); + + assertEquals(0.55f, queue.getMinPriority(), 0.0f); + + queue.add(new SimplePriorityAware(0.2f)); + + assertEquals(0.55f, queue.getMinPriority(), 0.0f); + + queue.add(new SimplePriorityAware(1.72f)); + + assertEquals(1.6f, queue.getMinPriority(), 0.0f); + } + private void addSpanEvents(int numberToAdd, DistributedSamplingPriorityQueue queue) { SpanEvent spanEvent = new SpanEventFactory("Unit Test") .setGuid("9") diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/SpanEventsServiceTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/SpanEventsServiceTest.java index 0ef04bf543..43e65b8635 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/SpanEventsServiceTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/SpanEventsServiceTest.java @@ -111,7 +111,7 @@ public void testSpanEvent() { spanEventsService.dispatcherTransactionFinished(transactionData, new TransactionStats()); SamplingPriorityQueue reservoir = spanEventsService.getOrCreateDistributedSamplingReservoir(APP_NAME); - assertEquals(1, reservoir.getSampled()); + assertEquals(1, reservoir.getTotalSampledPriorityEvents()); } @Test @@ -122,7 +122,6 @@ public void testMaxSamplesStored() { final SpanEvent event = new SpanEventFactory(APP_NAME) .setCategory(SpanCategory.generic) - .setDecider(true) .setPriority(1.23f) .setDurationInSeconds(1.3f) .setServerAddress("yourHost") @@ -175,7 +174,7 @@ public void testDoesNotCreateSpansIfToldNotTo() { spanEventsService.dispatcherTransactionFinished(transactionData, null); SamplingPriorityQueue reservoir = spanEventsService.getOrCreateDistributedSamplingReservoir(APP_NAME); - assertEquals(0, reservoir.getSampled()); + assertEquals(0, reservoir.getTotalSampledPriorityEvents()); } @Test diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/TracerToSpanEventTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/TracerToSpanEventTest.java index 9aab51f3ae..2893e6529f 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/TracerToSpanEventTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/service/analytics/TracerToSpanEventTest.java @@ -572,7 +572,6 @@ public void testAutoAppNaming() { .putAllAgentAttributes(expectedAgentAttributes) .putAllIntrinsics(expectedIntrinsicAttributes) .putAllUserAttributes(expectedUserAttributes) - .decider(true) .timestamp(timestamp) .build(); when(spanErrorBuilder.areErrorsEnabled()).thenReturn(true); @@ -755,7 +754,6 @@ private SpanEvent buildExpectedSpanEvent() { .putAllAgentAttributes(expectedAgentAttributes) .putAllIntrinsics(expectedIntrinsicAttributes) .putAllUserAttributes(expectedUserAttributes) - .decider(true) .timestamp(timestamp) .build(); } diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/BaseDistributedTraceTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/BaseDistributedTraceTest.java index 824aa7f6f2..5d6534a1dc 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/BaseDistributedTraceTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/BaseDistributedTraceTest.java @@ -78,11 +78,6 @@ public String getApplicationId() { return applicationId; } - @Override - public float calculatePriority(Float priority, SamplingPriorityQueue reservoir) { - return 1.0f; - } - @Override public Map getIntrinsics(DistributedTracePayloadImpl inboundPayload, String guid, String traceId, TransportType transportType, @@ -100,6 +95,16 @@ public String getTrustKey() { public DistributedTracePayload createDistributedTracePayload(Tracer tracer) { return null; } + + @Override + public float calculatePriorityRemoteParent(boolean remoteParentSampled, Float inboundPriority) { + return 0.0f; + } + + @Override + public float calculatePriorityRoot(){ + return 0.0f; + } }; serviceManager.setDistributedTraceService(distributedTraceService); } diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/DistributedTraceServiceImplTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/DistributedTraceServiceImplTest.java index 92f52c596b..db7801c3e5 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/DistributedTraceServiceImplTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/DistributedTraceServiceImplTest.java @@ -19,6 +19,8 @@ import com.newrelic.agent.Transaction; import com.newrelic.agent.TransactionData; import com.newrelic.agent.TransactionService; +import com.newrelic.agent.tracing.samplers.AdaptiveSampler; +import com.newrelic.agent.tracing.samplers.Sampler; import com.newrelic.api.agent.TransportType; import com.newrelic.agent.config.AgentConfig; import com.newrelic.agent.config.AgentConfigImpl; @@ -37,7 +39,6 @@ import com.newrelic.agent.service.analytics.TransactionEventsService; import com.newrelic.agent.stats.SimpleStatsEngine; import com.newrelic.agent.stats.StatsEngine; -import com.newrelic.agent.stats.StatsEngineImpl; import com.newrelic.agent.stats.StatsServiceImpl; import com.newrelic.agent.stats.TransactionStats; import com.newrelic.agent.trace.TransactionTraceService; @@ -59,12 +60,8 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.doReturn; +import static org.junit.Assert.*; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; public class DistributedTraceServiceImplTest { @@ -170,37 +167,6 @@ public void txnFinishedInboundPayload() { assertEquals(3.0f, unscopedStats.getOrCreateResponseTimeStats("TransportDuration/Browser/123456/6789/HTTPS/all").getTotal(), 0.01f); } - @Test - public void testShouldSampleFirst10() { - TransactionEventsService transactionEventsService = Mockito.mock(TransactionEventsService.class); - serviceManager.setTransactionEventsService(transactionEventsService); - - distributedTraceService = new DistributedTraceServiceImpl(); - serviceManager.setDistributedTraceService(distributedTraceService); - - DistributedSamplingPriorityQueue reservoir = Mockito.mock(DistributedSamplingPriorityQueue.class); - doReturn(reservoir).when(transactionEventsService).getOrCreateDistributedSamplingReservoir("Test"); - when(reservoir.getTarget()).thenReturn(10); - for (int i = 0; i < 10; i++) { - doReturn(i + 1).when(reservoir).getNumberOfTries(); - assertTrue(DistributedTraceUtil.isSampledPriority( - DistributedTraceServiceImplTest.distributedTraceService.calculatePriority(null, reservoir))); - } - - // Now that we've sampled the first 10, the rest will be ignored until after the harvest - doReturn(100001).when(reservoir).getNumberOfTries(); - doReturn(10).when(reservoir).getDecided(); - doReturn(100000).when(reservoir).getTarget(); // fake a very high target to ensure a sample later on - assertTrue(distributedTraceService.calculatePriority(null, reservoir) < 1.0f); - - // Fake a harvest - distributedTraceService.beforeHarvest("Test", null); - - // We should have "adaptively" sampled at least one more trace - assertTrue(DistributedTraceUtil.isSampledPriority( - DistributedTraceServiceImplTest.distributedTraceService.calculatePriority(null, reservoir))); - } - @Test public void testPriorityIsUsed() { rpmServiceManager.getOrCreateRPMService("Test"); @@ -248,61 +214,13 @@ public void testPriorityIsUsed() { assertEquals(3, events.size()); } - @Test - public void testExponentialBackoff() { - rpmServiceManager.getOrCreateRPMService("Test"); - - // Create reservoir - ServiceFactory.getTransactionEventsService().harvestEvents("Test"); - DistributedSamplingPriorityQueue reservoir = ServiceFactory.getTransactionEventsService() - .getOrCreateDistributedSamplingReservoir("Test"); - - // First 10 traces - for (int i = 0; i < 10; i++) { - assertTrue(DistributedTraceUtil.isSampledPriority(distributedTraceService.calculatePriority(null, reservoir))); - TransactionEvent transactionEvent = Mockito.mock(TransactionEvent.class); - when(transactionEvent.getPriority()).thenReturn(1.0f); - when(transactionEvent.decider()).thenReturn(true); - reservoir.add(transactionEvent); - } - - distributedTraceService.beforeHarvest("Test", new StatsEngineImpl()); - ServiceFactory.getTransactionEventsService().harvestEvents("Test"); - reservoir = ServiceFactory.getTransactionEventsService().getOrCreateDistributedSamplingReservoir("Test"); - assertEquals(0, reservoir.getSampled()); - - // Test that we hit target - for (int i = 0; i < reservoir.getTarget(); i++) { - if (DistributedTraceUtil.isSampledPriority(distributedTraceService.calculatePriority(null, reservoir))) { - TransactionEvent transactionEvent = Mockito.mock(TransactionEvent.class); - when(transactionEvent.getPriority()).thenReturn(1.0f); - when(transactionEvent.decider()).thenReturn(true); - reservoir.add(transactionEvent); - } - } - assertTrue("Sampled fewer than target transactions", reservoir.getSampled() >= reservoir.getTarget()); - - ServiceFactory.getTransactionEventsService().harvestEvents("Test"); - distributedTraceService.beforeHarvest("Test", new StatsEngineImpl()); - reservoir = ServiceFactory.getTransactionEventsService().getOrCreateDistributedSamplingReservoir("Test"); - - // Test that we do not go above 2x target - for (int i = 0; i < 1000 * reservoir.getTarget(); i++) { - if (DistributedTraceUtil.isSampledPriority(distributedTraceService.calculatePriority(null, reservoir))) { - TransactionEvent transactionEvent = Mockito.mock(TransactionEvent.class); - when(transactionEvent.getPriority()).thenReturn(1.0f); - when(transactionEvent.decider()).thenReturn(true); - reservoir.add(transactionEvent); - } - } - - assertTrue("Sampled fewer than target transactions", reservoir.getSampled() >= reservoir.getTarget()); - assertTrue("Sampled more than 2x target ", reservoir.getSampled() <= (2 * reservoir.getTarget())); - } - @Test public void testEventsByPriority() { - rpmServiceManager.getOrCreateRPMService("Test"); + + distributedTraceService.connected( + rpmServiceManager.getOrCreateRPMService("Test"), + ServiceFactory.getConfigService().getAgentConfig("Test") + ); // Create reservoir ServiceFactory.getTransactionEventsService().harvestEvents("Test"); @@ -315,30 +233,28 @@ public void testEventsByPriority() { for (int i = 0; i < 3000; i++) { TransactionEvent transactionEvent = Mockito.mock(TransactionEvent.class); - Float priority = DistributedTraceServiceImplTest.distributedTraceService.calculatePriority(null, reservoir); + Float priority = DistributedTraceServiceImplTest.distributedTraceService.calculatePriorityRoot(); minPriority = Math.min(priority, minPriority); // Store the smallest priority we've seen maxPriority = Math.max(priority, maxPriority); // Store the largest priority we've seen when(transactionEvent.getPriority()).thenReturn(priority); - when(transactionEvent.decider()).thenReturn(true); reservoir.add(transactionEvent); } for (int i = 0; i < 1000; i++) { TransactionEvent transactionEvent = Mockito.mock(TransactionEvent.class); - Float priority = DistributedTraceServiceImplTest.distributedTraceService.calculatePriority(null, reservoir); + Float priority = DistributedTraceServiceImplTest.distributedTraceService.calculatePriorityRemoteParent(true, 1.5f); when(transactionEvent.getPriority()).thenReturn(priority); - when(transactionEvent.decider()).thenReturn(false); reservoir.add(transactionEvent); } assertTrue(reservoir.peek().getPriority() > minPriority); assertEquals(maxPriority, reservoir.peek().getPriority(), 0.0f); assertEquals(4000, reservoir.getNumberOfTries()); - assertTrue(reservoir.getSampled() >= 11); + assertTrue(reservoir.getTotalSampledPriorityEvents() >= 1011); List events = reservoir.asList(); - int sampled = reservoir.getSampled(); - // verify that the number of "sampled" events equals the number of events with priority >= 1.0 where decider = true + int sampled = reservoir.getTotalSampledPriorityEvents(); + //verify that the number of "sampled" events equals the number of events with priority >= 1.0 for (int i = 0; i < sampled + 1; i++) { if (i < sampled) { assertTrue(DistributedTraceUtil.isSampledPriority(events.get(i).getPriority())); @@ -360,6 +276,80 @@ public void testEventsByPriority() { MetricNames.SUPPORTABILITY_TRANSACTION_EVENT_SERVICE_TRANSACTION_EVENT_SENT)).getCallCount()); } + @Test + public void testCalculatePriorityRootShouldRouteToAdaptiveSampler(){ + //this method doesn't do much right now. It might in the future. + IRPMService rpmService = rpmServiceManager.getOrCreateRPMService("Test"); + AgentConfig agentConfig = ServiceFactory.getConfigService().getAgentConfig("Test"); + DistributedTraceServiceImplTest.distributedTraceService.connected(rpmService, agentConfig); + assertEquals(Sampler.ADAPTIVE, distributedTraceService.getRootSampler().getType()); + //overwrite the configured sampler to ask Mockito how many times the sampler is called + Sampler mockAdaptiveSampler = Mockito.mock(AdaptiveSampler.class); + distributedTraceService.setRootSampler(mockAdaptiveSampler); + distributedTraceService.calculatePriorityRoot(); + Mockito.verify(mockAdaptiveSampler, times(1)).calculatePriority(); + } + + @Test + public void calculatePriorityRemoteParentSampledUsesSampler(){ + Map samplerSettings = new HashMap<>(); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_SAMPLED, "always_on"); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_NOT_SAMPLED, "always_off"); + Map dtSettings = new HashMap<>(); + dtSettings.put("sampler", samplerSettings); + Map config = new HashMap<>(); + config.put("distributed_tracing", dtSettings); + + AgentConfig agentConfig = AgentConfigImpl.createAgentConfig(config); + ConfigService configService = ConfigServiceFactory.createConfigService(agentConfig, Collections.emptyMap()); + serviceManager.setConfigService(configService); + distributedTraceService = new DistributedTraceServiceImpl(); + serviceManager.setDistributedTraceService(distributedTraceService); + + IRPMService rpmService = rpmServiceManager.getOrCreateRPMService("Test"); + DistributedTraceServiceImplTest.distributedTraceService.connected(rpmService, agentConfig); + assertEquals(Sampler.ALWAYS_ON, distributedTraceService.getRemoteParentSampledSampler().getType()); + + assertEquals(2.0f, distributedTraceService.calculatePriorityRemoteParent(true, 1.5f), 0.0f); + assertEquals(2.0f, distributedTraceService.calculatePriorityRemoteParent(true, null), 0.0f); + } + + @Test + public void calculatePriorityRemoteParentNotSampledUsesSampler(){ + Map samplerSettings = new HashMap<>(); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_SAMPLED, "always_on"); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_NOT_SAMPLED, "always_off"); + Map dtSettings = new HashMap<>(); + dtSettings.put("sampler", samplerSettings); + Map config = new HashMap<>(); + config.put("distributed_tracing", dtSettings); + + AgentConfig agentConfig = AgentConfigImpl.createAgentConfig(config); + ConfigService configService = ConfigServiceFactory.createConfigService(agentConfig, Collections.emptyMap()); + serviceManager.setConfigService(configService); + distributedTraceService = new DistributedTraceServiceImpl(); + serviceManager.setDistributedTraceService(distributedTraceService); + + IRPMService rpmService = rpmServiceManager.getOrCreateRPMService("Test"); + DistributedTraceServiceImplTest.distributedTraceService.connected(rpmService, agentConfig); + assertEquals(Sampler.ALWAYS_ON, distributedTraceService.getRemoteParentSampledSampler().getType()); + + assertEquals(0.0f, distributedTraceService.calculatePriorityRemoteParent(false, 1.5f), 0.0f); + assertEquals(0.0f, distributedTraceService.calculatePriorityRemoteParent(false, null), 0.0f); + } + + @Test + public void remoteParentSamplersUseInboundPriorityWhenSetToAdaptive(){ + IRPMService rpmService = rpmServiceManager.getOrCreateRPMService("Test"); + AgentConfig agentConfig = ServiceFactory.getConfigService().getAgentConfig("Test"); + DistributedTraceServiceImplTest.distributedTraceService.connected(rpmService, agentConfig); + assertEquals(Sampler.ADAPTIVE, distributedTraceService.getRemoteParentSampledSampler().getType()); + assertEquals(Sampler.ADAPTIVE, distributedTraceService.getRemoteParentNotSampledSampler().getType()); + + assertEquals(1.5f, distributedTraceService.calculatePriorityRemoteParent(true, 1.5f), 0.0f); + assertEquals(1.5f, distributedTraceService.calculatePriorityRemoteParent(false, 1.5f), 0.0f); + } + @Test public void testConnectFields() { assertNull(DistributedTraceServiceImplTest.distributedTraceService.getApplicationId()); @@ -381,6 +371,74 @@ public void testConnectFields() { assertEquals("trustKey", DistributedTraceServiceImplTest.distributedTraceService.getTrustKey()); } + @Test + public void testDTServiceSetsUpDefaultSamplers() { + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRootSampler().getType()); + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentSampledSampler().getType()); + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentNotSampledSampler().getType()); + + //the samplers should all be the same instance + Sampler baseSampler = AdaptiveSampler.getSharedInstance(); + assertEquals(baseSampler, DistributedTraceServiceImplTest.distributedTraceService.getRootSampler()); + assertEquals(baseSampler, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentSampledSampler()); + assertEquals(baseSampler, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentNotSampledSampler()); + + //the sampling target should be the default, 120 + assertEquals(120, ((AdaptiveSampler) DistributedTraceServiceImplTest.distributedTraceService.getRootSampler()).getTarget()); + } + + @Test + public void testDTServiceSetsUpConfiguredSamplers() { + Map samplerSettings = new HashMap<>(); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_SAMPLED, "always_on"); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_NOT_SAMPLED, "always_off"); + Map dtSettings = new HashMap<>(); + dtSettings.put("sampler", samplerSettings); + Map config = new HashMap<>(); + config.put("distributed_tracing", dtSettings); + + AgentConfig agentConfig = AgentConfigImpl.createAgentConfig(config); + ConfigService configService = ConfigServiceFactory.createConfigService(agentConfig, Collections.emptyMap()); + serviceManager.setConfigService(configService); + distributedTraceService = new DistributedTraceServiceImpl(); + serviceManager.setDistributedTraceService(distributedTraceService); + + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRootSampler().getType()); + assertEquals(Sampler.ALWAYS_ON, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentSampledSampler().getType()); + assertEquals(Sampler.ALWAYS_OFF, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentNotSampledSampler().getType()); + } + + @Test + public void testConnectResetsDefaultAdaptiveSamplingTarget() { + Map samplerSettings = new HashMap<>(); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_SAMPLED, "always_on"); + samplerSettings.put(DistributedTracingConfig.REMOTE_PARENT_NOT_SAMPLED, "always_off"); + Map dtSettings = new HashMap<>(); + dtSettings.put("sampler", samplerSettings); + Map config = new HashMap<>(); + config.put("distributed_tracing", dtSettings); + + AgentConfig agentConfig = AgentConfigImpl.createAgentConfig(config); + ConfigService configService = ConfigServiceFactory.createConfigService(agentConfig, Collections.emptyMap()); + serviceManager.setConfigService(configService); + distributedTraceService = new DistributedTraceServiceImpl(); + serviceManager.setDistributedTraceService(distributedTraceService); + + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRootSampler().getType()); + assertEquals(120, ((AdaptiveSampler) DistributedTraceServiceImplTest.distributedTraceService.getRootSampler()).getTarget()); + + IRPMService rpmService = rpmServiceManager.getOrCreateRPMService("Test"); + Map connectInfo = new HashMap<>(); + connectInfo.put("sampling_target", 10); + agentConfig = AgentHelper.createAgentConfig(true, Collections.emptyMap(), connectInfo); + DistributedTraceServiceImplTest.distributedTraceService.connected(rpmService, agentConfig); + + assertEquals(Sampler.ADAPTIVE, DistributedTraceServiceImplTest.distributedTraceService.getRootSampler().getType()); + assertEquals(10, ((AdaptiveSampler) DistributedTraceServiceImplTest.distributedTraceService.getRootSampler()).getTarget()); + assertEquals(Sampler.ALWAYS_ON, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentSampledSampler().getType()); + assertEquals(Sampler.ALWAYS_OFF, DistributedTraceServiceImplTest.distributedTraceService.getRemoteParentNotSampledSampler().getType()); + } + @Test public void testSpanEventsDisabled() { DistributedTracePayload payloadInterface = configureAndCreatePayload(false, false); diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/W3CTraceContextCrossAgentTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/W3CTraceContextCrossAgentTest.java index ff89ebe788..50c8124a49 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/W3CTraceContextCrossAgentTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/W3CTraceContextCrossAgentTest.java @@ -13,6 +13,8 @@ import com.newrelic.agent.attributes.CrossAgentInput; import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.agent.bridge.Instrumentation; +import com.newrelic.agent.tracing.samplers.AdaptiveSampler; +import com.newrelic.agent.tracing.samplers.Sampler; import com.newrelic.api.agent.TransportType; import com.newrelic.agent.config.*; import com.newrelic.agent.core.CoreService; @@ -34,6 +36,7 @@ import com.newrelic.agent.tracers.servlet.MockHttpRequest; import com.newrelic.agent.tracers.servlet.MockHttpResponse; import com.newrelic.test.marker.RequiresFork; +import org.eclipse.jetty.util.ajax.JSON; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -179,8 +182,13 @@ public void testTraceContext() throws Exception { String transportType = (String) testData.get("transport_type"); Boolean webTransaction = (Boolean) testData.get("web_transaction"); Boolean raisesException = (Boolean) testData.get("raises_exception"); - Boolean forceSampledTrue = (Boolean) testData.get("force_sampled_true"); + Boolean forceSampledTrue = (Boolean) testData.get("force_adaptive_sampled_true"); Boolean spanEventsEnabled = (Boolean) testData.get("span_events_enabled"); + JSONArray priorityRange = (JSONArray) testData.get("expected_priority_between"); + String remoteParentSampledSamplerType = (String) testData.get("remote_parent_sampled"); + String remoteParentNotSampledSamplerType = (String) testData.get("remote_parent_not_sampled"); + String rootSamplerType = (String) testData.get("root_sampler_type"); + replaceConfig(spanEventsEnabled); System.out.println("Running test: " + testName); @@ -203,6 +211,20 @@ public void testTraceContext() throws Exception { AgentConfig agentConfig = AgentHelper.createAgentConfig(true, Collections.emptyMap(), connectInfo); distributedTraceService.connected(null, agentConfig); + //have to force the adaptive sampler if configured + if (forceSampledTrue != null) { + Sampler forceSampler = getDefaultForceSampledAdaptiveSampler(forceSampledTrue); + if ("default".equals(remoteParentSampledSamplerType) || remoteParentSampledSamplerType == null) { + distributedTraceService.setRemoteParentSampledSampler(forceSampler); + } + if ("default".equals(remoteParentNotSampledSamplerType) || remoteParentNotSampledSamplerType == null) { + distributedTraceService.setRemoteParentNotSampledSampler(forceSampler); + } + if ("default".equals(rootSamplerType) || rootSamplerType == null) { + distributedTraceService.setRootSampler(forceSampler); + } + } + Transaction.clearTransaction(); TransactionActivity.clear(); spanEventService.clearReservoir(); @@ -257,9 +279,6 @@ public void testTraceContext() throws Exception { setTransportType(tx, transportType); - if (forceSampledTrue && tx.getPriority() < 1) { - tx.setPriorityIfNotNull(new Random().nextFloat() + 1.0f); - } if (outbound_payloads != null) { for (Object assertion : outbound_payloads) { @@ -288,6 +307,13 @@ public void testTraceContext() throws Exception { TransactionEvent transactionEvent = serviceManager.getTransactionEventsService().createEvent(transactionData, transactionStats, "wat"); JSONObject txnEvents = serializeAndParseEvents(transactionEvent); + if (priorityRange != null) { + assertPriorityBetween(priorityRange, transactionEvent.getPriority()); + for (SpanEvent s : spans) { + assertPriorityBetween(priorityRange, s.getPriority()); + } + } + StatsEngine statsEngine = statsService.getStatsEngineForHarvest(APP_NAME); assertExpectedMetrics(expectedMetrics, statsEngine); @@ -302,6 +328,18 @@ public void testTraceContext() throws Exception { } } + private void assertPriorityBetween(JSONArray priorityBetween, Float actualPriority) { + if (actualPriority == null) { + fail(); + } + long myVal = (long) priorityBetween.get(0); + long myVal2 = (long) priorityBetween.get(1); + float minPriority = (float) myVal; + float maxPriority = (float) myVal2; + System.out.println("Expected range: " + priorityBetween + " actual priority: " + actualPriority); + assertTrue(minPriority <= actualPriority && maxPriority >= actualPriority); + } + private void replaceConfig(boolean spanEventsEnabled) { Map config = new HashMap<>(); config.put(AgentConfigImpl.APP_NAME, APP_NAME); @@ -509,6 +547,10 @@ private void assertTransactionEvents(Map transactionAssertions, } private void assertExpectedMetrics(List metrics, StatsEngine statsEngine) { + if (metrics == null) { + return; + } + assertNotNull(statsEngine); for (Object metric : metrics) { @@ -537,4 +579,30 @@ private JSONObject serializeAndParseEvents(TransactionEvent transactionEvent) th return (JSONObject) json.get(0); } + //Our cross-agent tests include the setting force_adaptive_sampling_true option. + //This is a bit tricky as it describes the sampling decision ONLY of the adaptive sampler, + //and ONLY when the sampler is required to actually make a decision. + //To avoid making any changes to our real adaptive sampler, this subclass overrides + //the computeSampled method of the Adaptive Sampler. + //This means that the ForceSampledAdaptiveSampler will still run on a period/with a target, + //but will make deterministic sampling decisions as described by the forceSampled setting, + //instead of following the sampling algorithm. + private Sampler getDefaultForceSampledAdaptiveSampler(boolean forceSampled){ + AgentConfig config = ServiceFactory.getConfigService().getDefaultAgentConfig(); + return new ForceSampledAdaptiveSampler(config.getAdaptiveSamplingTarget(), config.getAdaptiveSamplingPeriodSeconds(), forceSampled); + } + + private static class ForceSampledAdaptiveSampler extends AdaptiveSampler { + boolean forceSampled; + + private ForceSampledAdaptiveSampler(int target, int reportPeriodSeconds, boolean forceSampled) { + super(target, reportPeriodSeconds); + this.forceSampled = forceSampled; + } + + @Override + protected boolean computeSampled() { + return forceSampled; + } + } } diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/AdaptiveSamplerTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/AdaptiveSamplerTest.java new file mode 100644 index 0000000000..370ab72982 --- /dev/null +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/AdaptiveSamplerTest.java @@ -0,0 +1,269 @@ +package com.newrelic.agent.tracing.samplers; + +import com.newrelic.agent.MockServiceManager; +import com.newrelic.agent.tracing.DistributedTraceUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The adaptive sampler is non-deterministic in its behavior, ie, we can almost never guarantee + * the exact number of things that will be sampled. Most of the tests + * have a margin of error (the errorDelta) around the expected value TARGET for this reason. + * If these tests start flaking, and there is no clear cause, consider adjusting the error margin to + * account for the non-deterministic behavior. + * + * Over time, the adaptive sampler should average to TARGET sampled=true decisions per period. + * In a given period, there could be considerable deviation from TARGET, but results should always + * be between 0 and TARGET*2. Because TARGET is the expected value in the long run, these tests should + * become more reliable the greater NUM_PERIODS we use. + * + * Also, the adaptive sampler runs on an internal timer. Most of the tests have a period + * that runs at a more rapid pace than we'd expect in the wild, because running multiple + * iterations of a 60sec period isn't practical. Even so, expect these tests to run for awhile. + */ +public class AdaptiveSamplerTest { + + static int DEFAULT_REQUESTS_PER_SEC = 20; + static float DEFAULT_ERROR_MARGIN = 0.15f; + static String errorMessage = "Expected %s sampled but actual sampled was %s"; + + private MockServiceManager serviceManager; + + @Before + public void setup(){ + serviceManager = new MockServiceManager(); + } + + @Test + public void testCalculatePriorityDefaultVals() throws InterruptedException{ + int DEFAULT_TARGET = 120; + int DEFAULT_REPORT_PERIOD = 60; + int numPeriods = 5; + long testLengthMillis = DEFAULT_REPORT_PERIOD * numPeriods * 1000; + + AdaptiveSampler defaultSampler = AdaptiveSampler.getSharedInstance(); + int totalSampled = runSamplerAndGetSampled(defaultSampler, testLengthMillis, DEFAULT_REQUESTS_PER_SEC); + + int expectedSampled = DEFAULT_TARGET * numPeriods; + int errorDelta = (int)(expectedSampled * DEFAULT_ERROR_MARGIN); + Assert.assertTrue(String.format(errorMessage, expectedSampled, totalSampled), + Math.abs(expectedSampled - totalSampled) < errorDelta); + } + + @Test + public void testTargetIsZeroShouldSampleNothing() throws InterruptedException{ + int target = 0; + int reportPeriod = 5; + int numPeriods = 12; + long testLengthMillis = reportPeriod * numPeriods * 1000; + + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + int totalSampled = runSamplerAndGetSampled(sampler, testLengthMillis, DEFAULT_REQUESTS_PER_SEC); + + int expectedSampled = 0; + Assert.assertEquals(String.format(errorMessage, expectedSampled, totalSampled), expectedSampled, totalSampled); + } + + @Test + public void testFirstPeriodShouldSampleTargetExactly() throws InterruptedException { + int target = 15; + int reportPeriod = 10; + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + long testLengthMillis = 12000L; //run test for 12 seconds, just over 1 period + + runSamplerAndGetSampledRandomLoad(sampler, testLengthMillis); + + int sampledFirstPeriod = sampler.getSampledCountLastPeriod(); + int expectedSampled = 15; + Assert.assertEquals(String.format(errorMessage, expectedSampled, sampledFirstPeriod), expectedSampled, sampledFirstPeriod); + } + + @Test + public void testIntermittentLoad() throws InterruptedException { + int target = 50; + int reportPeriod = 5; + int numPeriods = 6; + long testLengthMillis = numPeriods * reportPeriod * 1000L; //this test runs through this twice + + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + int totalSampledFirstLoad = runSamplerAndGetSampled(sampler, testLengthMillis, DEFAULT_REQUESTS_PER_SEC); + Thread.sleep(13000); //sleep to stop traffic to the sampler + int totalSampledSecondLoad = runSamplerAndGetSampled(sampler, testLengthMillis, DEFAULT_REQUESTS_PER_SEC); + int totalSampled = totalSampledFirstLoad + totalSampledSecondLoad; + + int expectedSampled = target * numPeriods * 2; + int errorDelta = (int)(expectedSampled * DEFAULT_ERROR_MARGIN); + System.out.println("Expected: " + expectedSampled + " Actual: " + totalSampled); + Assert.assertTrue(String.format(errorMessage, expectedSampled, totalSampled), Math.abs(totalSampled - expectedSampled) <= errorDelta); + } + + @Test + public void testDelayedLoad() throws InterruptedException { + int target = 50; + int reportPeriod = 5; + int numPeriods = 15; + long testLengthMillis = numPeriods * reportPeriod * 1000L; + + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + Thread.sleep(12000); //sleep for a few periods initially + int totalSampled = runSamplerAndGetSampled(sampler, testLengthMillis, DEFAULT_REQUESTS_PER_SEC); + + int expectedSampled = target * numPeriods; + int errorDelta = (int)(expectedSampled * DEFAULT_ERROR_MARGIN); + Assert.assertTrue(String.format(errorMessage, expectedSampled, totalSampled), Math.abs(totalSampled - expectedSampled) <= errorDelta); + } + + @Test + public void testExponentialBackoff() throws InterruptedException, ExecutionException { + //here, we overload the sampler aggressively for a few periods and verify that + //no more than 2x sampled count is sampled each time. + int target = 10; + int reportPeriod = 5; + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + + int overloadPhasePeriods = 10; + long overloadPhaseLength = 50000L; //50 seconds, 10 periods + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future f = executor.submit(() -> { + try { + return runSamplerAndGetSampled(sampler, overloadPhaseLength, 1000); + } catch (InterruptedException e) { + return null; + } + }); + + //While the sampler runs in the background, check in each period to see how many things + //it sampled most recently. + List sampledCountEachPeriod = new ArrayList<>(); + for (int i = 0; i < overloadPhasePeriods; i++) { + Thread.sleep(reportPeriod * 1000); + sampledCountEachPeriod.add(sampler.getSampledCountLastPeriod()); + } + + //Block until the thread is done + Integer actualTotalSampled = f.get(); + + //assert the total seems right + Assert.assertNotNull(actualTotalSampled); + int expectedTotalSampled = target * overloadPhasePeriods; + int expectedDelta = (int) (expectedTotalSampled * 0.2f); //this test is a bit flakier, setting higher error margin + Assert.assertTrue(String.format(errorMessage, expectedTotalSampled, actualTotalSampled), + Math.abs(expectedTotalSampled - actualTotalSampled) <= expectedDelta ); + + //iterate over the results from each period and assert that no individual + //period had a result exceeding 2*target, AND that the first time through we sampled exactly target. + Assert.assertEquals(target, (int) sampledCountEachPeriod.get(0)); + for (int sampledCount : sampledCountEachPeriod) { + Assert.assertTrue(String.format("Expected less than %s sampled for each period, but a period actually sampled %s", 2*target, sampledCount), + sampledCount <= 2*target); + } + } + + @Test + public void testCalculatePriorityMultithreaded() throws InterruptedException { + int target = 10; + int reportPeriod = 5; + int totalPeriods = 10; + long totalTestTimeMillis = reportPeriod * totalPeriods * 1000; + + AdaptiveSampler sampler = new AdaptiveSampler(target, reportPeriod); + int totalSampled = runSamplerConcurrentAndGetSampled(sampler, totalTestTimeMillis); + + int expectedSampled = target * totalPeriods; + int errorDelta = (int)(expectedSampled * DEFAULT_ERROR_MARGIN); + Assert.assertTrue(String.format(errorMessage, expectedSampled, totalSampled), Math.abs(totalSampled - expectedSampled) <= errorDelta); + } + + @Test + public void testGetAdaptiveSamplerInstanceFulfillsSingleton() throws InterruptedException, ExecutionException { + //The sampler is REQUIRED to be a singleton instance. + //This test verifies that access to the sampler always returns the same instance. + ExecutorService executor = Executors.newFixedThreadPool(5); + List> samplerArray = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + Future fut = executor.submit(AdaptiveSampler::getSharedInstance); + samplerArray.add(fut); + } + AdaptiveSampler baseSampler = AdaptiveSampler.getSharedInstance(); + Assert.assertNotNull(baseSampler); + for (Future f : samplerArray) { + Assert.assertEquals("All sampler instances retrieved by .getSharedInstance should be equal, but they were not", baseSampler, f.get()); + } + } + + private int runSamplerAndGetSampled(AdaptiveSampler sampler, long testLengthMillis, int requestsPerSecond) throws InterruptedException { + //fastest we're going to go for this test is 1 request per ms. + requestsPerSecond = Math.max(1000, requestsPerSecond); + long testStartTime = System.currentTimeMillis(); + int totalSampled = 0; + int waitBetweenSamples = 1000 / requestsPerSecond; + while (System.currentTimeMillis() - testStartTime < testLengthMillis) { + float priority = sampler.calculatePriority(); + boolean sampled = DistributedTraceUtil.isSampledPriority(priority); + if (sampled) { + totalSampled++; + } + Thread.sleep(waitBetweenSamples); + } + return totalSampled; + } + + private int runSamplerAndGetSampledRandomLoad(AdaptiveSampler sampler, long testLengthMillis) throws InterruptedException { + //in this iteration, we randomize the load on the sampler a bit. + //We'll hit the sampler randomly, with no more than 200 millis between accesses. + Random random = new Random(); + int MAX_WAIT_BETWEEN_SAMPLES = 200; + + long testStartTime = System.currentTimeMillis(); + int totalSampled = 0; + while (System.currentTimeMillis() - testStartTime < testLengthMillis) { + float priority = sampler.calculatePriority(); + boolean sampled = DistributedTraceUtil.isSampledPriority(priority); + if (sampled) { + totalSampled++; + } + Thread.sleep(random.nextInt(MAX_WAIT_BETWEEN_SAMPLES)); + } + return totalSampled; + } + + public int runSamplerConcurrentAndGetSampled(AdaptiveSampler sampler, long totalTestTimeMillis) throws InterruptedException { + int nThreads = 4; + ExecutorService executor = Executors.newFixedThreadPool(nThreads); + AtomicInteger totalSampled = new AtomicInteger(0); + //in this example, the wait is random, and the load is distributed across 4 threads. + Random random = new Random(); + int MAX_WAIT_BETWEEN_SAMPLES = 500; + //Start it up + long startTime = System.currentTimeMillis(); + for (int i = 0; i < nThreads ; i++) { + executor.submit(() -> { + while(System.currentTimeMillis() - startTime < totalTestTimeMillis) { + float priority = sampler.calculatePriority(); + boolean sampled = DistributedTraceUtil.isSampledPriority(priority); + if (sampled) { + totalSampled.incrementAndGet(); + } + try { + Thread.sleep(random.nextInt(MAX_WAIT_BETWEEN_SAMPLES)); + } catch(InterruptedException e){ + Thread.currentThread().interrupt(); + } + } + }); + } + Thread.sleep(totalTestTimeMillis + 1000); + return totalSampled.get(); + } + +} \ No newline at end of file diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/SamplerTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/SamplerTest.java new file mode 100644 index 0000000000..c8e1b02023 --- /dev/null +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracing/samplers/SamplerTest.java @@ -0,0 +1,46 @@ +package com.newrelic.agent.tracing.samplers; + +import com.newrelic.agent.MockServiceManager; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.ArrayList; + +import static org.junit.Assert.*; + +public class SamplerTest { + + private MockServiceManager serviceManager; + + @Before + public void setup(){ + serviceManager = new MockServiceManager(); + } + + @Test + public void testGetSamplerForType(){ + Sampler alwaysOnSampler = Sampler.getSamplerForType(Sampler.ALWAYS_ON); + assertEquals(Sampler.ALWAYS_ON, alwaysOnSampler.getType()); + + Sampler alwaysOffSampler = Sampler.getSamplerForType(Sampler.ALWAYS_OFF); + assertEquals(Sampler.ALWAYS_OFF, alwaysOffSampler.getType()); + + //these all should mean the same thing and retrieve an adaptive sampler. + List adaptiveSamplers = new ArrayList<>(); + Sampler adaptiveSampler = Sampler.getSamplerForType(Sampler.ADAPTIVE); + Sampler defaultSampler = Sampler.getSamplerForType("default"); + Sampler gibberishSampler = Sampler.getSamplerForType("madeUpType"); + Sampler noSampler = Sampler.getSamplerForType(""); + adaptiveSamplers.add(adaptiveSampler); + adaptiveSamplers.add(defaultSampler); + adaptiveSamplers.add(gibberishSampler); + adaptiveSamplers.add(noSampler); + + for (Sampler sampler: adaptiveSamplers) { + assertEquals(Sampler.ADAPTIVE, sampler.getType()); + } + + } + +} \ No newline at end of file diff --git a/newrelic-agent/src/test/resources/com/newrelic/agent/cross_agent_tests/distributed_tracing/trace_context.json b/newrelic-agent/src/test/resources/com/newrelic/agent/cross_agent_tests/distributed_tracing/trace_context.json index bae183f334..7493df212a 100644 --- a/newrelic-agent/src/test/resources/com/newrelic/agent/cross_agent_tests/distributed_tracing/trace_context.json +++ b/newrelic-agent/src/test/resources/com/newrelic/agent/cross_agent_tests/distributed_tracing/trace_context.json @@ -5,7 +5,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -60,7 +60,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -132,7 +132,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -191,7 +191,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -274,7 +274,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": false, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -344,7 +344,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": false, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -400,7 +400,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": true, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -458,7 +458,7 @@ "account_id": "33", "web_transaction": false, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -513,7 +513,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -568,7 +568,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "kafka", @@ -622,7 +622,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -700,7 +700,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -800,7 +800,7 @@ "raises_exception": false, "span_events_enabled": true, "transaction_events_enabled": true, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "transport_type": "HTTP", "inbound_headers": [ { @@ -875,7 +875,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -915,7 +915,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -963,7 +963,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1040,7 +1040,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1080,7 +1080,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1110,7 +1110,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1140,7 +1140,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1170,7 +1170,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1225,7 +1225,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1252,7 +1252,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1282,7 +1282,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1366,7 +1366,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1447,7 +1447,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1490,7 +1490,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1529,7 +1529,7 @@ "account_id": "99", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1608,7 +1608,7 @@ "account_id": "99", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1675,7 +1675,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1755,7 +1755,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1808,7 +1808,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1881,7 +1881,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -1966,7 +1966,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": true, "transaction_events_enabled": false, "transport_type": "HTTP", @@ -2040,7 +2040,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": true, + "force_adaptive_sampled_true": true, "span_events_enabled": false, "transaction_events_enabled": false, "transport_type": "HTTP", @@ -2089,7 +2089,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -2149,7 +2149,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -2208,7 +2208,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -2271,7 +2271,7 @@ "account_id": "33", "web_transaction": true, "raises_exception": false, - "force_sampled_true": false, + "force_adaptive_sampled_true": false, "span_events_enabled": true, "transaction_events_enabled": true, "transport_type": "HTTP", @@ -2318,5 +2318,294 @@ ["TransportDuration/App/33/Unknown/HTTP/all", 1], ["TransportDuration/App/33/Unknown/HTTP/allWeb", 1] ] + }, + { + "test_name": "w3c_sampled_remote_parent_sampled_default_uses_adaptive_sampling_algo", + "comment": "W3C parent header is used to determine remote parent is sampled but W3C trace state is not present so decision goes to random adaptive sampler algo", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "default", + "expected_priority_between": [ 0, 1 ], + "force_adaptive_sampled_true": false, + "inbound_headers": [ + { + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01", + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.234567,\"sa\":true,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "sampled": false + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "w3c_sampled_remote_parent_sampled_default_uses_w3c_trace_state_remote_sampled", + "comment": "W3C parent header is used to determine remote parent is sampled and W3C trace state is used to set sampling decision", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "default", + "force_adaptive_sampled_true": false, + "inbound_headers": [ + { + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01", + "tracestate": "33@nr=0-0-33-2827902-0af7651916cd43dd--1-1.2-1518469636035", + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":0,\"sa\":false,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 1.2, + "sampled": true + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "newrelic_sampled_remote_parent_sampled_default_uses_newrelic_remote_sampled", + "comment": "New Relic header is used to determine remote parent is sampled and to set sampling decision", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "default", + "force_adaptive_sampled_true": false, + "inbound_headers": [ + { + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.2,\"sa\":true,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 1.2, + "sampled": true + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "no_remote_parent_sampled_decision_so_default_uses_adaptive_sampling_algo", + "comment": "No headers so root default is used and sampling decision goes to random adaptive sampling algorithm", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "default", + "expected_priority_between": [ 1, 2 ], + "force_adaptive_sampled_true": true, + "inbound_headers": [ + {} + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "sampled": true + }, + "expected": [ "guid", "traceId" ] + } + } + }, + { + "test_name": "w3c_remote_parent_not_sampled_so_adaptive_uses_adaptive_sampling_algo", + "comment": "W3C parent header indicates remote parent not sampled, adaptive is used, and no W3C trace state header present so sampling decision goes to random adaptive sampling algorithm", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "adaptive", + "expected_priority_between": [ 1, 2 ], + "force_adaptive_sampled_true": true, + "inbound_headers": [ + { + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-00" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "sampled": true + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "w3c_remote_parent_not_sampled_so_always_off", + "comment": "W3C parent header indicates remote parent not sampled and is set to always off", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "always_off", + "force_adaptive_sampled_true": true, + "inbound_headers": [ + { + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-00", + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.2,\"sa\":true,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 0.0, + "sampled": false + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "newrelic_remote_parent_not_sampled_so_always_off", + "comment": "New Relic parent header indicates remote parent not sampled and is set to always off", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "always_off", + "force_adaptive_sampled_true": true, + "inbound_headers": [ + { + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.2,\"sa\":false,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 0.0, + "sampled": false + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "newrelic_remote_parent_not_sampled_so_always_on", + "comment": "New Relic parent header indicates remote parent not sampled and is set to always on", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "always_on", + "force_adaptive_sampled_true": true, + "inbound_headers": [ + { + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.2,\"sa\":false,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 2.0, + "sampled": true + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "newrelic_remote_parent_sampled_so_always_on", + "comment": "New Relic parent header indicates remote parent sampled and is set to always on", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "remote_parent_sampled": "always_on", + "remote_parent_not_sampled": "always_off", + "force_adaptive_sampled_true": true, + "inbound_headers": [ + { + "newrelic": "{\"v\":[0,1],\"d\":{\"ty\":\"App\",\"ac\":\"33\",\"ap\":\"2827902\",\"id\":\"7d3efb1b173fecfa\",\"tr\":\"0af7651916cd43dd8448eb211c80319c\",\"pr\":1.2,\"sa\":true,\"ti\":1518469636035,\"tx\":\"0af7651916cd43dd\"}}" + } + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "traceId": "0af7651916cd43dd8448eb211c80319c", + "priority": 2.0, + "sampled": true + }, + "expected": [ "guid" ] + } + } + }, + { + "test_name": "no_headers_root_always_off", + "comment": "Traces originating from current service are never sampled", + "trusted_account_key": "33", + "account_id": "33", + "web_transaction": true, + "raises_exception": false, + "span_events_enabled": true, + "transport_type": "HTTP", + "root": "always_off", + "remote_parent_sampled": "default", + "remote_parent_not_sampled": "default", + "force_adaptive_sampled_true": true, + "inbound_headers": [ + {} + ], + "intrinsics": { + "target_events": [ "Transaction" ], + "common": { + "exact": { + "priority": 0.0, + "sampled": false + }, + "expected": [ "guid", "traceId" ] + } + } } ]