/*
* Copyright (c) 2014, 2014, 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.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSpec;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.results.BenchmarkResult;
import org.openjdk.jmh.results.Result;
import org.openjdk.jmh.util.*;
import java.io.*;
import java.util.*;
Windows performance profiler based on "xperf" utility.
You must install Windows Performance Toolkit
. Once installed, locate directory with xperf.exe
file and either add it to PATH
environment variable, or set it to jmh.perfasm.xperf.dir
system property.
This profiler counts only SampledProfile
events. To achieve this, we set xperf
providers to loader+proc_thread+profile
. You may optionally save xperf
binary or parsed outputs using jmh.perfasm.savePerfBin
or jmh.perfasm.savePerf
system properties respectively. If you do so and want to log more events, you can use jmh.perfasm.xperf.providers
system property to override providers. However, you must specify loader
, proc_thread
and profile
providers anyway. Otherwise sample events will not be generated and profiler will show nothing.
By default JDK distributive do not have debug symbols. If you want to analyze JVM internals, you must build OpenJDK on your own. Once built, go to bin/server
directory and unpack jvm.diz
. Now you have jvm.pdb
file with JVM debug symbols. Finally, you must set debug symbols directory to jmh.perfasm.symbol.dir
system property.
This profiler behaves differently comparing to it's Linux counterpart LinuxPerfAsmProfiler
. Linux profiler employs perf
utility which can be used to profile a single process. Therefore, Linux profiler wraps forked JVM command line. In contrast, xperf
cannot profile only a single process. It have -PidNewProcess
argument, but it's sole purpose is to start profiling before the process is started, so that one can be sure that none events generated by this process are missed. It does not filter events from other processes anyhow. For this reason, this profiler doesn't alter forked JVM startup command. Instead, it starts xperf
recording in beforeTrial(BenchmarkParams)
method, and stops in ExternalProfiler.afterTrial(BenchmarkResult, long, File, File)
. This leaves possibility to run this profiler in conjunction with some other profiler requiring startup command alteration.
For this reason the profiler must know PID of forked JVM process.
/**
* Windows performance profiler based on "xperf" utility.
* <p>
* You must install {@code Windows Performance Toolkit}. Once installed, locate directory with {@code xperf.exe}
* file and either add it to {@code PATH} environment variable, or set it to {@code jmh.perfasm.xperf.dir} system
* property.
* <p>
* This profiler counts only {@code SampledProfile} events. To achieve this, we set {@code xperf} providers to
* {@code loader+proc_thread+profile}. You may optionally save {@code xperf} binary or parsed outputs using
* {@code jmh.perfasm.savePerfBin} or {@code jmh.perfasm.savePerf} system properties respectively. If you do so and
* want to log more events, you can use {@code jmh.perfasm.xperf.providers} system property to override providers.
* However, you must specify {@code loader}, {@code proc_thread} and {@code profile} providers anyway. Otherwise
* sample events will not be generated and profiler will show nothing.
* <p>
* By default JDK distributive do not have debug symbols. If you want to analyze JVM internals, you must build OpenJDK
* on your own. Once built, go to {@code bin/server} directory and unpack {@code jvm.diz}. Now you have {@code jvm.pdb}
* file with JVM debug symbols. Finally, you must set debug symbols directory to {@code jmh.perfasm.symbol.dir} system
* property.
* <p>
* This profiler behaves differently comparing to it's Linux counterpart {@link LinuxPerfAsmProfiler}. Linux profiler
* employs {@code perf} utility which can be used to profile a single process. Therefore, Linux profiler wraps forked
* JVM command line. In contrast, {@code xperf} cannot profile only a single process. It have {@code -PidNewProcess}
* argument, but it's sole purpose is to start profiling before the process is started, so that one can be sure that
* none events generated by this process are missed. It does not filter events from other processes anyhow. For this
* reason, this profiler doesn't alter forked JVM startup command. Instead, it starts {@code xperf} recording in
* {@link #beforeTrial(BenchmarkParams)} method, and stops in {@link ExternalProfiler#afterTrial(org.openjdk.jmh.results.BenchmarkResult, long, java.io.File, java.io.File)}. This
* leaves possibility to run this profiler in conjunction with some other profiler requiring startup command
* alteration.
* <p>
* For this reason the profiler must know PID of forked JVM process.
*/
public class WinPerfAsmProfiler extends AbstractPerfAsmProfiler {
private final String xperfProviders;
private final String symbolDir;
private final String path;
PID. /** PID. */
private volatile String pid;
private OptionSpec<String> optXperfDir;
private OptionSpec<String> optXperfProviders;
private OptionSpec<String> optSymbolDir;
Constructor.
/**
* Constructor.
*/
public WinPerfAsmProfiler(String initLine) throws ProfilerException {
super(initLine, "SampledProfile");
try {
String xperfDir = set.valueOf(optXperfDir);
xperfProviders = set.valueOf(optXperfProviders);
symbolDir = set.valueOf(optSymbolDir);
path = xperfDir != null && !xperfDir.isEmpty() ? xperfDir + File.separatorChar + "xperf" : "xperf";
} catch (OptionException e) {
throw new ProfilerException(e.getMessage());
}
Collection<String> errs = Utils.tryWith(path);
if (!errs.isEmpty()) {
throw new ProfilerException(errs.toString());
}
}
@Override
protected void addMyOptions(OptionParser parser) {
optXperfDir = parser.accepts("xperf.dir",
"Path to \"xperf\" installation directory. Empty by default, so that xperf is expected to be in PATH.")
.withRequiredArg().ofType(String.class).describedAs("path");
optXperfProviders = parser.accepts("xperf.providers",
"xperf providers to use.")
.withRequiredArg().ofType(String.class).describedAs("string").defaultsTo("loader+proc_thread+profile");
optSymbolDir = parser.accepts("symbol.dir",
"Path to a directory with jvm.dll symbols (optional).")
.withRequiredArg().ofType(String.class).describedAs("string");
}
@Override
public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
// "xperf" cannot be started to track particular process as "perf" in Linux does.
// Therefore we do not alter JVM invoke options anyhow. Instead, profiler will be started
// during "before-trial" stage.
return Collections.emptyList();
}
@Override
public void beforeTrial(BenchmarkParams params) {
// Start profiler before forked JVM is started.insta
Collection<String> errs = Utils.tryWith(path, "-on", xperfProviders);
if (!errs.isEmpty())
throw new IllegalStateException("Failed to start xperf: " + errs);
}
@Override
public Collection<? extends Result> afterTrial(BenchmarkResult br, long pid, File stdOut, File stdErr) {
if (pid == 0) {
throw new IllegalStateException("perfasm needs the forked VM PID, but it is not initialized.");
}
this.pid = String.valueOf(pid);
return super.afterTrial(br, pid, stdOut, stdErr);
}
@Override
public String getDescription() {
return "Windows xperf + PrintAssembly Profiler";
}
@Override
protected void parseEvents() {
// 1. Stop profiling by calling xperf dumper.
Collection<String> errs = Utils.tryWith(path, "-d", perfBinData.getAbsolutePath());
if (!errs.isEmpty())
throw new IllegalStateException("Failed to stop xperf: " + errs);
// 2. Convert binary data to text form.
try {
ProcessBuilder pb = new ProcessBuilder(path, "-i", perfBinData.getAbsolutePath(), "-symbols", "-a", "dumper");
if (symbolDir != null) {
pb.environment().put("_NT_SYMBOL_PATH", symbolDir);
}
Process p = pb.start();
FileOutputStream fos = new FileOutputStream(perfParsedData.file());
InputStreamDrainer errDrainer = new InputStreamDrainer(p.getErrorStream(), fos);
InputStreamDrainer outDrainer = new InputStreamDrainer(p.getInputStream(), fos);
errDrainer.start();
outDrainer.start();
p.waitFor();
errDrainer.join();
outDrainer.join();
} catch (IOException | InterruptedException ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected PerfEvents readEvents(double skipMs, double lenMs) {
double readFrom = skipMs / 1000D;
double readTo = (skipMs + lenMs) / 1000D;
try (FileReader fr = new FileReader(perfParsedData.file());
BufferedReader reader = new BufferedReader(fr)) {
Deduplicator<MethodDesc> dedup = new Deduplicator<>();
Multimap<MethodDesc, Long> methods = new HashMultimap<>();
Map<String, Multiset<Long>> events = new LinkedHashMap<>();
for (String evName : this.events) {
events.put(evName, new TreeMultiset<Long>());
}
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
String[] elems = line.split(",");
String evName = elems[0].trim();
// We work with only one event type - "SampledProfile".
if (!this.events.get(0).equals(evName))
continue;
// Check PID.
String pidStr = elems[2].trim();
int pidOpenIdx = pidStr.indexOf("(");
int pidCloseIdx = pidStr.indexOf(")");
if (pidOpenIdx == -1 || pidCloseIdx == -1 || pidCloseIdx < pidOpenIdx)
continue; // Malformed PID, probably this is the header.
pidStr = pidStr.substring(pidOpenIdx + 1, pidCloseIdx).trim();
if (!pid.equals(pidStr))
continue;
// Check timestamp
String timeStr = elems[1].trim();
double time = Double.valueOf(timeStr) / 1000000;
if (time < readFrom)
continue;
if (time > readTo)
continue;
// Get address.
String addrStr = elems[4].trim().replace("0x", "");
// Get lib and function name.
String libSymStr = elems[7].trim();
String lib = libSymStr.substring(0, libSymStr.indexOf('!'));
String symbol = libSymStr.substring(libSymStr.indexOf('!') + 1);
Multiset<Long> evs = events.get(evName);
assert evs != null;
try {
Long addr = Long.valueOf(addrStr, 16);
evs.add(addr);
methods.put(dedup.dedup(MethodDesc.nativeMethod(symbol, lib)), addr);
} catch (NumberFormatException e) {
// kernel addresses like "ffffffff810c1b00" overflow signed long,
// record them as dummy address
evs.add(0L);
}
}
IntervalMap<MethodDesc> methodMap = new IntervalMap<>();
for (MethodDesc md : methods.keys()) {
Collection<Long> longs = methods.get(md);
methodMap.add(md, Utils.min(longs), Utils.max(longs));
}
return new PerfEvents(this.events, events, methodMap);
} catch (IOException e) {
return new PerfEvents(events);
}
}
@Override
protected String perfBinaryExtension() {
// Files with ".etl" extension can be opened by "Windows Performance Analyzer" right away.
return ".etl";
}
}