From f375238d74130091288458de4469d50c1e36706d Mon Sep 17 00:00:00 2001 From: Winson Huang Date: Fri, 18 Aug 2023 11:03:26 +0800 Subject: [PATCH] Add -s,-g,-a,-l option for profiler command, better support farmat option (#2613) --- .../command/monitor200/ProfilerCommand.java | 168 +++++++++++++++++- site/docs/doc/profiler.md | 39 ++-- site/docs/en/doc/profiler.md | 39 ++-- 3 files changed, 217 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java index 8c1fb6e16..f42e27924 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java @@ -51,7 +51,7 @@ import one.profiler.Counter; + " 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" @@ -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 */ @@ -119,6 +139,27 @@ public class ProfilerCommand extends AnnotatedCommand { */ private List 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; @@ -184,15 +225,18 @@ public class ProfilerCommand extends AnnotatedCommand { } @Option(shortName = "f", longName = "file") - @Description("dump output to ") + @Description("dump output to , 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; } @@ -209,6 +253,30 @@ public class ProfilerCommand extends AnnotatedCommand { 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) { @@ -227,18 +295,50 @@ public class ProfilerCommand extends AnnotatedCommand { this.duration = duration; } - @Option(longName = "include") + @Option(shortName = "I", longName = "include") @Description("include stack traces containing PATTERN, for example: 'java/*'") public void setInclude(List 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 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("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + .replace(",", ","); + 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; @@ -306,6 +406,9 @@ public class ProfilerCommand extends AnnotatedCommand { 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(','); } @@ -315,6 +418,18 @@ public class ProfilerCommand extends AnnotatedCommand { 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(','); } @@ -332,6 +447,19 @@ public class ProfilerCommand extends AnnotatedCommand { } } + 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(); } @@ -460,18 +588,40 @@ public class ProfilerCommand extends AnnotatedCommand { 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; + } + private void appendExecuteResult(CommandProcess process, String result) { ProfilerModel profilerModel = createProfilerModel(result); process.appendResult(profilerModel); diff --git a/site/docs/doc/profiler.md b/site/docs/doc/profiler.md index ee817cf0d..92e1db6f8 100644 --- a/site/docs/doc/profiler.md +++ b/site/docs/doc/profiler.md @@ -10,6 +10,8 @@ `profiler` 命令基本运行结构是 `profiler action [actionArg]` +`profiler` 命令的格式基本与上游项目 [async-profiler](https://github.com/async-profiler/async-profiler) 保持一致,详细的使用方式可参考上游项目的 README、Github Disscussions 以及其他文档资料。 + ## 参数说明 | 参数名称 | 参数说明 | @@ -29,7 +31,7 @@ Started [cpu] profiling ``` ::: tip -默认情况下,生成的是 cpu 的火焰图,即 event 为`cpu`。可以用`--event`参数来指定。 +默认情况下,生成的是 cpu 的火焰图,即 event 为`cpu`。可以用`--event`参数指定其他性能分析模式,见下文。 ::: ## 获取已采集的 sample 的数量 @@ -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。 ```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 结果 @@ -100,6 +102,8 @@ Basic events: lock wall itimer +Java method calls: + ClassName.methodName Perf events: page-faults context-switches @@ -107,19 +111,23 @@ Perf events: 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 @@ -132,7 +140,7 @@ $ profiler resume Started [cpu] profiling ``` -`start`和`resume`的区别是:`start`是新开始采样,`resume`会保留上次`stop`时的数据。 +`start`和`resume`的区别是:`start`会清除已有的分析结果重新开始,`resume`则会保留已有的结果,将新的分析结果附加到已有结果中。 通过执行`profiler getSamples`可以查看 samples 的数量来验证。 @@ -183,7 +191,7 @@ profiler start --framebuf 5000000 profiler start --include 'java/*' --include 'com/demo/*' --exclude '*Unsafe.park*' ``` -> `--include/--exclude` 都支持设置多个值 ,但是需要配置在命令行的最后。 +> `--include/--exclude` 都支持多次设置,但是需要配置在命令行的最后。也可使用短参数格式 `-I/-X`。 ## 指定执行时间 @@ -199,6 +207,7 @@ profiler start --duration 300 ``` profiler start --file /tmp/test.jfr +profiler start -o jfr ``` `file`参数支持一些变量: @@ -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 --minwidth 15 --reverse +``` + ## 生成的火焰图里的 unknown - https://github.com/jvm-profiling-tools/async-profiler/discussions/409 diff --git a/site/docs/en/doc/profiler.md b/site/docs/en/doc/profiler.md index ed7b25abf..f970f75e5 100644 --- a/site/docs/en/doc/profiler.md +++ b/site/docs/en/doc/profiler.md @@ -6,10 +6,12 @@ Generate a flame graph using [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) ::: -The `profiler` command supports generate flame graph for application hotspots. +The `profiler` command supports generating flame graph for application hotspots. The basic usage of the `profiler` command is `profiler action [actionArg]` +The arguments of `profiler` command basically keeps consistent with upstream project [async-profiler](https://github.com/async-profiler/async-profiler), you can refer to its README, Github Discussions and other documentations for further information of usage. + ## Supported Options | Name | Specification | @@ -29,7 +31,7 @@ Started [cpu] profiling ``` ::: tip -By default, the sample event is `cpu`. Can be specified with the `--event` parameter. +By default, the sample event is `cpu`. Other valid profiling modes can be specified with the `--event` parameter, see relevant contents below. ::: ## Get the number of samples collected @@ -50,17 +52,17 @@ Can view which `event` and sampling time. ## Stop profiler -### Generating html format results +### Generating flame graph results -By default, the result file is `html` format. You can also specify it with the `--format` parameter: +By default, the result file is `html` file in [Flame Graph](https://github.com/BrendanGregg/FlameGraph) format. You can also specify other format with the `-o` or `--format` parameter, including flat, traces, collapsed, flamegraph, tree, jfr: ```bash -$ profiler stop --format html +$ profiler stop --format flamegraph profiler output file: /tmp/test/arthas-output/20211207-111550.html OK ``` -Or use the file name name format in the `--file` parameter. For example, `--file /tmp/result.html`. +When extension of filename in `--file` parameter is `html` or `jfr`, the output format can be infered. For example, `--file /tmp/result.html` will generate flamegraph automatically. ## View profiler results under arthas-output via browser @@ -100,6 +102,8 @@ Basic events: lock wall itimer +Java method calls: + ClassName.methodName Perf events: page-faults context-switches @@ -107,19 +111,23 @@ Perf events: 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 ``` If you encounter the permissions/configuration issues of the OS itself and then missing some events, you can refer to the [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) documentation. -You can use the `--event` parameter to specify the event to sample, such as sampling the `alloc` event: +You can use the `--event` parameter to specify the event to sample, for example, `alloc` event means heap memory allocation profiling: ```bash $ profiler start --event alloc @@ -132,7 +140,7 @@ $ profiler resume Started [cpu] profiling ``` -The difference between `start` and `resume` is: `start` is the new start sampling, `resume` will retain the data of the last `stop`. +The difference between `start` and `resume` is: `start` will clean existing result of last profiling before starting, `resume` will retain the existing result and add result of this time to it. You can verify the number of samples by executing `profiler getSamples`. @@ -185,7 +193,7 @@ If the application is complex and generates a lot of content, and you want to fo profiler start --include'java/*' --include 'com/demo/*' --exclude'*Unsafe.park*' ``` -> Both `--include/--exclude` support setting multiple values, but need to be configured at the end of the command line. +> Both `--include/--exclude` support being set multiple times, but need to be configured at the end of the command line. You can also use short parameter format `-I/-X`. ## Specify execution time @@ -201,6 +209,7 @@ profiler start --duration 300 ``` profiler start --file /tmp/test.jfr +profiler start -o jfr ``` The `file` parameter supports some variables: @@ -213,6 +222,16 @@ The generated results can be viewed with tools that support the jfr format. such - JDK Mission Control: https://github.com/openjdk/jmc - JProfiler: https://github.com/alibaba/arthas/issues/1416 +## Control details in result + +The `-s` parameter will use simple name instead of Fully qualified name, e.g. `MathGame.main` instead of `demo.MathGame.main`. The `-g` parameter will use method signatures instead of method names, e.g. `demo.MathGame.main([Ljava/lang/String;)V` instead of `demo.MathGame.main`. There are many parameters related to result format details, you can refer to [async-profiler README](https://github.com/async-profiler/async-profiler#readme) and [async-profiler Github Discussions](https://github.com/async-profiler/async-profiler/discussions) and other information. + +For example, in command below, `-s` use simple name for Java class, `-g` show method signatures, `-a` will annotate Java methods, `-l` will prepend library names for native method, `--title` specify a title for flame graph page, `--minwidth` will skip frames smaller than 15% in flame graph, `--reverse` will generate stack-reversed FlameGraph / Call tree. + +``` +profiler stop -s -g -a -l --title --minwidth 15 --reverse +``` + ## The 'unknown' in profiler result - https://github.com/jvm-profiling-tools/async-profiler/discussions/409