add jfr command. #2262

pull/2270/head
longxu0509 2 years ago committed by hengyunabc
parent 586e953e6c
commit c536748f20

@ -3,25 +3,10 @@ package com.taobao.arthas.core.command;
import java.util.ArrayList;
import java.util.List;
import com.taobao.arthas.core.command.basic1000.AuthCommand;
import com.taobao.arthas.core.command.basic1000.Base64Command;
import com.taobao.arthas.core.command.basic1000.CatCommand;
import com.taobao.arthas.core.command.basic1000.ClsCommand;
import com.taobao.arthas.core.command.basic1000.EchoCommand;
import com.taobao.arthas.core.command.basic1000.GrepCommand;
import com.taobao.arthas.core.command.basic1000.HelpCommand;
import com.taobao.arthas.core.command.basic1000.HistoryCommand;
import com.taobao.arthas.core.command.basic1000.KeymapCommand;
import com.taobao.arthas.core.command.basic1000.OptionsCommand;
import com.taobao.arthas.core.command.basic1000.PwdCommand;
import com.taobao.arthas.core.command.basic1000.ResetCommand;
import com.taobao.arthas.core.command.basic1000.SessionCommand;
import com.taobao.arthas.core.command.basic1000.StopCommand;
import com.taobao.arthas.core.command.basic1000.SystemEnvCommand;
import com.taobao.arthas.core.command.basic1000.SystemPropertyCommand;
import com.taobao.arthas.core.command.basic1000.TeeCommand;
import com.taobao.arthas.core.command.basic1000.VMOptionCommand;
import com.taobao.arthas.core.command.basic1000.VersionCommand;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.command.basic1000.*;
import com.taobao.arthas.core.command.hidden.JulyCommand;
import com.taobao.arthas.core.command.hidden.ThanksCommand;
import com.taobao.arthas.core.command.klass100.ClassLoaderCommand;
@ -59,7 +44,7 @@ import com.taobao.middleware.cli.annotations.Name;
* @author beiwei30 on 17/11/2016.
*/
public class BuiltinCommandPack implements CommandResolver {
private static final Logger logger = LoggerFactory.getLogger(BuiltinCommandPack.class);
private List<Command> commands = new ArrayList<Command>();
public BuiltinCommandPack(List<String> disabledCommands) {
@ -72,7 +57,7 @@ public class BuiltinCommandPack implements CommandResolver {
}
private void initCommands(List<String> disabledCommands) {
List<Class<? extends AnnotatedCommand>> commandClassList = new ArrayList<Class<? extends AnnotatedCommand>>(32);
List<Class<? extends AnnotatedCommand>> commandClassList = new ArrayList<Class<? extends AnnotatedCommand>>(33);
commandClassList.add(HelpCommand.class);
commandClassList.add(AuthCommand.class);
commandClassList.add(KeymapCommand.class);
@ -120,6 +105,13 @@ public class BuiltinCommandPack implements CommandResolver {
commandClassList.add(ProfilerCommand.class);
commandClassList.add(VmToolCommand.class);
commandClassList.add(StopCommand.class);
try {
if (ClassLoader.getSystemClassLoader().getResource("jdk/jfr/Recording.class") != null) {
commandClassList.add(JFRCommand.class);
}
} catch (Throwable e) {
logger.error("This jdk version not support jfr command");
}
for (Class<? extends AnnotatedCommand> clazz : commandClassList) {
Name name = clazz.getAnnotation(Name.class);

@ -0,0 +1,413 @@
package com.taobao.arthas.core.command.basic1000;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.JFRModel;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.cli.CliToken;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.*;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import jdk.jfr.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Name("jfr")
@Summary("Java Flight Recorder Command")
@Description(Constants.EXAMPLE +
" jfr start # start a new JFR recording\n" +
" jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr \n" +
" jfr status # list all recordings\n" +
" jfr status -r 1 # list recording id = 1 \n" +
" jfr status --state running # list recordings state = running\n" +
" jfr stop -r 1 # stop a JFR recording to default file\n" +
" jfr stop -r 1 -f /tmp/myRecording.jfr\n" +
" jfr dump -r 1 # copy contents of a JFR recording to default file\n" +
" jfr dump -r 1 -f /tmp/myRecording.jfr\n" +
Constants.WIKI + Constants.WIKI_HOME + "jfr")
public class JFRCommand extends AnnotatedCommand {
private String cmd;
private String name;
private String settings;
private Boolean dumpOnExit;
private String delay;
private String duration;
private String filename;
private String maxAge;
private String maxSize;
private Long recording;
private String state;
private JFRModel result = new JFRModel();
private static Map<Long, Recording> recordings = new ConcurrentHashMap<Long, Recording>();
@Argument(index = 0, argName = "cmd", required = true)
@Description("command name (start status stop dump)")
public void setCmd(String cmd) {
this.cmd = cmd;
}
@Option(shortName = "n", longName = "name")
@Description("Name that can be used to identify recording, e.g. \"My Recording\"")
public void setName(String name) {
this.name = name;
}
@Option(shortName = "s", longName = "settings")
@Description("Settings file(s), e.g. profile or default. See JRE_HOME/lib/jfr (STRING , default)")
public void setSettings(String settings) {
this.settings = settings;
}
@Option(longName = "dumponexit")
@Description("Dump running recording when JVM shuts down (BOOLEAN, false)")
public void setDumpOnExit(Boolean dumpOnExit) {
this.dumpOnExit = dumpOnExit;
}
@Option(shortName = "d", longName = "delay")
@Description("Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h. (NANOTIME, 0)")
public void setDelay(String delay) {
this.delay = delay;
}
@Option(longName = "duration")
@Description("Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s. (NANOTIME, 0)")
public void setDuration(String duration) {
this.duration = duration;
}
@Option(shortName = "f", longName = "filename")
@Description("Resulting recording filename, e.g. /tmp/MyRecording.jfr.")
public void setFilename(String filename) {
this.filename = filename;
}
@Option(longName = "maxage")
@Description("Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or default for no limit (NANOTIME, 0)")
public void setMaxAge(String maxAge) {
this.maxAge = maxAge;
}
@Option(longName = "maxsize")
@Description("Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, 0 for no limit (MEMORY SIZE, 250MB)")
public void setMaxSize(String maxSize) {
this.maxSize = maxSize;
}
@Option(shortName = "r", longName = "recording")
@Description("Recording number, or omit to see all recordings (LONG, -1)")
public void setRecording(Long recording) {
this.recording = recording;
}
@Option(longName = "state")
@Description("Query recordings by sate (new, delay, running, stopped, closed)")
public void setState(String state) {
this.state = state;
}
public String getCmd() {
return cmd;
}
public String getName() {
return name;
}
public String getSettings() {
return settings;
}
public Boolean isDumpOnExit() {
return dumpOnExit;
}
public String getDelay() {
return delay;
}
public String getDuration() {
return duration;
}
public String getFilename() {
return filename;
}
public String getMaxAge() {
return maxAge;
}
public String getMaxSize() {
return maxSize;
}
public Long getRecording() {
return recording;
}
public String getState() {
return state;
}
@Override
public void process(CommandProcess process) {
if (cmd.equals("start")) {
Configuration c = null;
try {
if (getSettings() == null) {
setSettings("default");
}
c = Configuration.getConfiguration(settings);
} catch (Throwable e) {
process.end(-1, "Could not start recording, not able to read settings");
}
Recording r = new Recording(c);
if (getFilename() != null) {
try {
r.setDestination(Paths.get(getFilename()));
} catch (IOException e) {
r.close();
process.end(-1, "Could not start recording, not able to write to file " + getFilename() + e.getMessage());
}
}
if (getMaxSize() != null) {
try {
r.setMaxSize(parseSize(getMaxSize()));
} catch (Exception e) {
process.end(-1, e.getMessage());
}
}
if (getMaxAge() != null) {
try {
r.setMaxAge(Duration.ofNanos(parseTimespan(getMaxAge())));
} catch (Exception e) {
process.end(-1, e.getMessage());
}
}
if (isDumpOnExit() != false) {
r.setDumpOnExit(isDumpOnExit().booleanValue());
}
if (getDuration() != null) {
try {
r.setDuration(Duration.ofNanos(parseTimespan(getDuration())));
} catch (Exception e) {
process.end(-1, e.getMessage());
}
}
if (getName() == null) {
r.setName("Recording-" + r.getId());
} else {
r.setName(getName());
}
long id = r.getId();
recordings.put(id, r);
if (getDelay() != null) {
try {
r.scheduleStart(Duration.ofNanos(parseTimespan(getDelay())));
} catch (Exception e) {
process.end(-1, e.getMessage());
}
result.setJfrOutput("Recording " + r.getId() + " scheduled to start in " + getDelay());
} else {
r.start();
result.setJfrOutput("Started recording " + r.getId() + ".");
}
if (duration == null && maxAge == null && maxSize == null) {
result.setJfrOutput(" No limit specified, using maxsize=250MB as default.");
r.setMaxSize(250*1024L*1024L);
}
if (filename != null && duration != null) {
result.setJfrOutput(" The result will be written to:\n" + filename);
}
} else if (cmd.equals("status")) {
// list recording id = recording
if (getRecording() != null) {
Recording r = recordings.get(getRecording());
if (r == null)
process.end(-1, "recording not exit");
printRecording(r);
} else {// list all recordings
List<Recording> recordingList;
if (state != null) {
recordingList = findRecordingByState(state);
} else {
recordingList = new ArrayList<Recording>(recordings.values());
}
if (recordingList.isEmpty()) {
process.end(-1, "No available recordings.\n Use jfr start to start a recording.\n");
} else {
for (Recording recording : recordingList) {
printRecording(recording);
}
}
}
} else if (cmd.equals("dump")) {
if (recordings.isEmpty()) {
process.end(-1,"No recordings to dump. Use jfr start to start a recording.");
}
if (getRecording() != null) {
Recording r = recordings.get(getRecording());
if (r == null)
process.end(-1, "recording not exit");
if (getFilename() == null) {
try {
setFilename(outputFile());
} catch (IOException e) {
process.end(-1, e.getMessage());
}
}
try {
r.dump(Paths.get(getFilename()));
} catch (IOException e) {
process.end(-1,"Could not to dump. "+ e.getMessage());
}
result.setJfrOutput("Dump recording " + r.getId() + ", The result will be written to:\n" + getFilename());
} else {
process.end(-1,"Failed to dump. Please input recording id");
}
} else if (cmd.equals("stop")) {
if (recordings.isEmpty()) {
process.end(-1,"No recordings to stop. Use jfr start to start a recording.");
}
if (getRecording() != null) {
Recording r = recordings.remove(getRecording());
if (r == null)
process.end(-1, "recording not exit");
if (r.getState().toString().equals("CLOSED") || r.getState().toString().equals("STOPPED"))
process.end(-1, "Failed to stop recording, state can not be closed/stopped");
if (getFilename() == null) {
try {
setFilename(outputFile());
} catch (IOException e) {
process.end(-1, e.getMessage());
}
}
try {
r.setDestination(Paths.get(getFilename()));
} catch (IOException e) {
process.end(-1, "Failed to stop" + r.getName() +". Could not set destination for "+ filename+ "to file" + e.getMessage());
}
r.stop();
result.setJfrOutput("Stop recording " + r.getId() + ", The result will be written to:\n" + getFilename());
r.close();
} else {
process.end(-1, "Failed to stop. please input recording id");
}
} else {
process.end(-1, "Please input correct jfr command (start status stop dump)");
}
process.appendResult(result);
process.end();
}
public long parseSize(String s) throws Exception{
s = s.toLowerCase();
if (s.endsWith("b")) {
return Long.parseLong(s.substring(0, s.length() - 1).trim());
} else if (s.endsWith("k")) {
return 1024 * Long.parseLong(s.substring(0, s.length() - 1).trim());
} else if (s.endsWith("m")) {
return 1024 * 1024 * Long.parseLong(s.substring(0, s.length() - 1).trim());
} else if (s.endsWith("g")) {
return 1024 * 1024 * 1024 * Long.parseLong(s.substring(0, s.length() - 1).trim());
} else {
try {
return Long.parseLong(s);
} catch (Exception e) {
throw new NumberFormatException("'" + s + "' is not a valid size. Shoule be numeric value followed by a unit, i.e. 20M. Valid units k, M, G");
}
}
}
public long parseTimespan(String s) throws Exception {
s = s.toLowerCase();
if (s.endsWith("s")) {
return TimeUnit.NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), TimeUnit.SECONDS);
} else if (s.endsWith("m")) {
return 60 * TimeUnit.NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), TimeUnit.SECONDS);
} else if (s.endsWith("h")) {
return 60 * 60 * TimeUnit.NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), TimeUnit.SECONDS);
} else if (s.endsWith("d")) {
return 24 * 60 * 60 * TimeUnit.NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), TimeUnit.SECONDS);
} else {
try {
return Long.parseLong(s);
} catch (NumberFormatException var2) {
throw new NumberFormatException("'" + s + "' is not a valid timespan. Shoule be numeric value followed by a unit, i.e. 20s. Valid units s, m, h and d.");
}
}
}
private List<Recording> findRecordingByState(String state) {
List<Recording> resultRecordingList = new ArrayList<Recording>();
Collection<Recording> recordingList = recordings.values();
for (Recording recording : recordingList) {
if (recording.getState().toString().toLowerCase().equals(state))
resultRecordingList.add(recording);
}
return resultRecordingList;
}
private void printRecording(Recording recording) {
String format = "Recording: recording="+recording.getId()+" name="+recording.getName()+"";
result.setJfrOutput(format);
Duration duration = recording.getDuration();
if (duration != null) {
result.setJfrOutput(" duration="+ duration.toString());
}
result.setJfrOutput(" (" + recording.getState().toString().toLowerCase() + ")\n");
}
private String outputFile() throws IOException {
if (this.filename == null) {
File outputPath = ArthasBootstrap.getInstance().getOutputPath();
if (outputPath != null) {
this.filename = new File(outputPath,
new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".jfr")
.getAbsolutePath();
} else {
this.filename = File.createTempFile("arthas-output", ".jfr").getAbsolutePath();
}
}
return filename;
}
@Override
public void complete(Completion completion) {
List<CliToken> tokens = completion.lineTokens();
String token = tokens.get(tokens.size() - 1).value();
if (token.startsWith("-")) {
super.complete(completion);
return;
}
List<String> cmd = Arrays.asList("start", "status", "dump", "stop");
CompletionUtils.complete(completion, cmd);
}
}

@ -0,0 +1,22 @@
package com.taobao.arthas.core.command.model;
/**
* @author xulong 2022/7/25
*/
public class JFRModel extends ResultModel {
private String jfrOutput = "";
@Override
public String getType() {
return "jfr";
}
public String getJfrOutput() {
return jfrOutput;
}
public void setJfrOutput(String jfrOutput) {
this.jfrOutput += jfrOutput;
}
}

@ -0,0 +1,14 @@
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.JFRModel;
import com.taobao.arthas.core.shell.command.CommandProcess;
/**
* @author longxu 2022/7/25
*/
public class JFRView extends ResultView<JFRModel>{
@Override
public void draw(CommandProcess process, JFRModel result) {
writeln(process, result.getJfrOutput());
}
}

@ -79,6 +79,7 @@ public class ResultViewResolver {
registerView(TraceView.class);
registerView(WatchView.class);
registerView(VmToolView.class);
registerView(JFRView.class);
} catch (Throwable e) {
logger.error("register result view failed", e);

@ -64,6 +64,7 @@ module.exports = {
"/en/doc/tt.md",
"/en/doc/watch.md",
"/en/doc/profiler.md",
"/en/doc/jfr.md",
"/en/doc/auth.md",
"/en/doc/options.md",
"/en/doc/base64.md",

@ -68,6 +68,7 @@ module.exports = {
"/doc/tt.md",
"/doc/watch.md",
"/doc/profiler.md",
"/doc/jfr.md",
"/doc/auth.md",
"/doc/options.md",
"/doc/base64.md",

@ -839,6 +839,18 @@
cn: "command-profiler-cn",
},
},
{
id: "command-jfr",
type: "COMMAND-ENHANCED",
names: {
en: "jfr",
cn: "jfr",
},
ids: {
en: "command-jfr-en",
cn: "command-jfr-cn",
},
},
{
id: "case-web-console",
type: "USERCASE",

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -43,6 +43,7 @@
## profiler/火焰图
- [profiler](profiler.md) - 使用[async-profiler](https://github.com/jvm-profiling-tools/async-profiler)对应用采样,生成火焰图
- [jfr](jfr.md) - 动态开启关闭 JFR 记录
## 鉴权

@ -0,0 +1,115 @@
# jfr
[`jfr`在线教程](https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn&id=command-jfr)
::: tip
Java Flight Recorder (JFR) 是一种用于收集有关正在运行的 Java 应用程序的诊断和分析数据的工具。它集成到 Java 虚拟机 (JVM) 中,几乎不会造成性能开销,因此即使在负载较重的生产环境中也可以使用。
:::
`jfr` 命令支持在程序动态运行过程中开启和关闭 JFR 记录。 记录收集有关 event 的数据。事件在特定时间点发生在 JVM 或 Java 应用程序中。每个事件都有一个名称、一个时间戳和一个可选的有效负载。负载是与事件相关的数据,例如 CPU 使用率、事件前后的 Java 堆大小、锁持有者的线程 ID 等。
`jfr` 命令基本运行结构是 `jfr cmd [actionArg]`
> 注意: JDK8 的 8u262 版本之后才支持 jfr
## 参数说明
| 参数名称 | 参数说明 |
| ------------: | :---------------------------------------------------------------------------------------- |
| _cmd_ | 要执行的操作 支持的命令【startstatusdumpstop】 |
| _actionArg_ | 属性名模式 |
| [n:] | 记录名称 |
| [r:] | 记录 id 值 |
| [dumponexit:] | 程序退出时,是否要 dump 出 .jfr 文件,默认为 false |
| [d:] | 延迟多久后启动 JFR 记录支持带单位配置eg: 60s, 2m, 5h, 3d. 不带单位就是秒,默认无延迟 |
| [duration:] | JFR 记录持续时间,支持单位配置,不带单位就是秒,默认一直记录 |
| [s:] | 采集 Event 的详细配置文件,默认是 default.jfc 位于 `$JAVA_HOME/lib/jfr/default.jfc` |
| [f:] | 将输出转储到指定路径 |
| [maxage:] | 缓冲区数据最大文件记录保存时间,支持单位配置,不带单位就是秒,默认是不限制 |
| [maxsize:] | 缓冲区的最大文件大小,支持单位配置, 不带单位是字节m 或者 M 代表 MBg 或者 G 代表 GB。 |
| [state:] | jfr 记录状态 |
## 启动 JFR 记录
```
$ jfr start
Started recording 1. No limit specified, using maxsize=250MB as default.
```
::: tip
默认情况下,开启的是默认参数的 jfr 记录
:::
启动 jfr 记录,指定记录名,记录持续时间,记录文件保存路径。
```
$ jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr
Started recording 2. The result will be written to:
/tmp/myRecording.jfr
```
## 查看 JFR 记录状态
默认是查看所有 JFR 记录信息
```bash
$ jfr status
Recording: recording=1 name=Recording-1 (running)
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
查看指定记录 id 的记录信息
```bash
$ jfr status -r 1
Recording: recording=1 name=Recording-1 (running)
```
查看指定状态的记录信息
```bash
$ jfr status --state closed
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
## dump jfr 记录
指定记录输出路径
```bash
$ jfr dump -r 1 -f /tmp/myRecording1.jfr
Dump recording 1, The result will be written to:
/tmp/myRecording1.jfr
```
不指定文件输出路径,默认是保存到`arthas-output`目录下
```bash
$ jfr dump -r 1
Dump recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-200915.jfr
```
## 停止 jfr 记录
不指定记录输出路径,默认是保存到`arthas-output`目录下
```bash
$ jfr stop -r 1
Stop recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-202049.jfr
```
> 注意一条记录只能停止一次。
也可以指定记录输出路径。
## 通过浏览器查看 arthas-output 下面 JFR 记录的结果
默认情况下arthas 使用 8563 端口,则可以打开: [http://localhost:8563/arthas-output/](http://localhost:8563/arthas-output/) 查看到`arthas-output`目录下面的 JFR 记录结果:
![](/images/arthas-output-recording.png)
生成的结果可以用支持 jfr 格式的工具来查看。比如:
- JDK Mission Control https://github.com/openjdk/jmc

@ -43,6 +43,7 @@
## profiler/flame graph
- [profiler](profiler.md) - use [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) to generate flame graph
- [jfr](jfr.md) - dynamic opening and closing of jfr recordings
## authentication
@ -50,7 +51,7 @@
## options
- [options](options.md) - check/set Arthas global options
- [options](options.md) - check/set Arthas global optionss
## pipe

@ -0,0 +1,115 @@
# jfr
[`jfr` online tutorial](https://arthas.aliyun.com/doc/arthas-tutorials.html?language=en&id=command-jfr)
::: tip
Java Flight Recorder (JFR) is a tool for collecting diagnostic and profiling data about a running Java application. It is integrated into the Java Virtual Machine (JVM) and causes almost no performance overhead, so it can be used even in heavily loaded production environments.
:::
The `jfr` command supports starting and stopping JFR recordings during dynamic program running. Recording collects data about _events_. Events occur in the JVM or the Java application at a specific point in time. Each event has a name, a time stamp, and an optional _payload_. The payload is the data associated with an event, for example, the CPU usage, the Java heap size before and after the event, the thread ID of the lock holder, and so on.
The basic usage of the `jfr` command is`jfr cmd [actionArg]`
> Note: jfr is supported only after the 8u262 version of jdk8
### Supported Options
| Name | Specification |
| ------------: | :------------------------------------------------------------------------------------------- |
| _cmd_ | Command to execute, support【startstatusdumpstop】 |
| _actionArg_ | Attribute name pattern |
| [n:] | Name of recording |
| [r:] | Recording id |
| [dumponexit:] | When the program exits, whether to dump the .jfr file. (boolean false) |
| [d:] | Duration of recording, i.e. 60s, 2m, 5h, 3d. default no delay |
| [duration:] | Duration of recording, default forever. |
| [s:] | Server-side template, The default is default.jfc located at `$JAVA_HOME/lib/jfr/default.jfc` |
| [f:] | Resulting recording filename |
| [maxage:] | Maximum age of buffer data |
| [maxsize:] | Maximum size of buffers in bytes |
| [state:] | Recording state |
## Start jfr recording
```
$ jfr start
Started recording 1. No limit specified, using maxsize=250MB as default.
```
::: tip
The default JFR record is started.
:::
Start the JFR recording, specify the recording name, duration, file saving path.
```
$ jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr
Started recording 2. The result will be written to:
/tmp/myRecording.jfr
```
##View jfr recordings status
The default is to view all JFR recordings.
```bash
$ jfr status
Recording: recording=1 name=Recording-1 (running)
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
View the records of the specified recording ID.
```bash
$ jfr status -r 1
Recording: recording=1 name=Recording-1 (running)
```
View recordings in a specified state.
```bash
$ jfr status --state closed
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
## dump jfr recording
Specifies the record output path.
```bash
$ jfr dump -r 1 -f /tmp/myRecording1.jfr
Dump recording 1, The result will be written to:
/tmp/myRecording1.jfr
```
The file output path is not specified. By default, it is saved to the `arthas-output` directory
```bash
$ jfr dump -r 1
Dump recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-200915.jfr
```
## Stop jfr recording
No recording output path is specified, default is saved to `arthas-output` directory.
```bash
$ jfr stop -r 1
Stop recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-202049.jfr
```
> notice: A recording can only be stopped once.
You can also specify the record output path.
## View JFR recording results under arthas-output via browser
By default, arthas uses http port 8563 , which can be opened:[http://localhost:8563/arthas-output/](http://localhost:8563/arthas-output/) View the `arthas-output` directory below JFR recording results:
![](/images/arthas-output-recording.png)
The resulting results can be viewed with tools that support the JFR format. Such as:
- JDK Mission Control https://github.com/openjdk/jmc

@ -0,0 +1,16 @@
在新的`Terminal 2`里,下载`arthas-boot.jar`,再用`java -jar`命令启动:
`wget https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --target-ip 0.0.0.0`{{execute T2}}
`arthas-boot`是`Arthas`的启动程序它启动后会列出所有的Java进程用户可以选择需要诊断的目标进程。
选择第一个进程,输入 `1`{{execute T2}} ,再`Enter/回车`
Attach成功之后会打印Arthas LOGO。输入 `help`{{execute T2}} 可以获取到更多的帮助信息。
![Arthas Boot](/arthas/scenarios/common-resources/assets/arthas-boot.png)

@ -0,0 +1,10 @@
下载`math-game.jar`,再用`java -jar`命令启动:
`wget https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar`{{execute T1}}
`math-game`是一个很简单的程序,它随机生成整数,再执行因式分解,把结果打印出来。如果生成的随机数是负数,则会打印提示信息。

@ -0,0 +1,11 @@
在“jfr”中我们演示了了Arthas的jfr命令。如果有更多的技巧或者使用疑问欢迎在Issue里提出。
* Issues: https://github.com/alibaba/arthas/issues
* 文档: https://arthas.aliyun.com/doc
如果您在使用Arthas请让我们知道。您的使用对我们非常重要[查看](https://github.com/alibaba/arthas/issues/111)
欢迎关注公众号获取Arthas项目的信息、源码分析、案例实践。
![Arthas公众号](/arthas/scenarios/common-resources/assets/qrcode_gongzhonghao.jpg)

@ -0,0 +1,35 @@
{
"title": "Arthas jfr命令",
"description": "Arthas jfr命令",
"difficulty": "精通者",
"time": "10-20 分钟",
"details": {
"steps": [
{
"title": "启动arthas demo",
"text": "arthas-demo.md"
},
{
"title": "启动arthas-boot",
"text": "arthas-boot.md"
},
{
"title": "jfr命令",
"text": "jfr.md"
}
],
"intro": {
"text": "intro.md"
},
"finish": {
"text": "finish.md"
}
},
"environment": {
"uilayout": "terminal"
},
"backend": {
"imageid": "openjdk:15",
"environmentsprotocol": "http"
}
}

@ -0,0 +1,23 @@
![Arthas](https://arthas.aliyun.com/doc/_images/arthas.png)
`Arthas` 是Alibaba开源的Java诊断工具深受开发者喜爱。在线排查问题无需重启动态跟踪Java代码实时监控JVM状态。
`Arthas` 支持JDK 6+支持Linux/Mac/Windows采用命令行交互模式同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。
当你遇到以下类似问题而束手无策时Arthas可以帮助你解决
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception
- 我改的代码为什么没有执行到?难道是我没 commit分支搞错了
- 遇到问题无法在线上 debug难道只能通过加日志再重新发布吗
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug线下无法重现
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态
- 怎么快速定位应用的热点,生成火焰图?
本教程会以一个简单的应用为例演示jfr命令。
* Github: https://github.com/alibaba/arthas
* 文档: https://arthas.aliyun.com/doc/

@ -0,0 +1,126 @@
::: tip
Java Flight Recorder (JFR) 是一种用于收集有关正在运行的 Java 应用程序的诊断和分析数据的工具。它集成到 Java 虚拟机 (JVM) 中,几乎不会造成性能开销,因此即使在负载较重的生产环境中也可以使用。
:::
`jfr` 命令支持在程序动态运行过程中开启和关闭JFR记录。 记录收集有关event的数据。事件在特定时间点发生在 JVM 或 Java 应用程序中。每个事件都有一个名称、一个时间戳和一个可选的有效负载。负载是与事件相关的数据,例如 CPU 使用率、事件前后的 Java 堆大小、锁持有者的线程 ID 等。
`jfr` 命令基本运行结构是 `jfr cmd [actionArg]`
> 注意: JDK8的8u262 版本之后才支持 jfr
## 参数说明
| 参数名称 | 参数说明 |
| ------------: | :----------------------------------------------------------- |
| _cmd_ | 要执行的命令支持的命令【startstatusdumpstop】 |
| _actionArg_ | 属性名模式 |
| [n:] | 记录名称 |
| [r:] | 记录id值 |
| [dumponexit:] | 程序退出时是否要dump出 .jfr文件默认为false |
| [d:] | 延迟多久后启动 JFR 记录支持带单位配置eg: 60s,2m,5h,3d. 不带单位就是秒,默认无延迟 |
| [duration:] | JFR 记录持续时间,支持单位配置,不带单位就是秒,默认一直记录 |
| [s:] | 采集 Event 的详细配置文件默认是default.jfc 位于 `$JAVA_HOME/lib/jfr/default.jfc` |
| [f:] | 将输出转储到指定路径 |
| [maxage:] | 缓冲区数据最大文件记录保存时间,支持单位配置,不带单位就是秒,默认是不限制 |
| [maxsize:] | 缓冲区的最大文件大小,支持单位配置, 不带单位是字节m或者M代表MBg或者G代表GB。 |
| [state:] | jfr记录状态 |
## 启动 JFR 记录
`jfr start`{{execute T2}}
```
$ jfr start
Started recording 1. No limit specified, using maxsize=250MB as default.
```
::: tip
开启的是默认参数的jfr记录
:::
启动jfr记录指定记录名记录持续时间记录文件保存路径。
`jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr`{{execute T2}}
```
$ jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr
Started recording 2. The result will be written to:
/tmp/myRecording.jfr
```
## 查看 JFR 记录状态
默认是查看所有JFR记录信息
`jfr status`{{execute T2}}
```bash
$ jfr status
Recording: recording=1 name=Recording-1 (running)
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
查看指定记录id的记录信息
`jfr status -r 1`{{execute T2}}
```bash
$ jfr status -r 1
Recording: recording=1 name=Recording-1 (running)
```
查看指定状态的记录信息
`jfr status --state closed`{{execute T2}}
```bash
$ jfr status --state closed
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
## dump jfr 记录
指定记录输出路径
`$ jfr dump -r 1 -f /tmp/myRecording1.jfr`{{execute T2}}
```bash
$ jfr dump -r 1 -f /tmp/myRecording1.jfr
Dump recording 1, The result will be written to:
/tmp/myRecording1.jfr
```
不指定文件输出路径,默认是保存到`arthas-output`目录下
`jfr dump -r 1`{{execute T2}}
```bash
$ jfr dump -r 1
Dump recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-200915.jfr
```
## 停止 jfr 记录
不指定记录输出路径,默认是保存到`arthas-output`目录下
`jfr stop -r 1`{{execute T2}}
```bash
$ jfr stop -r 1
Stop recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-202049.jfr
```
> 注意一条记录只能停止一次。
也可以指定记录输出路径。
## 通过浏览器查看 arthas-output 下面JFR记录的结果
默认情况下arthas 使用 8563 端口,则可以打开: [http://localhost:8563/arthas-output/](http://localhost:8563/arthas-output/) 查看到`arthas-output`目录下面的 JFR 记录结果:
![](/images/arthas-output-recording.png)
生成的结果可以用支持 jfr 格式的工具来查看。比如:
- JDK Mission Control https://github.com/openjdk/jmc

@ -0,0 +1,16 @@
In the new `Terminal 2`, download `arthas-boot.jar` and start with the `java -jar` command:
`wget https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --target-ip 0.0.0.0`{{execute T2}}
`arthas-boot` is the launcher for `Arthas`. It lists all the Java processes, and the user can select the target process to be diagnosed.
Select the first process, type `1`{{execute T2}} then type `Enter`
After the Attach is successful, Arthas LOGO is printed. Enter `help`{{execute T2}} for more help.
![Arthas Boot](/arthas/scenarios/common-resources/assets/arthas-boot.png)

@ -0,0 +1,11 @@
Download `math-game.jar` and start with the `java -jar` command:
`wget https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar`{{execute T1}}
`math-game` is a very simple program that randomly generates integers, performs factorization, and prints the results.
If the generated random number is negative, a error message will be printed.

@ -0,0 +1,7 @@
The `jfr Tutorial` demonstrates the usage of profiler. If you have more tips or questions, please feel free to tell or ask in Issue.
* Issues: https://github.com/alibaba/arthas/issues
* Documentation: https://arthas.aliyun.com/doc/en
If you are using Arthas, please let us know that. Your use is very important to us: [View](https://github.com/alibaba/arthas/issues/111)

@ -0,0 +1,35 @@
{
"title": "Arthas jfr Command",
"description": "Arthas jfr Command",
"difficulty": "master",
"time": "10-20 minutes",
"details": {
"steps": [
{
"title": "Arthas demo",
"text": "arthas-demo.md"
},
{
"title": "Start arthas-boot",
"text": "arthas-boot.md"
},
{
"title": "jfr Command",
"text": "jfr.md"
}
],
"intro": {
"text": "intro.md"
},
"finish": {
"text": "finish.md"
}
},
"environment": {
"uilayout": "terminal"
},
"backend": {
"imageid": "openjdk:15",
"environmentsprotocol": "http"
}
}

@ -0,0 +1,39 @@
![Arthas](https://arthas.aliyun.com/doc/_images/arthas.png)
`Arthas` is a Java diagnostic tool open-sourced by Alibaba middleware team. Arthas helps developers in trouble-shooting issues in production environment for Java based applications without modifying code or restarting servers.
`Arthas` supports JDK 6+, supports Linux/Mac/Windows.
## Background
Oftentimes the production system network is inaccessible from local development environment. If issues are encountered in production systems, it is impossible to use IDE to debug the application remotely. Whats even worse, debugging in production environment is unacceptable, as it will suspend all the threads, leading to services downtime.
Developers could always try to reproduce the same issue on the test/staging environment. However, this is tricky as some issues cannot be reproduced easily in a different environment, or even disappear once restarted.
And if youre thinking of adding some logs to your code to help trouble-shoot the issue, you will have to go through the following lifecycle: test, staging, and then to production. Time is money! This approach is inefficient! Worse still, the issue may not be fixed since it might be irreproducible once the JVM is restarted, as described above.
Arthas is built to solve these issues. A developer can troubleshoot production issues on the fly. No JVM restart, no additional code changes. Arthas works as an observer, that is, it will never suspend your running threads.
## Key features
- Check whether a class is loaded? Or where the class is loaded from? (Useful for trouble-shooting jar file conflicts)
- Decompile a class to ensure the code is running as expected.
- Check classloader statistics, e.g. the number of classloaders, the number of classes loaded per classloader, the classloader hierarchy, possible classloader leaks, etc.
- Check the method invocation details, e.g. method parameter, returned values, exceptions and etc.
- Check the stack trace of specified method invocation. This is useful when a developer wants to know the caller of the method.
- Trace the method invocation to find slow sub-invocations.
- Monitor method invocation statistics, e.g. QPS (Query Per Second), RT (Return Time), success rate and etc.
- Monitor system metrics, thread states and CPU usage, GC statistics and etc.
- Supports command line interactive mode, with auto-complete feature enabled.
- Supports telnet and WebSocket, which enables both local and remote diagnostics with command line and browsers.
- Supports profiler/Flame Graph
- Supports JDK 6+
- Supports Linux/Mac/Windows
This tutorial takes a simple application as an example to demonstrate the the usage of jfr.
* Github: https://github.com/alibaba/arthas
* Docs: https://arthas.aliyun.com/doc/en

@ -0,0 +1,126 @@
::: tip
Java Flight Recorder (JFR) is a tool for collecting diagnostic and profiling data about a running Java application. It is integrated into the Java Virtual Machine (JVM) and causes almost no performance overhead, so it can be used even in heavily loaded production environments.
:::
The `jfr` command supports starting and stopping JFR recordings during dynamic program running. Recording collects data about *events*. Events occur in the JVM or the Java application at a specific point in time. Each event has a name, a time stamp, and an optional *payload*. The payload is the data associated with an event, for example, the CPU usage, the Java heap size before and after the event, the thread ID of the lock holder, and so on.
The basic usage of the `jfr` command is`jfr cmd [actionArg]`
> Note: jfr is supported only after the 8u262 version of jdk8
### Supported Options
| Name | Specification |
| ------------: | :----------------------------------------------------------- |
| _cmd_ | Command to execute, support【startstatusdumpstop】 |
| _actionArg_ | Attribute name pattern |
| [n:] | Name of recording |
| [r:] | Recording id |
| [dumponexit:] | When the program exits, whether to dump the .jfr file. (boolean false) |
| [d:] | Duration of recording, i.e. 60s, 2m, 5h, 3d. default no delay |
| [duration:] | Duration of recording, default forever. |
| [s:] | Server-side template, The default is default.jfc located at `$JAVA_HOME/lib/jfr/default.jfc` |
| [f:] | Resulting recording filename |
| [maxage:] | Maximum age of buffer data |
| [maxsize:] | Maximum size of buffers in bytes |
| [state:] | Recording state |
## Start jfr recording
`jfr start`{{execute T2}}
```
$ jfr start
Started recording 1. No limit specified, using maxsize=250MB as default.
```
::: tip
The default JFR record is started.
:::
Start the JFR recording, specify the recording name, duration, file saving path.
`jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr`{{execute T2}}
```
$ jfr start -n myRecording --duration 60s -f /tmp/myRecording.jfr
Started recording 2. The result will be written to:
/tmp/myRecording.jfr
```
##View jfr recordings status
The default is to view all JFR recordings.
`jfr status`{{execute T2}}
```bash
$ jfr status
Recording: recording=1 name=Recording-1 (running)
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
View the records of the specified recording ID.
`jfr status -r 1`{{execute T2}}
```bash
$ jfr status -r 1
Recording: recording=1 name=Recording-1 (running)
```
View recordings in a specified state.
`jfr status --state closed`{{execute T2}}
```bash
$ jfr status --state closed
Recording: recording=2 name=myRecording duration=PT1M (closed)
```
## dump jfr recording
Specifies the record output path.
`$ jfr dump -r 1 -f /tmp/myRecording1.jfr`{{execute T2}}
```bash
$ jfr dump -r 1 -f /tmp/myRecording1.jfr
Dump recording 1, The result will be written to:
/tmp/myRecording1.jfr
```
The file output path is not specified. By default, it is saved to the `arthas-output` directory
`jfr dump -r 1`{{execute T2}}
```bash
$ jfr dump -r 1
Dump recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-200915.jfr
```
## Stop jfr recording
No recording output path is specified, default is saved to `arthas-output` directory.
`jfr stop -r 1`{{execute T2}}
```bash
$ jfr stop -r 1
Stop recording 1, The result will be written to:
/tmp/test/arthas-output/20220819-202049.jfr
```
> notice: A recording can only be stopped once.
You can also specify the record output path.
## View JFR recording results under arthas-output via browser
By default, arthas uses http port 8563 , which can be opened:[http://localhost:8563/arthas-output/](http://localhost:8563/arthas-output/) View the `arthas-output` directory below JFR recording results:
![](/images/arthas-output-recording.png)
The resulting results can be viewed with tools that support the JFR format. Such as:
- JDK Mission Control https://github.com/openjdk/jmc
Loading…
Cancel
Save