Patch: Integrate async-profiler
Jason Zaugg
jzaugg at gmail.com
Mon Aug 3 07:02:21 UTC 2020
Aleksey expressed an interest in having an async-profiler integration
in JMH core.
I previously authored a similar integration for the sbt-jmh project
and have reworked
that into something suitable for JMH.
I have previously submitted an OCA. This is my first patch
contribution to an OpenJDK project, so I may need a little hand
holding.
The discussion on an earlier version of this patch might aid review.
https://github.com/retronym/jmh/pull/2
Regards,
Jason
# HG changeset patch
# User Jason Zaugg <jzaugg.com>
# Date 1596431615 -36000
# Mon Aug 03 15:13:35 2020 +1000
# Branch async-profiler
# Node ID d92e7c5e51e1f918473431c34552fde0ecac7bbc
# Parent 3b2b0bf8e52427732a282e996ab95568dcdda430
Sanitize profiler file names derived from on user data.
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java
b/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/infra/BenchmarkParams.java
@@ -31,6 +31,9 @@
import org.openjdk.jmh.util.Version;
import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -448,4 +451,29 @@
return sb.toString();
}
+ /**
+ * @return a textual representation of these parameters suitable
for use as a file name.
+ */
+ public String sanitizedId() {
+ StringBuilder sb = new StringBuilder();
+ appendSanitized(sb, benchmark);
+ sb.append("-");
+ sb.append(mode);
+ for (String key : params.keys()) {
+ sb.append("-");
+ appendSanitized(sb, key);
+ sb.append("-");
+ appendSanitized(sb, params.get(key));
+ }
+ return sb.toString();
+ }
+
+ private static void appendSanitized(StringBuilder builder, String s) {
+ try {
+ builder.append(URLEncoder.encode(s,
StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java
b/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java
@@ -598,7 +598,7 @@
*/
if (savePerfOutput) {
String target = (savePerfOutputToFile == null) ?
- savePerfOutputTo + "/" + br.getParams().id() + ".perf" :
+ savePerfOutputTo + "/" + br.getParams().sanitizedId()
+ ".perf" :
savePerfOutputToFile;
try {
FileUtils.copy(perfParsedData.getAbsolutePath(), target);
@@ -613,7 +613,7 @@
*/
if (savePerfBin) {
String target = (savePerfBinFile == null) ?
- savePerfBinTo + "/" + br.getParams().id() +
perfBinaryExtension() :
+ savePerfBinTo + "/" + br.getParams().sanitizedId() +
perfBinaryExtension() :
savePerfBinFile;
try {
FileUtils.copy(perfBinData.getAbsolutePath(), target);
@@ -628,7 +628,7 @@
*/
if (saveLog) {
String target = (saveLogToFile == null) ?
- saveLogTo + "/" + br.getParams().id() + ".log" :
+ saveLogTo + "/" + br.getParams().sanitizedId() + ".log" :
saveLogToFile;
try (FileOutputStream asm = new FileOutputStream(target);
PrintWriter pwAsm = new PrintWriter(asm)) {
# HG changeset patch
# User Jason Zaugg <jzaugg.com>
# Date 1596431668 -36000
# Mon Aug 03 15:14:28 2020 +1000
# Branch async-profiler
# Node ID ef9aa9c0956a23ed45ff91e1e0601de25bb8168d
# Parent d92e7c5e51e1f918473431c34552fde0ecac7bbc
Improve error handling in profiler initialization.
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/runner/ProfilersFailedException.java
b/jmh-core/src/main/java/org/openjdk/jmh/runner/ProfilersFailedException.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/ProfilersFailedException.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/ProfilersFailedException.java
@@ -24,11 +24,12 @@
*/
package org.openjdk.jmh.runner;
+import org.openjdk.jmh.profile.ProfilerException;
+
public class ProfilersFailedException extends RunnerException {
private static final long serialVersionUID = 1446854343206595167L;
- public String getMessage() {
- return "Profilers failed to initialize, exiting.";
+ public ProfilersFailedException(ProfilerException e) {
+ super("Profilers failed to initialize, exiting.", e);
}
-
}
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java
@@ -229,7 +229,7 @@
private Collection<RunResult> internalRun() throws RunnerException {
Set<String> profilerClasses = new HashSet<>();
- boolean someProfilersFail = false;
+ ProfilersFailedException failedException = null;
for (ProfilerConfig p : options.getProfilers()) {
if (!profilerClasses.add(p.getKlass())) {
throw new RunnerException("Cannot instantiate the
same profiler more than once: " + p.getKlass());
@@ -237,12 +237,15 @@
try {
ProfilerFactory.getProfilerOrException(p);
} catch (ProfilerException e) {
- out.println(e.getMessage());
- someProfilersFail = true;
+ if (failedException == null) {
+ failedException = new ProfilersFailedException(e);
+ } else {
+ failedException.addSuppressed(e);
+ }
}
}
- if (someProfilersFail) {
- throw new ProfilersFailedException();
+ if (failedException != null) {
+ throw failedException;
}
// If user requested the result file in one way or the other,
touch the result file,
# HG changeset patch
# User Jason Zaugg <jzaugg.com>
# Date 1596431758 -36000
# Mon Aug 03 15:15:58 2020 +1000
# Branch async-profiler
# Node ID df0f746b44515324b692c59799635843b3b5a4ea
# Parent ef9aa9c0956a23ed45ff91e1e0601de25bb8168d
Publicise utility code needed for profilers.
I noticed these where only available to first-party
profiler implementations in JMH.
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java
b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java
@@ -31,7 +31,7 @@
import java.util.List;
import java.util.Map;
-class ProfilerOptionFormatter implements HelpFormatter {
+public class ProfilerOptionFormatter implements HelpFormatter {
private static final String LINE_SEPARATOR =
System.getProperty("line.separator");
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java
b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java
@@ -36,7 +36,7 @@
import java.io.StringWriter;
import java.util.concurrent.TimeUnit;
-class ProfilerUtils {
+public class ProfilerUtils {
public static OptionSet parseInitLine(String initLine,
OptionParser parser) throws ProfilerException {
parser.accepts("help", "Display help.");
# HG changeset patch
# User Jason Zaugg <jzaugg.com>
# Date 1596432769 -36000
# Mon Aug 03 15:32:49 2020 +1000
# Branch async-profiler
# Node ID b7aba49fecd41a1eaaf81c7cd3b22328b5056c34
# Parent df0f746b44515324b692c59799635843b3b5a4ea
Add a profiler based on async-profiler
async-profiler is "a low overhead sampling profiler for Java that
does not suffer from Safepoint bias problem. It features HotSpot-specific
APIs to collect stack traces and to track memory allocations."
An option parser handles the full range of current features of
async-profiler. Some measure of future proofing is provided with
the option `rawCommand` that exposes the underlying command API
of the profiler; this could be used to set newly added
configuration options without requiring a new release of JMH.
This integration controls the profiler as in internal profiler via
its Java API. Samples collected during the warmup period are
discarded.
async-profiler must be installed separately by the user. It can be
included in LD_LIBRARY_PATH, -Djava.library.path, or via an option
to the profiler.
At the conclusion of the final measurement iterations, the results
are rendered to one or more of the output formats supported by
the profiler (currently: flamegraph,tree,text,jfr). By default
both forward and reverse flamegraphs are generated; this behaviour
is configurable. If the text output is among the output formats,
it is also written to console.
All of these files are written to a per-trial directory that is
located within the user specified output directory. If the user
does not specify an output directory, one will be created automatically.
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/profile/AsyncProfiler.java
b/jmh-core/src/main/java/org/openjdk/jmh/profile/AsyncProfiler.java
new file mode 100644
--- /dev/null
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/AsyncProfiler.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.profile;
+
+import joptsimple.*;
+import org.openjdk.jmh.infra.BenchmarkParams;
+import org.openjdk.jmh.infra.IterationParams;
+import org.openjdk.jmh.results.BenchmarkResult;
+import org.openjdk.jmh.results.IterationResult;
+import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.TextResult;
+import org.openjdk.jmh.runner.IterationType;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+ * A profiler based on <a
href="https://github.com/jvm-profiling-tools/async-profiler/commits/master">async-profiler</a>.
+ *
+ * @author Jason Zaugg
+ */
+public final class AsyncProfiler implements ExternalProfiler,
InternalProfiler {
+ private static final String DEFAULT_EVENT = "cpu";
+
+ private final JavaApi instance;
+
+ private final boolean verbose;
+ private final Direction direction;
+ private final String profilerConfig;
+ private final List<OutputType> output;
+ private final String event;
+ private final File outDir;
+ private final int traces;
+ private final int flat;
+
+ private boolean warmupStarted = false;
+ private boolean measurementStarted = false;
+ private int measurementIterationCount = 0;
+ private final List<File> generated = new ArrayList<>();
+
+ public AsyncProfiler(String initLine) throws ProfilerException {
+ OptionParser parser = new OptionParser();
+
+ parser.formatHelpWith(new ProfilerOptionFormatter("async"));
+
+ OptionSpec<OutputType> output = parser.accepts("output", "Output
format(s). Supported: " + EnumSet.allOf(OutputType.class).toString())
+ .withRequiredArg().ofType(OutputType.class).withValuesSeparatedBy(",").describedAs("format+").defaultsTo(OutputType.text);
+ OptionSpec<Direction> direction = parser.accepts("direction",
"Direction(s) of flame graph. Supported: " +
+ EnumSet.allOf(Direction.class))
+ .withRequiredArg().ofType(Direction.class).describedAs("direction").defaultsTo(Direction.both);
+
+ OptionSpec<String> libPath = parser.accepts("libPath", "Location
of libasyncProfiler.so. " +
+ "If not specified, System.loadLibrary will be used and the
library must be made available to the forked JVM " +
+ "in an entry of -Djava.library.path or LD_LIBRARY_PATH.")
+ .withRequiredArg().ofType(String.class).describedAs("path");
+
+ OptionSpec<String> event = parser.accepts("event", "Event to
sample: cpu, alloc, wall, lock, cache-misses etc.")
+ .withRequiredArg().ofType(String.class).describedAs("event").defaultsTo(DEFAULT_EVENT);
+ OptionSpec<String> dir = parser.accepts("dir", "Output directory.")
+ .withRequiredArg().ofType(String.class).describedAs("dir");
+ OptionSpec<Long> interval = parser.accepts("interval", "Profiling
interval")
+ .withRequiredArg().ofType(Long.class).describedAs("ns");
+ OptionSpec<Integer> jstackdepth = parser.accepts("jstackdepth",
"Maximum Java stack depth")
+ .withRequiredArg().ofType(Integer.class).describedAs("frames");
+ OptionSpec<Long> framebuf = parser.accepts("framebuf", "Size of
profiler framebuffer")
+ .withRequiredArg().ofType(Long.class).describedAs("bytes");
+ OptionSpec<Boolean> threads = parser.accepts("threads", "Profile
threads separately")
+ .withRequiredArg().ofType(Boolean.class).describedAs("int");
+ OptionSpec<Boolean> simple = parser.accepts("simple", "Simple
class names instead of FQN")
+ .withRequiredArg().ofType(Boolean.class).describedAs("bool");
+ OptionSpec<Boolean> sig = parser.accepts("sig", "Print method signatures")
+ .withRequiredArg().ofType(Boolean.class).describedAs("bool");
+ OptionSpec<Boolean> ann = parser.accepts("ann", "Annotate Java
method names")
+ .withRequiredArg().ofType(Boolean.class).describedAs("bool");
+ OptionSpec<String> include = parser.accepts("include", "output
only stack traces containing the specified pattern")
+ .withRequiredArg()
+ .withValuesSeparatedBy(",").ofType(String.class).describedAs("regexp+");
+ OptionSpec<String> exclude = parser.accepts("exclude", "exclude
stack traces with the specified pattern")
+ .withRequiredArg()
+ .withValuesSeparatedBy(",").ofType(String.class).describedAs("regexp+");
+ OptionSpec<String> rawCommand = parser.accepts("rawCommand",
"Command to pass directly to async-profiler." +
+ " Use to access new features of JMH profiler that are not yet
supported in this option parser.")
+ .withRequiredArg().ofType(String.class).describedAs("command");
+
+ OptionSpec<String> title = parser.accepts("title", "SVG title")
+ .withRequiredArg().ofType(String.class).describedAs("string");
+ OptionSpec<Long> width = parser.accepts("width", "SVG width")
+ .withRequiredArg().ofType(Long.class).describedAs("pixels");
+ OptionSpec<Long> minWidth = parser.accepts("minwidth", "skip
frames smaller than px")
+ .withRequiredArg().ofType(Long.class).describedAs("pixels");
+
+ OptionSpec<Boolean> allKernel = parser.accepts("allkernel", "only
include kernel-mode events")
+ .withRequiredArg().ofType(Boolean.class).describedAs("bool");
+ OptionSpec<Boolean> allUser = parser.accepts("alluser", "only
include user-mode events")
+ .withRequiredArg().ofType(Boolean.class).describedAs("bool");
+ OptionSpec<CStackMode> cstack = parser.accepts("cstack", "how to
traverse C stack: Supported: " +
+ EnumSet.allOf(CStackMode.class))
+ .withRequiredArg().ofType(CStackMode.class).describedAs("bool");
+
+ OptionSpec<Boolean> verbose = parser.accepts("verbose", "Output
the sequence of commands")
+ .withRequiredArg().ofType(Boolean.class).defaultsTo(false).describedAs("bool");
+
+ OptionSpec<Integer> traces = parser.accepts("traces", "Number of
top traces to include in the default output")
+ .withRequiredArg().ofType(Integer.class).defaultsTo(200).describedAs("int");
+ OptionSpec<Integer> flat = parser.accepts("flat", "Number of top
flat profiles to include in the default output")
+ .withRequiredArg().ofType(Integer.class).defaultsTo(200).describedAs("int");
+
+ OptionSet set = ProfilerUtils.parseInitLine(initLine, parser);
+
+ StringBuilder profilerOptions = new StringBuilder();
+
+ try {
+ ProfilerOptionsBuilder builder = new
ProfilerOptionsBuilder(set, profilerOptions);
+ this.event = event.value(set);
+ builder.append(event);
+ if (!set.has(dir)) {
+ String prefix = "jmh-async-profiler-";
+ outDir = createTempDir(prefix, null);
+ } else {
+ outDir = new File(set.valueOf(dir));
+ }
+
+ builder.appendIfExists(interval);
+ builder.appendIfExists(jstackdepth);
+ builder.appendIfTrue(threads);
+ builder.appendIfTrue(simple);
+ builder.appendIfTrue(sig);
+ builder.appendIfTrue(ann);
+ builder.appendIfExists(framebuf);
+ builder.appendMulti(include);
+ builder.appendMulti(exclude);
+
+ builder.appendIfExists(title);
+ builder.appendIfExists(width);
+ builder.appendIfExists(minWidth);
+
+ builder.appendIfTrue(allKernel);
+ builder.appendIfTrue(allUser);
+ builder.appendIfExists(cstack);
+
+ if (set.has(rawCommand)) {
+ builder.appendRaw(rawCommand.value(set));
+ }
+
+ this.traces = traces.value(set);
+ this.flat = flat.value(set);
+
+ this.profilerConfig = profilerOptions.toString();
+
+ try {
+ if (set.has(libPath)) {
+ instance = JavaApi.getInstance(libPath.value(set));
+ } else {
+ instance = JavaApi.getInstance();
+ }
+ } catch (UnsatisfiedLinkError e) {
+ throw new ProfilerException(
+ "Unable to load async-profiler. Ensure
libasyncProfiler.so is on LD_LIBRARY_PATH, -Djava.library.path or
libPath=");
+ }
+ this.direction = direction.value(set);
+ this.output = output.values(set);
+ this.verbose = verbose.value(set);
+ } catch (OptionException e) {
+ throw new ProfilerException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void beforeIteration(BenchmarkParams benchmarkParams,
IterationParams iterationParams) {
+ if (iterationParams.getType() == IterationType.WARMUP) {
+ if (!warmupStarted) {
+ // Collect profiles during warmup to warmup the profiler itself.
+ execute("start," + profilerConfig);
+ warmupStarted = true;
+ }
+ }
+ if (iterationParams.getType() == IterationType.MEASUREMENT) {
+ if (!measurementStarted) {
+ // Discard samples collected during warmup and start collecting again.
+ execute("start," + profilerConfig);
+ measurementStarted = true;
+ }
+ }
+ }
+
+ @Override
+ public Collection<? extends Result> afterIteration(BenchmarkParams
benchmarkParams, IterationParams iterationParams,
+ IterationResult
iterationResult) {
+ if (iterationParams.getType() == IterationType.MEASUREMENT) {
+ measurementIterationCount += 1;
+ if (measurementIterationCount == iterationParams.getCount()) {
+ File trialOutDir = createTrialOutDir(benchmarkParams);
+ return Collections.singletonList(stopAndDump(trialOutDir));
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private File createTrialOutDir(BenchmarkParams benchmarkParams) {
+ String fileName = benchmarkParams.sanitizedId();
+ File trialOutDir = new File(this.outDir, fileName);
+ trialOutDir.mkdirs();
+ return trialOutDir;
+ }
+
+ private TextResult stopAndDump(File trialOutDir) {
+ execute("stop");
+
+ StringWriter output = new StringWriter();
+ PrintWriter pw = new PrintWriter(output);
+ for (OutputType outputType : this.output) {
+ switch (outputType) {
+ case text:
+ String textOutput = dump(trialOutDir, "summary-%s.txt",
"summary,flat=" + flat + ",traces=" + traces);
+ pw.println(textOutput);
+ break;
+ case collapsed:
+ dump(trialOutDir, "collapsed-%s.csv", "collapsed");
+ break;
+ case flamegraph:
+ if (direction == Direction.both || direction == Direction.forward) {
+ dump(trialOutDir, "flame-%s-forward.svg", "svg");
+ }
+ if (direction == Direction.both || direction == Direction.reverse) {
+ dump(trialOutDir, "flame-%s-reverse.svg", "svg,reverse");
+ }
+ break;
+ case tree:
+ dump(trialOutDir, "tree-%s.html", "tree");
+ break;
+ case jfr:
+ dump(trialOutDir, "%s.jfr", "jfr");
+ break;
+ }
+ }
+ for (File file : generated) {
+ pw.println(file.getAbsolutePath());
+ }
+ pw.flush();
+ pw.close();
+
+ return new TextResult(output.toString(), "async");
+ }
+
+ private String dump(File specificOutDir, String
fileNameFormatString, String content) {
+ File output = new File(specificOutDir,
String.format(fileNameFormatString, event));
+ generated.add(output);
+ String result = execute(content + "," + profilerConfig);
+ write(output, result);
+ return result;
+ }
+
+ private String execute(String command) {
+ if (verbose) {
+ System.out.println("[async-profiler] " + command);
+ }
+ try {
+ return instance.execute(command);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void write(File output, String s) {
+ try {
+ try (BufferedWriter writer = new BufferedWriter(new
OutputStreamWriter(new FileOutputStream(output)))) {
+ writer.write(s);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static File createTempDir(String prefix, File dir) {
+ try {
+ File tempFile = File.createTempFile(prefix, "", dir);
+ tempFile.delete();
+ tempFile.mkdir();
+ return tempFile;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public enum CStackMode {
+ fp,
+ lbr,
+ no
+ }
+
+ public enum OutputType {
+ //NONE,
+ text,
+ collapsed,
+ flamegraph,
+ tree,
+ jfr
+ }
+
+ public enum Direction {
+ forward,
+ reverse,
+ both,
+ }
+
+ private static class ProfilerOptionsBuilder {
+ private final OptionSet optionSet;
+ private final StringBuilder profilerOptions;
+
+ ProfilerOptionsBuilder(OptionSet optionSet, StringBuilder
profilerOptions) {
+ this.optionSet = optionSet;
+ this.profilerOptions = profilerOptions;
+ }
+
+ <T> void appendIfExists(OptionSpec<T> option) {
+ if (optionSet.has(option)) {
+ append(option);
+ }
+ }
+
+ <T> void append(OptionSpec<T> option) {
+ assert (option.options().size() == 1);
+ String optionName = option.options().iterator().next();
+ separate();
+ profilerOptions.append(optionName).append('=').append(optionSet.valueOf(option).toString());
+ }
+
+ void appendRaw(String command) {
+ separate();
+ profilerOptions.append(command);
+ }
+
+ private void separate() {
+ if (profilerOptions.length() > 0) {
+ profilerOptions.append(',');
+ }
+ }
+
+ void appendIfTrue(OptionSpec<Boolean> option) {
+ if (optionSet.has(option) && optionSet.valueOf(option)) {
+ append(option);
+ }
+ }
+
+ private <T> void appendMulti(OptionSpec<T> option) {
+ if (optionSet.has(option)) {
+ assert (option.options().size() == 1);
+ String optionName = option.options().iterator().next();
+ for (T value : optionSet.valuesOf(option)) {
+ profilerOptions.append(',').append(optionName).append('=').append(value.toString());
+ }
+ }
+ }
+ }
+
+ @Override
+ public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Collection<String> addJVMOptions(BenchmarkParams params) {
+ List<String> args = new ArrayList<>();
+ args.add("-XX:+UnlockDiagnosticVMOptions");
+ // Recommended option for async-profiler, enable automatically.
+ args.add("-XX:+DebugNonSafepoints");
+ return args;
+ }
+
+ @Override
+ public void beforeTrial(BenchmarkParams benchmarkParams) {
+ }
+
+ @Override
+ public Collection<? extends Result> afterTrial(BenchmarkResult br,
long pid, File stdOut, File stdErr) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean allowPrintOut() {
+ return true;
+ }
+
+ @Override
+ public boolean allowPrintErr() {
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "async-profiler profiler provider.";
+ }
+
+ // Made public so that power-users could can call filterThread from
within the workload
+ // to limit collection to a set of threads. This is useful for
wall-clock profiling.
+ // Adding support in JMH to pass the threads to profilers seems
like an invasive change for
+ // this niche use case.
+ public static final class JavaApi {
+ private static final Thread ALL_THREADS = null;
+ private static EnumSet<Thread.State> ignoredThreadStates =
EnumSet.of(Thread.State.NEW, Thread.State.TERMINATED);
+ private static JavaApi INSTANCE;
+
+ public static JavaApi getInstance(String libraryFileName) {
+ if (INSTANCE == null) {
+ synchronized(AsyncProfiler.class) {
+ INSTANCE = new JavaApi(libraryFileName);
+ }
+ }
+ return INSTANCE;
+ }
+ public static JavaApi getInstance() {
+ if (INSTANCE == null) {
+ synchronized(AsyncProfiler.class) {
+ INSTANCE = new JavaApi();
+ }
+ }
+ return INSTANCE;
+ }
+
+ private JavaApi(String libraryFileName) {
+ System.load(libraryFileName);
+ }
+ private JavaApi() {
+ System.loadLibrary("libasyncProfiler");
+ }
+ public String execute(String command) throws IOException {
+ return execute0(command);
+ }
+
+ /**
+ * Enable or disable profile collection for threads.
+ *
+ * @param thread The thread to enable or disable.
<code>null</code> is a wildcard.
+ * @param enable Whether to enable or disable.
+ */
+ public void filterThread(Thread thread, boolean enable) {
+ if (thread == ALL_THREADS) {
+ filterThread0(ALL_THREADS, enable);
+ } else {
+ synchronized (thread) {
+ Thread.State state = thread.getState();
+ if (!ignoredThreadStates.contains(state)) {
+ filterThread0(thread, enable);
+ }
+ }
+ }
+ }
+
+ // Loading async-profiler will automatically bind these native
methods to the profiler implementation.
+ private native void start0(String event, long interval, boolean
reset) throws IllegalStateException;
+ private native void stop0() throws IllegalStateException;
+ private native String execute0(String command) throws
IllegalArgumentException, IOException;
+ private native void filterThread0(Thread thread, boolean enable);
+ }
+}
diff --git a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java
b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java
@@ -164,6 +164,7 @@
static {
BUILT_IN = new TreeMap<>();
+ BUILT_IN.put("async", AsyncProfiler.class);
BUILT_IN.put("cl", ClassloaderProfiler.class);
BUILT_IN.put("comp", CompilerProfiler.class);
BUILT_IN.put("gc", GCProfiler.class);
More information about the jmh-dev
mailing list