Skip to content

Commit c2e0084

Browse files
Merge branch 'landerson/dd-dogstatsd-port' into landerson/fix-crashtrackingsmoke
2 parents 3df5bbf + e4b8c9e commit c2e0084

File tree

16 files changed

+255
-4
lines changed

16 files changed

+255
-4
lines changed

communication/src/main/java/datadog/communication/monitor/DDAgentStatsDClientManager.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package datadog.communication.monitor;
22

3-
import static datadog.trace.api.ConfigDefaults.DEFAULT_DOGSTATSD_PORT;
43
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.LOGGING_WRITER_TYPE;
54

65
import datadog.trace.api.Config;
@@ -22,7 +21,7 @@ public static StatsDClientManager statsDClientManager() {
2221
return INSTANCE;
2322
}
2423

25-
private static final AtomicInteger defaultStatsDPort = new AtomicInteger(DEFAULT_DOGSTATSD_PORT);
24+
private static final AtomicInteger defaultStatsDPort = new AtomicInteger(Config.get().getDogsStatsDPort());
2625

2726
public static void setDefaultStatsDPort(final int newPort) {
2827
if (newPort > 0 && defaultStatsDPort.getAndSet(newPort) != newPort) {

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static datadog.trace.api.ConfigDefaults.DEFAULT_STARTUP_LOGS_ENABLED;
44
import static datadog.trace.api.Platform.isJavaVersionAtLeast;
55
import static datadog.trace.api.Platform.isOracleJDK8;
6+
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
67
import static datadog.trace.bootstrap.Library.WILDFLY;
78
import static datadog.trace.bootstrap.Library.detectLibraries;
89
import static datadog.trace.util.AgentThreadFactory.AgentThread.JMX_STARTUP;
@@ -44,6 +45,7 @@
4445
import datadog.trace.util.AgentTaskScheduler;
4546
import datadog.trace.util.AgentThreadFactory.AgentThread;
4647
import datadog.trace.util.throwable.FatalAgentMisconfigurationError;
48+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4749
import java.lang.instrument.Instrumentation;
4850
import java.lang.reflect.InvocationTargetException;
4951
import java.lang.reflect.Method;
@@ -291,6 +293,8 @@ public static void start(
291293
codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN);
292294
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);
293295

296+
patchJPSAccess(inst);
297+
294298
if (profilingEnabled) {
295299
if (!isOracleJDK8()) {
296300
// Profiling agent startup code is written in a way to allow `startProfilingAgent` be called
@@ -420,6 +424,23 @@ private static void injectAgentArgsConfig(String agentArgs) {
420424
}
421425
}
422426

427+
@SuppressForbidden
428+
public static void patchJPSAccess(Instrumentation inst) {
429+
if (Platform.isJavaVersionAtLeast(9)) {
430+
// Unclear if supported for J9, may need to revisit
431+
try {
432+
Class.forName("datadog.trace.util.JPMSJPSAccess")
433+
.getMethod("patchModuleAccess", Instrumentation.class)
434+
.invoke(null, inst);
435+
} catch (Exception e) {
436+
log.debug(
437+
SEND_TELEMETRY,
438+
"Failed to patch module access for jvmstat and Java version "
439+
+ Platform.getRuntimeVersion());
440+
}
441+
}
442+
}
443+
423444
public static void shutdown(final boolean sync) {
424445
StaticEventLogger.end("Agent");
425446
StaticEventLogger.stop();

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+
}

internal-api/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ excludedClassesCoverage += [
175175
"datadog.trace.util.ComparableVersion.LongItem",
176176
"datadog.trace.util.ComparableVersion.StringItem",
177177
"datadog.trace.util.ConcurrentEnumMap",
178+
"datadog.trace.util.JPSUtils",
178179
"datadog.trace.util.MethodHandles",
179180
"datadog.trace.util.PidHelper",
180181
"datadog.trace.util.PidHelper.Fallback",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package datadog.trace.util;
2+
3+
import java.lang.instrument.Instrumentation;
4+
import java.util.Collections;
5+
import java.util.Map;
6+
import java.util.Set;
7+
8+
public class JPMSJPSAccess {
9+
public static void patchModuleAccess(Instrumentation inst) {
10+
Module unnamedModule = ClassLoader.getSystemClassLoader().getUnnamedModule();
11+
Module jvmstatModule = ModuleLayer.boot().findModule("jdk.internal.jvmstat").orElse(null);
12+
13+
if (jvmstatModule != null) {
14+
Map<String, Set<Module>> extraOpens = Map.of("sun.jvmstat.monitor", Set.of(unnamedModule));
15+
16+
// Redefine the module
17+
inst.redefineModule(
18+
jvmstatModule,
19+
Collections.emptySet(),
20+
extraOpens,
21+
extraOpens,
22+
Collections.emptySet(),
23+
Collections.emptyMap());
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package datadog.trace.util
22

33
import datadog.trace.test.util.DDSpecification
4+
import net.bytebuddy.agent.ByteBuddyAgent
45

56
class PidHelperTest extends DDSpecification {
67

78
def "PID is available everywhere we test"() {
89
expect:
910
!PidHelper.getPid().isEmpty()
1011
}
12+
13+
def "JPS via jvmstat is used when possible"() {
14+
when:
15+
def inst = ByteBuddyAgent.install()
16+
JPMSJPSAccess.patchModuleAccess(inst)
17+
18+
then:
19+
JPSUtils.VMPids != null
20+
}
1121
}

internal-api/src/main/java/datadog/trace/api/Config.java

+7
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ public static String getHostName() {
527527
private final List<String> traceAgentArgs;
528528
private final String dogStatsDPath;
529529
private final List<String> dogStatsDArgs;
530+
private final int dogStatsDPort;
530531

531532
private String env;
532533
private String version;
@@ -1084,6 +1085,8 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
10841085
configProvider.getInteger(
10851086
DOGSTATSD_START_DELAY, DEFAULT_DOGSTATSD_START_DELAY, JMX_FETCH_START_DELAY);
10861087

1088+
dogStatsDPort = configProvider.getInteger(DOGSTATSD_PORT, DEFAULT_DOGSTATSD_PORT);
1089+
10871090
statsDClientQueueSize = configProvider.getInteger(STATSD_CLIENT_QUEUE_SIZE);
10881091
statsDClientSocketBuffer = configProvider.getInteger(STATSD_CLIENT_SOCKET_BUFFER);
10891092
statsDClientSocketTimeout = configProvider.getInteger(STATSD_CLIENT_SOCKET_TIMEOUT);
@@ -3528,6 +3531,10 @@ public List<String> getDogStatsDArgs() {
35283531
return dogStatsDArgs;
35293532
}
35303533

3534+
public int getDogsStatsDPort() {
3535+
return dogStatsDPort;
3536+
}
3537+
35313538
public String getConfigFileStatus() {
35323539
return configFileStatus;
35333540
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package datadog.trace.util;
2+
3+
import de.thetaphi.forbiddenapis.SuppressForbidden;
4+
import java.lang.reflect.Method;
5+
import java.util.Set;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
public final class JPSUtils {
10+
private static final Logger log = LoggerFactory.getLogger(JPSUtils.class);
11+
12+
@SuppressForbidden
13+
public static Set<String> getVMPids() {
14+
try {
15+
Class<?> monitoredHostClass = Class.forName("sun.jvmstat.monitor.MonitoredHost");
16+
Method getMonitoredHostMethod =
17+
monitoredHostClass.getDeclaredMethod("getMonitoredHost", String.class);
18+
Object vmHost = getMonitoredHostMethod.invoke(null, "localhost");
19+
return (Set<String>) monitoredHostClass.getDeclaredMethod("activeVms").invoke(vmHost);
20+
} catch (Exception e) {
21+
log.debug("Failed to invoke jvmstat with exception ", e);
22+
return null;
23+
}
24+
}
25+
}

internal-api/src/main/java/datadog/trace/util/PidHelper.java

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ private static String findPid() {
6565
}
6666

6767
public static Set<String> getJavaPids() {
68+
// Attempt to use jvmstat directly, fall through to jps process fork strategy
69+
Set<String> directlyObtainedPids = JPSUtils.getVMPids();
70+
if (directlyObtainedPids != null) {
71+
return directlyObtainedPids;
72+
}
6873
// there is no supported Java API to achieve this
6974
// one could use sun.jvmstat.monitor.MonitoredHost but it is an internal API and can go away at
7075
// any time -

0 commit comments

Comments
 (0)