Skip to content

Commit c65d843

Browse files
authored
Exclude ProxyLeakTask exception from exception profiling (#8666)
1 parent 33fc3c9 commit c65d843

File tree

7 files changed

+159
-1
lines changed

7 files changed

+159
-1
lines changed

dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionProfiling.java

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datadog.trace.bootstrap.instrumentation.jfr.exceptions;
22

33
import datadog.trace.api.Config;
4+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
45

56
/**
67
* JVM-wide singleton exception profiling service. Uses {@linkplain Config} class to configure
@@ -13,6 +14,24 @@ private static final class Holder {
1314
static final ExceptionProfiling INSTANCE = new ExceptionProfiling(Config.get());
1415
}
1516

17+
/**
18+
* Support for excluding certain exception types because they are used for control flow or leak
19+
* detection.
20+
*/
21+
public static final class Exclusion {
22+
public static void enter() {
23+
CallDepthThreadLocalMap.incrementCallDepth(Exclusion.class);
24+
}
25+
26+
public static void exit() {
27+
CallDepthThreadLocalMap.decrementCallDepth(Exclusion.class);
28+
}
29+
30+
public static boolean isEffective() {
31+
return CallDepthThreadLocalMap.getCallDepth(Exclusion.class) > 0;
32+
}
33+
}
34+
1635
/**
1736
* Get a pre-configured shared instance.
1837
*

dd-java-agent/instrumentation/exception-profiling/build.gradle

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ apply from: "$rootDir/gradle/java.gradle"
1111
apply plugin: "idea"
1212

1313
dependencies {
14+
testImplementation 'de.thetaphi:forbiddenapis:3.8'
1415
testImplementation libs.bundles.junit5
1516
testImplementation libs.bundles.jmc
1617
testImplementation libs.commons.math
@@ -29,6 +30,11 @@ forbiddenApisMain_java11 {
2930
failOnMissingClasses = false
3031
}
3132

33+
test {
34+
useJUnit()
35+
useJUnitPlatform()
36+
}
37+
3238
idea {
3339
module {
3440
jdkName = '11'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package datadog.exceptions.instrumentation;
2+
3+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import datadog.trace.api.Platform;
9+
10+
/**
11+
* Provides instrumentation to exclude exception types known to be used for control flow or
12+
* 'connection leak' detection.
13+
*/
14+
@AutoService(InstrumenterModule.class)
15+
public final class KnownExcludesInstrumentation extends InstrumenterModule.Profiling
16+
implements Instrumenter.ForBootstrap, Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
17+
18+
public KnownExcludesInstrumentation() {
19+
// this instrumentation is controlled together with 'throwables' instrumentation
20+
super("throwables");
21+
}
22+
23+
@Override
24+
public boolean isEnabled() {
25+
return Platform.hasJfr() && super.isEnabled();
26+
}
27+
28+
@Override
29+
public void methodAdvice(MethodTransformer transformer) {
30+
transformer.applyAdvice(isConstructor(), packageName + ".ExclusionAdvice");
31+
}
32+
33+
@Override
34+
public String[] knownMatchingTypes() {
35+
return new String[] {"com.zaxxer.hikari.pool.ProxyLeakTask"};
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package datadog.exceptions.instrumentation;
2+
3+
import datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionProfiling;
4+
import net.bytebuddy.asm.Advice;
5+
6+
public class ExclusionAdvice {
7+
@Advice.OnMethodEnter(suppress = Throwable.class)
8+
public static void onEnter() {
9+
ExceptionProfiling.Exclusion.enter();
10+
}
11+
12+
@Advice.OnMethodExit(suppress = Throwable.class)
13+
public static void onExit(@Advice.This final Object t) {
14+
ExceptionProfiling.Exclusion.exit();
15+
}
16+
}

dd-java-agent/instrumentation/exception-profiling/src/main/java11/datadog/exceptions/instrumentation/ThrowableInstanceAdvice.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
public class ThrowableInstanceAdvice {
1313
@Advice.OnMethodExit(suppress = Throwable.class)
1414
public static void onExit(@Advice.This final Object t) {
15+
if (ExceptionProfiling.Exclusion.isEffective()) {
16+
return;
17+
}
1518
/*
1619
* This instrumentation handler is sensitive to any throwables thrown from its body -
1720
* it will go into infinite loop of trying to handle the new throwable instance and generating
@@ -40,7 +43,7 @@ public static void onExit(@Advice.This final Object t) {
4043
}
4144
/*
4245
* JFR will assign the stacktrace depending on the place where the event is committed.
43-
* Therefore we need to commit the event here, right in the 'Exception' constructor
46+
* Therefore, we need to commit the event here, right in the 'Exception' constructor
4447
*/
4548
final ExceptionSampleEvent event = ExceptionProfiling.getInstance().process((Throwable) t);
4649
if (event != null && event.shouldCommit()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import com.zaxxer.hikari.pool.ProxyLeakTask
2+
import datadog.trace.agent.test.AgentTestRunner
3+
import datadog.trace.bootstrap.instrumentation.jfr.InstrumentationBasedProfiling
4+
import jdk.jfr.Recording
5+
import org.openjdk.jmc.common.item.Attribute
6+
import org.openjdk.jmc.common.item.IAttribute
7+
import org.openjdk.jmc.common.item.ItemFilters
8+
import org.openjdk.jmc.common.unit.UnitLookup
9+
import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit
10+
import spock.lang.Shared
11+
12+
import java.nio.file.Files
13+
14+
class KnownExcludesForkedTest extends AgentTestRunner {
15+
private static final IAttribute<String> TYPE =
16+
Attribute.attr("type", "type", "Exception type", UnitLookup.PLAIN_TEXT)
17+
18+
@Shared
19+
Recording recording
20+
21+
@Override
22+
protected void configurePreAgent() {
23+
super.configurePreAgent()
24+
injectSysConfig("profiling.enabled", "true")
25+
injectSysConfig("profiling.start-force-first", "true")
26+
}
27+
28+
def setupSpec() {
29+
recording = new Recording()
30+
recording.enable("datadog.ExceptionCount")
31+
recording.start()
32+
InstrumentationBasedProfiling.enableInstrumentationBasedProfiling()
33+
}
34+
35+
def "obey excluded"() {
36+
when:
37+
println("Generating exceptions ...")
38+
for (int i = 0; i < 50; i++) {
39+
new ProxyLeakTask()
40+
new NullPointerException()
41+
}
42+
println("Exceptions generated")
43+
44+
def tempPath = Files.createTempDirectory("test-recording")
45+
def recFile = tempPath.resolve(KnownExcludesForkedTest.name.replace('.', '_') + ".jfr")
46+
recFile.toFile().deleteOnExit()
47+
recording.dump(recFile)
48+
recording.stop()
49+
50+
def events = JfrLoaderToolkit.loadEvents(recFile.toFile()).apply(ItemFilters.type("datadog.ExceptionCount"))
51+
52+
then:
53+
events.apply(ItemFilters.equals(TYPE, NullPointerException.canonicalName)).hasItems()
54+
!events.apply(ItemFilters.equals(TYPE, ProxyLeakTask.canonicalName)).hasItems()
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.zaxxer.hikari.pool;
2+
3+
public class ProxyLeakTask extends Exception {
4+
private static final long serialVersionUID = 1L;
5+
6+
public ProxyLeakTask() {
7+
super("Proxy leak detected");
8+
}
9+
10+
public ProxyLeakTask(String message) {
11+
super(message);
12+
}
13+
14+
public ProxyLeakTask(String message, Throwable cause) {
15+
super(message, cause);
16+
}
17+
18+
public ProxyLeakTask(Throwable cause) {
19+
super(cause);
20+
}
21+
}

0 commit comments

Comments
 (0)