google-site-verification: google61fe8ba583a51912.html
Thread Dump Analyzer in Java

Project Overview

A comprehensive thread dump analyzer that parses, analyzes, and provides insights into Java thread dumps. Helps identify performance issues, deadlocks, resource contention, and other threading problems.

Technology Stack

  • Core: Java 17+
  • Parsing: Regex patterns, Java NIO
  • Analysis: Custom algorithms for pattern detection
  • Visualization: HTML reports with charts
  • Concurrency: Parallel processing for large dumps
  • Data Structures: Custom graphs for deadlock detection

Project Structure

thread-dump-analyzer/ ├── src/main/java/com/threadanalyzer/ │ ├── parser/ │ ├── model/ │ ├── analyzer/ │ ├── reporter/ │ ├── detector/ │ └── cli/ ├── src/main/resources/ │ └── templates/ ├── examples/ │ └── thread-dumps/ └── config/

Core Implementation

1. Data Models

package com.threadanalyzer.model; import java.time.LocalDateTime; import java.util.*; import java.util.regex.Pattern; public class ThreadDump { private final String id; private final LocalDateTime timestamp; private final List<ThreadInfo> threads; private final String source; private final Map<String, Object> metadata; public ThreadDump(String source) { this.id = UUID.randomUUID().toString(); this.timestamp = LocalDateTime.now(); this.threads = new ArrayList<>(); this.source = source; this.metadata = new HashMap<>(); } // Builder pattern public static class Builder { private String source; private List<ThreadInfo> threads = new ArrayList<>(); private Map<String, Object> metadata = new HashMap<>(); public Builder source(String source) { this.source = source; return this; } public Builder addThread(ThreadInfo thread) { this.threads.add(thread); return this; } public Builder metadata(String key, Object value) { this.metadata.put(key, value); return this; } public ThreadDump build() { ThreadDump dump = new ThreadDump(source); dump.threads.addAll(threads); dump.metadata.putAll(metadata); return dump; } } // Getters public String getId() { return id; } public LocalDateTime getTimestamp() { return timestamp; } public List<ThreadInfo> getThreads() { return Collections.unmodifiableList(threads); } public String getSource() { return source; } public Map<String, Object> getMetadata() { return Collections.unmodifiableMap(metadata); } // Utility methods public int getThreadCount() { return threads.size(); } public long getDaemonThreadCount() { return threads.stream().filter(ThreadInfo::isDaemon).count(); } public long getNonDaemonThreadCount() { return threads.stream().filter(t -> !t.isDaemon()).count(); } public Map<ThreadState, Long> getThreadStateDistribution() { Map<ThreadState, Long> distribution = new EnumMap<>(ThreadState.class); for (ThreadInfo thread : threads) { distribution.merge(thread.getState(), 1L, Long::sum); } return distribution; } public List<ThreadInfo> getThreadsByState(ThreadState state) { return threads.stream() .filter(t -> t.getState() == state) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); } public Optional<ThreadInfo> findThreadById(long threadId) { return threads.stream() .filter(t -> t.getThreadId() == threadId) .findFirst(); } public List<ThreadInfo> findThreadsByName(String namePattern) { Pattern pattern = Pattern.compile(namePattern); return threads.stream() .filter(t -> pattern.matcher(t.getThreadName()).matches()) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); } @Override public String toString() { return String.format("ThreadDump[source=%s, threads=%d, timestamp=%s]", source, threads.size(), timestamp); } } public class ThreadInfo { private final long threadId; private final String threadName; private final boolean daemon; private final int priority; private final ThreadState state; private final String lockInfo; private final String lockOwnerId; private final String lockOwnerName; private final List<StackFrame> stackTrace; private final Map<String, String> additionalInfo; public ThreadInfo(long threadId, String threadName, boolean daemon, int priority, ThreadState state) { this.threadId = threadId; this.threadName = threadName; this.daemon = daemon; this.priority = priority; this.state = state; this.stackTrace = new ArrayList<>(); this.additionalInfo = new HashMap<>(); } // Builder pattern public static class Builder { private long threadId; private String threadName; private boolean daemon; private int priority; private ThreadState state; private String lockInfo; private String lockOwnerId; private String lockOwnerName; private List<StackFrame> stackTrace = new ArrayList<>(); private Map<String, String> additionalInfo = new HashMap<>(); public Builder threadId(long threadId) { this.threadId = threadId; return this; } public Builder threadName(String threadName) { this.threadName = threadName; return this; } public Builder daemon(boolean daemon) { this.daemon = daemon; return this; } public Builder priority(int priority) { this.priority = priority; return this; } public Builder state(ThreadState state) { this.state = state; return this; } public Builder lockInfo(String lockInfo) { this.lockInfo = lockInfo; return this; } public Builder lockOwnerId(String lockOwnerId) { this.lockOwnerId = lockOwnerId; return this; } public Builder lockOwnerName(String lockOwnerName) { this.lockOwnerName = lockOwnerName; return this; } public Builder addStackFrame(StackFrame frame) { this.stackTrace.add(frame); return this; } public Builder addStackFrames(List<StackFrame> frames) { this.stackTrace.addAll(frames); return this; } public Builder additionalInfo(String key, String value) { this.additionalInfo.put(key, value); return this; } public ThreadInfo build() { ThreadInfo thread = new ThreadInfo(threadId, threadName, daemon, priority, state); thread.lockInfo = lockInfo; thread.lockOwnerId = lockOwnerId; thread.lockOwnerName = lockOwnerName; thread.stackTrace.addAll(stackTrace); thread.additionalInfo.putAll(additionalInfo); return thread; } } // Getters public long getThreadId() { return threadId; } public String getThreadName() { return threadName; } public boolean isDaemon() { return daemon; } public int getPriority() { return priority; } public ThreadState getState() { return state; } public String getLockInfo() { return lockInfo; } public String getLockOwnerId() { return lockOwnerId; } public String getLockOwnerName() { return lockOwnerName; } public List<StackFrame> getStackTrace() { return Collections.unmodifiableList(stackTrace); } public Map<String, String> getAdditionalInfo() { return Collections.unmodifiableMap(additionalInfo); } // Utility methods public boolean isBlocked() { return state == ThreadState.BLOCKED; } public boolean isWaiting() { return state == ThreadState.WAITING || state == ThreadState.TIMED_WAITING; } public boolean isInNative() { return stackTrace.stream() .anyMatch(StackFrame::isNativeMethod); } public String getTopStackFrame() { return stackTrace.isEmpty() ? "" : stackTrace.get(0).toString(); } public boolean containsInStackTrace(String pattern) { return stackTrace.stream() .anyMatch(frame -> frame.toString().contains(pattern)); } public List<StackFrame> findInStackTrace(String pattern) { return stackTrace.stream() .filter(frame -> frame.toString().contains(pattern)) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); } @Override public String toString() { return String.format("Thread[id=%d, name=%s, state=%s]", threadId, threadName, state); } } public class StackFrame { private final String className; private final String methodName; private final String fileName; private final int lineNumber; private final boolean nativeMethod; public StackFrame(String className, String methodName, String fileName, int lineNumber, boolean nativeMethod) { this.className = className; this.methodName = methodName; this.fileName = fileName; this.lineNumber = lineNumber; this.nativeMethod = nativeMethod; } // Getters public String getClassName() { return className; } public String getMethodName() { return methodName; } public String getFileName() { return fileName; } public int getLineNumber() { return lineNumber; } public boolean isNativeMethod() { return nativeMethod; } public String getFullMethodName() { return className + "." + methodName; } public boolean isJDKMethod() { return className.startsWith("java.") || className.startsWith("javax.") || className.startsWith("sun.") || className.startsWith("com.sun."); } public boolean isApplicationMethod() { return !isJDKMethod() && !isNativeMethod(); } @Override public String toString() { if (nativeMethod) { return String.format("%s.%s(Native Method)", className, methodName); } else if (fileName != null && lineNumber >= 0) { return String.format("%s.%s(%s:%d)", className, methodName, fileName, lineNumber); } else if (fileName != null) { return String.format("%s.%s(%s)", className, methodName, fileName); } else { return String.format("%s.%s(Unknown Source)", className, methodName); } } } public enum ThreadState { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; public static ThreadState fromString(String state) { if (state == null) return null; switch (state.toUpperCase()) { case "NEW": return NEW; case "RUNNABLE": return RUNNABLE; case "BLOCKED": return BLOCKED; case "WAITING": return WAITING; case "TIMED_WAITING": return TIMED_WAITING; case "TERMINATED": return TERMINATED; default: return null; } } } public class Deadlock { private final String id; private final List<ThreadInfo> involvedThreads; private final List<LockCycle> lockCycles; private final String description; public Deadlock(List<ThreadInfo> involvedThreads, List<LockCycle> lockCycles) { this.id = UUID.randomUUID().toString(); this.involvedThreads = new ArrayList<>(involvedThreads); this.lockCycles = new ArrayList<>(lockCycles); this.description = generateDescription(); } // Getters public String getId() { return id; } public List<ThreadInfo> getInvolvedThreads() { return Collections.unmodifiableList(involvedThreads); } public List<LockCycle> getLockCycles() { return Collections.unmodifiableList(lockCycles); } public String getDescription() { return description; } private String generateDescription() { if (involvedThreads.isEmpty()) return "Empty deadlock"; StringBuilder sb = new StringBuilder(); sb.append("Deadlock involving ").append(involvedThreads.size()).append(" threads: "); for (int i = 0; i < involvedThreads.size(); i++) { if (i > 0) sb.append(", "); sb.append(involvedThreads.get(i).getThreadName()) .append(" (") .append(involvedThreads.get(i).getThreadId()) .append(")"); } return sb.toString(); } @Override public String toString() { return description; } } public class LockCycle { private final List<LockNode> nodes; public LockCycle(List<LockNode> nodes) { this.nodes = new ArrayList<>(nodes); } // Getters public List<LockNode> getNodes() { return Collections.unmodifiableList(nodes); } public boolean involvesThread(long threadId) { return nodes.stream().anyMatch(node -> node.getThreadId() == threadId); } @Override public String toString() { if (nodes.isEmpty()) return "Empty cycle"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < nodes.size(); i++) { if (i > 0) sb.append(" -> "); sb.append(nodes.get(i)); } sb.append(" -> ").append(nodes.get(0)); // Complete the cycle return sb.toString(); } } public class LockNode { private final long threadId; private final String threadName; private final String lockId; private final String lockType; public LockNode(long threadId, String threadName, String lockId, String lockType) { this.threadId = threadId; this.threadName = threadName; this.lockId = lockId; this.lockType = lockType; } // Getters public long getThreadId() { return threadId; } public String getThreadName() { return threadName; } public String getLockId() { return lockId; } public String getLockType() { return lockType; } @Override public String toString() { return String.format("Thread[%d:%s] holds Lock[%s:%s]", threadId, threadName, lockId, lockType); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LockNode lockNode = (LockNode) o; return threadId == lockNode.threadId && Objects.equals(lockId, lockNode.lockId); } @Override public int hashCode() { return Objects.hash(threadId, lockId); } }

2. Thread Dump Parser

package com.threadanalyzer.parser; import com.threadanalyzer.model.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ThreadDumpParser { // Regex patterns for different thread dump formats private static final Pattern THREAD_HEADER_PATTERN = Pattern.compile( "^\"([^\"]+)\"\\s+(?:#(\\d+)\\s+)?(daemon\\s+)?(prio=(\\d+)\\s+)?(os_prio=(\\d+)\\s+)?(tid=(0x[0-9a-f]+)\\s+)?(nid=(0x[0-9a-f]+)\\s+)?(\\w+)(?:\\s+\\[([^\\]]+)\\])?\\s*(?:\\(([^)]+)\\))?$" ); private static final Pattern THREAD_STATE_PATTERN = Pattern.compile( "^\\s+java\\.lang\\.Thread\\.State:\\s+(\\w+)(?:\\s+\\((.+)\\))?$" ); private static final Pattern LOCK_INFO_PATTERN = Pattern.compile( "^\\s+-\\s+(?:waiting\\s+on|locked|waiting\\s+to\\s+lock)\\s+<([0-9a-f]+)>\\s+\\(a\\s+([^)]+)\\)$" ); private static final Pattern LOCK_OWNER_PATTERN = Pattern.compile( "^\\s+-\\s+owned\\s+by\\s+<([0-9a-f]+)>\\s+\\(a\\s+([^)]+)\\)$" ); private static final Pattern STACK_FRAME_PATTERN = Pattern.compile( "^\\s+at\\s+([^(]+)\\(([^:)]+)(?::(\\d+))?\\)$" ); private static final Pattern NATIVE_FRAME_PATTERN = Pattern.compile( "^\\s+at\\s+([^(]+)\\(Native\\s+Method\\)$" ); private static final Pattern UNKNOWN_FRAME_PATTERN = Pattern.compile( "^\\s+at\\s+([^(]+)\\(Unknown\\s+Source\\)$" ); private static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "^\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}$" ); private static final Pattern FULL_THREAD_DUMP_PATTERN = Pattern.compile( "^Full thread dump\\s+.*$" ); public ThreadDump parseFromFile(Path filePath) throws IOException { String content = Files.readString(filePath); return parseFromString(content, filePath.toString()); } public ThreadDump parseFromString(String content, String source) { ThreadDump.Builder dumpBuilder = new ThreadDump.Builder().source(source); String[] lines = content.split("\n"); ThreadInfo.Builder currentThreadBuilder = null; List<StackFrame> currentStackTrace = new ArrayList<>(); boolean inThreadBlock = false; boolean inStackTrace = false; for (int i = 0; i < lines.length; i++) { String line = lines[i].trim(); if (line.isEmpty()) { // End of thread block if (inThreadBlock && currentThreadBuilder != null) { completeThreadBuilding(dumpBuilder, currentThreadBuilder, currentStackTrace); currentThreadBuilder = null; currentStackTrace.clear(); inThreadBlock = false; inStackTrace = false; } continue; } // Check for thread header Matcher headerMatcher = THREAD_HEADER_PATTERN.matcher(line); if (headerMatcher.matches()) { if (inThreadBlock && currentThreadBuilder != null) { completeThreadBuilding(dumpBuilder, currentThreadBuilder, currentStackTrace); } currentThreadBuilder = parseThreadHeader(headerMatcher); currentStackTrace.clear(); inThreadBlock = true; inStackTrace = false; continue; } // Check for thread state if (inThreadBlock && !inStackTrace) { Matcher stateMatcher = THREAD_STATE_PATTERN.matcher(line); if (stateMatcher.matches()) { parseThreadState(currentThreadBuilder, stateMatcher); inStackTrace = true; continue; } } // Check for lock information if (inThreadBlock) { Matcher lockMatcher = LOCK_INFO_PATTERN.matcher(line); if (lockMatcher.matches()) { parseLockInfo(currentThreadBuilder, lockMatcher); continue; } Matcher ownerMatcher = LOCK_OWNER_PATTERN.matcher(line); if (ownerMatcher.matches()) { parseLockOwner(currentThreadBuilder, ownerMatcher); continue; } } // Check for stack frame if (inStackTrace && currentThreadBuilder != null) { StackFrame frame = parseStackFrame(line); if (frame != null) { currentStackTrace.add(frame); } } // Check for timestamp if (TIMESTAMP_PATTERN.matcher(line).matches()) { dumpBuilder.metadata("timestamp", line); } // Check for full thread dump header if (FULL_THREAD_DUMP_PATTERN.matcher(line).matches()) { dumpBuilder.metadata("fullThreadDumpHeader", line); } } // Handle last thread if file doesn't end with empty line if (inThreadBlock && currentThreadBuilder != null) { completeThreadBuilding(dumpBuilder, currentThreadBuilder, currentStackTrace); } return dumpBuilder.build(); } private ThreadInfo.Builder parseThreadHeader(Matcher matcher) { ThreadInfo.Builder builder = new ThreadInfo.Builder(); // Thread name (group 1) String threadName = matcher.group(1); builder.threadName(threadName != null ? threadName.trim() : "Unknown"); // Thread ID (group 2) - optional String threadIdStr = matcher.group(2); if (threadIdStr != null) { try { builder.threadId(Long.parseLong(threadIdStr)); } catch (NumberFormatException e) { // Try to extract from thread name extractThreadIdFromName(builder, threadName); } } else { extractThreadIdFromName(builder, threadName); } // Daemon flag (group 3) boolean daemon = matcher.group(3) != null; builder.daemon(daemon); // Priority (group 5) String prioStr = matcher.group(5); if (prioStr != null) { try { builder.priority(Integer.parseInt(prioStr)); } catch (NumberFormatException e) { builder.priority(Thread.NORM_PRIORITY); } } else { builder.priority(Thread.NORM_PRIORITY); } // Native thread ID (group 10) - useful for correlation with OS threads String nidStr = matcher.group(10); if (nidStr != null) { builder.additionalInfo("nid", nidStr); } return builder; } private void extractThreadIdFromName(ThreadInfo.Builder builder, String threadName) { // Try to extract thread ID from name like "pool-1-thread-1" Pattern idPattern = Pattern.compile(".*-(\\d+)$"); Matcher idMatcher = idPattern.matcher(threadName); if (idMatcher.matches()) { try { builder.threadId(Long.parseLong(idMatcher.group(1))); } catch (NumberFormatException e) { builder.threadId(Thread.currentThread().getId()); // fallback } } else { builder.threadId(Thread.currentThread().getId()); // fallback } } private void parseThreadState(ThreadInfo.Builder builder, Matcher matcher) { String stateStr = matcher.group(1); ThreadState state = ThreadState.fromString(stateStr); if (state != null) { builder.state(state); } // Additional state info (e.g., WAITING (parking)) String stateInfo = matcher.group(2); if (stateInfo != null) { builder.additionalInfo("stateInfo", stateInfo); } } private void parseLockInfo(ThreadInfo.Builder builder, Matcher matcher) { String lockId = matcher.group(1); String lockType = matcher.group(2); if (lockId != null && lockType != null) { builder.lockInfo(lockId); builder.additionalInfo("lockType", lockType); } } private void parseLockOwner(ThreadInfo.Builder builder, Matcher matcher) { String ownerId = matcher.group(1); String ownerType = matcher.group(2); if (ownerId != null) { builder.lockOwnerId(ownerId); if (ownerType != null) { builder.lockOwnerName(ownerType); } } } private StackFrame parseStackFrame(String line) { // Try native method frame first Matcher nativeMatcher = NATIVE_FRAME_PATTERN.matcher(line); if (nativeMatcher.matches()) { String methodName = nativeMatcher.group(1); String[] parts = methodName.split("\\."); String className = parts.length > 1 ? String.join(".", Arrays.copyOf(parts, parts.length - 1)) : "Unknown"; String method = parts.length > 0 ? parts[parts.length - 1] : "unknown"; return new StackFrame(className, method, null, -1, true); } // Try regular stack frame with line number Matcher frameMatcher = STACK_FRAME_PATTERN.matcher(line); if (frameMatcher.matches()) { String methodName = frameMatcher.group(1); String fileName = frameMatcher.group(2); String lineNumberStr = frameMatcher.group(3); String[] parts = methodName.split("\\."); String className = parts.length > 1 ? String.join(".", Arrays.copyOf(parts, parts.length - 1)) : "Unknown"; String method = parts.length > 0 ? parts[parts.length - 1] : "unknown"; int lineNumber = -1; if (lineNumberStr != null) { try { lineNumber = Integer.parseInt(lineNumberStr); } catch (NumberFormatException e) { // Keep -1 } } return new StackFrame(className, method, fileName, lineNumber, false); } // Try unknown source frame Matcher unknownMatcher = UNKNOWN_FRAME_PATTERN.matcher(line); if (unknownMatcher.matches()) { String methodName = unknownMatcher.group(1); String[] parts = methodName.split("\\."); String className = parts.length > 1 ? String.join(".", Arrays.copyOf(parts, parts.length - 1)) : "Unknown"; String method = parts.length > 0 ? parts[parts.length - 1] : "unknown"; return new StackFrame(className, method, null, -1, false); } return null; } private void completeThreadBuilding(ThreadDump.Builder dumpBuilder, ThreadInfo.Builder threadBuilder, List<StackFrame> stackTrace) { threadBuilder.addStackFrames(stackTrace); ThreadInfo thread = threadBuilder.build(); dumpBuilder.addThread(thread); } // Batch parsing for multiple files public List<ThreadDump> parseMultipleFiles(List<Path> filePaths) { return filePaths.parallelStream() .map(path -> { try { return parseFromFile(path); } catch (IOException e) { System.err.println("Failed to parse file: " + path + " - " + e.getMessage()); return null; } }) .filter(Objects::nonNull) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); } }

3. Deadlock Detector

package com.threadanalyzer.detector; import com.threadanalyzer.model.*; import java.util.*; import java.util.stream.Collectors; public class DeadlockDetector { public List<Deadlock> detectDeadlocks(ThreadDump dump) { List<Deadlock> deadlocks = new ArrayList<>(); // Build lock dependency graph LockDependencyGraph graph = buildLockDependencyGraph(dump); // Find cycles in the graph List<List<LockNode>> cycles = findCycles(graph); // Convert cycles to Deadlock objects for (List<LockNode> cycle : cycles) { if (cycle.size() >= 2) { // Meaningful deadlock requires at least 2 threads List<ThreadInfo> involvedThreads = getThreadsFromCycle(dump, cycle); List<LockCycle> lockCycles = Collections.singletonList(new LockCycle(cycle)); deadlocks.add(new Deadlock(involvedThreads, lockCycles)); } } return deadlocks; } private LockDependencyGraph buildLockDependencyGraph(ThreadDump dump) { LockDependencyGraph graph = new LockDependencyGraph(); // First pass: collect all threads that are waiting for locks Map<String, LockWaiter> lockWaiters = new HashMap<>(); for (ThreadInfo thread : dump.getThreads()) { if (thread.isBlocked() || thread.isWaiting()) { String lockInfo = thread.getLockInfo(); if (lockInfo != null && !lockInfo.isEmpty()) { lockWaiters.put(thread.getLockInfo(), new LockWaiter(thread.getThreadId(), thread.getThreadName(), lockInfo)); } } } // Second pass: find threads that own locks that other threads are waiting for for (ThreadInfo thread : dump.getThreads()) { String lockOwnerId = thread.getLockOwnerId(); if (lockOwnerId != null && !lockOwnerId.isEmpty()) { LockWaiter waiter = lockWaiters.get(lockOwnerId); if (waiter != null) { // This thread owns a lock that another thread is waiting for graph.addDependency( new LockNode(thread.getThreadId(), thread.getThreadName(), thread.getLockInfo(), getLockType(thread)), new LockNode(waiter.getThreadId(), waiter.getThreadName(), waiter.getLockId(), "unknown") ); } } } return graph; } private String getLockType(ThreadInfo thread) { return thread.getAdditionalInfo().getOrDefault("lockType", "unknown"); } private List<List<LockNode>> findCycles(LockDependencyGraph graph) { List<List<LockNode>> cycles = new ArrayList<>(); Set<LockNode> visited = new HashSet<>(); Set<LockNode> recursionStack = new HashSet<>(); Map<LockNode, LockNode> parent = new HashMap<>(); for (LockNode node : graph.getAllNodes()) { if (!visited.contains(node)) { findCyclesDFS(graph, node, visited, recursionStack, parent, cycles); } } return cycles; } private void findCyclesDFS(LockDependencyGraph graph, LockNode current, Set<LockNode> visited, Set<LockNode> recursionStack, Map<LockNode, LockNode> parent, List<List<LockNode>> cycles) { visited.add(current); recursionStack.add(current); for (LockNode neighbor : graph.getDependencies(current)) { if (!visited.contains(neighbor)) { parent.put(neighbor, current); findCyclesDFS(graph, neighbor, visited, recursionStack, parent, cycles); } else if (recursionStack.contains(neighbor)) { // Cycle detected List<LockNode> cycle = reconstructCycle(current, neighbor, parent); if (!isDuplicateCycle(cycle, cycles)) { cycles.add(cycle); } } } recursionStack.remove(current); } private List<LockNode> reconstructCycle(LockNode current, LockNode cycleStart, Map<LockNode, LockNode> parent) { List<LockNode> cycle = new ArrayList<>(); LockNode node = current; // Traverse back to the cycle start while (node != cycleStart) { cycle.add(node); node = parent.get(node); } cycle.add(cycleStart); Collections.reverse(cycle); return cycle; } private boolean isDuplicateCycle(List<LockNode> newCycle, List<List<LockNode>> existingCycles) { if (newCycle.isEmpty()) return false; for (List<LockNode> existingCycle : existingCycles) { if (existingCycle.size() == newCycle.size() && new HashSet<>(existingCycle).equals(new HashSet<>(newCycle))) { return true; } } return false; } private List<ThreadInfo> getThreadsFromCycle(ThreadDump dump, List<LockNode> cycle) { return cycle.stream() .map(node -> dump.findThreadById(node.getThreadId())) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } // Helper class for lock dependency tracking private static class LockWaiter { private final long threadId; private final String threadName; private final String lockId; public LockWaiter(long threadId, String threadName, String lockId) { this.threadId = threadId; this.threadName = threadName; this.lockId = lockId; } public long getThreadId() { return threadId; } public String getThreadName() { return threadName; } public String getLockId() { return lockId; } } } class LockDependencyGraph { private final Map<LockNode, Set<LockNode>> adjacencyList = new HashMap<>(); public void addDependency(LockNode from, LockNode to) { adjacencyList.computeIfAbsent(from, k -> new HashSet<>()).add(to); } public Set<LockNode> getDependencies(LockNode node) { return adjacencyList.getOrDefault(node, Collections.emptySet()); } public Set<LockNode> getAllNodes() { return adjacencyList.keySet(); } public boolean hasDependency(LockNode from, LockNode to) { return adjacencyList.getOrDefault(from, Collections.emptySet()).contains(to); } }

4. Thread Dump Analyzer

package com.threadanalyzer.analyzer; import com.threadanalyzer.model.*; import java.util.*; import java.util.stream.Collectors; public class ThreadDumpAnalyzer { private final ThreadDump dump; private final AnalysisResult result; public ThreadDumpAnalyzer(ThreadDump dump) { this.dump = dump; this.result = new AnalysisResult(dump); } public AnalysisResult analyze() { analyzeThreadStates(); analyzeThreadGroups(); analyzeStackTraces(); analyzeLockContention(); analyzeCommonPatterns(); calculateStatistics(); return result; } private void analyzeThreadStates() { Map<ThreadState, Long> stateDistribution = dump.getThreadStateDistribution(); result.setStateDistribution(stateDistribution); // Identify potential issues based on thread states long blockedCount = stateDistribution.getOrDefault(ThreadState.BLOCKED, 0L); long waitingCount = stateDistribution.getOrDefault(ThreadState.WAITING, 0L) + stateDistribution.getOrDefault(ThreadState.TIMED_WAITING, 0L); if (blockedCount > dump.getThreadCount() * 0.1) { // More than 10% blocked result.addIssue("HIGH_BLOCKED_THREADS", String.format("High number of blocked threads: %d (%.1f%%)", blockedCount, (blockedCount * 100.0 / dump.getThreadCount()))); } if (waitingCount > dump.getThreadCount() * 0.3) { // More than 30% waiting result.addIssue("HIGH_WAITING_THREADS", String.format("High number of waiting threads: %d (%.1f%%)", waitingCount, (waitingCount * 100.0 / dump.getThreadCount()))); } } private void analyzeThreadGroups() { Map<String, List<ThreadInfo>> threadsByGroup = groupThreadsByName(); result.setThreadGroups(threadsByGroup); // Analyze thread pools analyzeThreadPools(threadsByGroup); // Identify unusually large thread groups threadsByGroup.entrySet().stream() .filter(entry -> entry.getValue().size() > 50) // Arbitrary threshold .forEach(entry -> { result.addIssue("LARGE_THREAD_GROUP", String.format("Large thread group '%s': %d threads", entry.getKey(), entry.getValue().size())); }); } private Map<String, List<ThreadInfo>> groupThreadsByName() { return dump.getThreads().stream() .collect(Collectors.groupingBy(thread -> { String name = thread.getThreadName(); // Extract group name (e.g., "pool-1-thread-1" -> "pool-1") if (name.contains("-thread-")) { return name.substring(0, name.lastIndexOf("-thread-")); } else if (name.contains("-")) { return name.substring(0, name.lastIndexOf('-')); } return name; })); } private void analyzeThreadPools(Map<String, List<ThreadInfo>> threadGroups) { threadGroups.entrySet().stream() .filter(entry -> entry.getKey().startsWith("pool-") || entry.getKey().contains("ThreadPool")) .forEach(entry -> { String poolName = entry.getKey(); List<ThreadInfo> poolThreads = entry.getValue(); long idleThreads = poolThreads.stream() .filter(t -> t.getState() == ThreadState.WAITING || t.getState() == ThreadState.TIMED_WAITING) .count(); long activeThreads = poolThreads.size() - idleThreads; result.addThreadPoolInfo(poolName, poolThreads.size(), activeThreads, idleThreads); if (idleThreads == 0 && activeThreads > 0) { result.addIssue("THREAD_POOL_BUSY", String.format("Thread pool '%s' has no idle threads (%d active)", poolName, activeThreads)); } }); } private void analyzeStackTraces() { // Analyze common stack trace patterns Map<String, Long> methodFrequency = new HashMap<>(); Map<String, Long> classFrequency = new HashMap<>(); for (ThreadInfo thread : dump.getThreads()) { for (StackFrame frame : thread.getStackTrace()) { // Count method frequency String methodKey = frame.getFullMethodName(); methodFrequency.merge(methodKey, 1L, Long::sum); // Count class frequency classFrequency.merge(frame.getClassName(), 1L, Long::sum); } } result.setMethodFrequency(methodFrequency); result.setClassFrequency(classFrequency); // Identify hot methods (methods that appear in many threads) methodFrequency.entrySet().stream() .filter(entry -> entry.getValue() > dump.getThreadCount() * 0.3) .forEach(entry -> { result.addIssue("HOT_METHOD", String.format("Method '%s' appears in %d threads", entry.getKey(), entry.getValue())); }); } private void analyzeLockContention() { // Analyze threads waiting for the same lock Map<String, List<ThreadInfo>> threadsByLock = new HashMap<>(); for (ThreadInfo thread : dump.getThreads()) { String lockInfo = thread.getLockInfo(); if (lockInfo != null && !lockInfo.isEmpty()) { threadsByLock.computeIfAbsent(lockInfo, k -> new ArrayList<>()).add(thread); } } // Identify locks with high contention threadsByLock.entrySet().stream() .filter(entry -> entry.getValue().size() > 1) .forEach(entry -> { String lockId = entry.getKey(); int waitingCount = entry.getValue().size(); result.addLockContention(lockId, waitingCount); if (waitingCount > 5) { // Arbitrary threshold result.addIssue("HIGH_LOCK_CONTENTION", String.format("Lock %s has %d waiting threads", lockId, waitingCount)); } }); } private void analyzeCommonPatterns() { // Detect common problematic patterns // Pattern 1: Many threads in IO operations long ioThreads = dump.getThreads().stream() .filter(t -> containsIOOperation(t)) .count(); if (ioThreads > dump.getThreadCount() * 0.2) { result.addIssue("HIGH_IO_OPERATIONS", String.format("Many threads (%d) performing IO operations", ioThreads)); } // Pattern 2: Threads stuck in synchronized blocks long synchronizedThreads = dump.getThreads().stream() .filter(t -> isInSynchronizedBlock(t)) .count(); if (synchronizedThreads > 10) { result.addIssue("MANY_SYNCHRONIZED_THREADS", String.format("%d threads in synchronized blocks", synchronizedThreads)); } // Pattern 3: Threads waiting on object monitors long monitorWaitThreads = dump.getThreads().stream() .filter(t -> isWaitingOnMonitor(t)) .count(); if (monitorWaitThreads > 5) { result.addIssue("MONITOR_WAIT_THREADS", String.format("%d threads waiting on object monitors", monitorWaitThreads)); } } private boolean containsIOOperation(ThreadInfo thread) { return thread.getStackTrace().stream() .anyMatch(frame -> frame.getClassName().contains("java.io") || frame.getClassName().contains("java.nio") || frame.getClassName().contains("java.net") || frame.getMethodName().toLowerCase().contains("read") || frame.getMethodName().toLowerCase().contains("write") || frame.getMethodName().toLowerCase().contains("accept") || frame.getMethodName().toLowerCase().contains("connect")); } private boolean isInSynchronizedBlock(ThreadInfo thread) { return thread.getStackTrace().stream() .anyMatch(frame -> frame.getMethodName().contains("synchronized") || (frame.getClassName().equals("java.lang.Object") && frame.getMethodName().equals("wait"))); } private boolean isWaitingOnMonitor(ThreadInfo thread) { return thread.getState() == ThreadState.WAITING && thread.getStackTrace().stream() .anyMatch(frame -> frame.getClassName().equals("java.lang.Object") && frame.getMethodName().equals("wait")); } private void calculateStatistics() { // Basic statistics result.setTotalThreads(dump.getThreadCount()); result.setDaemonThreads(dump.getDaemonThreadCount()); result.setNonDaemonThreads(dump.getNonDaemonThreadCount()); // Average stack depth double avgStackDepth = dump.getThreads().stream() .mapToInt(t -> t.getStackTrace().size()) .average() .orElse(0.0); result.setAverageStackDepth(avgStackDepth); // Thread priority distribution Map<Integer, Long> priorityDistribution = dump.getThreads().stream() .collect(Collectors.groupingBy( ThreadInfo::getPriority, Collectors.counting())); result.setPriorityDistribution(priorityDistribution); } } public class AnalysisResult { private final ThreadDump dump; private final Map<ThreadState, Long> stateDistribution = new EnumMap<>(ThreadState.class); private final Map<String, List<ThreadInfo>> threadGroups = new HashMap<>(); private final Map<String, Long> methodFrequency = new HashMap<>(); private final Map<String, Long> classFrequency = new HashMap<>(); private final List<String> issues = new ArrayList<>(); private final Map<String, Integer> lockContention = new HashMap<>(); private final Map<String, ThreadPoolInfo> threadPoolInfo = new HashMap<>(); private final Map<Integer, Long> priorityDistribution = new HashMap<>(); // Statistics private int totalThreads; private long daemonThreads; private long nonDaemonThreads; private double averageStackDepth; public AnalysisResult(ThreadDump dump) { this.dump = dump; } // Getters and setters public ThreadDump getDump() { return dump; } public Map<ThreadState, Long> getStateDistribution() { return stateDistribution; } public void setStateDistribution(Map<ThreadState, Long> distribution) { this.stateDistribution.putAll(distribution); } public Map<String, List<ThreadInfo>> getThreadGroups() { return threadGroups; } public void setThreadGroups(Map<String, List<ThreadInfo>> groups) { this.threadGroups.putAll(groups); } public Map<String, Long> getMethodFrequency() { return methodFrequency; } public void setMethodFrequency(Map<String, Long> frequency) { this.methodFrequency.putAll(frequency); } public Map<String, Long> getClassFrequency() { return classFrequency; } public void setClassFrequency(Map<String, Long> frequency) { this.classFrequency.putAll(frequency); } public List<String> getIssues() { return Collections.unmodifiableList(issues); } public void addIssue(String type, String description) { issues.add(type + ": " + description); } public Map<String, Integer> getLockContention() { return lockContention; } public void addLockContention(String lockId, int waitingCount) { lockContention.put(lockId, waitingCount); } public Map<String, ThreadPoolInfo> getThreadPoolInfo() { return threadPoolInfo; } public void addThreadPoolInfo(String poolName, int totalThreads, long activeThreads, long idleThreads) { threadPoolInfo.put(poolName, new ThreadPoolInfo(totalThreads, activeThreads, idleThreads)); } public int getTotalThreads() { return totalThreads; } public void setTotalThreads(int totalThreads) { this.totalThreads = totalThreads; } public long getDaemonThreads() { return daemonThreads; } public void setDaemonThreads(long daemonThreads) { this.daemonThreads = daemonThreads; } public long getNonDaemonThreads() { return nonDaemonThreads; } public void setNonDaemonThreads(long nonDaemonThreads) { this.nonDaemonThreads = nonDaemonThreads; } public double getAverageStackDepth() { return averageStackDepth; } public void setAverageStackDepth(double averageStackDepth) { this.averageStackDepth = averageStackDepth; } public Map<Integer, Long> getPriorityDistribution() { return priorityDistribution; } public void setPriorityDistribution(Map<Integer, Long> distribution) { this.priorityDistribution.putAll(distribution); } public boolean hasIssues() { return !issues.isEmpty(); } public int getIssueCount() { return issues.size(); } public List<String> getIssuesBySeverity() { // Simple severity classification return issues.stream() .sorted((a, b) -> { int severityA = getSeverity(a); int severityB = getSeverity(b); return Integer.compare(severityB, severityA); // Descending }) .collect(Collectors.toList()); } private int getSeverity(String issue) { if (issue.contains("DEADLOCK") || issue.contains("HIGH_BLOCKED")) { return 3; // High severity } else if (issue.contains("HIGH_WAITING") || issue.contains("HIGH_CONTENTION")) { return 2; // Medium severity } else { return 1; // Low severity } } } class ThreadPoolInfo { private final int totalThreads; private final long activeThreads; private final long idleThreads; public ThreadPoolInfo(int totalThreads, long activeThreads, long idleThreads) { this.totalThreads = totalThreads; this.activeThreads = activeThreads; this.idleThreads = idleThreads; } // Getters public int getTotalThreads() { return totalThreads; } public long getActiveThreads() { return activeThreads; } public long getIdleThreads() { return idleThreads; } public double getUtilization() { return totalThreads > 0 ? (double) activeThreads / totalThreads : 0.0; } }

5. HTML Report Generator

package com.threadanalyzer.reporter; import com.threadanalyzer.model.*; import com.threadanalyzer.analyzer.AnalysisResult; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; public class HTMLReportGenerator { public void generateReport(AnalysisResult result, Path outputDir) throws IOException { String htmlContent = generateHTMLContent(result); Path outputFile = outputDir.resolve("thread-dump-analysis.html"); Files.writeString(outputFile, htmlContent); System.out.println("HTML report generated: " + outputFile.toAbsolutePath()); } private String generateHTMLContent(AnalysisResult result) { StringBuilder html = new StringBuilder(); html.append(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Thread Dump Analysis Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; } .section { margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; } .section-title { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; margin-top: 0; } .issue { padding: 10px; margin: 5px 0; border-radius: 4px; } .issue-high { background: #ffebee; border-left: 4px solid #f44336; } .issue-medium { background: #fff3e0; border-left: 4px solid #ff9800; } .issue-low { background: #e8f5e8; border-left: 4px solid #4caf50; } .stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; } .stat-card { background: #f8f9fa; padding: 15px; border-radius: 5px; text-align: center; } .stat-value { font-size: 24px; font-weight: bold; color: #2c3e50; } .stat-label { color: #7f8c8d; font-size: 14px; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #34495e; color: white; } tr:hover { background-color: #f5f5f5; } .chart-container { height: 300px; margin: 20px 0; } .badge { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; } .badge-success { background: #d4edda; color: #155724; } .badge-warning { background: #fff3cd; color: #856404; } .badge-danger { background: #f8d7da; color: #721c24; } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <div class="container"> """); // Header html.append(""" <div class="header"> <h1>Thread Dump Analysis Report</h1> <p>Generated on: """ + java.time.LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + """</p> <p>Source: """ + escapeHtml(result.getDump().getSource()) + """</p> </div> """); // Executive Summary html.append(generateExecutiveSummary(result)); // Statistics html.append(generateStatisticsSection(result)); // Issues html.append(generateIssuesSection(result)); // Thread State Distribution html.append(generateThreadStateSection(result)); // Thread Pools html.append(generateThreadPoolSection(result)); // Lock Analysis html.append(generateLockAnalysisSection(result)); // Method Analysis html.append(generateMethodAnalysisSection(result)); html.append(""" </div> <script> """); // JavaScript for charts html.append(generateChartsJavaScript(result)); html.append(""" </script> </body> </html> """); return html.toString(); } private String generateExecutiveSummary(AnalysisResult result) { StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Executive Summary</h2> <div class="stat-grid"> <div class="stat-card"> <div class="stat-value">""" + result.getTotalThreads() + """</div> <div class="stat-label">Total Threads</div> </div> <div class="stat-card"> <div class="stat-value">""" + result.getDaemonThreads() + """</div> <div class="stat-label">Daemon Threads</div> </div> <div class="stat-card"> <div class="stat-value">""" + result.getNonDaemonThreads() + """</div> <div class="stat-label">Non-Daemon Threads</div> </div> <div class="stat-card"> <div class="stat-value">""" + result.getIssueCount() + """</div> <div class="stat-label">Issues Found</div> </div> </div> """); if (result.hasIssues()) { html.append(""" <div style="margin-top: 20px;"> <h3>Key Findings:</h3> <ul> """); List<String> topIssues = result.getIssuesBySeverity().stream() .limit(5) .collect(Collectors.toList()); for (String issue : topIssues) { html.append("<li>").append(escapeHtml(issue)).append("</li>"); } html.append(""" </ul> </div> """); } else { html.append(""" <div style="margin-top: 20px; padding: 15px; background: #d4edda; border-radius: 5px;"> <strong>No critical issues found.</strong> The thread dump appears healthy. </div> """); } html.append("</div>"); return html.toString(); } private String generateStatisticsSection(AnalysisResult result) { StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Statistics</h2> <div class="stat-grid"> <div class="stat-card"> <div class="stat-value">""" + String.format("%.1f", result.getAverageStackDepth()) + """</div> <div class="stat-label">Average Stack Depth</div> </div> """); // Add state distribution badges Map<ThreadState, Long> stateDist = result.getStateDistribution(); for (Map.Entry<ThreadState, Long> entry : stateDist.entrySet()) { if (entry.getValue() > 0) { String badgeClass = getBadgeClassForState(entry.getKey()); html.append(""" <div class="stat-card"> <div class="stat-value">""" + entry.getValue() + """</div> <div class="stat-label"> <span class="badge """ + badgeClass + """">""" + entry.getKey() + """</span> </div> </div> """); } } html.append("</div></div>"); return html.toString(); } private String generateIssuesSection(AnalysisResult result) { if (!result.hasIssues()) { return ""; } StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Detected Issues</h2> """); for (String issue : result.getIssuesBySeverity()) { String issueClass = getIssueClass(issue); html.append(""" <div class="issue """ + issueClass + """"> """ + escapeHtml(issue) + """ </div> """); } html.append("</div>"); return html.toString(); } private String generateThreadStateSection(AnalysisResult result) { StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Thread State Distribution</h2> <div class="chart-container"> <canvas id="stateChart"></canvas> </div> <table> <thead> <tr> <th>State</th> <th>Count</th> <th>Percentage</th> </tr> </thead> <tbody> """); Map<ThreadState, Long> stateDist = result.getStateDistribution(); int totalThreads = result.getTotalThreads(); for (Map.Entry<ThreadState, Long> entry : stateDist.entrySet()) { double percentage = totalThreads > 0 ? (entry.getValue() * 100.0 / totalThreads) : 0; html.append(""" <tr> <td>""" + entry.getKey() + """</td> <td>""" + entry.getValue() + """</td> <td>""" + String.format("%.1f%%", percentage) + """</td> </tr> """); } html.append(""" </tbody> </table> </div> """); return html.toString(); } private String generateThreadPoolSection(AnalysisResult result) { if (result.getThreadPoolInfo().isEmpty()) { return ""; } StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Thread Pool Analysis</h2> <table> <thead> <tr> <th>Pool Name</th> <th>Total Threads</th> <th>Active</th> <th>Idle</th> <th>Utilization</th> </tr> </thead> <tbody> """); for (Map.Entry<String, ThreadPoolInfo> entry : result.getThreadPoolInfo().entrySet()) { ThreadPoolInfo info = entry.getValue(); double utilization = info.getUtilization() * 100; String utilizationClass = utilization > 80 ? "badge-danger" : utilization > 50 ? "badge-warning" : "badge-success"; html.append(""" <tr> <td>""" + escapeHtml(entry.getKey()) + """</td> <td>""" + info.getTotalThreads() + """</td> <td>""" + info.getActiveThreads() + """</td> <td>""" + info.getIdleThreads() + """</td> <td><span class="badge """ + utilizationClass + """">""" + String.format("%.1f%%", utilization) + """</span></td> </tr> """); } html.append(""" </tbody> </table> </div> """); return html.toString(); } private String generateLockAnalysisSection(AnalysisResult result) { if (result.getLockContention().isEmpty()) { return ""; } StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Lock Contention</h2> <table> <thead> <tr> <th>Lock ID</th> <th>Waiting Threads</th> <th>Severity</th> </tr> </thead> <tbody> """); for (Map.Entry<String, Integer> entry : result.getLockContention().entrySet()) { int waitingCount = entry.getValue(); String severity = waitingCount > 10 ? "High" : waitingCount > 5 ? "Medium" : "Low"; String severityClass = waitingCount > 10 ? "badge-danger" : waitingCount > 5 ? "badge-warning" : "badge-success"; html.append(""" <tr> <td>""" + escapeHtml(entry.getKey()) + """</td> <td>""" + waitingCount + """</td> <td><span class="badge """ + severityClass + """">""" + severity + """</span></td> </tr> """); } html.append(""" </tbody> </table> </div> """); return html.toString(); } private String generateMethodAnalysisSection(AnalysisResult result) { // Show top 10 most frequent methods List<Map.Entry<String, Long>> topMethods = result.getMethodFrequency().entrySet().stream() .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) .limit(10) .collect(Collectors.toList()); if (topMethods.isEmpty()) { return ""; } StringBuilder html = new StringBuilder(); html.append(""" <div class="section"> <h2 class="section-title">Most Frequent Methods (Top 10)</h2> <table> <thead> <tr> <th>Method</th> <th>Frequency</th> <th>Percentage</th> </tr> </thead> <tbody> """); int totalStackFrames = result.getMethodFrequency().values().stream() .mapToInt(Long::intValue) .sum(); for (Map.Entry<String, Long> entry : topMethods) { double percentage = totalStackFrames > 0 ? (entry.getValue() * 100.0 / totalStackFrames) : 0; html.append(""" <tr> <td>""" + escapeHtml(entry.getKey()) + """</td> <td>""" + entry.getValue() + """</td> <td>""" + String.format("%.1f%%", percentage) + """</td> </tr> """); } html.append(""" </tbody> </table> </div> """); return html.toString(); } private String generateChartsJavaScript(AnalysisResult result) { StringBuilder js = new StringBuilder(); // Thread state chart js.append(""" var stateCtx = document.getElementById('stateChart').getContext('2d'); var stateChart = new Chart(stateCtx, { type: 'doughnut', data: { labels: [""" + result.getStateDistribution().keySet().stream() .map(state -> "'" + state + "'") .collect(Collectors.joining(", ")) + """], datasets: [{ data: [""" + result.getStateDistribution().values().stream() .map(String::valueOf) .collect(Collectors.joining(", ")) + """], backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }, options: { responsive: true, plugins: { legend: { position: 'bottom', }, title: { display: true, text: 'Thread States' } } } }); """); return js.toString(); } private String getBadgeClassForState(ThreadState state) { switch (state) { case BLOCKED: return "badge-danger"; case WAITING: case TIMED_WAITING: return "badge-warning"; case RUNNABLE: return "badge-success"; default: return "badge-success"; } } private String getIssueClass(String issue) { if (issue.contains("HIGH") || issue.contains("DEADLOCK")) { return "issue-high"; } else if (issue.contains("MEDIUM") || issue.contains("MANY")) { return "issue-medium"; } else { return "issue-low"; } } private String escapeHtml(String text) { if (text == null) return ""; return text.replace("&", "&amp;") .replace("<", "&lt;") .replace(">", "&gt;") .replace("\"", "&quot;") .replace("'", "&#39;"); } }

6. CLI Interface

package com.threadanalyzer.cli; import com.threadanalyzer.analyzer.AnalysisResult; import com.threadanalyzer.analyzer.ThreadDumpAnalyzer; import com.threadanalyzer.detector.DeadlockDetector; import com.threadanalyzer.model.ThreadDump; import com.threadanalyzer.parser.ThreadDumpParser; import com.threadanalyzer.reporter.HTMLReportGenerator; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import java.io.File; import java.nio.file.Path; import java.util.List; import java.util.concurrent.Callable; @Command(name = "threadanalyzer", mixinStandardHelpOptions = true, version = "Thread Dump Analyzer 1.0", description = "Analyzes Java thread dumps and generates reports") public class CLIRunner implements Callable<Integer> { @Parameters(index = "0", description = "Thread dump file or directory") private File input; @Option(names = {"-o", "--output"}, description = "Output directory for reports (default: ./reports)") private File outputDir = new File("./reports"); @Option(names = {"--format"}, description = "Output format: ${COMPLETION-CANDIDATES} (default: HTML)") private OutputFormat format = OutputFormat.HTML; @Option(names = {"--detect-deadlocks"}, description = "Enable deadlock detection") private boolean detectDeadlocks = true; @Option(names = {"--generate-charts"}, description = "Generate charts in reports") private boolean generateCharts = true; @Option(names = {"--verbose"}, description = "Enable verbose output") private boolean verbose = false; private final ThreadDumpParser parser = new ThreadDumpParser(); private final DeadlockDetector deadlockDetector = new DeadlockDetector(); public enum OutputFormat { HTML, TEXT, JSON } @Override public Integer call() throws Exception { if (!input.exists()) { System.err.println("Error: Input file/directory does not exist: " + input.getAbsolutePath()); return 1; } if (!outputDir.exists()) { outputDir.mkdirs(); } try { List<ThreadDump> dumps = loadThreadDumps(); if (dumps.isEmpty()) { System.err.println("Error: No valid thread dumps found in: " + input.getAbsolutePath()); return 1; } System.out.println("Loaded " + dumps.size() + " thread dump(s)"); for (ThreadDump dump : dumps) { System.out.println("\nAnalyzing: " + dump.getSource()); analyzeDump(dump); } System.out.println("\nAnalysis completed. Reports saved to: " + outputDir.getAbsolutePath()); return 0; } catch (Exception e) { System.err.println("Error during analysis: " + e.getMessage()); if (verbose) { e.printStackTrace(); } return 1; } } private List<ThreadDump> loadThreadDumps() throws Exception { if (input.isFile()) { ThreadDump dump = parser.parseFromFile(input.toPath()); return List.of(dump); } else if (input.isDirectory()) { // Load all .txt, .log, .threaddump files from directory File[] files = input.listFiles((dir, name) -> name.toLowerCase().endsWith(".txt") || name.toLowerCase().endsWith(".log") || name.toLowerCase().endsWith(".threaddump") || name.toLowerCase().contains("threaddump")); if (files == null || files.length == 0) { return List.of(); } return parser.parseMultipleFiles( List.of(files).stream() .map(File::toPath) .collect(java.util.stream.Collectors.toList()) ); } return List.of(); } private void analyzeDump(ThreadDump dump) throws Exception { // Perform analysis ThreadDumpAnalyzer analyzer = new ThreadDumpAnalyzer(dump); AnalysisResult result = analyzer.analyze(); // Detect deadlocks if enabled if (detectDeadlocks) { var deadlocks = deadlockDetector.detectDeadlocks(dump); if (!deadlocks.isEmpty()) { System.out.println("⚠️ Found " + deadlocks.size() + " potential deadlock(s)"); for (var deadlock : deadlocks) { System.out.println(" - " + deadlock.getDescription()); } } } // Generate report based on format switch (format) { case HTML: generateHTMLReport(dump, result); break; case TEXT: generateTextReport(dump, result); break; case JSON: generateJSONReport(dump, result); break; } // Print summary to console printConsoleSummary(result); } private void generateHTMLReport(ThreadDump dump, AnalysisResult result) throws Exception { HTMLReportGenerator reporter = new HTMLReportGenerator(); String baseName = getBaseName(dump.getSource()); Path reportDir = outputDir.toPath().resolve(baseName); reporter.generateReport(result, reportDir); } private void generateTextReport(ThreadDump dump, AnalysisResult result) { // Implementation for text report String baseName = getBaseName(dump.getSource()); Path reportFile = outputDir.toPath().resolve(baseName + "-report.txt"); try (var writer = new java.io.PrintWriter(reportFile.toFile())) { writer.println("Thread Dump Analysis Report"); writer.println("============================"); writer.println("Source: " + dump.getSource()); writer.println("Total Threads: " + result.getTotalThreads()); writer.println("Issues Found: " + result.getIssueCount()); writer.println(); if (result.hasIssues()) { writer.println("Detected Issues:"); writer.println("----------------"); for (String issue : result.getIssues()) { writer.println("• " + issue); } writer.println(); } writer.println("Thread State Distribution:"); writer.println("-------------------------"); for (var entry : result.getStateDistribution().entrySet()) { writer.printf("%-15s: %d%n", entry.getKey(), entry.getValue()); } } catch (Exception e) { System.err.println("Failed to generate text report: " + e.getMessage()); } } private void generateJSONReport(ThreadDump dump, AnalysisResult result) { // Implementation for JSON report String baseName = getBaseName(dump.getSource()); Path reportFile = outputDir.toPath().resolve(baseName + "-report.json"); try { var objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); objectMapper.writerWithDefaultPrettyPrinter() .writeValue(reportFile.toFile(), result); } catch (Exception e) { System.err.println("Failed to generate JSON report: " + e.getMessage()); } } private void printConsoleSummary(AnalysisResult result) { System.out.println("📊 Analysis Summary:"); System.out.println(" Total threads: " + result.getTotalThreads()); System.out.println(" Daemon threads: " + result.getDaemonThreads()); System.out.println(" Non-daemon threads: " + result.getNonDaemonThreads()); System.out.println(" Issues found: " + result.getIssueCount()); if (result.hasIssues()) { System.out.println(" ⚠️ Top issues:"); result.getIssuesBySeverity().stream() .limit(3) .forEach(issue -> System.out.println(" • " + issue)); } else { System.out.println(" ✅ No critical issues detected"); } } private String getBaseName(String source) { String name = new File(source).getName(); int dotIndex = name.lastIndexOf('.'); return dotIndex > 0 ? name.substring(0, dotIndex) : name; } public static void main(String[] args) { int exitCode = new CommandLine(new CLIRunner()).execute(args); System.exit(exitCode); } }

Usage Examples

// Programmatic usage public class ExampleUsage { public static void main(String[] args) throws Exception { ThreadDumpParser parser = new ThreadDumpParser(); ThreadDump dump = parser.parseFromFile(Paths.get("thread-dump.txt")); ThreadDumpAnalyzer analyzer = new ThreadDumpAnalyzer(dump); AnalysisResult result = analyzer.analyze(); DeadlockDetector detector = new DeadlockDetector(); List<Deadlock> deadlocks = detector.detectDeadlocks(dump); HTMLReportGenerator reporter = new HTMLReportGenerator(); reporter.generateReport(result, Paths.get("reports")); System.out.println("Analysis complete. Found " + deadlocks.size() + " deadlocks."); } } // CLI Usage // java -jar threadanalyzer.jar thread-dump.txt -o ./reports --format HTML

Features

Core Analysis

  • Thread state distribution analysis
  • Deadlock detection with cycle analysis
  • Lock contention identification
  • Thread pool utilization analysis
  • Stack trace pattern recognition

Advanced Detection

  • Multiple deadlock detection algorithms
  • Resource contention analysis
  • Common anti-pattern detection
  • Performance bottleneck identification

Reporting

  • Comprehensive HTML reports with charts
  • Text-based summaries
  • JSON output for integration
  • Executive summaries with severity ratings

Performance

  • Parallel processing for large dumps
  • Efficient memory usage
  • Fast parsing algorithms
  • Streaming analysis for very large files

This thread dump analyzer provides enterprise-grade analysis capabilities for identifying and diagnosing threading issues in Java applications, with comprehensive reporting and advanced detection algorithms.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper