Summary: A new profile that is more resilient to minor binary modifications. BranchData is eliminated. For calls, the data is converted into instruction annotations if the profile matches a function. If a profile cannot be matched, AllCallSites data should have call sites profiles. The new profile format is YAML, which is quite verbose. It still takes less space than the older format because we avoid function name repetition. The plan is to get rid of the old profile format eventually. merge-fdata does not work with the new format yet. (cherry picked from FBD6753747)
1022 lines
30 KiB
C++
1022 lines
30 KiB
C++
//===-- 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";
|
|
|
|
}
|
|
|
|
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;
|
|
SmallVector<StringRef, 3> Redirects;
|
|
SmallVector<const StringRef*, 3> RedirectPtrs;
|
|
|
|
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);
|
|
}
|
|
|
|
Redirects.push_back(""); // Stdin
|
|
Redirects.push_back(StringRef(PerfBranchEventsOutputPath.data())); // Stdout
|
|
Redirects.push_back(StringRef(PerfBranchEventsErrPath.data())); // Stderr
|
|
RedirectPtrs.push_back(&Redirects[0]);
|
|
RedirectPtrs.push_back(&Redirects[1]);
|
|
RedirectPtrs.push_back(&Redirects[2]);
|
|
|
|
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
|
|
<< PerfBranchEventsOutputPath.data() << " 2> "
|
|
<< PerfBranchEventsErrPath.data() << "\n");
|
|
|
|
BranchEventsPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
|
|
/*envp*/ nullptr, &RedirectPtrs[0]);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DataAggregator::launchPerfMemEventsNoWait() {
|
|
SmallVector<const char*, 4> Argv;
|
|
SmallVector<StringRef, 3> Redirects;
|
|
SmallVector<const StringRef*, 3> RedirectPtrs;
|
|
|
|
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);
|
|
}
|
|
|
|
Redirects.push_back(""); // Stdin
|
|
Redirects.push_back(StringRef(PerfMemEventsOutputPath.data())); // Stdout
|
|
Redirects.push_back(StringRef(PerfMemEventsErrPath.data())); // Stderr
|
|
RedirectPtrs.push_back(&Redirects[0]);
|
|
RedirectPtrs.push_back(&Redirects[1]);
|
|
RedirectPtrs.push_back(&Redirects[2]);
|
|
|
|
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
|
|
<< PerfMemEventsOutputPath.data() << " 2> "
|
|
<< PerfMemEventsErrPath.data() << "\n");
|
|
|
|
MemEventsPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
|
|
/*envp*/ nullptr, &RedirectPtrs[0]);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DataAggregator::launchPerfTasksNoWait() {
|
|
SmallVector<const char*, 4> Argv;
|
|
SmallVector<StringRef, 3> Redirects;
|
|
SmallVector<const StringRef*, 3> RedirectPtrs;
|
|
|
|
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);
|
|
}
|
|
|
|
Redirects.push_back(""); // Stdin
|
|
Redirects.push_back(StringRef(PerfTasksOutputPath.data())); // Stdout
|
|
Redirects.push_back(StringRef(PerfTasksErrPath.data())); // Stderr
|
|
RedirectPtrs.push_back(&Redirects[0]);
|
|
RedirectPtrs.push_back(&Redirects[1]);
|
|
RedirectPtrs.push_back(&Redirects[2]);
|
|
|
|
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
|
|
<< PerfTasksOutputPath.data() << " 2> "
|
|
<< PerfTasksErrPath.data() << "\n");
|
|
|
|
TasksPI = sys::ExecuteNoWait(PerfPath.data(), Argv.data(),
|
|
/*envp*/ nullptr, &RedirectPtrs[0]);
|
|
|
|
return true;
|
|
}
|
|
|
|
Optional<std::string> DataAggregator::getPerfBuildID() {
|
|
SmallVector<const char *, 4> Argv;
|
|
SmallVector<StringRef, 3> Redirects;
|
|
SmallVector<const StringRef*, 3> RedirectPtrs;
|
|
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);
|
|
}
|
|
|
|
Redirects.push_back(""); // Stdin
|
|
Redirects.push_back(StringRef(OutputPath.data())); // Stdout
|
|
Redirects.push_back(StringRef(ErrPath.data())); // Stderr
|
|
RedirectPtrs.push_back(&Redirects[0]);
|
|
RedirectPtrs.push_back(&Redirects[1]);
|
|
RedirectPtrs.push_back(&Redirects[2]);
|
|
|
|
DEBUG(dbgs() << "Launching perf: " << PerfPath.data() << " 1> "
|
|
<< OutputPath.data() << " 2> "
|
|
<< ErrPath.data() << "\n");
|
|
|
|
auto RetCode = sys::ExecuteAndWait(PerfPath.data(), Argv.data(),
|
|
/*envp*/ nullptr, &RedirectPtrs[0]);
|
|
|
|
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("Branch samples parsing", TimerGroupName,
|
|
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("Mem samples parsing", TimerGroupName, 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("Tasks parsing", TimerGroupName, 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";
|
|
}
|