org.openjdk.jmh.profile.LinuxPerfNormProfiler Maven / Gradle / Ivy
/*
* 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.OptionSet;
import joptsimple.OptionSpec;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.results.*;
import org.openjdk.jmh.util.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.*;
public class LinuxPerfNormProfiler implements ExternalProfiler {
/** This is a non-exhaustive list of events we care about. */
private static final String[] interestingEvents = new String[]{
"cycles", "instructions",
"branches", "branch-misses",
"L1-dcache-loads", "L1-dcache-load-misses",
"L1-dcache-stores", "L1-dcache-store-misses",
"L1-icache-loads", "L1-icache-load-misses",
"LLC-loads", "LLC-load-misses",
"LLC-stores", "LLC-store-misses",
"dTLB-loads", "dTLB-load-misses",
"dTLB-stores", "dTLB-store-misses",
"iTLB-loads", "iTLB-load-misses",
"stalled-cycles-frontend", "stalled-cycles-backend",
};
private final int delayMs;
private final int lengthMs;
private final boolean useDefaultStats;
private final long highPassFilter;
private final int incrementInterval;
private final boolean isIncrementable;
private final Collection supportedEvents = new ArrayList<>();
public LinuxPerfNormProfiler(String initLine) throws ProfilerException {
OptionParser parser = new OptionParser();
parser.formatHelpWith(new ProfilerOptionFormatter("perfnorm"));
OptionSpec optEvents = parser.accepts("events",
"Events to gather.")
.withRequiredArg().ofType(String.class).withValuesSeparatedBy(",").describedAs("event+");
OptionSpec optDelay = parser.accepts("delay",
"Delay collection for a given time, in milliseconds; -1 to detect automatically.")
.withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(-1);
OptionSpec optLength = parser.accepts("length",
"Do the collection for a given time, in milliseconds; -1 to detect automatically.")
.withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(-1);
OptionSpec optIncrementInterval = parser.accepts("interval",
"The interval between incremental updates from a concurrently running perf. " +
"Lower values may improve accuracy, while increasing the profiling overhead.")
.withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(100);
OptionSpec optHighPassFilter = parser.accepts("highPassFilter",
"Ignore event increments larger that this.")
.withRequiredArg().ofType(Long.class).describedAs("#").defaultsTo(100000000000L);
OptionSpec optDefaultStat = parser.accepts("useDefaultStat",
"Use \"perf stat -d -d -d\" instead of explicit counter list.")
.withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
OptionSet set = ProfilerUtils.parseInitLine(initLine, parser);
Collection userEvents;
try {
delayMs = set.valueOf(optDelay);
lengthMs = set.valueOf(optLength);
incrementInterval = set.valueOf(optIncrementInterval);
highPassFilter = set.valueOf(optHighPassFilter);
useDefaultStats = set.valueOf(optDefaultStat);
userEvents = set.valuesOf(optEvents);
} catch (OptionException e) {
throw new ProfilerException(e.getMessage());
}
Collection msgs = Utils.tryWith(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "--field-separator", ",", "echo", "1");
if (!msgs.isEmpty()) {
throw new ProfilerException(msgs.toString());
}
Collection incremental = Utils.tryWith(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "--field-separator", ",", "--interval-print", String.valueOf(incrementInterval), "echo", "1");
isIncrementable = incremental.isEmpty();
if (userEvents != null) {
for (String ev : userEvents) {
if (ev.trim().isEmpty()) continue;
supportedEvents.add(ev);
}
}
if (supportedEvents.isEmpty()) {
for (String ev : interestingEvents) {
Collection res = Utils.tryWith(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "--field-separator", ",", "--event", ev, "echo", "1");
if (res.isEmpty()) {
supportedEvents.add(ev);
}
}
}
}
@Override
public Collection addJVMInvokeOptions(BenchmarkParams params) {
List cmd = new ArrayList<>();
if (useDefaultStats) {
cmd.addAll(Arrays.asList(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "--field-separator", ",", "--detailed", "--detailed", "--detailed"));
} else {
cmd.addAll(Arrays.asList(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "--field-separator", ",", "--event", Utils.join(supportedEvents, ",")));
}
if (isIncrementable) {
cmd.addAll(Arrays.asList("-I", String.valueOf(incrementInterval)));
}
return cmd;
}
@Override
public Collection addJVMOptions(BenchmarkParams params) {
return Collections.emptyList();
}
@Override
public void beforeTrial(BenchmarkParams params) {
// do nothing
}
@Override
public Collection extends Result> afterTrial(BenchmarkResult br, long pid, File stdOut, File stdErr) {
return process(br, stdOut, stdErr);
}
@Override
public boolean allowPrintOut() {
return true;
}
@Override
public boolean allowPrintErr() {
return false;
}
@Override
public String getDescription() {
return "Linux perf statistics, normalized by operation count";
}
private Collection extends Result> process(BenchmarkResult br, File stdOut, File stdErr) {
Multiset events = new HashMultiset<>();
try (FileReader fr = new FileReader(stdErr);
BufferedReader reader = new BufferedReader(fr)) {
long skipMs;
if (delayMs == -1) { // not set
skipMs = ProfilerUtils.measurementDelayMs(br);
} else {
skipMs = delayMs;
}
double lenMs;
if (lengthMs == -1) { // not set
lenMs = ProfilerUtils.measuredTimeMs(br);
} else {
lenMs = lengthMs;
}
double readFrom = skipMs / 1000D;
double softTo = (skipMs + lenMs) / 1000D;
double readTo = (skipMs + lenMs + incrementInterval) / 1000D;
NumberFormat nf = NumberFormat.getInstance();
String line;
nextline:
while ((line = reader.readLine()) != null) {
if (line.startsWith("#")) continue;
if (isIncrementable) {
String[] split = line.split(",");
String time;
String count;
String event;
if (split.length == 3) {
// perf 3.13: time,count,event
time = split[0].trim();
count = split[1].trim();
event = split[2].trim();
} else if (split.length >= 4) {
// perf >3.13: time,count,,event,
time = split[0].trim();
count = split[1].trim();
event = split[3].trim();
} else {
// Malformed line, ignore
continue nextline;
}
double multiplier = 1D;
try {
double timeSec = nf.parse(time).doubleValue();
if (timeSec < readFrom) {
// warmup, ignore
continue nextline;
}
if (timeSec > readTo) {
// post-run, ignore
continue nextline;
}
// Handle partial events:
double intervalSec = incrementInterval / 1000D;
if (timeSec - intervalSec < readFrom) {
// Event _starts_ before the measurement window
// .............[============|============
// readFrom timeSec
// [<----------------->| // event
// incrementInterval
//
// Only count the tail after readFrom:
multiplier = (timeSec - readFrom) / intervalSec;
}
if (timeSec > softTo) {
// Event is past the measurement window
// =============].............|............
// softTo timeSec
// [<----------------->| // event
// incrementInterval
//
// Only count the head before softTo:
multiplier = 1 - (timeSec - softTo) / intervalSec;
}
// Defensive, keep multiplier in bounds:
multiplier = Math.max(1D, Math.min(0D, multiplier));
} catch (ParseException e) {
// don't care then, continue
continue nextline;
}
try {
long lValue = nf.parse(count).longValue();
if (lValue > highPassFilter) {
// anomalous value, pretend we did not see it
continue nextline;
}
events.add(event, (long) (lValue * multiplier));
} catch (ParseException e) {
// do nothing, continue
continue nextline;
}
} else {
int idx = line.lastIndexOf(",");
// Malformed line, ignore
if (idx == -1) continue nextline;
String count = line.substring(0, idx).trim();
String event = line.substring(idx + 1).trim();
try {
long lValue = nf.parse(count).longValue();
events.add(event, lValue);
} catch (ParseException e) {
// do nothing, continue
continue nextline;
}
}
}
if (!isIncrementable) {
System.out.println();
System.out.println();
System.out.println("WARNING: Your system uses old \"perf\", which cannot print data incrementally (-I).\n" +
"Therefore, perf performance data includes benchmark warmup.");
}
long totalOpts;
BenchmarkResultMetaData md = br.getMetadata();
if (md != null) {
if (isIncrementable) {
totalOpts = md.getMeasurementOps();
} else {
totalOpts = md.getWarmupOps() + md.getMeasurementOps();
}
Collection results = new ArrayList<>();
for (String key : events.keys()) {
results.add(new PerfResult(key, events.count(key) * 1.0 / totalOpts));
}
// Also figure out CPI, if enough counters available:
{
long cycles = events.count("cycles");
long instructions = events.count("instructions");
if (cycles != 0 && instructions != 0) {
results.add(new PerfResult("CPI", 1.0 * cycles / instructions));
}
}
return results;
} else {
return Collections.singleton(new PerfResult("N/A", Double.NaN));
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
static class PerfResult extends ScalarResult {
private static final long serialVersionUID = -1262685915873231436L;
public PerfResult(String key, double value) {
super(key, value, "#/op", AggregationPolicy.AVG);
}
@Override
public String extendedInfo() {
// omit printing in extended info
return "";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy