/*
 * 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"; } }