Skip to content

Commit c93a11a

Browse files
authored
Respect parent sampled unless override match (#1573)
* Respect parent sampled unless override match * Add test * Fix test
1 parent 669ef92 commit c93a11a

File tree

14 files changed

+117
-35
lines changed

14 files changed

+117
-35
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/AiComponentInstaller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public void run() {
180180

181181
RpConfiguration rpConfiguration = MainEntryPoint.getRpConfiguration();
182182
if (rpConfiguration != null) {
183-
RpConfigurationPolling.startPolling(rpConfiguration, config.preview.sampling.overrides);
183+
RpConfigurationPolling.startPolling(rpConfiguration, config);
184184
}
185185
}
186186

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/RpConfigurationPolling.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.microsoft.applicationinsights.TelemetryConfiguration;
3232
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
3333
import com.microsoft.applicationinsights.agent.internal.sampling.Samplers;
34+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration;
3435
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SamplingOverride;
3536
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.ConfigurationBuilder;
3637
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
@@ -46,17 +47,17 @@ public class RpConfigurationPolling implements Runnable {
4647
private static final Logger logger = LoggerFactory.getLogger(RpConfigurationPolling.class);
4748

4849
private volatile RpConfiguration rpConfiguration;
49-
private final List<SamplingOverride> samplingOverrides;
50+
private final Configuration configuration;
5051

51-
public static void startPolling(RpConfiguration rpConfiguration, List<SamplingOverride> samplingOverrides) {
52+
public static void startPolling(RpConfiguration rpConfiguration, Configuration configuration) {
5253
Executors.newSingleThreadScheduledExecutor(ThreadPoolUtils.createDaemonThreadFactory(RpConfigurationPolling.class))
53-
.scheduleWithFixedDelay(new RpConfigurationPolling(rpConfiguration, samplingOverrides), 60, 60, SECONDS);
54+
.scheduleWithFixedDelay(new RpConfigurationPolling(rpConfiguration, configuration), 60, 60, SECONDS);
5455
}
5556

5657
// visible for testing
57-
RpConfigurationPolling(RpConfiguration rpConfiguration, List<SamplingOverride> samplingOverrides) {
58+
RpConfigurationPolling(RpConfiguration rpConfiguration, Configuration configuration) {
5859
this.rpConfiguration = rpConfiguration;
59-
this.samplingOverrides = samplingOverrides;
60+
this.configuration = configuration;
6061
}
6162

6263
@Override
@@ -85,7 +86,7 @@ public void run() {
8586
if (newRpConfiguration.sampling.percentage != rpConfiguration.sampling.percentage) {
8687
logger.debug("Updating sampling percentage from {} to {}", rpConfiguration.sampling.percentage, newRpConfiguration.sampling.percentage);
8788
double roundedSamplingPercentage = ConfigurationBuilder.roundToNearest(newRpConfiguration.sampling.percentage);
88-
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(roundedSamplingPercentage, samplingOverrides));
89+
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(roundedSamplingPercentage, configuration));
8990
Global.setSamplingPercentage(roundedSamplingPercentage);
9091
rpConfiguration.sampling.percentage = newRpConfiguration.sampling.percentage;
9192
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/AiSampler.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,22 @@ class AiSampler implements Sampler {
3333

3434
private final SamplingResult dropDecision;
3535

36-
AiSampler(double samplingPercentage, SamplingOverrides samplingOverrides) {
36+
private final BehaviorIfNoMatchingOverrides behaviorIfNoMatchingOverrides;
37+
38+
// samplingPercentage is still used in BehaviorIfNoMatchingOverrides.RECORD_AND_SAMPLE
39+
// to set an approximate value for the span attribute "applicationinsights.internal.sampling_percentage"
40+
//
41+
// in the future the sampling percentage (or its invert "count") will be
42+
// carried down by trace state to set the accurate value
43+
AiSampler(double samplingPercentage, SamplingOverrides samplingOverrides,
44+
BehaviorIfNoMatchingOverrides behaviorIfNoMatchingOverrides) {
3745
this.defaultSamplingPercentage = samplingPercentage;
3846
defaultRecordAndSampleResult = SamplingOverrides.getRecordAndSampleResult(defaultSamplingPercentage);
3947

4048
this.samplingOverrides = samplingOverrides;
4149

50+
this.behaviorIfNoMatchingOverrides = behaviorIfNoMatchingOverrides;
51+
4252
dropDecision = SamplingResult.create(SamplingDecision.DROP, Attributes.empty());
4353
}
4454

@@ -52,17 +62,21 @@ public SamplingResult shouldSample(@Nullable Context parentContext,
5262

5363
MatcherGroup override = samplingOverrides.getOverride(attributes);
5464

55-
double percentage;
56-
SamplingResult recordAndSampleResult;
5765
if (override != null) {
58-
percentage = override.getPercentage();
59-
recordAndSampleResult = override.getRecordAndSampleResult();
60-
} else {
61-
// no overrides, so fall back to the default sampling percentage
62-
percentage = defaultSamplingPercentage;
63-
recordAndSampleResult = defaultRecordAndSampleResult;
66+
return getSamplingResult(override.getPercentage(), override.getRecordAndSampleResult(), traceId, name);
67+
}
68+
69+
switch (behaviorIfNoMatchingOverrides) {
70+
case RECORD_AND_SAMPLE:
71+
return defaultRecordAndSampleResult;
72+
case USE_DEFAULT_SAMPLING_PERCENTAGE:
73+
return getSamplingResult(defaultSamplingPercentage, defaultRecordAndSampleResult, traceId, name);
74+
default:
75+
throw new IllegalStateException("Unexpected BehaviorIfNoMatchingOverrides: " + behaviorIfNoMatchingOverrides);
6476
}
77+
}
6578

79+
private SamplingResult getSamplingResult(double percentage, SamplingResult recordAndSampleResult, String traceId, String name) {
6680
if (percentage == 100) {
6781
// optimization, no need to calculate score in this case
6882
return recordAndSampleResult;
@@ -82,4 +96,9 @@ public SamplingResult shouldSample(@Nullable Context parentContext,
8296
public String getDescription() {
8397
return "ApplicationInsights-specific trace id based sampler, with default sampling percentage: " + defaultSamplingPercentage;
8498
}
99+
100+
enum BehaviorIfNoMatchingOverrides {
101+
USE_DEFAULT_SAMPLING_PERCENTAGE,
102+
RECORD_AND_SAMPLE
103+
}
85104
}
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
package com.microsoft.applicationinsights.agent.internal.sampling;
22

3-
import java.util.List;
4-
5-
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SamplingOverride;
3+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration;
64
import io.opentelemetry.sdk.trace.samplers.Sampler;
75

86
public class Samplers {
97

10-
public static Sampler getSampler(double samplingPercentage, List<SamplingOverride> overrides) {
11-
SamplingOverrides samplingOverrides = new SamplingOverrides(overrides);
12-
AiSampler aiSampler = new AiSampler(samplingPercentage, samplingOverrides);
13-
return Sampler.parentBasedBuilder(aiSampler)
14-
// TODO change the default behavior, and provide preview flag to treat "00" as
15-
// for now, we have to override default behavior of "alwaysOff"
16-
// because .NET SDK always propagates trace flags "00" (not sampled)
17-
.setRemoteParentNotSampled(aiSampler)
8+
public static Sampler getSampler(double samplingPercentage, Configuration config) {
9+
SamplingOverrides samplingOverrides = new SamplingOverrides(config.preview.sampling.overrides);
10+
AiSampler rootSampler = new AiSampler(samplingPercentage, samplingOverrides,
11+
AiSampler.BehaviorIfNoMatchingOverrides.USE_DEFAULT_SAMPLING_PERCENTAGE);
12+
AiSampler parentSampledSampler = new AiSampler(samplingPercentage, samplingOverrides,
13+
AiSampler.BehaviorIfNoMatchingOverrides.RECORD_AND_SAMPLE);
14+
// ignoreRemoteParentNotSampled is sometimes needed
15+
// because .NET SDK always propagates trace flags "00" (not sampled)
16+
Sampler remoteParentNotSampled = config.preview.ignoreRemoteParentNotSampled ? rootSampler : Sampler.alwaysOff();
17+
return Sampler.parentBasedBuilder(rootSampler)
18+
.setRemoteParentNotSampled(remoteParentNotSampled)
1819
// this is the default, just including it for completeness
1920
// intentionally not allowing to capture a downstream span when upstream span has not been sampled
2021
// because this will lead to broken traces in A (sampled) -> B (not sampled) -> C (sampled)
2122
// C will point to parent B, but B will not be exported
2223
.setLocalParentNotSampled(Sampler.alwaysOff())
2324
// can filter out subtree of sampled trace, by applying sampling override
24-
.setRemoteParentSampled(aiSampler)
25+
.setRemoteParentSampled(parentSampledSampler)
2526
// can filter out subtree of sampled trace, by applying sampling override
26-
.setLocalParentSampled(aiSampler)
27+
.setLocalParentSampled(parentSampledSampler)
2728
.build();
2829
}
2930
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/wasbootstrap/OpenTelemetryConfigurer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void configure(SdkTracerProviderBuilder tracerProvider) {
3636

3737
if (config.connectionString != null) {
3838
DelegatingPropagator.getInstance().setUpStandardDelegate();
39-
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(config.sampling.percentage, config.preview.sampling.overrides));
39+
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(config.sampling.percentage, config));
4040
} else {
4141
// in Azure Functions, we configure later on, once we know user has opted in to tracing
4242
// (note: the default for DelegatingPropagator is to not propagate anything

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/wasbootstrap/configuration/Configuration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ public static class PreviewConfiguration {
171171
// not sure if we'll be able to have different metric intervals in future OpenTelemetry metrics world,
172172
// so safer to only allow single interval for now
173173
public int metricIntervalSeconds = 60;
174+
public boolean ignoreRemoteParentNotSampled;
174175
public LiveMetrics liveMetrics = new LiveMetrics();
175176
}
176177

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/wasbootstrap/configuration/ConfigurationBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ private static void overlayRpConfiguration(Configuration config, RpConfiguration
262262
if (rpConfiguration.sampling != null) {
263263
config.sampling.percentage = rpConfiguration.sampling.percentage;
264264
}
265+
if (rpConfiguration.ignoreRemoteParentNotSampled != null) {
266+
config.preview.ignoreRemoteParentNotSampled = rpConfiguration.ignoreRemoteParentNotSampled;
267+
}
265268
}
266269

267270
private static String getConfigPath() {

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/wasbootstrap/configuration/RpConfiguration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121

2222
package com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration;
2323

24+
import org.checkerframework.checker.nullness.qual.Nullable;
25+
2426
import java.nio.file.Path;
2527

26-
// currently only Azure Spring Cloud uses this
2728
public class RpConfiguration {
2829

2930
// transient so that Moshi will ignore when binding from json
@@ -33,9 +34,14 @@ public class RpConfiguration {
3334
public transient long lastModifiedTime;
3435

3536
public String connectionString;
37+
3638
// intentionally null, so that we can tell if rp is providing or not
3739
public Sampling sampling = new Sampling();
3840

41+
// this is needed in Azure Functions because .NET SDK always propagates trace flags "00" (not sampled)
42+
// null means do not override the users selection
43+
public @Nullable Boolean ignoreRemoteParentNotSampled;
44+
3945
public static class Sampling {
4046

4147
public double percentage = 100;

agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/RpConfigurationPollingTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.common.io.Resources;
2828
import com.microsoft.applicationinsights.TelemetryConfiguration;
2929
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
30+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration;
3031
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.ConfigurationBuilder;
3132
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
3233
import org.junit.*;
@@ -59,7 +60,7 @@ public void shouldUpdate() {
5960
Global.setSamplingPercentage(ConfigurationBuilder.roundToNearest(rpConfiguration.sampling.percentage));
6061

6162
// when
62-
new RpConfigurationPolling(rpConfiguration, Collections.emptyList()).run();
63+
new RpConfigurationPolling(rpConfiguration, new Configuration()).run();
6364

6465
// then
6566
assertEquals("InstrumentationKey=00000000-0000-0000-0000-000000000000", TelemetryConfiguration.getActive().getConnectionString());
@@ -81,7 +82,7 @@ public void shouldStillUpdate() {
8182
envVars.set("APPLICATIONINSIGHTS_SAMPLING_PERCENTAGE", "90");
8283

8384
// when
84-
new RpConfigurationPolling(rpConfiguration, Collections.emptyList()).run();
85+
new RpConfigurationPolling(rpConfiguration, new Configuration()).run();
8586

8687
// then
8788
assertEquals("InstrumentationKey=00000000-0000-0000-0000-000000000000", TelemetryConfiguration.getActive().getConnectionString());
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"connectionString": "InstrumentationKey=00000000-0000-0000-0000-0FEEDDADBEEF;IngestionEndpoint=http://fakeingestion:6060/",
3+
"sampling": {
4+
"percentage": 50
5+
},
6+
"preview": {
7+
"sampling": {
8+
"overrides": [
9+
{
10+
"attributes": [
11+
{
12+
"key": "http.url",
13+
"value": ".*/login",
14+
"matchType": "regexp"
15+
}
16+
],
17+
"percentage": 100,
18+
"id": "capture all login telemetry"
19+
}
20+
]
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)