package org.openjdk.jmh.profile;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSpec;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.util.*;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
public class LinuxPerfAsmProfiler extends AbstractPerfAsmProfiler {
private final long sampleFrequency;
private OptionSpec<Long> optFrequency;
public LinuxPerfAsmProfiler(String initLine) throws ProfilerException {
super(initLine, "cycles");
Collection<String> failMsg = Utils.tryWith(PerfSupport.PERF_EXEC, "stat", "--log-fd", "2", "echo", "1");
if (!failMsg.isEmpty()) {
throw new ProfilerException(failMsg.toString());
}
try {
sampleFrequency = set.valueOf(optFrequency);
} catch (OptionException e) {
throw new ProfilerException(e.getMessage());
}
}
@Override
protected void addMyOptions(OptionParser parser) {
optFrequency = parser.accepts("frequency",
"Sampling frequency. This is synonymous to perf record --freq #")
.withRequiredArg().ofType(Long.class).describedAs("freq").defaultsTo(1000L);
}
@Override
public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
return Arrays.asList(PerfSupport.PERF_EXEC, "record", "--freq", String.valueOf(sampleFrequency), "--event", Utils.join(requestedEventNames, ","), "--output", perfBinData.getAbsolutePath());
}
@Override
public String getDescription() {
return "Linux perf + PrintAssembly Profiler";
}
@Override
protected void parseEvents() {
try (FileOutputStream fos = new FileOutputStream(perfParsedData.file())) {
ProcessBuilder pb = new ProcessBuilder(PerfSupport.PERF_EXEC, "script", "--fields", "time,event,ip,sym,dso", "--input", perfBinData.getAbsolutePath());
Process p = pb.start();
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);
}
}
static PerfLine parsePerfLine(String line) {
if (line.startsWith("#")) {
return null;
}
int lastLength = -1;
while (line.length() != lastLength) {
lastLength = line.length();
line = line.replace(" ", " ");
}
int timeIdx = line.indexOf(": ");
if (timeIdx == -1) return null;
String strTime = line.substring(0, timeIdx);
line = line.substring(timeIdx + 2);
double time;
try {
time = Double.parseDouble(strTime);
} catch (NumberFormatException e) {
return null;
}
int libIdx = line.lastIndexOf(" (");
if (libIdx == -1) return null;
String lib = line.substring(libIdx);
lib = lib.substring(lib.lastIndexOf("/") + 1).replace("(", "").replace(")", "");
line = line.substring(0, libIdx);
int evIdx = line.indexOf(": ");
if (evIdx == -1) return null;
String evName = line.substring(0, evIdx);
int tagIdx = evName.lastIndexOf(":");
if (tagIdx != -1) {
evName = evName.substring(0, tagIdx);
}
line = line.substring(evIdx + 2);
int addrIdx = line.indexOf(" ");
if (addrIdx == -1) return null;
String strAddr = line.substring(0, addrIdx);
line = line.substring(addrIdx + 1);
long addr;
try {
addr = Long.parseLong(strAddr, 16);
} catch (NumberFormatException e) {
try {
addr = new BigInteger(strAddr, 16).longValue();
if (addr < 0L && lib.contains("unknown")) {
lib = "kernel";
}
} catch (NumberFormatException e1) {
addr = 0L;
}
}
String symbol = line;
return new PerfLine(time, evName, addr, symbol, lib);
}
static class PerfLine {
final double time;
final String event;
final long addr;
final String symbol;
final String lib;
public PerfLine(double time, String event, long addr, String symbol, String lib) {
this.time = time;
this.event = event;
this.addr = addr;
this.symbol = symbol;
this.lib = lib;
}
public double time() {
return time;
}
public String eventName() {
return event;
}
public long addr() {
return addr;
}
public String symbol() {
return symbol;
}
public String lib() {
return lib;
}
}
@Override
protected PerfEvents readEvents(double skipMs, double lenMs) {
double readFrom = skipMs / 1000D;
double readTo = (skipMs + lenMs) / 1000D;
List<String> evNames = stripEventNames(requestedEventNames);
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 : evNames) {
events.put(evName, new TreeMultiset<Long>());
}
Double startTime = null;
String line;
while ((line = reader.readLine()) != null) {
PerfLine perfline = parsePerfLine(line);
if (perfline == null) {
continue;
}
if (startTime == null) {
startTime = perfline.time();
} else {
if (perfline.time() - startTime < readFrom) {
continue;
}
if (perfline.time() - startTime > readTo) {
continue;
}
}
Multiset<Long> evs = events.get(perfline.eventName());
if (evs == null) {
continue;
}
evs.add(perfline.addr());
MethodDesc desc = dedup.dedup(MethodDesc.nativeMethod(perfline.symbol(), perfline.lib()));
methods.put(desc, perfline.addr());
}
IntervalMap<MethodDesc> methodMap = new IntervalMap<>();
for (MethodDesc md : methods.keys()) {
Collection<Long> addrs = methods.get(md);
methodMap.add(md, Utils.min(addrs), Utils.max(addrs));
}
return new PerfEvents(evNames, events, methodMap);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected String perfBinaryExtension() {
return ".perfbin";
}
@Override
protected List<String> stripEventNames(List<String> events) {
return stripPerfEventNames(events);
}
static List<String> stripPerfEventNames(List<String> events) {
List<String> res = new ArrayList<>();
for (String ev : events) {
int tagIdx = ev.indexOf(':');
if (tagIdx != -1) {
res.add(ev.substring(0, tagIdx));
} else {
res.add(ev);
}
}
return res;
}
}