Skip to content

Commit ff6d717

Browse files
authored
Add telemetry filters (#1564)
* Add telemetry filtering * Feedback * Feedback
1 parent 3e7ff94 commit ff6d717

File tree

22 files changed

+799
-162
lines changed

22 files changed

+799
-162
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import com.microsoft.applicationinsights.agent.internal.instrumentation.sdk.RequestTelemetryClassFileTransformer;
4545
import com.microsoft.applicationinsights.agent.internal.instrumentation.sdk.TelemetryClientClassFileTransformer;
4646
import com.microsoft.applicationinsights.agent.internal.instrumentation.sdk.WebRequestTrackingFilterClassFileTransformer;
47-
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingPercentage;
4847
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
4948
import com.microsoft.applicationinsights.common.CommonUtils;
5049
import com.microsoft.applicationinsights.customExceptions.FriendlyException;
@@ -152,7 +151,7 @@ private static void start(Instrumentation instrumentation) {
152151
configuration.getContextInitializers().add(new SdkVersionContextInitializer());
153152
configuration.getContextInitializers().add(new ResourceAttributesContextInitializer(config.customDimensions));
154153

155-
Global.setSamplingPercentage(SamplingPercentage.roundToNearest(config.sampling.percentage));
154+
Global.setSamplingPercentage(config.sampling.percentage);
156155
final TelemetryClient telemetryClient = new TelemetryClient();
157156
Global.setTelemetryClient(telemetryClient);
158157

@@ -181,7 +180,7 @@ public void run() {
181180

182181
RpConfiguration rpConfiguration = MainEntryPoint.getRpConfiguration();
183182
if (rpConfiguration != null) {
184-
RpConfigurationPolling.startPolling(rpConfiguration);
183+
RpConfigurationPolling.startPolling(rpConfiguration, config.preview.sampling.overrides);
185184
}
186185
}
187186

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
import java.nio.file.Files;
2626
import java.nio.file.attribute.BasicFileAttributes;
2727
import java.nio.file.attribute.FileTime;
28+
import java.util.List;
2829
import java.util.concurrent.Executors;
2930

3031
import com.microsoft.applicationinsights.TelemetryConfiguration;
3132
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
3233
import com.microsoft.applicationinsights.agent.internal.sampling.Samplers;
33-
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingPercentage;
34+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SamplingOverride;
35+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.ConfigurationBuilder;
3436
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
3537
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfigurationBuilder;
3638
import com.microsoft.applicationinsights.internal.util.ThreadPoolUtils;
@@ -41,17 +43,20 @@
4143

4244
public class RpConfigurationPolling implements Runnable {
4345

44-
private volatile RpConfiguration rpConfiguration;
4546
private static final Logger logger = LoggerFactory.getLogger(RpConfigurationPolling.class);
4647

47-
// visible for testing
48-
RpConfigurationPolling(RpConfiguration rpConfiguration) {
49-
this.rpConfiguration = rpConfiguration;
50-
}
48+
private volatile RpConfiguration rpConfiguration;
49+
private final List<SamplingOverride> samplingOverrides;
5150

52-
public static void startPolling(RpConfiguration rpConfiguration) {
51+
public static void startPolling(RpConfiguration rpConfiguration, List<SamplingOverride> samplingOverrides) {
5352
Executors.newSingleThreadScheduledExecutor(ThreadPoolUtils.createDaemonThreadFactory(RpConfigurationPolling.class))
54-
.scheduleWithFixedDelay(new RpConfigurationPolling(rpConfiguration), 60, 60, SECONDS);
53+
.scheduleWithFixedDelay(new RpConfigurationPolling(rpConfiguration, samplingOverrides), 60, 60, SECONDS);
54+
}
55+
56+
// visible for testing
57+
RpConfigurationPolling(RpConfiguration rpConfiguration, List<SamplingOverride> samplingOverrides) {
58+
this.rpConfiguration = rpConfiguration;
59+
this.samplingOverrides = samplingOverrides;
5560
}
5661

5762
@Override
@@ -79,8 +84,8 @@ public void run() {
7984

8085
if (newRpConfiguration.sampling.percentage != rpConfiguration.sampling.percentage) {
8186
logger.debug("Updating sampling percentage from {} to {}", rpConfiguration.sampling.percentage, newRpConfiguration.sampling.percentage);
82-
double roundedSamplingPercentage = SamplingPercentage.roundToNearest(newRpConfiguration.sampling.percentage);
83-
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(roundedSamplingPercentage));
87+
double roundedSamplingPercentage = ConfigurationBuilder.roundToNearest(newRpConfiguration.sampling.percentage);
88+
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(roundedSamplingPercentage, samplingOverrides));
8489
Global.setSamplingPercentage(roundedSamplingPercentage);
8590
rpConfiguration.sampling.percentage = newRpConfiguration.sampling.percentage;
8691
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.ProcessorAttribute;
1111
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.ProcessorIncludeExclude;
12-
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.ProcessorMatchType;
12+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.MatchType;
1313
import io.opentelemetry.api.common.AttributeKey;
1414
import io.opentelemetry.sdk.trace.data.SpanData;
1515
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -25,7 +25,7 @@ public AgentProcessor(@Nullable IncludeExclude include,
2525
}
2626

2727
protected static AttributeProcessor.IncludeExclude getNormalizedIncludeExclude(ProcessorIncludeExclude includeExclude) {
28-
return includeExclude.matchType == ProcessorMatchType.strict ? AgentProcessor.StrictIncludeExclude.create(includeExclude) : AgentProcessor.RegexpIncludeExclude.create(includeExclude);
28+
return includeExclude.matchType == MatchType.strict ? AgentProcessor.StrictIncludeExclude.create(includeExclude) : AgentProcessor.RegexpIncludeExclude.create(includeExclude);
2929
}
3030

3131
public @Nullable IncludeExclude getInclude() {

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

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.util.List;
44
import javax.annotation.Nullable;
55

6-
import com.microsoft.applicationinsights.agent.Exporter;
6+
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingOverrides.MatcherGroup;
77
import io.opentelemetry.api.common.Attributes;
88
import io.opentelemetry.api.trace.SpanKind;
99
import io.opentelemetry.context.Context;
@@ -18,29 +18,28 @@
1818
// * implements same trace id hashing algorithm so that traces are sampled the same across multiple nodes
1919
// when some of those nodes are being monitored by other Application Insights SDKs (and 2.x Java SDK)
2020
// * adds sampling percentage to span attribute (TODO this is not being applied to child spans)
21-
public final class AiSampler implements Sampler {
21+
class AiSampler implements Sampler {
2222

2323
private static final Logger logger = LoggerFactory.getLogger(AiSampler.class);
2424

2525
// all sampling percentage must be in a ratio of 100/N where N is a whole number (2, 3, 4, ...)
2626
// e.g. 50 for 1/2 or 33.33 for 1/3
2727
//
2828
// failure to follow this pattern can result in unexpected / incorrect computation of values in the portal
29-
private final double samplingPercentage;
29+
private final double defaultSamplingPercentage;
30+
private final SamplingResult defaultRecordAndSampleResult;
3031

31-
private final SamplingResult alwaysOnDecision;
32-
private final SamplingResult alwaysOffDecision;
32+
private final SamplingOverrides samplingOverrides;
3333

34-
public AiSampler(double samplingPercentage) {
35-
this.samplingPercentage = samplingPercentage;
36-
Attributes alwaysOnAttributes;
37-
if (samplingPercentage != 100) {
38-
alwaysOnAttributes = Attributes.of(Exporter.AI_SAMPLING_PERCENTAGE_KEY, samplingPercentage);
39-
} else {
40-
alwaysOnAttributes = Attributes.empty();
41-
}
42-
alwaysOnDecision = new FixedRateSamplerDecision(SamplingDecision.RECORD_AND_SAMPLE, alwaysOnAttributes);
43-
alwaysOffDecision = new FixedRateSamplerDecision(SamplingDecision.DROP, Attributes.empty());
34+
private final SamplingResult dropDecision;
35+
36+
AiSampler(double samplingPercentage, SamplingOverrides samplingOverrides) {
37+
this.defaultSamplingPercentage = samplingPercentage;
38+
defaultRecordAndSampleResult = SamplingOverrides.getRecordAndSampleResult(defaultSamplingPercentage);
39+
40+
this.samplingOverrides = samplingOverrides;
41+
42+
dropDecision = SamplingResult.create(SamplingDecision.DROP, Attributes.empty());
4443
}
4544

4645
@Override
@@ -50,39 +49,37 @@ public SamplingResult shouldSample(@Nullable Context parentContext,
5049
SpanKind spanKind,
5150
Attributes attributes,
5251
List<LinkData> parentLinks) {
53-
if (samplingPercentage == 100) {
54-
return alwaysOnDecision;
52+
53+
MatcherGroup override = samplingOverrides.getOverride(attributes);
54+
55+
double percentage;
56+
SamplingResult recordAndSampleResult;
57+
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;
64+
}
65+
66+
if (percentage == 100) {
67+
// optimization, no need to calculate score in this case
68+
return recordAndSampleResult;
5569
}
56-
if (SamplingScoreGeneratorV2.getSamplingScore(traceId) >= samplingPercentage) {
70+
if (percentage == 0) {
71+
// optimization, no need to calculate score in this case
72+
return dropDecision;
73+
}
74+
if (SamplingScoreGeneratorV2.getSamplingScore(traceId) >= percentage) {
5775
logger.debug("Item {} sampled out", name);
58-
return alwaysOffDecision;
76+
return dropDecision;
5977
}
60-
return alwaysOnDecision;
78+
return recordAndSampleResult;
6179
}
6280

6381
@Override
6482
public String getDescription() {
65-
return "ApplicationInsights-specific trace id based sampler, with sampling percentage: " + samplingPercentage;
66-
}
67-
68-
private static final class FixedRateSamplerDecision implements SamplingResult {
69-
70-
private final SamplingDecision decision;
71-
private final Attributes attributes;
72-
73-
private FixedRateSamplerDecision(SamplingDecision decision, Attributes attributes) {
74-
this.decision = decision;
75-
this.attributes = attributes;
76-
}
77-
78-
@Override
79-
public SamplingDecision getDecision() {
80-
return decision;
81-
}
82-
83-
@Override
84-
public Attributes getAttributes() {
85-
return attributes;
86-
}
83+
return "ApplicationInsights-specific trace id based sampler, with default sampling percentage: " + defaultSamplingPercentage;
8784
}
8885
}
Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
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;
36
import io.opentelemetry.sdk.trace.samplers.Sampler;
47

58
public class Samplers {
69

7-
public static Sampler getSampler(double samplingPercentage) {
8-
if (samplingPercentage != 100) {
9-
AiSampler aiSampler = new AiSampler(samplingPercentage);
10-
return Sampler.parentBasedBuilder(aiSampler)
11-
// for now, we have to override default behavior for "not sampled" remote parents
12-
// because .NET SDK always propagates trace flags "00" (not sampled)
13-
.setRemoteParentNotSampled(aiSampler)
14-
.build();
15-
} else {
16-
// OpenTelemetry default sampling is "parent based", which means don't sample if remote traceparent sampled flag was not set,
17-
// but Application Insights SDKs do not send the sampled flag (since they perform sampling during export instead of head-based sampling)
18-
// so need to use "always on" in this case
19-
return io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn();
20-
}
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)
18+
// this is the default, just including it for completeness
19+
// intentionally not allowing to capture a downstream span when upstream span has not been sampled
20+
// because this will lead to broken traces in A (sampled) -> B (not sampled) -> C (sampled)
21+
// C will point to parent B, but B will not be exported
22+
.setLocalParentNotSampled(Sampler.alwaysOff())
23+
// can filter out subtree of sampled trace, by applying sampling override
24+
.setRemoteParentSampled(aiSampler)
25+
// can filter out subtree of sampled trace, by applying sampling override
26+
.setLocalParentSampled(aiSampler)
27+
.build();
2128
}
2229
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.microsoft.applicationinsights.agent.internal.sampling;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.function.Predicate;
6+
import java.util.regex.Pattern;
7+
8+
import com.microsoft.applicationinsights.agent.Exporter;
9+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.MatchType;
10+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SamplingOverride;
11+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SamplingOverrideAttribute;
12+
import io.opentelemetry.api.common.AttributeKey;
13+
import io.opentelemetry.api.common.Attributes;
14+
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
15+
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
16+
17+
// TODO find a better name for this class (and MatcherGroup too)
18+
class SamplingOverrides {
19+
20+
private final List<MatcherGroup> matcherGroups;
21+
22+
SamplingOverrides(List<SamplingOverride> overrides) {
23+
matcherGroups = new ArrayList<>();
24+
for (SamplingOverride override : overrides) {
25+
matcherGroups.add(new MatcherGroup(override));
26+
}
27+
}
28+
29+
MatcherGroup getOverride(Attributes attributes) {
30+
for (MatcherGroup matcherGroups : matcherGroups) {
31+
if (matcherGroups.matches(attributes)) {
32+
return matcherGroups;
33+
}
34+
}
35+
return null;
36+
}
37+
38+
static SamplingResult getRecordAndSampleResult(double percentage) {
39+
Attributes alwaysOnAttributes;
40+
if (percentage != 100) {
41+
alwaysOnAttributes = Attributes.of(Exporter.AI_SAMPLING_PERCENTAGE_KEY, percentage);
42+
} else {
43+
// the exporter assumes 100 when the AI_SAMPLING_PERCENTAGE_KEY attribute is not present
44+
alwaysOnAttributes = Attributes.empty();
45+
}
46+
return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE, alwaysOnAttributes);
47+
}
48+
49+
static class MatcherGroup {
50+
private final List<Predicate<Attributes>> predicates;
51+
private final double percentage;
52+
private final SamplingResult recordAndSampleResult;
53+
54+
private MatcherGroup(SamplingOverride override) {
55+
predicates = new ArrayList<>();
56+
for (SamplingOverrideAttribute attribute : override.attributes) {
57+
predicates.add(toPredicate(attribute));
58+
}
59+
percentage = override.percentage;
60+
recordAndSampleResult = SamplingOverrides.getRecordAndSampleResult(percentage);
61+
}
62+
63+
double getPercentage() {
64+
return percentage;
65+
}
66+
67+
SamplingResult getRecordAndSampleResult() {
68+
return recordAndSampleResult;
69+
}
70+
71+
private boolean matches(Attributes attributes) {
72+
for (Predicate<Attributes> predicate : predicates) {
73+
if (!predicate.test(attributes)) {
74+
return false;
75+
}
76+
}
77+
return true;
78+
}
79+
80+
private static Predicate<Attributes> toPredicate(SamplingOverrideAttribute attribute) {
81+
if (attribute.matchType == MatchType.strict) {
82+
return new StrictMatcher(attribute.key, attribute.value);
83+
} else if (attribute.matchType == MatchType.regexp) {
84+
return new RegexpMatcher(attribute.key, attribute.value);
85+
} else {
86+
throw new IllegalStateException("Unexpected match type: " + attribute.matchType);
87+
}
88+
}
89+
}
90+
91+
private static class StrictMatcher implements Predicate<Attributes> {
92+
private final AttributeKey<String> key;
93+
private final String value;
94+
95+
private StrictMatcher(String key, String value) {
96+
this.key = AttributeKey.stringKey(key);
97+
this.value = value;
98+
}
99+
100+
@Override
101+
public boolean test(Attributes attributes) {
102+
String val = attributes.get(key);
103+
return value.equals(val);
104+
}
105+
}
106+
107+
private static class RegexpMatcher implements Predicate<Attributes> {
108+
private final AttributeKey<String> key;
109+
private final Pattern value;
110+
111+
private RegexpMatcher(String key, String value) {
112+
this.key = AttributeKey.stringKey(key);
113+
this.value = Pattern.compile(value);
114+
}
115+
116+
@Override
117+
public boolean test(Attributes attributes) {
118+
String val = attributes.get(key);
119+
return val != null && value.matcher(val).matches();
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)