diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index 36d8c42b85..b84f4bd172 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -488,6 +488,13 @@ public String toString(Collection value) { "exist and use it to dump bytecode of instrumented classes.") .buildWithDefault(""); + private final ConfigurationOption threadDumpInterval = TimeDurationValueConverter.durationOption("ms") + .key("thread_dump_interval") + .configurationCategory(CORE_CATEGORY) + .tags("internal") + .description("Triggers a thread dump at regular frequency, should be used to help configure trace_methods without knowledge of application code") + .buildWithDefault(TimeDuration.of("0ms")); + private final ConfigurationOption typeMatchingWithNamePreFilter = ConfigurationOption.booleanOption() .key("enable_type_matching_name_pre_filtering") .configurationCategory(CORE_CATEGORY) @@ -1017,6 +1024,10 @@ public String getBytecodeDumpPath() { return bytecodeDumpPath.get(); } + public long getThreadDumpInterval() { + return threadDumpInterval.get().getMillis(); + } + public boolean isTypeMatchingWithNamePreFilter() { return typeMatchingWithNamePreFilter.get(); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ThreadDump.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ThreadDump.java new file mode 100644 index 0000000000..a6316f0d19 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ThreadDump.java @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.util; + +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.tracer.AbstractLifecycleListener; +import co.elastic.apm.agent.tracer.Tracer; + +import javax.annotation.Nullable; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadDump extends AbstractLifecycleListener { + + private Logger log = LoggerFactory.getLogger(ThreadDump.class); + + @Nullable + private ScheduledThreadPoolExecutor executor; + + @Override + public void start(Tracer tracer) throws Exception { + + long threadDumpInterval = tracer.getConfig(CoreConfiguration.class).getThreadDumpInterval(); + if (threadDumpInterval <= 0) { + return; + } + + if (threadDumpInterval < 100) { + log.error("thread dump frequency too high, adjusted to every 100ms"); + threadDumpInterval = 100; + } + + log.warn("thread dump will be generated every %s ms", threadDumpInterval); + + executor = ExecutorUtils.createSingleThreadSchedulingDaemonPool("thread-dump"); + + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + StringBuilder sb = new StringBuilder(); + for (ThreadInfo threadInfo : threadMXBean.dumpAllThreads(true, true)) { + sb.append(threadInfo.toString()); + } + + log.debug("thread dump: \n\n {}", sb); + + } + }, threadDumpInterval, threadDumpInterval, TimeUnit.MILLISECONDS); + } + + @Override + public void stop() throws Exception { + if (executor == null) { + return; + } + ExecutorUtils.shutdownAndWaitTermination(executor); + } +} diff --git a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.tracer.LifecycleListener b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.tracer.LifecycleListener index fb1f5e9680..4425ab21a6 100644 --- a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.tracer.LifecycleListener +++ b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.tracer.LifecycleListener @@ -10,3 +10,4 @@ co.elastic.apm.agent.metrics.builtin.AgentOverheadMetrics co.elastic.apm.agent.impl.circuitbreaker.CircuitBreaker co.elastic.apm.agent.collections.WeakMapCleaner co.elastic.apm.agent.report.serialize.MetricRegistryReporter +co.elastic.apm.agent.util.ThreadDump