Skip to content

Commit e29a25f

Browse files
committed
feat: add profiler mcp tool
1 parent cdd032b commit e29a25f

File tree

1 file changed

+279
-0
lines changed
  • core/src/main/java/com/taobao/arthas/core/mcp/tool/function/monitor200

1 file changed

+279
-0
lines changed
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
package com.taobao.arthas.core.mcp.tool.function.monitor200;
2+
3+
import com.taobao.arthas.core.mcp.tool.function.AbstractArthasTool;
4+
import com.taobao.arthas.mcp.server.tool.ToolContext;
5+
import com.taobao.arthas.mcp.server.tool.annotation.Tool;
6+
import com.taobao.arthas.mcp.server.tool.annotation.ToolParam;
7+
8+
/**
9+
* profiler MCP Tool: Async Profiler(async-profiler)能力封装,对应 Arthas 的 profiler 命令。
10+
* <p>
11+
* 参考:{@link com.taobao.arthas.core.command.monitor200.ProfilerCommand}
12+
*/
13+
public class ProfilerTool extends AbstractArthasTool {
14+
15+
private static final String[] SUPPORTED_ACTIONS = new String[] {
16+
"start",
17+
"resume",
18+
"stop",
19+
"dump",
20+
"check",
21+
"status",
22+
"meminfo",
23+
"list",
24+
"version",
25+
"load",
26+
"execute",
27+
"dumpCollapsed",
28+
"dumpFlat",
29+
"dumpTraces",
30+
"getSamples",
31+
"actions"
32+
};
33+
34+
@Tool(
35+
name = "profiler",
36+
description = "Async Profiler 诊断工具: 对应 Arthas 的 profiler 命令,用于采样 CPU/alloc/lock 等事件并输出 flamegraph/jfr 等格式。\n"
37+
+ "常用示例:\n"
38+
+ "- start: 开始采样,如 action=start, event=cpu\n"
39+
+ "- stop: 停止并输出文件,如 action=stop, format=flamegraph, file=/tmp/result.html\n"
40+
+ "- status/list/actions: 查看状态/支持事件/支持动作\n"
41+
+ "- execute: 直接传递 async-profiler agent 兼容参数,如 action=execute, actionArg=\"stop,file=/tmp/result.html\""
42+
)
43+
public String profiler(
44+
@ToolParam(description = "动作(必填),可选值: start/resume/stop/dump/check/status/meminfo/list/version/load/execute/dumpCollapsed/dumpFlat/dumpTraces/getSamples/actions")
45+
String action,
46+
47+
@ToolParam(description = "动作参数(可选)。当 action=execute 时必填,示例: \"stop,file=/tmp/result.html\"", required = false)
48+
String actionArg,
49+
50+
@ToolParam(description = "采样事件 (--event),如 cpu/alloc/lock/wall,默认 cpu", required = false)
51+
String event,
52+
53+
@ToolParam(description = "采样间隔 ns (--interval),默认 10000000(10ms)", required = false)
54+
Long interval,
55+
56+
@ToolParam(description = "最大 Java 栈深 (--jstackdepth),默认 2048", required = false)
57+
Integer jstackdepth,
58+
59+
@ToolParam(description = "输出文件路径 (--file)。如果以 .html/.jfr 结尾可推断 format;也可包含 %t 等占位符(如 /tmp/result-%t.html)", required = false)
60+
String file,
61+
62+
@ToolParam(description = "输出格式 (--format),支持 flat[=N]|traces[=N]|collapsed|flamegraph|tree|jfr(兼容传入 html)", required = false)
63+
String format,
64+
65+
@ToolParam(description = "alloc 事件采样间隔字节数 (--alloc),如 1m/512k/1000 等", required = false)
66+
String alloc,
67+
68+
@ToolParam(description = "仅对存活对象做 alloc 统计 (--live)", required = false)
69+
Boolean live,
70+
71+
@ToolParam(description = "lock 事件阈值 ns (--lock),如 10ms/10000000 等", required = false)
72+
String lock,
73+
74+
@ToolParam(description = "与 profiler 一起启动 JFR (--jfrsync),可为预置 profile 名称、.jfc 路径或 + 事件列表", required = false)
75+
String jfrsync,
76+
77+
@ToolParam(description = "wall clock 采样间隔 ms (--wall),推荐 200", required = false)
78+
Long wall,
79+
80+
@ToolParam(description = "按线程区分采样 (--threads)", required = false)
81+
Boolean threads,
82+
83+
@ToolParam(description = "按调度策略分组线程 (--sched)", required = false)
84+
Boolean sched,
85+
86+
@ToolParam(description = "C 栈采样方式 (--cstack),可选 fp|dwarf|lbr|no", required = false)
87+
String cstack,
88+
89+
@ToolParam(description = "使用简单类名 (-s)", required = false)
90+
Boolean simple,
91+
92+
@ToolParam(description = "打印方法签名 (-g)", required = false)
93+
Boolean sig,
94+
95+
@ToolParam(description = "注解 Java 方法 (-a)", required = false)
96+
Boolean ann,
97+
98+
@ToolParam(description = "前置库名 (-l)", required = false)
99+
Boolean lib,
100+
101+
@ToolParam(description = "仅包含用户态事件 (--all-user)", required = false)
102+
Boolean allUser,
103+
104+
@ToolParam(description = "规范化方法名,移除 lambda 类的数字后缀 (--norm)", required = false)
105+
Boolean norm,
106+
107+
@ToolParam(description = "仅包含匹配的栈帧(可重复多次),等价 --include 'java/*'。传入数组。", required = false)
108+
String[] include,
109+
110+
@ToolParam(description = "排除匹配的栈帧(可重复多次),等价 --exclude '*Unsafe.park*'。传入数组。", required = false)
111+
String[] exclude,
112+
113+
@ToolParam(description = "当指定 native 函数执行时自动开始采样 (--begin)", required = false)
114+
String begin,
115+
116+
@ToolParam(description = "当指定 native 函数执行时自动停止采样 (--end)", required = false)
117+
String end,
118+
119+
@ToolParam(description = "time-to-safepoint 采样别名开关 (--ttsp),等价设置 begin/end 为特定函数", required = false)
120+
Boolean ttsp,
121+
122+
@ToolParam(description = "FlameGraph 标题 (--title)", required = false)
123+
String title,
124+
125+
@ToolParam(description = "FlameGraph 最小帧宽百分比 (--minwidth)", required = false)
126+
String minwidth,
127+
128+
@ToolParam(description = "生成反向 FlameGraph/Call tree (--reverse)", required = false)
129+
Boolean reverse,
130+
131+
@ToolParam(description = "统计总量而非样本数 (--total)", required = false)
132+
Boolean total,
133+
134+
@ToolParam(description = "JFR chunk 大小 (--chunksize),默认 100MB 或其它单位", required = false)
135+
String chunksize,
136+
137+
@ToolParam(description = "JFR chunk 时间 (--chunktime),默认 1h", required = false)
138+
String chunktime,
139+
140+
@ToolParam(description = "循环采样参数 (--loop),用于 continuous profiling,如 300s", required = false)
141+
String loop,
142+
143+
@ToolParam(description = "自动停止时间 (--timeout),绝对或相对时间,如 300s", required = false)
144+
String timeout,
145+
146+
@ToolParam(description = "持续采样秒数 (--duration)。注意:到时自动 stop 在后台执行,stop 的结果不会回传到当前命令输出。", required = false)
147+
Long duration,
148+
149+
@ToolParam(description = "启用的特性集合 (--features)", required = false)
150+
String features,
151+
152+
@ToolParam(description = "采样信号 (--signal)", required = false)
153+
String signal,
154+
155+
@ToolParam(description = "时间戳时钟源 (--clock),可选 monotonic 或 tsc", required = false)
156+
String clock,
157+
158+
ToolContext toolContext
159+
) {
160+
String normalizedAction = normalizeAction(action);
161+
if ("execute".equals(normalizedAction) && (actionArg == null || actionArg.trim().isEmpty())) {
162+
throw new IllegalArgumentException("actionArg is required when action=execute");
163+
}
164+
165+
StringBuilder cmd = buildCommand("profiler");
166+
cmd.append(" ").append(normalizedAction);
167+
168+
if (actionArg != null && !actionArg.trim().isEmpty()) {
169+
addParameter(cmd, actionArg);
170+
}
171+
172+
// profiler options
173+
addOption(cmd, "--event", event);
174+
addOption(cmd, "--alloc", alloc);
175+
addFlag(cmd, "--live", live);
176+
addOption(cmd, "--lock", lock);
177+
addOption(cmd, "--jfrsync", jfrsync);
178+
179+
addOption(cmd, "--file", file);
180+
addOption(cmd, "--format", format);
181+
addOption(cmd, "--interval", interval);
182+
addOption(cmd, "--jstackdepth", jstackdepth);
183+
addOption(cmd, "--wall", wall);
184+
185+
addOption(cmd, "--features", features);
186+
addOption(cmd, "--signal", signal);
187+
addOption(cmd, "--clock", clock);
188+
189+
addFlag(cmd, "--threads", threads);
190+
addFlag(cmd, "--sched", sched);
191+
addOption(cmd, "--cstack", cstack);
192+
193+
addFlag(cmd, "-s", simple);
194+
addFlag(cmd, "-g", sig);
195+
addFlag(cmd, "-a", ann);
196+
addFlag(cmd, "-l", lib);
197+
addFlag(cmd, "--all-user", allUser);
198+
addFlag(cmd, "--norm", norm);
199+
200+
addRepeatableOption(cmd, "--include", include);
201+
addRepeatableOption(cmd, "--exclude", exclude);
202+
203+
addOption(cmd, "--begin", begin);
204+
addOption(cmd, "--end", end);
205+
addFlag(cmd, "--ttsp", ttsp);
206+
207+
addOption(cmd, "--title", title);
208+
addOption(cmd, "--minwidth", minwidth);
209+
addFlag(cmd, "--reverse", reverse);
210+
addFlag(cmd, "--total", total);
211+
212+
addOption(cmd, "--chunksize", chunksize);
213+
addOption(cmd, "--chunktime", chunktime);
214+
addOption(cmd, "--loop", loop);
215+
addOption(cmd, "--timeout", timeout);
216+
addOption(cmd, "--duration", duration);
217+
218+
logger.info("Executing profiler command: {}", cmd);
219+
return executeSync(toolContext, cmd.toString());
220+
}
221+
222+
private static String normalizeAction(String action) {
223+
if (action == null || action.trim().isEmpty()) {
224+
throw new IllegalArgumentException("action is required");
225+
}
226+
227+
String input = action.trim();
228+
for (String supported : SUPPORTED_ACTIONS) {
229+
if (supported.equalsIgnoreCase(input)) {
230+
return supported;
231+
}
232+
}
233+
234+
StringBuilder supportedList = new StringBuilder();
235+
for (int i = 0; i < SUPPORTED_ACTIONS.length; i++) {
236+
if (i > 0) {
237+
supportedList.append(", ");
238+
}
239+
supportedList.append(SUPPORTED_ACTIONS[i]);
240+
}
241+
throw new IllegalArgumentException("Unsupported action: " + input + ". Supported actions: " + supportedList);
242+
}
243+
244+
private void addOption(StringBuilder cmd, String option, String value) {
245+
if (value == null || value.trim().isEmpty()) {
246+
return;
247+
}
248+
cmd.append(" ").append(option);
249+
addParameter(cmd, value);
250+
}
251+
252+
private void addOption(StringBuilder cmd, String option, Long value) {
253+
if (value == null) {
254+
return;
255+
}
256+
cmd.append(" ").append(option).append(" ").append(value);
257+
}
258+
259+
private void addOption(StringBuilder cmd, String option, Integer value) {
260+
if (value == null) {
261+
return;
262+
}
263+
cmd.append(" ").append(option).append(" ").append(value);
264+
}
265+
266+
private void addRepeatableOption(StringBuilder cmd, String option, String[] values) {
267+
if (values == null || values.length == 0) {
268+
return;
269+
}
270+
for (String value : values) {
271+
if (value == null || value.trim().isEmpty()) {
272+
continue;
273+
}
274+
cmd.append(" ").append(option);
275+
addParameter(cmd, value);
276+
}
277+
}
278+
}
279+

0 commit comments

Comments
 (0)