88import static datadog .trace .util .AgentThreadFactory .AgentThread .TRACE_STARTUP ;
99import static datadog .trace .util .AgentThreadFactory .newAgentThread ;
1010
11+ import datadog .trace .util .AgentTaskScheduler ;
1112import datadog .trace .util .AgentThreadFactory .AgentThread ;
1213import java .lang .instrument .Instrumentation ;
1314import java .lang .management .ManagementFactory ;
1617import java .lang .reflect .Method ;
1718import java .net .URL ;
1819import java .util .EnumSet ;
20+ import java .util .concurrent .TimeUnit ;
21+ import java .util .concurrent .atomic .AtomicBoolean ;
1922import org .slf4j .Logger ;
2023import org .slf4j .LoggerFactory ;
2124
@@ -39,6 +42,8 @@ public class Agent {
3942 private static final String SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY =
4043 "datadog.slf4j.simpleLogger.defaultLogLevel" ;
4144
45+ private static final int DEFAULT_JMX_FETCH_START_DELAY = 15 ; // seconds
46+
4247 // We cannot use lombok here because we need to configure logger first
4348 private static final Logger log ;
4449
@@ -48,6 +53,8 @@ public class Agent {
4853 log = LoggerFactory .getLogger (Agent .class );
4954 }
5055
56+ private static final AtomicBoolean jmxStarting = new AtomicBoolean ();
57+
5158 // fields must be managed under class lock
5259 private static ClassLoader PARENT_CLASSLOADER = null ;
5360 private static ClassLoader BOOTSTRAP_PROXY = null ;
@@ -88,14 +95,17 @@ public static void start(final Instrumentation inst, final URL bootstrapURL) {
8895 * JMXFetch until we detect the custom MBeanServerBuilder is being used. This takes precedence over the custom
8996 * log manager check because any custom log manager will be installed before any custom MBeanServerBuilder.
9097 */
98+ int jmxStartDelay = getJmxStartDelay ();
9199 if (appUsingCustomJMXBuilder ) {
92100 log .debug ("Custom JMX builder detected. Delaying JMXFetch initialization." );
93- registerMBeanServerBuilderCallback (new StartJmxCallback (bootstrapURL ));
101+ registerMBeanServerBuilderCallback (new StartJmxCallback (bootstrapURL , jmxStartDelay ));
102+ // one minute fail-safe in case nothing touches JMX and and callback isn't triggered
103+ scheduleJmxStart (bootstrapURL , 60 + jmxStartDelay );
94104 } else if (appUsingCustomLogManager ) {
95105 log .debug ("Custom logger detected. Delaying JMXFetch initialization." );
96- registerLogManagerCallback (new StartJmxCallback (bootstrapURL ));
106+ registerLogManagerCallback (new StartJmxCallback (bootstrapURL , jmxStartDelay ));
97107 } else {
98- startJmx (bootstrapURL );
108+ scheduleJmxStart (bootstrapURL , jmxStartDelay );
99109 }
100110
101111 /*
@@ -184,8 +194,11 @@ public void run() {
184194 }
185195
186196 protected static class StartJmxCallback extends ClassLoadCallBack {
187- StartJmxCallback (final URL bootstrapURL ) {
197+ private final int jmxStartDelay ;
198+
199+ StartJmxCallback (final URL bootstrapURL , final int jmxStartDelay ) {
188200 super (bootstrapURL );
201+ this .jmxStartDelay = jmxStartDelay ;
189202 }
190203
191204 @ Override
@@ -195,7 +208,9 @@ public AgentThread agentThread() {
195208
196209 @ Override
197210 public void execute () {
198- startJmx (bootstrapURL );
211+ // finish building platform JMX from context of custom builder before starting JMXFetch
212+ ManagementFactory .getPlatformMBeanServer ();
213+ scheduleJmxStart (bootstrapURL , jmxStartDelay );
199214 }
200215 }
201216
@@ -291,17 +306,31 @@ private static synchronized void installDatadogTracer() {
291306 }
292307 }
293308
294- private static synchronized void startJmx (final URL bootstrapURL ) {
295- // load core JMX using the inherited thread-context-classloader
296- ManagementFactory .getPlatformMBeanServer ();
309+ private static void scheduleJmxStart (final URL bootstrapURL , final int jmxStartDelay ) {
310+ if (jmxStartDelay > 0 ) {
311+ AgentTaskScheduler .INSTANCE .scheduleWithJitter (
312+ new JmxStartTask (), bootstrapURL , jmxStartDelay , TimeUnit .SECONDS );
313+ } else {
314+ startJmx (bootstrapURL );
315+ }
316+ }
297317
298- startJmxFetch (bootstrapURL );
318+ static final class JmxStartTask implements AgentTaskScheduler .Task <URL > {
319+ @ Override
320+ public void run (final URL bootstrapURL ) {
321+ startJmx (bootstrapURL );
322+ }
323+ }
299324
325+ private static synchronized void startJmx (final URL bootstrapURL ) {
300326 if (AGENT_CLASSLOADER == null ) {
301327 throw new IllegalStateException ("Datadog agent should have been started already" );
302328 }
329+ if (jmxStarting .getAndSet (true )) {
330+ return ; // another thread is already in startJmx
331+ }
332+ startJmxFetch (bootstrapURL );
303333 initializeJmxSystemAccessProvider (AGENT_CLASSLOADER );
304-
305334 registerDeadlockDetectionEvent (bootstrapURL );
306335 }
307336
@@ -491,6 +520,23 @@ private static boolean isStartupLogsEnabled() {
491520 return !"false" .equalsIgnoreCase (startupLogsEnabled );
492521 }
493522
523+ /** @return configured JMX start delay in seconds */
524+ private static int getJmxStartDelay () {
525+ final String jmxStartDelaySysprop = "dd.jmxfetch.start-delay" ;
526+ String jmxStartDelay = System .getProperty (jmxStartDelaySysprop );
527+ if (jmxStartDelay == null ) {
528+ jmxStartDelay = ddGetEnv (jmxStartDelaySysprop );
529+ }
530+ if (jmxStartDelay != null ) {
531+ try {
532+ return Integer .parseInt (jmxStartDelay );
533+ } catch (NumberFormatException e ) {
534+ // fall back to default delay
535+ }
536+ }
537+ return DEFAULT_JMX_FETCH_START_DELAY ;
538+ }
539+
494540 /**
495541 * Search for java or datadog-tracer sysprops which indicate that a custom log manager will be
496542 * used. Also search for any app classes known to set a custom log manager.
@@ -548,10 +594,9 @@ private static boolean isAppUsingCustomJMXBuilder(final EnumSet<Library> librari
548594 || Boolean .parseBoolean (customJMXBuilderEnv );
549595 }
550596
551- // FIXME: uncomment this when we add delayed JMX startup
552- // if (libraries.contains(WILDFLY)) {
553- // return true; // Wildfly is known to set a custom JMX builder after startup.
554- // }
597+ if (libraries .contains (WILDFLY )) {
598+ return true ; // Wildfly is known to set a custom JMX builder after startup.
599+ }
555600
556601 final String jmxBuilderProp = System .getProperty ("javax.management.builder.initial" );
557602 if (jmxBuilderProp != null ) {
@@ -570,7 +615,7 @@ private static boolean isAppUsingCustomJMXBuilder(final EnumSet<Library> librari
570615
571616 /** Looks for the "DD_" environment variable equivalent of the given "dd." system property. */
572617 private static String ddGetEnv (final String sysProp ) {
573- return System .getenv (sysProp .replace ('.' , '_' ).toUpperCase ());
618+ return System .getenv (sysProp .replace ('.' , '_' ).replace ( '-' , '_' ). toUpperCase ());
574619 }
575620
576621 private static boolean isJavaBefore9WithJFR () {
0 commit comments