All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.core.test.ThreadDumper Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.core.test;

import com.azure.core.test.implementation.TestingHelpers;
import com.azure.core.util.logging.ClientLogger;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * A utility and extension to dump threads after 30 minutes from starting tests. It can be used as standalone utility
 * via {@link #initialize()} as well as hooked up with JUnit by implementing
 * {@link BeforeAllCallback#beforeAll(ExtensionContext)}.
 * 

* ThreadDumper also tracks which tests are running and when they began running. These tests and how long they've been * running will be included in the thread dumps if they've been running longer than 5 minutes. */ public class ThreadDumper implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { private static final ClientLogger LOGGER = new ClientLogger(ThreadDumper.class); private static volatile ExecutorService executorService; // Assume that after 30 minutes test job hangs. private static final int INITIAL_DELAY_IN_MINUTES = 30; // Log every minute after initial interval passes. private static final int RATE_IN_MINUTES = 2; private static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis(); private static final Map RUNNING_TEST_TIMES = new ConcurrentHashMap<>(); /** * Creates a new instance of {@link ThreadDumper}. */ public ThreadDumper() { } /** * Initializes the singleton dumper. Can be called multiple times safely. */ public static void initialize() { if (executorService == null) { synchronized (ThreadDumper.class) { if (executorService == null) { executorService = createExecutorService(); } } } } private static ExecutorService createExecutorService() { ScheduledExecutorService service = Executors.newScheduledThreadPool(1, r -> { Thread t = new Thread(r); t.setDaemon(true); return t; }); Runtime.getRuntime().addShutdownHook(new Thread(service::shutdown)); service.scheduleAtFixedRate(ThreadDumper::printThreadStacks, INITIAL_DELAY_IN_MINUTES, RATE_IN_MINUTES, TimeUnit.MINUTES); return service; } private static void printThreadStacks() { final StringBuilder dump = new StringBuilder("============= THREAD DUMP START ========="); final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100); for (ThreadInfo threadInfo : threadInfos) { dump.append('"'); dump.append(threadInfo.getThreadName()); dump.append("\" "); final Thread.State state = threadInfo.getThreadState(); dump.append("\n java.lang.Thread.State: "); dump.append(state); final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace(); for (final StackTraceElement stackTraceElement : stackTraceElements) { dump.append("\n at "); dump.append(stackTraceElement); } dump.append("\n\n"); } dump.append("============= THREAD DUMP END =========") .append(System.lineSeparator()) .append("========= RUNNING TESTS START ========="); long nowMillis = System.currentTimeMillis(); for (Map.Entry runningTest : RUNNING_TEST_TIMES.entrySet()) { if (nowMillis - runningTest.getValue() > FIVE_MINUTES_MILLIS) { dump.append(System.lineSeparator()) .append(runningTest.getKey()) .append(": ") .append(nowMillis - runningTest.getValue()) .append(" millis"); } } dump.append("========== RUNNING TESTS END ==========\n"); String output = dump.toString(); // Log to both console and logs System.err.println(output); LOGGER.info(output); } @Override public void beforeAll(ExtensionContext context) { initialize(); } @Override public void beforeEach(ExtensionContext context) { // If the test class is an instance of TestBase or is a subtype of TestBase, then we don't need to track // anything here as TestBase handles this logic in it's Before and After test methods. Class clazz = context.getTestClass().orElse(null); if (clazz != null && TestBase.class.isAssignableFrom(clazz)) { return; } RUNNING_TEST_TIMES.put(getFullTestName(context), System.currentTimeMillis()); } static void addRunningTest(String testName) { RUNNING_TEST_TIMES.put(testName, System.currentTimeMillis()); } @Override public void afterEach(ExtensionContext context) { // If the test class is an instance of TestBase or is a subtype of TestBase, then we don't need to track // anything here as TestBase handles this logic in it's Before and After test methods. Class clazz = context.getTestClass().orElse(null); if (clazz != null && TestBase.class.isAssignableFrom(clazz)) { return; } RUNNING_TEST_TIMES.remove(getFullTestName(context)); } static void removeRunningTest(String testName) { RUNNING_TEST_TIMES.remove(testName); } private static String getFullTestName(ExtensionContext context) { return TestingHelpers.getTestName(context.getTestMethod(), context.getDisplayName(), context.getTestClass()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy