javaperf 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/mcp.json +12 -0
- package/README.md +185 -0
- package/README_RU.md +185 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +255 -0
- package/dist/tools/analyze_threads.d.ts +13 -0
- package/dist/tools/analyze_threads.js +26 -0
- package/dist/tools/heap_dump.d.ts +10 -0
- package/dist/tools/heap_dump.js +29 -0
- package/dist/tools/heap_histogram.d.ts +16 -0
- package/dist/tools/heap_histogram.js +30 -0
- package/dist/tools/heap_info.d.ts +10 -0
- package/dist/tools/heap_info.js +10 -0
- package/dist/tools/list_procs.d.ts +10 -0
- package/dist/tools/list_procs.js +11 -0
- package/dist/tools/parse_jfr.d.ts +16 -0
- package/dist/tools/parse_jfr.js +64 -0
- package/dist/tools/profile_frequency.d.ts +13 -0
- package/dist/tools/profile_frequency.js +39 -0
- package/dist/tools/profile_memory.d.ts +13 -0
- package/dist/tools/profile_memory.js +67 -0
- package/dist/tools/profile_time.d.ts +13 -0
- package/dist/tools/profile_time.js +38 -0
- package/dist/tools/start_profiling.d.ts +13 -0
- package/dist/tools/start_profiling.js +36 -0
- package/dist/tools/stop_profiling.d.ts +13 -0
- package/dist/tools/stop_profiling.js +30 -0
- package/dist/tools/trace_method.d.ts +22 -0
- package/dist/tools/trace_method.js +58 -0
- package/dist/tools/vm_info.d.ts +10 -0
- package/dist/tools/vm_info.js +19 -0
- package/dist/utils/jdk.d.ts +17 -0
- package/dist/utils/jdk.js +133 -0
- package/dist/utils/jfr-json.d.ts +28 -0
- package/dist/utils/jfr-json.js +46 -0
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.js +13 -0
- package/eslint.config.js +23 -0
- package/package.json +29 -0
- package/src/index.ts +322 -0
- package/src/tools/analyze_threads.ts +30 -0
- package/src/tools/heap_dump.ts +42 -0
- package/src/tools/heap_histogram.ts +43 -0
- package/src/tools/heap_info.ts +14 -0
- package/src/tools/list_procs.ts +15 -0
- package/src/tools/parse_jfr.ts +75 -0
- package/src/tools/profile_frequency.ts +48 -0
- package/src/tools/profile_memory.ts +82 -0
- package/src/tools/profile_time.ts +47 -0
- package/src/tools/start_profiling.ts +54 -0
- package/src/tools/stop_profiling.ts +42 -0
- package/src/tools/trace_method.ts +72 -0
- package/src/tools/vm_info.ts +26 -0
- package/src/utils/jdk.ts +149 -0
- package/src/utils/jfr-json.ts +55 -0
- package/src/utils/paths.ts +13 -0
- package/tsconfig.json +15 -0
package/.cursor/mcp.json
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# javaperf
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/javaperf)
|
|
4
|
+
|
|
5
|
+
> MCP (Model Context Protocol) server for profiling Java applications via JDK utilities (jcmd, jfr, jps)
|
|
6
|
+
|
|
7
|
+
Enables AI assistants to diagnose performance, analyze threads, and inspect JFR recordings without manual CLI usage.
|
|
8
|
+
|
|
9
|
+
📦 **Install**: `npm install -g javaperf` or use via npx
|
|
10
|
+
🌐 **npm**: https://www.npmjs.com/package/javaperf
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- **Node.js** v18+
|
|
15
|
+
- **JDK** 8u262+ or 11+ with JFR support
|
|
16
|
+
|
|
17
|
+
JDK tools (`jps`, `jcmd`, `jfr`) are auto-detected via `JAVA_HOME` or `which java`. If not found, set `JAVA_HOME` to your JDK root.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### For Users (using npm package)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# No installation needed - use directly in Cursor/Claude Desktop
|
|
25
|
+
# Just configure it as described in Integration section below
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### For Developers
|
|
29
|
+
|
|
30
|
+
1. Clone the repository:
|
|
31
|
+
```bash
|
|
32
|
+
git clone <repo-url>
|
|
33
|
+
cd mcp-jperf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Install dependencies:
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. Build the project:
|
|
42
|
+
```bash
|
|
43
|
+
npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Development Mode
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Production Mode
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm start
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### MCP Inspector
|
|
61
|
+
|
|
62
|
+
Debug and test with MCP Inspector:
|
|
63
|
+
```bash
|
|
64
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Integration
|
|
68
|
+
|
|
69
|
+
### Cursor IDE
|
|
70
|
+
|
|
71
|
+
1. Open Cursor Settings → Features → Model Context Protocol
|
|
72
|
+
2. Click "Edit Config" button
|
|
73
|
+
3. Add one of the configurations below
|
|
74
|
+
|
|
75
|
+
#### Option 1: Via npm (Recommended)
|
|
76
|
+
|
|
77
|
+
Installs from npm registry automatically:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"javaperf": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "javaperf"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Option 2: Via npm link (Development)
|
|
91
|
+
|
|
92
|
+
For local development with live changes:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"javaperf": {
|
|
98
|
+
"command": "javaperf"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Requires: `cd /path/to/mcp-jperf && npm link -g`
|
|
105
|
+
|
|
106
|
+
#### Option 3: Direct path
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"javaperf": {
|
|
112
|
+
"command": "node",
|
|
113
|
+
"args": ["dist/index.js"],
|
|
114
|
+
"cwd": "${workspaceFolder}",
|
|
115
|
+
"env": {
|
|
116
|
+
"JAVA_HOME": "/path/to/your/jdk"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If `list_java_processes` fails with "jps not found", the MCP server may not inherit your shell's `JAVA_HOME`. Add the `env` block above with your JDK root path (e.g. `/usr/lib/jvm/java-17` or `~/.sdkman/candidates/java/current`).
|
|
124
|
+
|
|
125
|
+
### Claude Desktop
|
|
126
|
+
|
|
127
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"mcpServers": {
|
|
132
|
+
"javaperf": {
|
|
133
|
+
"command": "npx",
|
|
134
|
+
"args": ["-y", "javaperf"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Continue.dev
|
|
141
|
+
|
|
142
|
+
Edit `.continue/config.json`:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": {
|
|
147
|
+
"javaperf": {
|
|
148
|
+
"command": "npx",
|
|
149
|
+
"args": ["-y", "javaperf"]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Tools
|
|
156
|
+
|
|
157
|
+
| Tool | Description |
|
|
158
|
+
|------|-------------|
|
|
159
|
+
| `list_java_processes` | List running Java processes (pid, mainClass, args). Use `topN` (default 10) to limit. |
|
|
160
|
+
| `start_profiling` | Start JFR recording with `settings=profile`. Pass `pid`, `duration` (seconds), optional `recordingName`. |
|
|
161
|
+
| `stop_profiling` | Stop recording and save to file. Requires `pid` and `recordingId` from start_profiling. |
|
|
162
|
+
| `analyze_threads` | Thread dump (jstack). Pass `pid`, optional `topN` (default 10) to limit threads. |
|
|
163
|
+
| `heap_histogram` | Class histogram (GC.class_histogram). Top classes by instances/bytes. Pass `pid`, optional `topN` (20), `all` (include unreachable). |
|
|
164
|
+
| `heap_dump` | Create .hprof heap dump for MAT/VisualVM. Pass `pid`. Saved to recordings/heap_dump.hprof. |
|
|
165
|
+
| `heap_info` | Brief heap summary. Pass `pid`. |
|
|
166
|
+
| `vm_info` | JVM info: uptime, version, flags. Pass `pid`. |
|
|
167
|
+
| `trace_method` | Build call tree for a method from a .jfr file. Pass `filepath`, `className`, `methodName`, optional `topN`. |
|
|
168
|
+
| `parse_jfr_summary` | Parse .jfr into summary: top methods, GC stats, anomalies. Pass `filepath`, optional `events`, `topN`. |
|
|
169
|
+
| `profile_memory` | Memory profile: top allocators, GC, potential leaks. Pass `filepath`, optional `topN`. |
|
|
170
|
+
| `profile_time` | CPU bottleneck profile (bottom-up). Pass `filepath`, optional `topN`. |
|
|
171
|
+
| `profile_frequency` | Call frequency profile (leaf frames). Pass `filepath`, optional `topN`. |
|
|
172
|
+
|
|
173
|
+
## Example Workflow
|
|
174
|
+
|
|
175
|
+
1. **List processes** → `list_java_processes`
|
|
176
|
+
2. **Start recording** → `start_profiling` with `pid` and `duration` (e.g. 60)
|
|
177
|
+
3. Wait for `duration` seconds (or let it run)
|
|
178
|
+
4. **Stop and save** → `stop_profiling` with `pid` and `recordingId`
|
|
179
|
+
5. **Analyze** → Use `parse_jfr_summary`, `profile_memory`, `profile_time`, `profile_frequency`, or `trace_method` with the saved .jfr path
|
|
180
|
+
|
|
181
|
+
## Limitations
|
|
182
|
+
|
|
183
|
+
- **Sampling**: JFR samples ~10ms; fast methods may not appear in ExecutionSample
|
|
184
|
+
- **Local only**: Runs on the machine where MCP is started
|
|
185
|
+
- **Permissions**: Must run as same user as target JVM for jcmd access
|
package/README_RU.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# javaperf
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/javaperf)
|
|
4
|
+
|
|
5
|
+
> MCP-сервер для профилирования Java-приложений через утилиты JDK (jcmd, jfr, jps)
|
|
6
|
+
|
|
7
|
+
Позволяет AI-ассистентам диагностировать производительность, анализировать потоки и просматривать JFR-записи без ручного использования CLI.
|
|
8
|
+
|
|
9
|
+
📦 **Установка**: `npm install -g javaperf` или через npx
|
|
10
|
+
🌐 **npm**: https://www.npmjs.com/package/javaperf
|
|
11
|
+
|
|
12
|
+
## Требования
|
|
13
|
+
|
|
14
|
+
- **Node.js** v18+
|
|
15
|
+
- **JDK** 8u262+ или 11+ с поддержкой JFR
|
|
16
|
+
|
|
17
|
+
Утилиты JDK (`jps`, `jcmd`, `jfr`) находятся автоматически через `JAVA_HOME` или `which java`. Если не найдены — задайте `JAVA_HOME` на корень JDK.
|
|
18
|
+
|
|
19
|
+
## Быстрый старт
|
|
20
|
+
|
|
21
|
+
### Для пользователей (через npm)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Установка не требуется — можно использовать прямо в Cursor/Claude Desktop
|
|
25
|
+
# Настройте по инструкции в разделе Интеграция ниже
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Для разработчиков
|
|
29
|
+
|
|
30
|
+
1. Клонируйте репозиторий:
|
|
31
|
+
```bash
|
|
32
|
+
git clone <repo-url>
|
|
33
|
+
cd mcp-jperf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Установите зависимости:
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. Соберите проект:
|
|
42
|
+
```bash
|
|
43
|
+
npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Использование
|
|
47
|
+
|
|
48
|
+
### Режим разработки
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Production
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm start
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### MCP Inspector
|
|
61
|
+
|
|
62
|
+
Отладка и тестирование:
|
|
63
|
+
```bash
|
|
64
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Интеграция
|
|
68
|
+
|
|
69
|
+
### Cursor IDE
|
|
70
|
+
|
|
71
|
+
1. Откройте Cursor Settings → Features → Model Context Protocol
|
|
72
|
+
2. Нажмите "Edit Config"
|
|
73
|
+
3. Добавьте одну из конфигураций ниже
|
|
74
|
+
|
|
75
|
+
#### Вариант 1: Через npm (рекомендуется)
|
|
76
|
+
|
|
77
|
+
Устанавливается из npm автоматически:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"javaperf": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "javaperf"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Вариант 2: Через npm link (для разработки)
|
|
91
|
+
|
|
92
|
+
Для локальной разработки с живыми изменениями:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"javaperf": {
|
|
98
|
+
"command": "javaperf"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Требуется: `cd /путь/к/mcp-jperf && npm link -g`
|
|
105
|
+
|
|
106
|
+
#### Вариант 3: Прямой путь
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"javaperf": {
|
|
112
|
+
"command": "node",
|
|
113
|
+
"args": ["dist/index.js"],
|
|
114
|
+
"cwd": "${workspaceFolder}",
|
|
115
|
+
"env": {
|
|
116
|
+
"JAVA_HOME": "/путь/к/вашему/jdk"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Если `list_java_processes` выдаёт "jps not found", MCP-сервер может не наследовать `JAVA_HOME` из shell. Добавьте блок `env` с путём к корню JDK (например `/usr/lib/jvm/java-17` или `~/.sdkman/candidates/java/current`).
|
|
124
|
+
|
|
125
|
+
### Claude Desktop
|
|
126
|
+
|
|
127
|
+
Редактировать `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) или `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"mcpServers": {
|
|
132
|
+
"javaperf": {
|
|
133
|
+
"command": "npx",
|
|
134
|
+
"args": ["-y", "javaperf"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Continue.dev
|
|
141
|
+
|
|
142
|
+
Редактировать `.continue/config.json`:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": {
|
|
147
|
+
"javaperf": {
|
|
148
|
+
"command": "npx",
|
|
149
|
+
"args": ["-y", "javaperf"]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Инструменты
|
|
156
|
+
|
|
157
|
+
| Инструмент | Описание |
|
|
158
|
+
|------------|----------|
|
|
159
|
+
| `list_java_processes` | Список Java-процессов (pid, mainClass, args). Параметр `topN` (по умолчанию 10) ограничивает вывод. |
|
|
160
|
+
| `start_profiling` | Запуск JFR-записи с `settings=profile`. Параметры: `pid`, `duration` (сек), опционально `recordingName`. |
|
|
161
|
+
| `stop_profiling` | Остановка записи и сохранение в файл. Требует `pid` и `recordingId` из start_profiling. |
|
|
162
|
+
| `analyze_threads` | Дамп потоков (jstack). Параметры: `pid`, опционально `topN` (по умолчанию 10). |
|
|
163
|
+
| `heap_histogram` | Гистограмма классов (GC.class_histogram). Топ классов по количеству объектов и памяти. Параметры: `pid`, опционально `topN` (20), `all`. |
|
|
164
|
+
| `heap_dump` | Создание .hprof дампа кучи для MAT/VisualVM. Параметр: `pid`. Сохраняется в recordings/heap_dump.hprof. |
|
|
165
|
+
| `heap_info` | Краткая сводка по куче. Параметр: `pid`. |
|
|
166
|
+
| `vm_info` | Информация о JVM: uptime, version, flags. Параметр: `pid`. |
|
|
167
|
+
| `trace_method` | Построение дерева вызовов метода из .jfr. Параметры: `filepath`, `className`, `methodName`, опционально `topN`. |
|
|
168
|
+
| `parse_jfr_summary` | Разбор .jfr в сводку: топ методов, GC, аномалии. Параметры: `filepath`, опционально `events`, `topN`. |
|
|
169
|
+
| `profile_memory` | Профиль по памяти: топ аллокаторов, GC, утечки. Параметры: `filepath`, опционально `topN`. |
|
|
170
|
+
| `profile_time` | Профиль по времени (узкие места CPU). Параметры: `filepath`, опционально `topN`. |
|
|
171
|
+
| `profile_frequency` | Профиль по частоте вызовов. Параметры: `filepath`, опционально `topN`. |
|
|
172
|
+
|
|
173
|
+
## Пример работы
|
|
174
|
+
|
|
175
|
+
1. **Список процессов** → `list_java_processes`
|
|
176
|
+
2. **Старт записи** → `start_profiling` с `pid` и `duration` (например 60)
|
|
177
|
+
3. Подождать `duration` секунд
|
|
178
|
+
4. **Остановка и сохранение** → `stop_profiling` с `pid` и `recordingId`
|
|
179
|
+
5. **Анализ** → Использовать `parse_jfr_summary`, `profile_memory`, `profile_time`, `profile_frequency` или `trace_method` с путём к сохранённому .jfr
|
|
180
|
+
|
|
181
|
+
## Ограничения
|
|
182
|
+
|
|
183
|
+
- **Семплинг**: JFR делает снимки ~10 мс; быстрые методы могут не попасть в ExecutionSample
|
|
184
|
+
- **Локальность**: Работает только на машине, где запущен MCP
|
|
185
|
+
- **Права**: Нужен доступ к целевой JVM (пользователь MCP = пользователь JVM)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { listJavaProcesses } from "./tools/list_procs.js";
|
|
6
|
+
import { startProfiling } from "./tools/start_profiling.js";
|
|
7
|
+
import { stopProfiling } from "./tools/stop_profiling.js";
|
|
8
|
+
import { analyzeThreads } from "./tools/analyze_threads.js";
|
|
9
|
+
import { traceMethod } from "./tools/trace_method.js";
|
|
10
|
+
import { parseJfrSummary } from "./tools/parse_jfr.js";
|
|
11
|
+
import { profileMemory } from "./tools/profile_memory.js";
|
|
12
|
+
import { profileTime } from "./tools/profile_time.js";
|
|
13
|
+
import { profileFrequency } from "./tools/profile_frequency.js";
|
|
14
|
+
import { heapHistogram } from "./tools/heap_histogram.js";
|
|
15
|
+
import { heapDump } from "./tools/heap_dump.js";
|
|
16
|
+
import { heapInfo } from "./tools/heap_info.js";
|
|
17
|
+
import { vmInfo } from "./tools/vm_info.js";
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "javaperf",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
});
|
|
22
|
+
server.registerTool("list_java_processes", {
|
|
23
|
+
description: "Lists all running Java processes on the machine. Returns an array of objects with pid, mainClass, and args. Use this tool first to discover the target process PID before calling start_profiling or analyze_threads. Data is obtained via jps -l -m.",
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
topN: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(100)
|
|
30
|
+
.optional()
|
|
31
|
+
.default(10)
|
|
32
|
+
.describe("Maximum number of processes to return in the list. Default: 10. Use higher values if many Java processes are running."),
|
|
33
|
+
}),
|
|
34
|
+
}, async ({ topN }) => ({
|
|
35
|
+
content: [{ type: "text", text: await listJavaProcesses({ topN }) }],
|
|
36
|
+
}));
|
|
37
|
+
server.registerTool("start_profiling", {
|
|
38
|
+
description: "Starts a Java Flight Recorder (JFR) recording on the specified Java process. Uses settings=profile for a full dump. Before starting, rotates files: deletes old_profile.jfr, renames new_profile.jfr → old_profile.jfr. This keeps only 2 files for before/after comparison. Call stop_profiling after duration to save to recordings/new_profile.jfr.",
|
|
39
|
+
inputSchema: z.object({
|
|
40
|
+
pid: z
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.positive()
|
|
44
|
+
.describe("Process ID of the Java application to profile. Get this from list_java_processes."),
|
|
45
|
+
duration: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.positive()
|
|
49
|
+
.describe("Recording duration in seconds. Typical values: 10–60 for quick checks, 300+ for load testing."),
|
|
50
|
+
}),
|
|
51
|
+
}, async (args) => ({
|
|
52
|
+
content: [{ type: "text", text: await startProfiling(args) }],
|
|
53
|
+
}));
|
|
54
|
+
server.registerTool("stop_profiling", {
|
|
55
|
+
description: "Stops an active JFR recording and saves it to recordings/new_profile.jfr. Use recordings/new_profile.jfr for current data, recordings/old_profile.jfr for previous (before/after comparison).",
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
pid: z
|
|
58
|
+
.number()
|
|
59
|
+
.int()
|
|
60
|
+
.positive()
|
|
61
|
+
.describe("Process ID of the Java process that has the active recording. Must match the pid used in start_profiling."),
|
|
62
|
+
recordingId: z
|
|
63
|
+
.string()
|
|
64
|
+
.describe("ID of the recording to stop. This is the recordingId returned by start_profiling (e.g. '1' or '2')."),
|
|
65
|
+
}),
|
|
66
|
+
}, async (args) => ({
|
|
67
|
+
content: [{ type: "text", text: await stopProfiling(args) }],
|
|
68
|
+
}));
|
|
69
|
+
server.registerTool("analyze_threads", {
|
|
70
|
+
description: "Produces a thread dump of the specified Java process (equivalent to jstack -l). Shows each thread's name, state, and full stack trace with lock information. Use for diagnosing deadlocks, blocked threads, or high thread counts.",
|
|
71
|
+
inputSchema: z.object({
|
|
72
|
+
pid: z
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.positive()
|
|
76
|
+
.describe("Process ID of the Java application. Get this from list_java_processes."),
|
|
77
|
+
topN: z
|
|
78
|
+
.number()
|
|
79
|
+
.int()
|
|
80
|
+
.min(1)
|
|
81
|
+
.max(500)
|
|
82
|
+
.optional()
|
|
83
|
+
.default(10)
|
|
84
|
+
.describe("Maximum number of threads to include in the output. Default: 10. Increase for applications with many threads."),
|
|
85
|
+
}),
|
|
86
|
+
}, async (args) => ({
|
|
87
|
+
content: [{ type: "text", text: await analyzeThreads(args) }],
|
|
88
|
+
}));
|
|
89
|
+
server.registerTool("heap_histogram", {
|
|
90
|
+
description: "Class histogram of live objects in the heap (jcmd GC.class_histogram). Returns top classes by memory usage — useful for memory leak investigation. Classes with unusually high instance count or bytes may indicate a leak.",
|
|
91
|
+
inputSchema: z.object({
|
|
92
|
+
pid: z
|
|
93
|
+
.number()
|
|
94
|
+
.int()
|
|
95
|
+
.positive()
|
|
96
|
+
.describe("Process ID of the Java application. Get this from list_java_processes."),
|
|
97
|
+
topN: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.min(1)
|
|
101
|
+
.max(200)
|
|
102
|
+
.optional()
|
|
103
|
+
.default(20)
|
|
104
|
+
.describe("Maximum number of top classes to return. Default: 20."),
|
|
105
|
+
all: z
|
|
106
|
+
.boolean()
|
|
107
|
+
.optional()
|
|
108
|
+
.default(false)
|
|
109
|
+
.describe("Include unreachable objects (full GC). Use with caution — can cause pause."),
|
|
110
|
+
}),
|
|
111
|
+
}, async (args) => ({
|
|
112
|
+
content: [{ type: "text", text: await heapHistogram(args) }],
|
|
113
|
+
}));
|
|
114
|
+
server.registerTool("heap_dump", {
|
|
115
|
+
description: "Creates a heap dump (.hprof file) for offline analysis in Eclipse MAT, VisualVM, or JProfiler. Saved to recordings/heap_dump.hprof (overwritten each call). Warning: file can be large (hundreds of MB to GB).",
|
|
116
|
+
inputSchema: z.object({
|
|
117
|
+
pid: z
|
|
118
|
+
.number()
|
|
119
|
+
.int()
|
|
120
|
+
.positive()
|
|
121
|
+
.describe("Process ID of the Java application. Get this from list_java_processes."),
|
|
122
|
+
}),
|
|
123
|
+
}, async (args) => ({
|
|
124
|
+
content: [{ type: "text", text: await heapDump(args) }],
|
|
125
|
+
}));
|
|
126
|
+
server.registerTool("heap_info", {
|
|
127
|
+
description: "Brief heap usage summary: capacities, used, committed regions. Quick snapshot without full dump.",
|
|
128
|
+
inputSchema: z.object({
|
|
129
|
+
pid: z
|
|
130
|
+
.number()
|
|
131
|
+
.int()
|
|
132
|
+
.positive()
|
|
133
|
+
.describe("Process ID of the Java application. Get this from list_java_processes."),
|
|
134
|
+
}),
|
|
135
|
+
}, async (args) => ({
|
|
136
|
+
content: [{ type: "text", text: await heapInfo(args) }],
|
|
137
|
+
}));
|
|
138
|
+
server.registerTool("vm_info", {
|
|
139
|
+
description: "JVM information: uptime, version, and flags. Useful for environment verification.",
|
|
140
|
+
inputSchema: z.object({
|
|
141
|
+
pid: z
|
|
142
|
+
.number()
|
|
143
|
+
.int()
|
|
144
|
+
.positive()
|
|
145
|
+
.describe("Process ID of the Java application. Get this from list_java_processes."),
|
|
146
|
+
}),
|
|
147
|
+
}, async (args) => ({
|
|
148
|
+
content: [{ type: "text", text: await vmInfo(args) }],
|
|
149
|
+
}));
|
|
150
|
+
server.registerTool("trace_method", {
|
|
151
|
+
description: "Builds a call tree for a specific method from a .jfr file. Filters ExecutionSample events to find stack traces containing the given class and method, then aggregates call paths. Use when you want to see who calls a particular method and from where. Limitation: JFR sampling (~10 ms) may miss very fast methods.",
|
|
152
|
+
inputSchema: z.object({
|
|
153
|
+
filepath: z
|
|
154
|
+
.string()
|
|
155
|
+
.describe("Path to .jfr file. Shortcuts: 'new_profile' (current) or 'old_profile' (previous). Or full path e.g. recordings/new_profile.jfr."),
|
|
156
|
+
className: z
|
|
157
|
+
.string()
|
|
158
|
+
.describe("Fully qualified class name (e.g. com.example.MyService) or a substring to match. Used to filter stack frames."),
|
|
159
|
+
methodName: z
|
|
160
|
+
.string()
|
|
161
|
+
.describe("Method name to search for (e.g. processRequest). Matches the method in the stack trace."),
|
|
162
|
+
events: z
|
|
163
|
+
.array(z.string())
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("Optional list of JFR event types to parse. Default: jdk.ExecutionSample. Advanced users can specify other event types."),
|
|
166
|
+
topN: z
|
|
167
|
+
.number()
|
|
168
|
+
.int()
|
|
169
|
+
.min(1)
|
|
170
|
+
.max(100)
|
|
171
|
+
.optional()
|
|
172
|
+
.default(10)
|
|
173
|
+
.describe("Maximum number of call paths (branches) to return in the call tree. Default: 10."),
|
|
174
|
+
}),
|
|
175
|
+
}, async (args) => ({
|
|
176
|
+
content: [{ type: "text", text: await traceMethod(args) }],
|
|
177
|
+
}));
|
|
178
|
+
server.registerTool("parse_jfr_summary", {
|
|
179
|
+
description: "Parses a .jfr file and returns a structured summary: top methods by CPU samples, GC statistics, thread allocation stats, and anomaly hints (e.g. high GC count). Use for a quick high-level overview of the recording before diving into specific profiles.",
|
|
180
|
+
inputSchema: z.object({
|
|
181
|
+
filepath: z
|
|
182
|
+
.string()
|
|
183
|
+
.describe("Path to .jfr file. Shortcuts: 'new_profile' (current) or 'old_profile' (previous). Or full path e.g. recordings/new_profile.jfr."),
|
|
184
|
+
events: z
|
|
185
|
+
.array(z.string())
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Optional list of JFR event types to include. Default: jdk.ExecutionSample, jdk.GarbageCollection, jdk.JavaThreadStatistics, jdk.ThreadAllocationStatistics."),
|
|
188
|
+
topN: z
|
|
189
|
+
.number()
|
|
190
|
+
.int()
|
|
191
|
+
.min(1)
|
|
192
|
+
.max(100)
|
|
193
|
+
.optional()
|
|
194
|
+
.default(10)
|
|
195
|
+
.describe("Maximum number of top methods to include in the summary. Default: 10."),
|
|
196
|
+
}),
|
|
197
|
+
}, async (args) => ({
|
|
198
|
+
content: [{ type: "text", text: await parseJfrSummary(args) }],
|
|
199
|
+
}));
|
|
200
|
+
server.registerTool("profile_memory", {
|
|
201
|
+
description: "Memory-focused profile from a .jfr file. Returns top memory allocators (class+method), GC statistics, and potential leak candidates from OldObjectSample events. Use when the goal is to find who allocates the most memory or identify memory leaks. Requires a recording made with settings=profile (which start_profiling uses by default).",
|
|
202
|
+
inputSchema: z.object({
|
|
203
|
+
filepath: z
|
|
204
|
+
.string()
|
|
205
|
+
.describe("Path to .jfr file. Shortcuts: 'new_profile' (current) or 'old_profile' (previous). Or full path e.g. recordings/new_profile.jfr."),
|
|
206
|
+
topN: z
|
|
207
|
+
.number()
|
|
208
|
+
.int()
|
|
209
|
+
.min(1)
|
|
210
|
+
.max(100)
|
|
211
|
+
.optional()
|
|
212
|
+
.default(10)
|
|
213
|
+
.describe("Maximum number of top allocators to return. Default: 10."),
|
|
214
|
+
}),
|
|
215
|
+
}, async (args) => ({
|
|
216
|
+
content: [{ type: "text", text: await profileMemory(args) }],
|
|
217
|
+
}));
|
|
218
|
+
server.registerTool("profile_time", {
|
|
219
|
+
description: "CPU time (bottleneck) profile from a .jfr file. Uses bottom-up aggregation: each method is counted in every sample where it appears in the stack, including time spent in callees. Returns methods consuming the most CPU time. Use when the goal is to find performance bottlenecks and slow code paths.",
|
|
220
|
+
inputSchema: z.object({
|
|
221
|
+
filepath: z
|
|
222
|
+
.string()
|
|
223
|
+
.describe("Path to .jfr file. Shortcuts: 'new_profile' (current) or 'old_profile' (previous). Or full path e.g. recordings/new_profile.jfr."),
|
|
224
|
+
topN: z
|
|
225
|
+
.number()
|
|
226
|
+
.int()
|
|
227
|
+
.min(1)
|
|
228
|
+
.max(100)
|
|
229
|
+
.optional()
|
|
230
|
+
.default(10)
|
|
231
|
+
.describe("Maximum number of top methods by CPU time to return. Default: 10."),
|
|
232
|
+
}),
|
|
233
|
+
}, async (args) => ({
|
|
234
|
+
content: [{ type: "text", text: await profileTime(args) }],
|
|
235
|
+
}));
|
|
236
|
+
server.registerTool("profile_frequency", {
|
|
237
|
+
description: "Call frequency profile from a .jfr file. Counts methods that appear at the leaf (top) of the stack in ExecutionSample events — i.e. methods that were actively executing when sampled. Returns the most frequently sampled methods (exclusive, not cumulative). Use when looking for hot spots or the most often executed code paths.",
|
|
238
|
+
inputSchema: z.object({
|
|
239
|
+
filepath: z
|
|
240
|
+
.string()
|
|
241
|
+
.describe("Path to .jfr file. Shortcuts: 'new_profile' (current) or 'old_profile' (previous). Or full path e.g. recordings/new_profile.jfr."),
|
|
242
|
+
topN: z
|
|
243
|
+
.number()
|
|
244
|
+
.int()
|
|
245
|
+
.min(1)
|
|
246
|
+
.max(100)
|
|
247
|
+
.optional()
|
|
248
|
+
.default(10)
|
|
249
|
+
.describe("Maximum number of top methods by call frequency to return. Default: 10."),
|
|
250
|
+
}),
|
|
251
|
+
}, async (args) => ({
|
|
252
|
+
content: [{ type: "text", text: await profileFrequency(args) }],
|
|
253
|
+
}));
|
|
254
|
+
const transport = new StdioServerTransport();
|
|
255
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const analyzeThreadsSchema: z.ZodObject<{
|
|
3
|
+
pid: z.ZodNumber;
|
|
4
|
+
topN: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
topN: number;
|
|
7
|
+
pid: number;
|
|
8
|
+
}, {
|
|
9
|
+
pid: number;
|
|
10
|
+
topN?: number | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export type AnalyzeThreadsInput = z.infer<typeof analyzeThreadsSchema>;
|
|
13
|
+
export declare function analyzeThreads(input: AnalyzeThreadsInput): Promise<string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runJcmd } from "../utils/jdk.js";
|
|
3
|
+
export const analyzeThreadsSchema = z.object({
|
|
4
|
+
pid: z.number().int().positive(),
|
|
5
|
+
topN: z.number().int().min(1).max(500).optional().default(10),
|
|
6
|
+
});
|
|
7
|
+
export async function analyzeThreads(input) {
|
|
8
|
+
const { pid, topN } = input;
|
|
9
|
+
const output = runJcmd(pid, "Thread.print -l");
|
|
10
|
+
const threadSections = [];
|
|
11
|
+
let current = "";
|
|
12
|
+
for (const line of output.split("\n")) {
|
|
13
|
+
if (line.match(/^"\S/)) {
|
|
14
|
+
if (current.trim())
|
|
15
|
+
threadSections.push(current.trim());
|
|
16
|
+
current = line + "\n";
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
current += line + "\n";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (current.trim())
|
|
23
|
+
threadSections.push(current.trim());
|
|
24
|
+
const limited = threadSections.slice(0, topN);
|
|
25
|
+
return limited.join("\n\n");
|
|
26
|
+
}
|