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

Try to add back benchmarks #134

Closed
Closed
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "benchmark-bridge"]
path = benchmark-bridge
url = https://github.com/scalacenter/compiler-benchmark
1 change: 1 addition & 0 deletions benchmark-bridge
Submodule benchmark-bridge added at ef7fca
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
package pl.project13.scala.jmh.extras.profiler;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.infra.IterationParams;
import org.openjdk.jmh.profile.InternalProfiler;
import org.openjdk.jmh.profile.ProfilerException;
import org.openjdk.jmh.results.IterationResult;
import org.openjdk.jmh.results.Result;
import org.openjdk.jmh.runner.IterationType;

public class ForkedAsyncProfiler implements InternalProfiler {
public static String HELLO = "";
private static final String ASYNC_PROFILER_DIR = "ASYNC_PROFILER_DIR";

private static final String DEFAULT_EVENT = "cpu";
private static final long DEFAULT_FRAMEBUF = 8 * 1024 * 1024;
private static final long DEFAULT_INTERVAL = 1000000;

private final String event;
private final Directions directions;
private final Path asyncProfilerDir;
private final boolean threads;
private final Boolean simpleName;
private Path outputDir;
private boolean started;
private final Path pidFile;
private final Boolean jfr;

private Path profiler;
private Path jattach;
private Long framebuf;
private Long interval;
private long pid = -1;
private int measurementIterationCount;
private Path flameGraphDir;
private Collection<? extends String> flameGraphOpts = Collections.emptyList();
private boolean verbose = false;
private List<Path> generated = new ArrayList<>();

public ForkedAsyncProfiler(String initLine) throws ProfilerException {
OptionParser parser = new OptionParser();
OptionSpec<String> outputDir = parser.accepts("dir", "Output directory").withRequiredArg().describedAs("directory").ofType(String.class);
OptionSpec<String> asyncProfilerDir = parser.accepts("asyncProfilerDir", "Location of clone of https://github.com/jvm-profiling-tools/async-profiler. Also can be provided as $" + ASYNC_PROFILER_DIR).withRequiredArg().ofType(String.class).describedAs("directory");
OptionSpec<String> pidFile = parser.accepts("pidFile", "File location where the PID of the process will be read from").withRequiredArg().ofType(String.class).describedAs("pidFile");
OptionSpec<String> event = parser.accepts("event", "Event to sample: cpu, alloc, lock, cache-misses etc.").withRequiredArg().ofType(String.class).defaultsTo("cpu");
OptionSpec<Long> framebuf = parser.accepts("framebuf", "Size of profiler framebuffer").withRequiredArg().ofType(Long.class).defaultsTo(DEFAULT_FRAMEBUF);
OptionSpec<Long> interval = parser.accepts("interval", "Profiling interval, in nanoseconds").withRequiredArg().ofType(Long.class).defaultsTo(DEFAULT_INTERVAL);
OptionSpec<Boolean> threads = parser.accepts("threads", "Profile threads separately").withRequiredArg().ofType(Boolean.class).defaultsTo(false,true);
OptionSpec<Boolean> verbose = parser.accepts("verbose", "Output the sequence of commands").withRequiredArg().ofType(Boolean.class).defaultsTo(false);
OptionSpec<String> flameGraphOpts = parser.accepts("flameGraphOpts", "Options passed to FlameGraph.pl").withRequiredArg().withValuesSeparatedBy(',').ofType(String.class);
OptionSpec<Directions> flameGraphDirection = parser.accepts("flameGraphDirection", "Directions to generate flamegraphs").withRequiredArg().ofType(Directions.class).defaultsTo(Directions.values());
OptionSpec<String> flameGraphDir = ProfilerUtils.addFlameGraphDirOption(parser);
OptionSpec<Boolean> simpleName = parser.accepts("simpleName", "Use simple names in flamegraphs").withRequiredArg().ofType(Boolean.class);
OptionSpec<Boolean> jfr = parser.accepts("jfr", "Also dump profiles from async-profiler in Java Flight Recorder format").withRequiredArg().ofType(Boolean.class);

OptionSet options = ProfilerUtils.parseInitLine(initLine, parser);
if (options.has(event)) {
this.event = options.valueOf(event);
} else {
this.event = DEFAULT_EVENT;
}
if (options.has(framebuf)) {
this.framebuf = options.valueOf(framebuf);
} else {
this.framebuf = DEFAULT_FRAMEBUF;
}
if (options.has(interval)) {
this.interval = options.valueOf(interval);
} else {
this.interval = DEFAULT_INTERVAL;
}
if (options.has(outputDir)) {
this.outputDir = Paths.get(options.valueOf(outputDir));
createOutputDirectories();
}

if (options.has(flameGraphOpts)) {
this.flameGraphOpts = options.valuesOf(flameGraphOpts);
}
if (options.has(flameGraphDirection)) {
this.directions = options.valueOf(flameGraphDirection);
} else {
this.directions = Directions.BOTH;
}
if (options.has(threads)) {
this.threads = options.valueOf(threads);
} else {
this.threads = false;
}
if (options.has(verbose)) {
this.verbose = options.valueOf(verbose);
}
if (options.has(simpleName)) {
this.simpleName = options.valueOf(simpleName);
} else {
this.simpleName = false;
}
if (options.has(jfr)) {
this.jfr = options.valueOf(jfr);
} else {
this.jfr = false;
}
this.flameGraphDir = ProfilerUtils.findFlamegraphDir(flameGraphDir, options);
this.asyncProfilerDir = lookupAsyncProfilerHome(asyncProfilerDir, options);
this.pidFile = lookupPidFile(pidFile, options);
Path build = this.asyncProfilerDir.resolve("build");
Path profiler1 = build.resolve("libasyncProfiler.so");
if (!Files.exists(profiler1)) {
throw new ProfilerException(profiler1 + " does not exist");
} else {
this.profiler = profiler1;
Path jattach1 = build.resolve("jattach");
if (!Files.exists(jattach1)) {
throw new ProfilerException(jattach1 + " does not exist");
} else {
this.jattach = jattach1;
}
}

}

private Path lookupPidFile(OptionSpec<String> pidFile, OptionSet options) throws ProfilerException {
if (options.has(pidFile)) {
return Paths.get(options.valueOf(pidFile));
} else {
throw new ProfilerException("Location of pid file must be provided to enable async profiler");
}
}

private Path lookupAsyncProfilerHome(OptionSpec<String> asyncProfilerDir, OptionSet options) throws ProfilerException {
if (options.has(asyncProfilerDir)) {
return Paths.get(options.valueOf(asyncProfilerDir));
} else {
String env = System.getenv(ASYNC_PROFILER_DIR);
if (env == null) {
throw new ProfilerException("Location of async-profiler-dir must be set with environment variable ASYNC_PROFILER_DIR or corresponding profiler option");
}
return Paths.get(env);
}
}

private void createOutputDirectories() {
try {
Files.createDirectories(this.outputDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) {
if (!started && iterationParams.getType() == IterationType.MEASUREMENT) {
try {
if (this.pid == -1) {
this.pid = Long.parseLong(new String(Files.readAllBytes(this.pidFile)));
}
if (outputDir == null) {
outputDir = createTempDir(benchmarkParams.id().replaceAll("/", "-"));
}

String threadOpt = this.threads ? ",threads" : "";
String jfrOpt = this.jfr ? ",jfr,file=" + jfrFile().toAbsolutePath().toString() : "";
profilerCommand(String.format("start,event=%s%s%s,framebuf=%d,interval=%d", event, jfrOpt, threadOpt, framebuf, interval));
started = true;
} catch (IOException e) {
throw new RuntimeException("PID file " + this.pidFile.toAbsolutePath().toString() + " could not be read!");
}
}
}

@Override
public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
if (iterationParams.getType() == IterationType.MEASUREMENT) {
measurementIterationCount += 1;
if (measurementIterationCount == iterationParams.getCount()) {
if (jfr) {
Path jfrDump = jfrFile();
generated.add(jfrDump);
profilerCommand(String.format("stop,file=%s,jfr", jfrDump));
}
Path collapsedPath = outputDir.resolve("collapsed-" + event.toLowerCase() + ".txt");
profilerCommand(String.format("stop,file=%s,collapsed", collapsedPath));
generated.add(collapsedPath);
Path collapsedProcessedPath = collapsedPath;
if (simpleName) {
collapsedProcessedPath = outputDir.resolve("collapsed-simple-" + event.toLowerCase() + ".txt");
generated.add(collapsedProcessedPath);
replaceAllInFileLines(collapsedPath, collapsedProcessedPath, Pattern.compile("(^|;)[^;]*\\/"));
}

Path summaryPath = outputDir.resolve("summary.txt");
profilerCommand(String.format("stop,file=%s,summary", summaryPath));
generated.add(summaryPath);
if (flameGraphDir != null) {
if (EnumSet.of(Directions.FORWARD, Directions.BOTH).contains(directions)) {
flameGraph(collapsedProcessedPath, Collections.emptyList(), "");
}
if (EnumSet.of(Directions.REVERSE, Directions.BOTH).contains(directions)) {
flameGraph(collapsedProcessedPath, Arrays.asList("--reverse"), "-reverse");
}
}
}
}

return Collections.singletonList(result());
}

private Path jfrFile() {
return outputDir.resolve("profile-" + event.toLowerCase() + ".jfr");
}

private void replaceAllInFileLines(Path in, Path out, Pattern pattern) {
try (Stream<String> lines = Files.lines(in)){
Stream<CharSequence> mapped = lines.map(line -> pattern.matcher(line).replaceAll("$1"));
Files.write(out, mapped::iterator);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private void flameGraph(Path collapsedPath, List<String> extra, String suffix) {
generated.add(ProfilerUtils.flameGraph(collapsedPath, extra, suffix, flameGraphDir, flameGraphOpts, outputDir, event, verbose));
}

private NoResult result() {
StringBuilder result = new StringBuilder();
for (Path path : generated) {
result.append("\n").append(path.toAbsolutePath().toString());
}
return new NoResult("async-profiler", result.toString());
}

public static synchronized long getPidOfProcess(Process p) {
long pid = -1;

try {
if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getLong(p);
f.setAccessible(false);
}
} catch (Exception e) {
pid = -1;
}
return pid;
}

private void profilerCommand(String command) {
long pid = this.pid;

ProcessBuilder processBuilder = new ProcessBuilder(jattach.toAbsolutePath().toString(), String.valueOf(pid), "load", profiler.toAbsolutePath().toString(), "true", command);
if (verbose) {
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
}
ProfilerUtils.startAndWait(processBuilder, verbose);
}

private Path createTempDir(String prefix) {
try {
return Files.createTempDirectory(prefix);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public String getDescription() {
return "Profiling using async-profiler";
}
}
Loading