Skip to content

Commit f8c0267

Browse files
authored
Merge pull request #28 from irockel/feat/Dump_Dashboard
feat: improved thread dump dashboard
2 parents 82b6a6f + 3b2365f commit f8c0267

File tree

2 files changed

+318
-58
lines changed

2 files changed

+318
-58
lines changed

tda/src/main/java/de/grimmfrost/tda/ThreadDumpInfo.java

Lines changed: 163 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -102,42 +102,144 @@ public String getOverview() {
102102
* creates the overview information for this thread dump.
103103
*/
104104
private void createOverview() {
105-
StringBuffer statData = new StringBuffer("<body bgcolor=\"#ffffff\"><font face=System " +
106-
"><table border=0><tr bgcolor=\"#dddddd\"><td><font face=System " +
107-
">Overall Thread Count</td><td width=\"150\"></td><td><b><font face=System>");
108-
statData.append(getThreads() == null? 0 : getThreads().getNodeCount());
109-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#eeeeee\"><td><font face=System" +
110-
">Overall Monitor Count</td><td></td><td><b><font face=System>");
111-
statData.append(getMonitors() == null? 0 : getMonitors().getNodeCount());
112-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#dddddd\"><td><font face=System " +
113-
">Number of threads waiting for a monitor</td><td></td><td><b><font face=System>");
114-
statData.append(getWaitingThreads() == null? 0 : getWaitingThreads().getNodeCount());
115-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#eeeeee\"><td><font face=System " +
116-
">Number of threads locking a monitor</td><td></td><td><b><font face=System size>");
117-
statData.append(getLockingThreads() == null? 0 : getLockingThreads().getNodeCount());
118-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#dddddd\"><td><font face=System " +
119-
">Number of threads sleeping on a monitor</td><td></td><td><b><font face=System>");
120-
statData.append(getSleepingThreads() == null? 0 : getSleepingThreads().getNodeCount());
121-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#eeeeee\"><td><font face=System " +
122-
">Number of deadlocks</td><td></td><td><b><font face=System>");
123-
statData.append(getDeadlocks() == null? 0 : getDeadlocks().getNodeCount());
124-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#dddddd\"><td><font face=System " +
125-
">Number of Monitors without locking threads</td><td></td><td><b><font face=System>");
126-
statData.append(getMonitorsWithoutLocks() == null? 0 : getMonitorsWithoutLocks().getNodeCount());
127-
statData.append("</b></td></tr>");
105+
int threadsCount = getThreads() == null ? 0 : getThreads().getNodeCount();
106+
int monitorsCount = getMonitors() == null ? 0 : getMonitors().getNodeCount();
107+
int waitingCount = getWaitingThreads() == null ? 0 : getWaitingThreads().getNodeCount();
108+
int lockingCount = getLockingThreads() == null ? 0 : getLockingThreads().getNodeCount();
109+
int sleepingCount = getSleepingThreads() == null ? 0 : getSleepingThreads().getNodeCount();
110+
int deadlocksCount = getDeadlocks() == null ? 0 : getDeadlocks().getNodeCount();
111+
int monitorsNoLockCount = getMonitorsWithoutLocks() == null ? 0 : getMonitorsWithoutLocks().getNodeCount();
112+
113+
StringBuilder statData = new StringBuilder();
114+
statData.append("<html><body style=\"background-color: #ffffff; font-family: sans-serif; margin: 20px; color: #333;\">");
115+
116+
statData.append("<h2 style=\"color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;\">Thread Dump Overview</h2>");
117+
118+
// Thread State Distribution (Visual Chart)
119+
if (threadsCount > 0) {
120+
java.util.Map<String, Integer> stateDistribution = new java.util.HashMap<>();
121+
Category threadsCat = getThreads();
122+
for (int i = 0; i < threadsCount; i++) {
123+
javax.swing.tree.DefaultMutableTreeNode node = (javax.swing.tree.DefaultMutableTreeNode) threadsCat.getNodeAt(i);
124+
ThreadInfo ti = (ThreadInfo) node.getUserObject();
125+
String[] tokens = ti.getTokens();
126+
String state = "UNKNOWN";
127+
if (tokens != null) {
128+
if (tokens.length >= 7) {
129+
// For prio= formats, state information might be harder to extract from tokens alone if not parsed.
130+
// But SunJDKParser puts it in title if missing.
131+
// Actually, tokens[2] is prio, [3] is tid, [4] is nid...
132+
// If it's the 3-token format: [0]=name, [1]=id, [2]=state
133+
state = tokens.length == 3 ? tokens[2] : "OTHER";
134+
} else if (tokens.length == 3) {
135+
state = tokens[2];
136+
}
137+
}
138+
139+
// Try to extract state from title if it's "state=..."
140+
if ("UNKNOWN".equals(state) || "OTHER".equals(state)) {
141+
String name = ti.getName();
142+
if (name.contains("state=")) {
143+
int start = name.indexOf("state=") + 6;
144+
int end = name.indexOf(' ', start);
145+
state = end > start ? name.substring(start, end) : name.substring(start);
146+
} else if (ti.getContent().contains("java.lang.Thread.State: ")) {
147+
String content = ti.getContent();
148+
int start = content.indexOf("java.lang.Thread.State: ") + 24;
149+
int end = content.indexOf('\n', start);
150+
state = content.substring(start, end).trim();
151+
if (state.indexOf(' ') > 0) {
152+
state = state.substring(0, state.indexOf(' '));
153+
}
154+
}
155+
}
156+
157+
state = state.toUpperCase();
158+
stateDistribution.put(state, stateDistribution.getOrDefault(state, 0) + 1);
159+
}
160+
161+
statData.append("<div style=\"margin-bottom: 25px; padding: 15px; background-color: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef;\">");
162+
statData.append("<h4 style=\"margin-top: 0; color: #495057;\">Thread State Distribution (").append(threadsCount).append(" threads)</h4>");
163+
statData.append("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"height: 30px; border: 1px solid #dee2e6; border-radius: 4px; overflow: hidden;\"><tr>");
164+
165+
String[] commonStates = {"RUNNABLE", "WAITING", "TIMED_WAITING", "BLOCKED", "PARKING"};
166+
String[] colors = {"#28a745", "#ffc107", "#fd7e14", "#dc3545", "#6f42c1"};
167+
168+
int accounted = 0;
169+
for (int i = 0; i < commonStates.length; i++) {
170+
int count = stateDistribution.getOrDefault(commonStates[i], 0);
171+
if (count > 0) {
172+
double percent = (count * 100.0) / threadsCount;
173+
statData.append("<td width=\"").append(percent).append("%\" bgcolor=\"").append(colors[i])
174+
.append("\" title=\"").append(commonStates[i]).append(": ").append(count).append("\"></td>");
175+
accounted += count;
176+
}
177+
}
178+
179+
int otherCount = threadsCount - accounted;
180+
if (otherCount > 0) {
181+
double percent = (otherCount * 100.0) / threadsCount;
182+
statData.append("<td width=\"").append(percent).append("%\" bgcolor=\"#6c757d\" title=\"OTHER: ").append(otherCount).append("\"></td>");
183+
}
184+
statData.append("</tr></table>");
185+
186+
// Legend
187+
statData.append("<div style=\"margin-top: 10px; font-size: 11px;\">");
188+
for (int i = 0; i < commonStates.length; i++) {
189+
int count = stateDistribution.getOrDefault(commonStates[i], 0);
190+
if (count > 0) {
191+
statData.append("<span style=\"display: inline-block; width: 10px; height: 10px; background-color: ").append(colors[i]).append("; margin-right: 4px;\"></span>")
192+
.append(commonStates[i]).append(" (").append(count).append(")&nbsp;&nbsp;&nbsp;");
193+
}
194+
}
195+
if (otherCount > 0) {
196+
statData.append("<span style=\"display: inline-block; width: 10px; height: 10px; background-color: #6c757d; margin-right: 4px;\"></span>")
197+
.append("OTHER (").append(otherCount).append(")");
198+
}
199+
statData.append("</div></div>");
200+
}
201+
202+
// Statistics Table
203+
statData.append("<table width=\"100%\" style=\"border-collapse: collapse; margin-bottom: 20px;\">");
128204

129-
// add hints concerning possible hot spots found in this thread dump.
130-
statData.append(getDumpAnalyzer().analyzeDump());
205+
statData.append("<tr>");
206+
statData.append("<td width=\"50%\" style=\"padding: 8px; border-bottom: 1px solid #eee; background-color: #fcfcfc;\"><b>Overall Monitor Count:</b> ").append(monitorsCount).append("</td>");
207+
statData.append("<td width=\"50%\" style=\"padding: 8px; border-bottom: 1px solid #eee; background-color: #fcfcfc;\"><b>Deadlocks:</b> <span style=\"").append(deadlocksCount > 0 ? "color: #dc3545; font-weight: bold;" : "").append("\">").append(deadlocksCount).append("</span></td>");
208+
statData.append("</tr>");
209+
210+
statData.append("<tr>");
211+
statData.append("<td style=\"padding: 8px; border-bottom: 1px solid #eee;\"><b>Threads locking:</b> ").append(lockingCount).append("</td>");
212+
statData.append("<td style=\"padding: 8px; border-bottom: 1px solid #eee;\"><b>Monitors without locking:</b> ").append(monitorsNoLockCount).append("</td>");
213+
statData.append("</tr>");
214+
215+
statData.append("<tr>");
216+
statData.append("<td style=\"padding: 8px; border-bottom: 1px solid #eee; background-color: #fcfcfc;\"><b>Threads waiting:</b> ").append(waitingCount).append("</td>");
217+
statData.append("<td style=\"padding: 8px; border-bottom: 1px solid #eee; background-color: #fcfcfc;\"><b>Threads sleeping:</b> ").append(sleepingCount).append("</td>");
218+
statData.append("</tr>");
219+
220+
statData.append("</table>");
221+
222+
// Hints and Heap Info
223+
String hints = getDumpAnalyzer().analyzeDump();
224+
if (hints != null && !hints.isEmpty()) {
225+
statData.append("<div style=\"margin-top: 20px; padding: 15px; background-color: #fff3cd; border: 1px solid #ffeeba; border-radius: 8px; color: #856404;\">");
226+
statData.append("<h4 style=\"margin-top: 0;\">Analysis Hints</h4>");
227+
statData.append("<table border=\"0\" width=\"100%\" style=\"color: #856404;\">").append(hints).append("</table>");
228+
statData.append("</div>");
229+
}
131230

132-
if(getHeapInfo() != null) {
231+
if (getHeapInfo() != null) {
232+
statData.append("<div style=\"margin-top: 20px; padding: 15px; background-color: #e2e3e5; border: 1px solid #d6d8db; border-radius: 8px;\">");
233+
statData.append("<h4 style=\"margin-top: 0;\">Heap Information</h4>");
133234
statData.append(getHeapInfo());
235+
statData.append("</div>");
134236
}
135237

136-
statData.append("</table>");
238+
statData.append("</body></html>");
137239

138240
setOverview(statData.toString());
139-
140241
}
242+
141243

142244
/**
143245
* generate a monitor info node from the given information.
@@ -147,41 +249,44 @@ private void createOverview() {
147249
* @return a info node for the monitor.
148250
*/
149251
public static String getMonitorInfo(int locks, int waits, int sleeps ) {
150-
StringBuffer statData = new StringBuffer("<body bgcolor=\"ffffff\"><table border=0 bgcolor=\"#dddddd\"><tr><td><font face=System" +
151-
">Threads locking monitor</td><td><b><font face=System>");
152-
statData.append(locks);
153-
statData.append("</b></td></tr>\n\n<tr bgcolor=\"#eeeeee\"><td>");
154-
statData.append("<font face=System>Threads sleeping on monitor</td><td><b><font face=System>");
155-
statData.append(sleeps);
156-
statData.append("</b></td></tr>\n\n<tr><td>");
157-
statData.append("<font face=System>Threads waiting to lock monitor</td><td><b><font face=System>");
158-
statData.append(waits);
159-
statData.append("</b></td></tr>\n\n");
252+
StringBuilder statData = new StringBuilder();
253+
statData.append("<html><body style=\"background-color: #ffffff; font-family: sans-serif; margin: 10px; color: #333;\">");
254+
statData.append("<table width=\"100%\" style=\"border-collapse: collapse; background-color: #f8f9fa; border: 1px solid #dee2e6;\">");
255+
256+
addMonitorStatRow(statData, "Threads locking monitor", locks, "#ffffff");
257+
addMonitorStatRow(statData, "Threads sleeping on monitor", sleeps, "#f2f2f2");
258+
addMonitorStatRow(statData, "Threads waiting to lock monitor", waits, "#ffffff");
259+
260+
statData.append("</table>");
261+
160262
if (locks == 0) {
161-
statData.append("<tr bgcolor=\"#ffffff\"<td></td></tr>");
162-
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086475
163-
statData.append("<tr bgcolor=\"#cccccc\"><td><font face=System> " +
164-
"<p>This monitor doesn't have a thread locking it. This means one of the following is true:</p>" +
165-
"<ul><li>a VM Thread is holding it." +
166-
"<li>This lock is a <tt>java.util.concurrent</tt> lock and the thread holding it is not reported in the stack trace " +
167-
"because the JVM option -XX:+PrintConcurrentLocks is not present." +
168-
"<li>This lock is a custom java.util.concurrent lock either not based off of" +
169-
" <tt>AbstractOwnableSynchronizer</tt> or not setting the exclusive owner when a lock is granted.</ul>");
170-
statData.append("If you see many monitors having no locking thread (and the latter two conditions above do " +
171-
"not apply), this usually means the garbage collector is running.<br>");
172-
statData.append("In this case you should consider analyzing the Garbage Collector output. If the dump has many monitors with no locking thread<br>");
173-
statData.append("a click on the <a href=\"dump://\">dump node</a> will give you additional information.<br></td></tr>");
263+
statData.append("<div style=\"margin-top: 15px; padding: 10px; background-color: #e9ecef; border-left: 5px solid #6c757d; font-size: 12px;\">");
264+
statData.append("<p><b>No locking thread detected.</b> Possible reasons:</p>");
265+
statData.append("<ul><li>A VM Thread is holding it.</li>");
266+
statData.append("<li>It is a <tt>java.util.concurrent</tt> lock and -XX:+PrintConcurrentLocks is missing.</li>");
267+
statData.append("<li>It is a custom lock not based on <tt>AbstractOwnableSynchronizer</tt>.</li></ul>");
268+
statData.append("<p>If many monitors have no locking thread, the garbage collector might be running.</p>");
269+
statData.append("<p>Check the <a href=\"dump://\">dump node</a> for more info.</p>");
270+
statData.append("</div>");
174271
}
272+
175273
if (areALotOfWaiting(waits)) {
176-
statData.append("<tr bgcolor=\"#ffffff\"<td></td></tr>");
177-
statData.append("<tr bgcolor=\"#cccccc\"><td><font face=System " +
178-
"<p>A lot of threads are waiting for this monitor to become available again.</p><br>");
179-
statData.append("This might indicate a congestion. You also should analyze other locks blocked by threads waiting<br>");
180-
statData.append("for this monitor as there might be much more threads waiting for it.<br></td></tr>");
274+
statData.append("<div style=\"margin-top: 15px; padding: 10px; background-color: #f8d7da; border-left: 5px solid #dc3545; color: #721c24; font-size: 12px;\">");
275+
statData.append("<p><b>High congestion!</b> A lot of threads are waiting for this monitor.</p>");
276+
statData.append("<p>Analyze other blocked locks as well, as there might be a chain of waiting threads.</p>");
277+
statData.append("</div>");
181278
}
182-
statData.append("</table>");
279+
280+
statData.append("</body></html>");
281+
282+
return statData.toString();
283+
}
183284

184-
return (statData.toString());
285+
private static void addMonitorStatRow(StringBuilder sb, String label, int value, String bgColor) {
286+
sb.append("<tr style=\"background-color: ").append(bgColor).append(";\">");
287+
sb.append("<td style=\"padding: 8px; border-bottom: 1px solid #dee2e6;\">").append(label).append("</td>");
288+
sb.append("<td style=\"padding: 8px; border-bottom: 1px solid #dee2e6; text-align: right;\"><b>").append(value).append("</b></td>");
289+
sb.append("</tr>");
185290
}
186291

187292
/**

0 commit comments

Comments
 (0)