Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade "stop action" in profiler command #2613

Merged
merged 15 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
+ " profiler list # list all supported events\n"
+ " profiler actions # list all supported actions\n"
+ " profiler start --event alloc\n"
+ " profiler stop --format html # output file format, support html,jfr\n"
+ " profiler stop --format html # output file format, support flat[=N]|traces[=N]|collapsed|flamegraph|tree|jfr\n"
+ " profiler stop --file /tmp/result.html\n"
+ " profiler stop --threads \n"
+ " profiler start --include 'java/*' --include 'com/demo/*' --exclude '*Unsafe.park*'\n"
Expand Down Expand Up @@ -94,6 +94,26 @@ public class ProfilerCommand extends AnnotatedCommand {
*/
private boolean threads;

/**
* use simple class names instead of FQN
*/
private boolean simple;

/**
* print method signatures
*/
private boolean sig;

/**
* annotate Java methods
*/
private boolean ann;

/**
* prepend library names
*/
private boolean lib;

/**
* include only kernel-mode events
*/
Expand All @@ -119,6 +139,27 @@ public class ProfilerCommand extends AnnotatedCommand {
*/
private List<String> excludes;


/**
* FlameGraph title
*/
private String title;

/**
* FlameGraph minimum frame width in percent
*/
private String minwidth;

/**
* generate stack-reversed FlameGraph / Call tree
*/
private boolean reverse;

/**
* count the total value (time, bytes, etc.) instead of samples
*/
private boolean total;

private static String libPath;
private static AsyncProfiler profiler = null;

Expand Down Expand Up @@ -184,15 +225,18 @@ public void setFramebuf(long framebuf) {
}

@Option(shortName = "f", longName = "file")
@Description("dump output to <filename>")
@Description("dump output to <filename>, if ends with html or jfr, content format can be infered")
public void setFile(String file) {
this.file = file;
}

@Option(longName = "format")
@Description("dump output file format(html, jfr), default valut is html")
@DefaultValue("html")
@Option(shortName = "o", longName = "format")
@Description("dump output content format(flat[=N]|traces[=N]|collapsed|flamegraph|tree|jfr)")
public void setFormat(String format) {
// only for backward compatibility
if ("html".equals(format)) {
format = "flamegraph";
}
this.format = format;
}

Expand All @@ -209,6 +253,30 @@ public void setThreads(boolean threads) {
this.threads = threads;
}

@Option(shortName = "s", flag = true)
@Description("use simple class names instead of FQN")
public void setSimple(boolean simple) {
this.simple = simple;
}

@Option(shortName = "g", flag = true)
@Description("print method signatures")
public void setSig(boolean sig) {
this.sig = sig;
}

@Option(shortName = "a", flag = true)
@Description("annotate Java methods")
public void setAnn(boolean ann) {
this.ann = ann;
}

@Option(shortName = "l", flag = true)
@Description("prepend library names")
public void setLib(boolean lib) {
this.lib = lib;
}

@Option(longName = "allkernel", flag = true)
@Description("include only kernel-mode events")
public void setAllkernel(boolean allkernel) {
Expand All @@ -227,18 +295,50 @@ public void setDuration(long duration) {
this.duration = duration;
}

@Option(longName = "include")
@Option(shortName = "I", longName = "include")
@Description("include stack traces containing PATTERN, for example: 'java/*'")
public void setInclude(List<String> includes) {
this.includes = includes;
}

@Option(longName = "exclude")
@Option(shortName = "X", longName = "exclude")
@Description("exclude stack traces containing PATTERN, for example: '*Unsafe.park*'")
public void setExclude(List<String> excludes) {
this.excludes = excludes;
}

@Option(longName = "title")
@Description("FlameGraph title")
public void setTitle(String title) {
// escape HTML special characters
// and escape comma to avoid conflicts with JVM TI
title = title.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;")
.replace(",", "&#44;");
this.title = title;
}

@Option(longName = "minwidth")
@Description("FlameGraph minimum frame width in percent")
public void setMinwidth(String minwidth) {
this.minwidth = minwidth;
}

@Option(longName = "reverse", flag = true)
@Description("generate stack-reversed FlameGraph / Call tree")
public void setReverse(boolean reverse) {
this.reverse = reverse;
}

@Option(longName = "total", flag = true)
@Description("count the total value (time, bytes, etc.) instead of samples")
public void setTotal(boolean total) {
this.total = total;
}

private AsyncProfiler profilerInstance() {
if (profiler != null) {
return profiler;
Expand Down Expand Up @@ -306,6 +406,9 @@ private String executeArgs(ProfilerAction action) {
if (this.file != null) {
sb.append("file=").append(this.file).append(',');
}
if (this.format != null) {
sb.append(this.format).append(',');
}
if (this.interval != null) {
sb.append("interval=").append(this.interval).append(',');
}
Expand All @@ -315,6 +418,18 @@ private String executeArgs(ProfilerAction action) {
if (this.threads) {
sb.append("threads").append(',');
}
if (this.simple) {
sb.append("simple").append(",");
}
if (this.sig) {
sb.append("sig").append(",");
}
if (this.ann) {
sb.append("ann").append(",");
}
if (this.lib) {
sb.append("lib").append(",");
}
if (this.allkernel) {
sb.append("allkernel").append(',');
}
Expand All @@ -332,6 +447,19 @@ private String executeArgs(ProfilerAction action) {
}
}

if (this.title != null) {
sb.append("title=").append(this.title).append(',');
}
if (this.minwidth != null) {
sb.append("minwidth=").append(this.minwidth).append(',');
}
if (this.reverse) {
sb.append("reverse").append(',');
}
if (this.total) {
sb.append("total").append(',');
}

return sb.toString();
}

Expand Down Expand Up @@ -460,18 +588,40 @@ private ProfilerModel processStop(AsyncProfiler asyncProfiler) throws IOExceptio

private String outputFile() throws IOException {
if (this.file == null) {
String fileExt = outputFileExt();
File outputPath = ArthasBootstrap.getInstance().getOutputPath();
if (outputPath != null) {
this.file = new File(outputPath,
new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "." + this.format)
new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "." + fileExt)
.getAbsolutePath();
} else {
this.file = File.createTempFile("arthas-output", "." + this.format).getAbsolutePath();
this.file = File.createTempFile("arthas-output", "." + fileExt).getAbsolutePath();
}
}
return file;
}

/**
* This method should only be called when {@code this.file == null} is true.
*/
private String outputFileExt() {
String fileExt = "";
if (this.format == null) {
fileExt = "html";
} else if (this.format.startsWith("flat") || this.format.startsWith("traces")
|| this.format.equals("collapsed")) {
fileExt = "txt";
} else if (this.format.equals("flamegraph") || this.format.equals("tree")) {
fileExt = "html";
} else if (this.format.equals("jfr")) {
fileExt = "jfr";
} else {
// illegal -o option makes async-profiler use flat
fileExt = "txt";
}
return fileExt;
}

Comment on lines 589 to +624
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format 选项相关的关键修改在这两个方法中。

private void appendExecuteResult(CommandProcess process, String result) {
ProfilerModel profilerModel = createProfilerModel(result);
process.appendResult(profilerModel);
Expand Down
39 changes: 29 additions & 10 deletions site/docs/doc/profiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

`profiler` 命令基本运行结构是 `profiler action [actionArg]`

`profiler` 命令的格式基本与上游项目 [async-profiler](https://github.com/async-profiler/async-profiler) 保持一致,详细的使用方式可参考上游项目的 README、Github Disscussions 以及其他文档资料。

## 参数说明

| 参数名称 | 参数说明 |
Expand All @@ -29,7 +31,7 @@ Started [cpu] profiling
```

::: tip
默认情况下,生成的是 cpu 的火焰图,即 event 为`cpu`。可以用`--event`参数来指定
默认情况下,生成的是 cpu 的火焰图,即 event 为`cpu`。可以用`--event`参数指定其他性能分析模式,见下文
:::

## 获取已采集的 sample 的数量
Expand All @@ -50,17 +52,17 @@ $ profiler status

## 停止 profiler

### 生成 html 格式结果
### 生成火焰图格式结果

默认情况下,结果文件是`html`格式,也可以用`--format`参数指定:
默认情况下,结果是 [Flame Graph](https://github.com/BrendanGregg/FlameGraph) 格式的 `html` 文件,也可以用 `-o` 或 `--format` 参数指定其他内容格式,包括 flat、traces、collapsed、flamegraph、tree、jfr。
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这些新加的 format 都测试支持不?我之前删掉了一些,只保留 html ,因为好像只支持 html 了。

Copy link
Contributor Author

@Winson-Huang Winson-Huang Aug 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

正如我在最开始的说明中提到的,在本改动以前,arthas 中没有 -o 选项,仅有一个相关的 format 选项,它仅用于构造默认文件名的后缀(format 的值并未传递给 execute 方法)且只有两个可选值,【分析结果】的内容格式是让 async-profiler 通过这个后缀名推断的。

在本改动之后,format 选项更改为与 async-profiler 的 -o 选项完全对应,也就是仅控制分析结果的内容格式,与文件后缀名在一定程度上解耦合,当不明确指定输出文件名时,文件名后缀将通过 -o 选项的各种情况生成合适的值,或者使用默认值 html。具体的逻辑细节,可以查看代码中的改动。

经测试,指定这些格式后,当不明确指定文件名时,文件名后缀均可正确推断出来,且文件内容的格式也正确。


```bash
$ profiler stop --format html
$ profiler stop --format flamegraph
profiler output file: /tmp/test/arthas-output/20211207-111550.html
OK
```

或者在`--file`参数里用文件名指名格式。比如`--file /tmp/result.html` 。
`--file`参数指定的文件名后缀为 `html` 或 `jfr` 时,文件格式可以被推断出来。比如`--file /tmp/result.html` 将自动生成火焰图

## 通过浏览器查看 arthas-output 下面的 profiler 结果

Expand Down Expand Up @@ -100,26 +102,32 @@ Basic events:
lock
wall
itimer
Java method calls:
ClassName.methodName
Perf events:
page-faults
context-switches
cycles
instructions
cache-references
cache-misses
branches
branch-instructions
branch-misses
bus-cycles
L1-dcache-load-misses
LLC-load-misses
dTLB-load-misses
rNNN
pmu/event-descriptor/
mem:breakpoint
trace:tracepoint
kprobe:func
uprobe:path
```

如果遇到 OS 本身的权限/配置问题,然后  缺少部分 event,可以参考`async-profiler`本身文档:[async-profiler](https://github.com/jvm-profiling-tools/async-profiler)
如果遇到 OS 本身的权限/配置问题,然后缺少部分 event,可以参考 [async-profiler 的文档](https://github.com/jvm-profiling-tools/async-profiler)

可以用`--event`参数指定要采样的事件,比如对`alloc`事件进入采样
可以用`--event`参数指定要采样的事件,比如 `alloc` 表示分析内存分配情况

```bash
$ profiler start --event alloc
Expand All @@ -132,7 +140,7 @@ $ profiler resume
Started [cpu] profiling
```

`start`和`resume`的区别是:`start`是新开始采样,`resume`会保留上次`stop`时的数据
`start`和`resume`的区别是:`start`会清除已有的分析结果重新开始,`resume`则会保留已有的结果,将新的分析结果附加到已有结果中

通过执行`profiler getSamples`可以查看 samples 的数量来验证。

Expand Down Expand Up @@ -183,7 +191,7 @@ profiler start --framebuf 5000000
profiler start --include 'java/*' --include 'com/demo/*' --exclude '*Unsafe.park*'
```

> `--include/--exclude` 都支持设置多个值 ,但是需要配置在命令行的最后。
> `--include/--exclude` 都支持多次设置,但是需要配置在命令行的最后。也可使用短参数格式 `-I/-X`

## 指定执行时间

Expand All @@ -199,6 +207,7 @@ profiler start --duration 300

```
profiler start --file /tmp/test.jfr
profiler start -o jfr
```

`file`参数支持一些变量:
Expand All @@ -211,6 +220,16 @@ profiler start --file /tmp/test.jfr
- JDK Mission Control : https://github.com/openjdk/jmc
- JProfiler : https://github.com/alibaba/arthas/issues/1416

## 控制分析结果的格式

使用 `-s` 选项将结果中的 Fully qualified name 替换为简单名称,如 `demo.MathGame.main` 替换为 `MathGame.main`。使用 `-g` 选项指定输出方法签名,如 `demo.MathGame.main` 替换为 `demo.MathGame.main([Ljava/lang/String;)V`。此外还有许多可调整分析结果格式的选项,可参考 [async-profiler 的 README 文档](https://github.com/async-profiler/async-profiler#readme) 以及 [async-profiler 的 Github Discussions](https://github.com/async-profiler/async-profiler/discussions) 等材料。

例如,以下命令中,`-s` 将输出中的类名称指定为简短格式,`-g` 显示方法的完整签名,`-a` 标注出 Java 方法,`-l` 为原生方法增加库名称,`--title` 为生成火焰图页面指定标题,`--minwidth` 将过滤火焰图中宽度为 15% 以下的帧,`--reverse` 将火焰图倒置。

```
profiler stop -s -g -a -l --title <flametitle> --minwidth 15 --reverse
```

## 生成的火焰图里的 unknown

- https://github.com/jvm-profiling-tools/async-profiler/discussions/409
Loading