Skip to content

Commit fe1e429

Browse files
committed
added analyze virtual threads command to mcp server
1 parent 44a4e6b commit fe1e429

File tree

4 files changed

+58
-5
lines changed

4 files changed

+58
-5
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ The MCP server exposes the following tools:
108108
| `get_summary` | None | Returns a summary of all parsed thread dumps (index, name, timestamp, thread/deadlock counts). |
109109
| `check_deadlocks` | None | Checks for and returns information about any deadlocks detected in the parsed thread dumps. |
110110
| `find_long_running` | None | Identifies threads that remain in the same state/stack trace across consecutive dumps. |
111+
| `analyze_virtual_threads` | None | Detects virtual threads where the carrier thread is stuck in application code. |
111112
| `clear` | None | Resets the server state and clears the internal thread store for a new log file. |
112113

113114
#### Troubleshooting
@@ -139,7 +140,7 @@ When you encounter a log file that appears to contain Java thread dumps:
139140
1. DO NOT try to read or "cat" the entire file if it's large.
140141
2. Use the `tda-analyzer` MCP toolset.
141142
3. First, call `parse_log(path="...")` to initialize the analysis.
142-
4. Use `get_summary()`, `check_deadlocks()`, and `find_long_running()` to perform the analysis.
143+
4. Use `get_summary()`, `check_deadlocks()`, `find_long_running()`, and `analyze_virtual_threads()` to perform the analysis.
143144
5. Provide your insights based on the structured data returned by these tools rather than the raw log text.
144145
```
145146
This configuration makes the analysis much faster and significantly reduces token usage.

tda/src/main/java/de/grimmfrost/tda/mcp/HeadlessAnalysisProvider.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,10 @@ public List<String> findLongRunningThreads() {
8383
paths[i] = new TreePath(topNodes.get(i).getPath());
8484
}
8585

86-
// AbstractDumpParser.findLongRunningThreads is protected or package private?
87-
// No, it is public in AbstractDumpParser but SunJDKParser might override it.
88-
// Actually it's public in AbstractDumpParser.
86+
// AbstractDumpParser.findLongRunningThreads is public in AbstractDumpParser.
8987

9088
// We need a parser instance to call findLongRunningThreads
91-
// Let's use the first one if possible or just any SunJDKParser
89+
// Let's use any SunJDKParser
9290
SunJDKParser dummyParser = new SunJDKParser(null, threadStore, 0, false, 0, new DateMatcher());
9391

9492
DefaultMutableTreeNode longRunningRoot = new DefaultMutableTreeNode("Long Running Threads");
@@ -106,6 +104,33 @@ public List<String> findLongRunningThreads() {
106104
return results;
107105
}
108106

107+
public List<String> analyzeVirtualThreads() {
108+
List<String> results = new ArrayList<>();
109+
int totalStuck = 0;
110+
for (DefaultMutableTreeNode node : topNodes) {
111+
ThreadDumpInfo tdi = (ThreadDumpInfo) node.getUserObject();
112+
int stuckCarrierThreads = 0;
113+
Category threadsCat = tdi.getThreads();
114+
if (threadsCat != null) {
115+
int threadCount = threadsCat.getNodeCount();
116+
for (int i = 0; i < threadCount; i++) {
117+
DefaultMutableTreeNode threadNode = (DefaultMutableTreeNode) threadsCat.getNodeAt(i);
118+
ThreadInfo ti = (ThreadInfo) threadNode.getUserObject();
119+
if (ti.getContent().contains("carrier thread seems to be stuck in application code")) {
120+
stuckCarrierThreads++;
121+
results.add("Stuck carrier thread in dump '" + tdi.getName() + "': " + ti.getName());
122+
}
123+
}
124+
}
125+
totalStuck += stuckCarrierThreads;
126+
}
127+
128+
if (totalStuck == 0) {
129+
results.add("No virtual threads with stuck carrier threads detected in " + topNodes.size() + " dumps.");
130+
}
131+
return results;
132+
}
133+
109134
public void clear() {
110135
threadStore.clear();
111136
topNodes.clear();

tda/src/main/java/de/grimmfrost/tda/mcp/MCPServer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ private static void handleListTools(JsonObject request) {
9191

9292
tools.add(createTool("find_long_running", "Identifies threads that appear in multiple consecutive thread dumps.", new JsonObject()));
9393

94+
tools.add(createTool("analyze_virtual_threads", "Detects virtual threads where the carrier thread is stuck in application code.", new JsonObject()));
95+
9496
result.add("tools", gson.toJsonTree(tools));
9597
sendResponse(request.get("id").getAsInt(), result);
9698
}
@@ -146,6 +148,8 @@ private static Object handleGenericRequest(String method, JsonObject params) thr
146148
return provider.checkForDeadlocks();
147149
case "find_long_running":
148150
return provider.findLongRunningThreads();
151+
case "analyze_virtual_threads":
152+
return provider.analyzeVirtualThreads();
149153
case "clear":
150154
provider.clear();
151155
return "Cleared thread store.";

tda/src/test/java/de/grimmfrost/tda/mcp/HeadlessAnalysisProviderTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,27 @@ public void testSummary() throws Exception {
4646
assertEquals(2, summary.size());
4747
assertTrue(summary.get(0).get("name").toString().contains("Dump"));
4848
}
49+
50+
@Test
51+
public void testVirtualThreadAnalysis() throws Exception {
52+
HeadlessAnalysisProvider provider = new HeadlessAnalysisProvider();
53+
String logPath = "src/test/resources/carrier_stuck.log";
54+
File logFile = new File(logPath);
55+
if (!logFile.exists()) {
56+
System.out.println("[DEBUG_LOG] Skip test, carrier_stuck.log not found");
57+
return;
58+
}
59+
60+
provider.parseLogFile(logPath);
61+
List<String> results = provider.analyzeVirtualThreads();
62+
63+
boolean found = false;
64+
for (String msg : results) {
65+
if (msg.contains("Stuck carrier thread")) {
66+
found = true;
67+
break;
68+
}
69+
}
70+
assertTrue(found, "Should find stuck carrier thread in carrier_stuck.log");
71+
}
4972
}

0 commit comments

Comments
 (0)