blob: 32145fd32c736a89139fa89f93b28098bf1556a1 [file] [log] [blame]
//===-- DataAggregator.cpp - Perf data aggregator ---------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This family of functions reads profile data written by perf record,
// aggregate it and then write it back to an output file.
//
//===----------------------------------------------------------------------===//
#include "BinaryContext.h"
#include "BinaryFunction.h"
#include "DataAggregator.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Options.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/Timer.h"
#include <unistd.h>
#define DEBUG_TYPE "aggregator"
using namespace llvm;
using namespace bolt;
namespace opts {
extern cl::OptionCategory AggregatorCategory;
static llvm::cl::opt<bool>
TimeAggregator("time-aggr",
cl::desc("time BOLT aggregator"),
cl::init(false),
cl::ZeroOrMore,
cl::cat(AggregatorCategory));
}
namespace {
const char TimerGroupName[] = "aggregator";
const char TimerGroupDesc[] = "Aggregator";
}
void DataAggregator::findPerfExecutable() {
auto PerfExecutable = sys::Process::FindInEnvPath("PATH", "perf");
if (!PerfExecutable) {
outs() << "PERF2BOLT: No perf executable found!\n";
exit(1);
}
PerfPath = *PerfExecutable;
}
void DataAggregator::start(StringRef PerfDataFilename) {
Enabled = true;
this->PerfDataFilename = PerfDataFilename;
outs() << "PERF2BOLT: Starting data aggregation job for " << PerfDataFilename
<< "\n";
findPerfExecutable();
launchPerfBranchEventsNoWait();
launchPerfMemEventsNoWait();
launchPerfTasksNoWait();
}
void DataAggregator::abort() {
std::string Error;
// Kill subprocesses in case they are not finished
sys::Wait(TasksPI, 1, false, &Error);
sys::Wait(BranchEventsPI, 1, false, &Error);
sys::Wait(MemEventsPI, 1, false, &Error);
deleteTempFiles();
}
bool DataAggregator::launchPerfBranchEventsNoWait() {
SmallVector<const char*, 4> Argv;
outs() << "PERF2BOLT: Spawning perf-script job to read branch events\n";
Argv.push_back(PerfPath.data());
Argv.push_back("script");
Argv.push_back("-F");
Argv.push_back("pid,brstack");
Argv.push_back("-i");
Argv.push_back(PerfDataFilename.data());
Argv.push_back(nullptr);
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "out",
PerfBranchEventsOutputPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfBranchEventsOutputPath << " with error " << Errc.message() << "\n";
exit(1);
}
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "err",
PerfBranchEventsErrPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfBranchEventsErrPath << " with error " << Errc.message() << "\n";
exit(1);
}
Optional<StringRef> Redirects[] = {
llvm::None, // Stdin
StringRef(PerfBranchEventsOutputPath.data()), // Stdout
StringRef(PerfBranchEventsErrPath.data())}; // Stderr
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
<< PerfBranchEventsOutputPath.data() << " 2> "
<< PerfBranchEventsErrPath.data() << "\n");
BranchEventsPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
/*envp*/ nullptr, Redirects);
return true;
}
bool DataAggregator::launchPerfMemEventsNoWait() {
SmallVector<const char*, 4> Argv;
outs() << "PERF2BOLT: Spawning perf-script job to read mem events\n";
Argv.push_back(PerfPath.data());
Argv.push_back("script");
Argv.push_back("-F");
Argv.push_back("pid,event,addr,ip");
Argv.push_back("-i");
Argv.push_back(PerfDataFilename.data());
Argv.push_back(nullptr);
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "out",
PerfMemEventsOutputPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfMemEventsOutputPath << " with error " << Errc.message() << "\n";
exit(1);
}
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "err",
PerfMemEventsErrPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfMemEventsErrPath << " with error " << Errc.message() << "\n";
exit(1);
}
Optional<StringRef> Redirects[] = {
llvm::None, // Stdin
StringRef(PerfMemEventsOutputPath.data()), // Stdout
StringRef(PerfMemEventsErrPath.data())}; // Stderr
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
<< PerfMemEventsOutputPath.data() << " 2> "
<< PerfMemEventsErrPath.data() << "\n");
MemEventsPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
/*envp*/ nullptr, Redirects);
return true;
}
bool DataAggregator::launchPerfTasksNoWait() {
SmallVector<const char*, 4> Argv;
outs() << "PERF2BOLT: Spawning perf-script job to read tasks\n";
Argv.push_back(PerfPath.data());
Argv.push_back("script");
Argv.push_back("--show-task-events");
Argv.push_back("-i");
Argv.push_back(PerfDataFilename.data());
Argv.push_back(nullptr);
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "out",
PerfTasksOutputPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfTasksOutputPath << " with error " << Errc.message() << "\n";
exit(1);
}
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "err",
PerfTasksErrPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< PerfTasksErrPath << " with error " << Errc.message() << "\n";
exit(1);
}
Optional<StringRef> Redirects[] = {
llvm::None, // Stdin
StringRef(PerfTasksOutputPath.data()), // Stdout
StringRef(PerfTasksErrPath.data())}; // Stderr
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
<< PerfTasksOutputPath.data() << " 2> "
<< PerfTasksErrPath.data() << "\n");
TasksPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
/*envp*/ nullptr, Redirects);
return true;
}
Optional<std::string> DataAggregator::getPerfBuildID() {
SmallVector<const char *, 4> Argv;
SmallVector<char, 256> OutputPath;
SmallVector<char, 256> ErrPath;
Argv.push_back(PerfPath.data());
Argv.push_back("buildid-list");
Argv.push_back("-i");
Argv.push_back(PerfDataFilename.data());
Argv.push_back(nullptr);
if (auto Errc = sys::fs::createTemporaryFile("perf.buildid", "out",
OutputPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< OutputPath << " with error " << Errc.message() << "\n";
exit(1);
}
if (auto Errc = sys::fs::createTemporaryFile("perf.script", "err",
ErrPath)) {
outs() << "PERF2BOLT: Failed to create temporary file "
<< ErrPath << " with error " << Errc.message() << "\n";
exit(1);
}
Optional<StringRef> Redirects[] = {
llvm::None, // Stdin
StringRef(OutputPath.data()), // Stdout
StringRef(ErrPath.data())}; // Stderr
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
<< OutputPath.data() << " 2> "
<< ErrPath.data() << "\n");
auto RetCode = sys::ExecuteAndWait(PerfPath.data(), Argv.data(),
/*envp*/ nullptr, Redirects);
if (RetCode != 0) {
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(ErrPath.data());
StringRef ErrBuf = (*MB)->getBuffer();
errs() << "PERF-ERROR: Return code " << RetCode << "\n";
errs() << ErrBuf;
deleteTempFile(ErrPath.data());
deleteTempFile(OutputPath.data());
return NoneType();
}
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(OutputPath.data());
if (std::error_code EC = MB.getError()) {
errs() << "Cannot open " << PerfTasksOutputPath.data() << ": "
<< EC.message() << "\n";
deleteTempFile(ErrPath.data());
deleteTempFile(OutputPath.data());
return NoneType();
}
FileBuf.reset(MB->release());
ParsingBuf = FileBuf->getBuffer();
Col = 0;
Line = 1;
auto ParseResult = parsePerfBuildID();
if (!ParseResult) {
outs() << "PERF2BOLT: Failed to parse build-id from perf output\n";
deleteTempFile(ErrPath.data());
deleteTempFile(OutputPath.data());
return NoneType();
}
outs() << "PERF2BOLT: Perf.data build-id is: " << *ParseResult << "\n";
deleteTempFile(ErrPath.data());
deleteTempFile(OutputPath.data());
return std::string(ParseResult->data(), ParseResult->size());
}
bool DataAggregator::checkPerfDataMagic(StringRef FileName) {
int FD;
if (sys::fs::openFileForRead(FileName, FD)) {
return false;
}
char Buf[7] = {0, 0, 0, 0, 0, 0, 0};
if (::read(FD, Buf, 7) == -1) {
::close(FD);
return false;
}
::close(FD);
if (strncmp(Buf, "PERFILE", 7) == 0)
return true;
return false;
}
void DataAggregator::deleteTempFile(StringRef File) {
if (auto Errc = sys::fs::remove(File.data())) {
outs() << "PERF2BOLT: Failed to delete temporary file "
<< File << " with error " << Errc.message() << "\n";
}
}
void DataAggregator::deleteTempFiles() {
deleteTempFile(PerfBranchEventsErrPath.data());
deleteTempFile(PerfBranchEventsOutputPath.data());
deleteTempFile(PerfMemEventsErrPath.data());
deleteTempFile(PerfMemEventsOutputPath.data());
deleteTempFile(PerfTasksErrPath.data());
deleteTempFile(PerfTasksOutputPath.data());
}
bool DataAggregator::aggregate(BinaryContext &BC,
std::map<uint64_t, BinaryFunction> &BFs) {
std::string Error;
this->BC = &BC;
this->BFs = &BFs;
outs() << "PERF2BOLT: Waiting for perf tasks collection to finish...\n";
auto PI1 = sys::Wait(TasksPI, 0, true, &Error);
if (!Error.empty()) {
errs() << "PERF-ERROR: " << Error << "\n";
deleteTempFiles();
exit(1);
}
if (PI1.ReturnCode != 0) {
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(PerfTasksErrPath.data());
StringRef ErrBuf = (*MB)->getBuffer();
errs() << "PERF-ERROR: Return code " << PI1.ReturnCode << "\n";
errs() << ErrBuf;
deleteTempFiles();
exit(1);
}
ErrorOr<std::unique_ptr<MemoryBuffer>> MB1 =
MemoryBuffer::getFileOrSTDIN(PerfTasksOutputPath.data());
if (std::error_code EC = MB1.getError()) {
errs() << "Cannot open " << PerfTasksOutputPath.data() << ": "
<< EC.message() << "\n";
deleteTempFiles();
exit(1);
}
FileBuf.reset(MB1->release());
ParsingBuf = FileBuf->getBuffer();
Col = 0;
Line = 1;
if (parseTasks()) {
outs() << "PERF2BOLT: Failed to parse tasks\n";
}
outs()
<< "PERF2BOLT: Waiting for perf events collection to finish...\n";
auto PI2 = sys::Wait(BranchEventsPI, 0, true, &Error);
if (!Error.empty()) {
errs() << "PERF-ERROR: " << Error << "\n";
deleteTempFiles();
exit(1);
}
if (PI2.ReturnCode != 0) {
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(PerfBranchEventsErrPath.data());
StringRef ErrBuf = (*MB)->getBuffer();
errs() << "PERF-ERROR: Return code " << PI2.ReturnCode << "\n";
errs() << ErrBuf;
deleteTempFiles();
exit(1);
}
ErrorOr<std::unique_ptr<MemoryBuffer>> MB2 =
MemoryBuffer::getFileOrSTDIN(PerfBranchEventsOutputPath.data());
if (std::error_code EC = MB2.getError()) {
errs() << "Cannot open " << PerfBranchEventsOutputPath.data() << ": "
<< EC.message() << "\n";
deleteTempFiles();
exit(1);
}
FileBuf.reset(MB2->release());
ParsingBuf = FileBuf->getBuffer();
Col = 0;
Line = 1;
if (parseBranchEvents()) {
outs() << "PERF2BOLT: Failed to parse branch events\n";
}
// Mark all functions with registered events as having a valid profile.
for (auto &BFI : BFs) {
auto &BF = BFI.second;
if (BF.getBranchData()) {
BF.markProfiled();
}
}
auto PI3 = sys::Wait(MemEventsPI, 0, true, &Error);
if (PI3.ReturnCode != 0) {
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(PerfMemEventsErrPath.data());
StringRef ErrBuf = (*MB)->getBuffer();
deleteTempFiles();
Regex NoData("Samples for '.*' event do not have ADDR attribute set. "
"Cannot print 'addr' field.");
if (!NoData.match(ErrBuf)) {
errs() << "PERF-ERROR: Return code " << PI3.ReturnCode << "\n";
errs() << ErrBuf;
exit(1);
}
return true;
}
ErrorOr<std::unique_ptr<MemoryBuffer>> MB3 =
MemoryBuffer::getFileOrSTDIN(PerfMemEventsOutputPath.data());
if (std::error_code EC = MB3.getError()) {
errs() << "Cannot open " << PerfMemEventsOutputPath.data() << ": "
<< EC.message() << "\n";
deleteTempFiles();
exit(1);
}
FileBuf.reset(MB3->release());
ParsingBuf = FileBuf->getBuffer();
Col = 0;
Line = 1;
if (parseMemEvents()) {
outs() << "PERF2BOLT: Failed to parse memory events\n";
}
deleteTempFiles();
return true;
}
BinaryFunction *
DataAggregator::getBinaryFunctionContainingAddress(uint64_t Address) {
auto FI = BFs->upper_bound(Address);
if (FI == BFs->begin())
return nullptr;
--FI;
const auto UsedSize = FI->second.getMaxSize();
if (Address >= FI->first + UsedSize)
return nullptr;
return &FI->second;
}
bool
DataAggregator::doIntraBranch(BinaryFunction *Func, const LBREntry &Branch) {
FuncBranchData *AggrData = Func->getBranchData();
if (!AggrData) {
AggrData = &FuncsToBranches[Func->getNames()[0]];
AggrData->Name = Func->getNames()[0];
Func->setBranchData(AggrData);
}
AggrData->bumpBranchCount(Branch.From - Func->getAddress(),
Branch.To - Func->getAddress(),
Branch.Mispred);
return true;
}
bool DataAggregator::doInterBranch(BinaryFunction *FromFunc,
BinaryFunction *ToFunc,
const LBREntry &Branch) {
FuncBranchData *FromAggrData{nullptr};
FuncBranchData *ToAggrData{nullptr};
StringRef SrcFunc;
StringRef DstFunc;
auto From = Branch.From;
auto To = Branch.To;
if (FromFunc) {
SrcFunc = FromFunc->getNames()[0];
FromAggrData = FromFunc->getBranchData();
if (!FromAggrData) {
FromAggrData = &FuncsToBranches[SrcFunc];
FromAggrData->Name = SrcFunc;
FromFunc->setBranchData(FromAggrData);
}
From -= FromFunc->getAddress();
FromFunc->recordExit(From, Branch.Mispred);
}
if (ToFunc) {
DstFunc = ToFunc->getNames()[0];
ToAggrData = ToFunc->getBranchData();
if (!ToAggrData) {
ToAggrData = &FuncsToBranches[DstFunc];
ToAggrData->Name = DstFunc;
ToFunc->setBranchData(ToAggrData);
}
To -= ToFunc->getAddress();
ToFunc->recordEntry(To, Branch.Mispred);
}
if (FromAggrData)
FromAggrData->bumpCallCount(From, Location(!DstFunc.empty(), DstFunc, To),
Branch.Mispred);
if (ToAggrData)
ToAggrData->bumpEntryCount(Location(!SrcFunc.empty(), SrcFunc, From), To,
Branch.Mispred);
return true;
}
bool DataAggregator::doBranch(const LBREntry &Branch) {
auto *FromFunc = getBinaryFunctionContainingAddress(Branch.From);
auto *ToFunc = getBinaryFunctionContainingAddress(Branch.To);
if (!FromFunc && !ToFunc)
return false;
if (FromFunc == ToFunc) {
FromFunc->recordBranch(Branch.From - FromFunc->getAddress(),
Branch.To - FromFunc->getAddress(),
1,
Branch.Mispred);
return doIntraBranch(FromFunc, Branch);
}
return doInterBranch(FromFunc, ToFunc, Branch);
}
bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second) {
auto *FromFunc = getBinaryFunctionContainingAddress(First.To);
auto *ToFunc = getBinaryFunctionContainingAddress(Second.From);
if (!FromFunc || !ToFunc) {
++NumLongRangeTraces;
return false;
}
if (FromFunc != ToFunc) {
++NumInvalidTraces;
DEBUG(dbgs() << "Trace starting in " << FromFunc->getPrintName() << " @ "
<< Twine::utohexstr(First.To - FromFunc->getAddress())
<< " and ending in " << ToFunc->getPrintName() << " @ "
<< ToFunc->getPrintName() << " @ "
<< Twine::utohexstr(Second.From - ToFunc->getAddress())
<< '\n');
return false;
}
auto FTs = FromFunc->getFallthroughsInTrace(First, Second);
if (!FTs) {
++NumInvalidTraces;
return false;
}
for (const auto &Pair : *FTs) {
doIntraBranch(FromFunc,
LBREntry{Pair.first + FromFunc->getAddress(),
Pair.second + FromFunc->getAddress(),
false});
}
return true;
}
ErrorOr<LBREntry> DataAggregator::parseLBREntry() {
LBREntry Res;
auto FromStrRes = parseString('/');
if (std::error_code EC = FromStrRes.getError())
return EC;
StringRef OffsetStr = FromStrRes.get();
if (OffsetStr.getAsInteger(0, Res.From)) {
reportError("expected hexadecimal number with From address");
Diag << "Found: " << OffsetStr << "\n";
return make_error_code(llvm::errc::io_error);
}
auto ToStrRes = parseString('/');
if (std::error_code EC = ToStrRes.getError())
return EC;
OffsetStr = ToStrRes.get();
if (OffsetStr.getAsInteger(0, Res.To)) {
reportError("expected hexadecimal number with To address");
Diag << "Found: " << OffsetStr << "\n";
return make_error_code(llvm::errc::io_error);
}
auto MispredStrRes = parseString('/');
if (std::error_code EC = MispredStrRes.getError())
return EC;
StringRef MispredStr = MispredStrRes.get();
if (MispredStr.size() != 1 ||
(MispredStr[0] != 'P' && MispredStr[0] != 'M')) {
reportError("expected single char for mispred bit");
Diag << "Found: " << OffsetStr << "\n";
return make_error_code(llvm::errc::io_error);
}
Res.Mispred = MispredStr[0] == 'M';
auto Rest = parseString(FieldSeparator, true);
if (std::error_code EC = Rest.getError())
return EC;
if (Rest.get().size() < 5) {
reportError("expected rest of LBR entry");
Diag << "Found: " << OffsetStr << "\n";
return make_error_code(llvm::errc::io_error);
}
return Res;
}
bool DataAggregator::checkAndConsumeFS() {
if (ParsingBuf[0] != FieldSeparator) {
return false;
}
ParsingBuf = ParsingBuf.drop_front(1);
Col += 1;
return true;
}
void DataAggregator::consumeRestOfLine() {
auto LineEnd = ParsingBuf.find_first_of('\n');
if (LineEnd == StringRef::npos) {
ParsingBuf = StringRef();
Col = 0;
Line += 1;
return;
}
ParsingBuf = ParsingBuf.drop_front(LineEnd + 1);
Col = 0;
Line += 1;
}
ErrorOr<PerfBranchSample> DataAggregator::parseBranchSample() {
PerfBranchSample Res;
while (checkAndConsumeFS()) {}
auto PIDRes = parseNumberField(FieldSeparator, true);
if (std::error_code EC = PIDRes.getError())
return EC;
if (!PIDs.empty() && !PIDs.count(PIDRes.get())) {
consumeRestOfLine();
return Res;
}
while (!checkAndConsumeNewLine()) {
checkAndConsumeFS();
auto LBRRes = parseLBREntry();
if (std::error_code EC = LBRRes.getError())
return EC;
Res.LBR.push_back(LBRRes.get());
}
return Res;
}
ErrorOr<PerfMemSample> DataAggregator::parseMemSample() {
PerfMemSample Res{0,0};
while (checkAndConsumeFS()) {}
auto PIDRes = parseNumberField(FieldSeparator, true);
if (std::error_code EC = PIDRes.getError())
return EC;
if (!PIDs.empty() && !PIDs.count(PIDRes.get())) {
consumeRestOfLine();
return Res;
}
while (checkAndConsumeFS()) {}
auto Event = parseString(FieldSeparator);
if (std::error_code EC = Event.getError())
return EC;
if (Event.get().find("mem-loads") == StringRef::npos) {
consumeRestOfLine();
return Res;
}
while (checkAndConsumeFS()) {}
auto AddrRes = parseHexField(FieldSeparator);
if (std::error_code EC = AddrRes.getError()) {
return EC;
}
while (checkAndConsumeFS()) {}
auto PCRes = parseHexField(FieldSeparator, true);
if (std::error_code EC = PCRes.getError()) {
consumeRestOfLine();
return EC;
}
checkAndConsumeNewLine();
return PerfMemSample{PCRes.get(), AddrRes.get()};
}
bool DataAggregator::hasData() {
if (ParsingBuf.size() == 0)
return false;
return true;
}
std::error_code DataAggregator::parseBranchEvents() {
outs() << "PERF2BOLT: Aggregating branch events...\n";
NamedRegionTimer T("parseBranch", "Branch samples parsing", TimerGroupName,
TimerGroupDesc, opts::TimeAggregator);
uint64_t NumEntries{0};
uint64_t NumSamples{0};
uint64_t NumTraces{0};
while (hasData()) {
auto SampleRes = parseBranchSample();
if (std::error_code EC = SampleRes.getError())
return EC;
auto &Sample = SampleRes.get();
if (Sample.LBR.empty())
continue;
++NumSamples;
NumEntries += Sample.LBR.size();
// LBRs are stored in reverse execution order. NextLBR refers to the next
// executed branch record.
const LBREntry *NextLBR{nullptr};
for (const auto &LBR : Sample.LBR) {
if (NextLBR) {
doTrace(LBR, *NextLBR);
++NumTraces;
}
doBranch(LBR);
NextLBR = &LBR;
}
}
outs() << "PERF2BOLT: Read " << NumSamples << " samples and "
<< NumEntries << " LBR entries\n";
outs() << "PERF2BOLT: Traces mismatching disassembled function contents: "
<< NumInvalidTraces;
float Perc{0.0f};
if (NumTraces > 0) {
outs() << " (";
Perc = NumInvalidTraces * 100.0f / NumTraces;
if (outs().has_colors()) {
if (Perc > 10.0f) {
outs().changeColor(raw_ostream::RED);
} else if (Perc > 5.0f) {
outs().changeColor(raw_ostream::YELLOW);
} else {
outs().changeColor(raw_ostream::GREEN);
}
}
outs() << format("%.1f%%", Perc);
if (outs().has_colors())
outs().resetColor();
outs() << ")";
}
outs() << "\n";
if (Perc > 10.0f) {
outs() << "\n !! WARNING !! This high mismatch ratio indicates the input "
"binary is probably not the same binary used during profiling "
"collection. The generated data may be ineffective for improving "
"performance.\n\n";
}
outs() << "PERF2BOLT: Out of range traces involving unknown regions: "
<< NumLongRangeTraces;
if (NumTraces > 0) {
outs() << format(" (%.1f%%)", NumLongRangeTraces * 100.0f / NumTraces);
}
outs() << "\n";
return std::error_code();
}
std::error_code DataAggregator::parseMemEvents() {
outs() << "PERF2BOLT: Aggregating memory events...\n";
NamedRegionTimer T("memevents", "Mem samples parsing", TimerGroupName,
TimerGroupDesc, opts::TimeAggregator);
while (hasData()) {
auto SampleRes = parseMemSample();
if (std::error_code EC = SampleRes.getError())
return EC;
auto PC = SampleRes.get().PC;
auto Addr = SampleRes.get().Addr;
StringRef FuncName;
StringRef MemName;
// Try to resolve symbol for PC
auto *Func = getBinaryFunctionContainingAddress(PC);
if (Func) {
FuncName = Func->getNames()[0];
PC -= Func->getAddress();
}
// Try to resolve symbol for memory load
auto *MemFunc = getBinaryFunctionContainingAddress(Addr);
if (MemFunc) {
MemName = MemFunc->getNames()[0];
Addr -= MemFunc->getAddress();
} else {
// TODO: global symbol size?
auto Sym = BC->getGlobalSymbolAtAddress(Addr);
if (Sym) {
MemName = Sym->getName();
Addr = 0;
}
}
const Location FuncLoc(!FuncName.empty(), FuncName, PC);
const Location AddrLoc(!MemName.empty(), MemName, Addr);
// TODO what does it mean when PC is 0 (or not a known function)?
DEBUG(if (!Func && PC != 0) {
dbgs() << "Skipped mem event: " << FuncLoc << " = " << AddrLoc << "\n";
});
if (Func) {
auto *MemData = &FuncsToMemEvents[FuncName];
Func->setMemData(MemData);
MemData->update(FuncLoc, AddrLoc);
DEBUG(dbgs() << "Mem event: " << FuncLoc << " = " << AddrLoc << "\n");
}
}
return std::error_code();
}
ErrorOr<int64_t> DataAggregator::parseTaskPID() {
while (checkAndConsumeFS()) {}
auto CommNameStr = parseString(FieldSeparator, true);
if (std::error_code EC = CommNameStr.getError())
return EC;
if (CommNameStr.get() != BinaryName) {
consumeRestOfLine();
return -1;
}
auto LineEnd = ParsingBuf.find_first_of("\n");
if (LineEnd == StringRef::npos) {
reportError("expected rest of line");
Diag << "Found: " << ParsingBuf << "\n";
return make_error_code(llvm::errc::io_error);
}
StringRef Line = ParsingBuf.substr(0, LineEnd);
if (Line.find("PERF_RECORD_COMM") != StringRef::npos) {
int64_t PID;
StringRef PIDStr = Line.rsplit(':').second.split('/').first;
if (PIDStr.getAsInteger(10, PID)) {
reportError("expected PID");
Diag << "Found: " << PIDStr << "\n";
return make_error_code(llvm::errc::io_error);
}
return PID;
}
consumeRestOfLine();
return -1;
}
std::error_code DataAggregator::parseTasks() {
outs() << "PERF2BOLT: Parsing perf-script tasks output\n";
NamedRegionTimer T("parseTasks", "Tasks parsing", TimerGroupName,
TimerGroupDesc, opts::TimeAggregator);
while (hasData()) {
auto PIDRes = parseTaskPID();
if (std::error_code EC = PIDRes.getError())
return EC;
auto PID = PIDRes.get();
if (PID == -1) {
continue;
}
PIDs.insert(PID);
}
if (!PIDs.empty())
outs() << "PERF2BOLT: Input binary is associated with " << PIDs.size()
<< " PID(s)\n";
else
outs() << "PERF2BOLT: Could not bind input binary to a PID - will parse "
"all samples in perf data.\n";
return std::error_code();
}
Optional<std::pair<StringRef, StringRef>>
DataAggregator::parseNameBuildIDPair() {
while (checkAndConsumeFS()) {}
auto BuildIDStr = parseString(FieldSeparator, true);
if (std::error_code EC = BuildIDStr.getError())
return NoneType();
auto NameStr = parseString(FieldSeparator, true);
if (std::error_code EC = NameStr.getError())
return NoneType();
consumeRestOfLine();
return std::make_pair(NameStr.get(), BuildIDStr.get());
}
Optional<StringRef> DataAggregator::parsePerfBuildID() {
while (hasData()) {
auto IDPair = parseNameBuildIDPair();
if (!IDPair)
return NoneType();
if (sys::path::filename(IDPair->first) != BinaryName)
continue;
return IDPair->second;
}
return NoneType();
}
std::error_code DataAggregator::writeAggregatedFile() const {
std::error_code EC;
raw_fd_ostream OutFile(OutputFDataName, EC, sys::fs::OpenFlags::F_None);
if (EC)
return EC;
bool WriteMemLocs = false;
auto writeLocation = [&OutFile,&WriteMemLocs](const Location &Loc) {
if (WriteMemLocs)
OutFile << (Loc.IsSymbol ? "4 " : "3 ");
else
OutFile << (Loc.IsSymbol ? "1 " : "0 ");
OutFile << (Loc.Name.empty() ? "[unknown]" : Loc.Name) << " "
<< Twine::utohexstr(Loc.Offset)
<< FieldSeparator;
};
uint64_t BranchValues{0};
uint64_t MemValues{0};
for (const auto &Func : FuncsToBranches) {
for (const auto &BI : Func.getValue().Data) {
writeLocation(BI.From);
writeLocation(BI.To);
OutFile << BI.Mispreds << " " << BI.Branches << "\n";
++BranchValues;
}
for (const auto &BI : Func.getValue().EntryData) {
// Do not output if source is a known symbol, since this was already
// accounted for in the source function
if (BI.From.IsSymbol)
continue;
writeLocation(BI.From);
writeLocation(BI.To);
OutFile << BI.Mispreds << " " << BI.Branches << "\n";
++BranchValues;
}
}
WriteMemLocs = true;
for (const auto &Func : FuncsToMemEvents) {
for (const auto &MemEvent : Func.getValue().Data) {
writeLocation(MemEvent.Offset);
writeLocation(MemEvent.Addr);
OutFile << MemEvent.Count << "\n";
++MemValues;
}
}
outs() << "PERF2BOLT: Wrote " << BranchValues << " branch objects and "
<< MemValues << " memory objects to " << OutputFDataName << "\n";
return std::error_code();
}
void DataAggregator::dump() const {
DataReader::dump();
}
void DataAggregator::dump(const LBREntry &LBR) const {
Diag << "From: " << Twine::utohexstr(LBR.From)
<< " To: " << Twine::utohexstr(LBR.To) << " Mispred? " << LBR.Mispred
<< "\n";
}
void DataAggregator::dump(const PerfBranchSample &Sample) const {
Diag << "Sample LBR entries: " << Sample.LBR.size() << "\n";
for (const auto &LBR : Sample.LBR) {
dump(LBR);
}
}
void DataAggregator::dump(const PerfMemSample &Sample) const {
Diag << "Sample mem entries: " << Sample.PC << ": " << Sample.Addr << "\n";
}