#include "PerfReader.h"
#include "ProfileGenerator.h"
#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Process.h"
#define DEBUG_TYPE "perf-reader"
cl::opt<bool> SkipSymbolization("skip-symbolization",
cl::desc("Dump the unsymbolized profile to the "
"output file. It will show unwinder "
"output for CS profile generation."));
static cl::opt<bool> ShowMmapEvents("show-mmap-events",
cl::desc("Print binary load events."));
static cl::opt<bool>
UseOffset("use-offset", cl::init(true),
cl::desc("Work with `--skip-symbolization` or "
"`--unsymbolized-profile` to write/read the "
"offset instead of virtual address."));
static cl::opt<bool> UseLoadableSegmentAsBase(
"use-first-loadable-segment-as-base",
cl::desc("Use first loadable segment address as base address "
"for offsets in unsymbolized profile. By default "
"first executable segment address is used"));
static cl::opt<bool>
IgnoreStackSamples("ignore-stack-samples",
cl::desc("Ignore call stack samples for hybrid samples "
"and produce context-insensitive profile."));
cl::opt<bool> ShowDetailedWarning("show-detailed-warning",
cl::desc("Show detailed warning message."));
extern cl::opt<std::string> PerfTraceFilename;
extern cl::opt<bool> ShowDisassemblyOnly;
extern cl::opt<bool> ShowSourceLocations;
extern cl::opt<std::string> OutputFilename;
namespace llvm {
namespace sampleprof {
void VirtualUnwinder::unwindCall(UnwindState &State) {
uint64_t Source = State.getCurrentLBRSource();
auto *ParentFrame = State.getParentFrame();
if (ParentFrame == State.getDummyRootPtr() ||
ParentFrame->Address != Source) {
State.switchToFrame(Source);
if (ParentFrame != State.getDummyRootPtr()) {
if (Source == ExternalAddr)
NumMismatchedExtCallBranch++;
else
NumMismatchedProEpiBranch++;
}
} else {
State.popFrame();
}
State.InstPtr.update(Source);
}
void VirtualUnwinder::unwindLinear(UnwindState &State, uint64_t Repeat) {
InstructionPointer &IP = State.InstPtr;
uint64_t Target = State.getCurrentLBRTarget();
uint64_t End = IP.Address;
if (End == ExternalAddr && Target == ExternalAddr) {
NumPairedExtAddr++;
return;
}
if (End == ExternalAddr || Target == ExternalAddr) {
NumUnpairedExtAddr++;
State.setInvalid();
return;
}
if (!isValidFallThroughRange(Binary->virtualAddrToOffset(Target),
Binary->virtualAddrToOffset(End), Binary)) {
State.setInvalid();
return;
}
if (Binary->usePseudoProbes()) {
State.getParentFrame()->recordRangeCount(Target, End, Repeat);
} else {
while (IP.Address > Target) {
uint64_t PrevIP = IP.Address;
IP.backward();
bool SameInlinee = Binary->inlineContextEqual(PrevIP, IP.Address);
if (!SameInlinee) {
State.switchToFrame(PrevIP);
State.CurrentLeafFrame->recordRangeCount(PrevIP, End, Repeat);
End = IP.Address;
}
}
assert(IP.Address == Target && "The last one must be the target address.");
State.switchToFrame(IP.Address);
State.CurrentLeafFrame->recordRangeCount(IP.Address, End, Repeat);
}
}
void VirtualUnwinder::unwindReturn(UnwindState &State) {
const LBREntry &LBR = State.getCurrentLBR();
uint64_t CallAddr = Binary->getCallAddrFromFrameAddr(LBR.Target);
State.switchToFrame(CallAddr);
State.pushFrame(LBR.Source);
State.InstPtr.update(LBR.Source);
}
void VirtualUnwinder::unwindBranch(UnwindState &State) {
uint64_t Source = State.getCurrentLBRSource();
State.switchToFrame(Source);
State.InstPtr.update(Source);
}
std::shared_ptr<StringBasedCtxKey> FrameStack::getContextKey() {
std::shared_ptr<StringBasedCtxKey> KeyStr =
std::make_shared<StringBasedCtxKey>();
KeyStr->Context = Binary->getExpandedContext(Stack, KeyStr->WasLeafInlined);
return KeyStr;
}
std::shared_ptr<AddrBasedCtxKey> AddressStack::getContextKey() {
std::shared_ptr<AddrBasedCtxKey> KeyStr = std::make_shared<AddrBasedCtxKey>();
KeyStr->Context = Stack;
CSProfileGenerator::compressRecursionContext<uint64_t>(KeyStr->Context);
CSProfileGenerator::trimContext<uint64_t>(KeyStr->Context);
return KeyStr;
}
template <typename T>
void VirtualUnwinder::collectSamplesFromFrame(UnwindState::ProfiledFrame *Cur,
T &Stack) {
if (Cur->RangeSamples.empty() && Cur->BranchSamples.empty())
return;
std::shared_ptr<ContextKey> Key = Stack.getContextKey();
if (Key == nullptr)
return;
auto Ret = CtxCounterMap->emplace(Hashable<ContextKey>(Key), SampleCounter());
SampleCounter &SCounter = Ret.first->second;
for (auto &Item : Cur->RangeSamples) {
uint64_t StartOffset = Binary->virtualAddrToOffset(std::get<0>(Item));
uint64_t EndOffset = Binary->virtualAddrToOffset(std::get<1>(Item));
SCounter.recordRangeCount(StartOffset, EndOffset, std::get<2>(Item));
}
for (auto &Item : Cur->BranchSamples) {
uint64_t SourceOffset = Binary->virtualAddrToOffset(std::get<0>(Item));
uint64_t TargetOffset = Binary->virtualAddrToOffset(std::get<1>(Item));
SCounter.recordBranchCount(SourceOffset, TargetOffset, std::get<2>(Item));
}
}
template <typename T>
void VirtualUnwinder::collectSamplesFromFrameTrie(
UnwindState::ProfiledFrame *Cur, T &Stack) {
if (!Cur->isDummyRoot()) {
if (Cur->isExternalFrame() || !Stack.pushFrame(Cur)) {
T EmptyStack(Binary);
collectSamplesFromFrame(Cur, EmptyStack);
for (const auto &Item : Cur->Children) {
collectSamplesFromFrameTrie(Item.second.get(), EmptyStack);
}
if (!Cur->isLeafFrame())
UntrackedCallsites.insert(Cur->Address);
return;
}
}
collectSamplesFromFrame(Cur, Stack);
for (const auto &Item : Cur->Children) {
collectSamplesFromFrameTrie(Item.second.get(), Stack);
}
Stack.popFrame();
}
void VirtualUnwinder::collectSamplesFromFrameTrie(
UnwindState::ProfiledFrame *Cur) {
if (Binary->usePseudoProbes()) {
AddressStack Stack(Binary);
collectSamplesFromFrameTrie<AddressStack>(Cur, Stack);
} else {
FrameStack Stack(Binary);
collectSamplesFromFrameTrie<FrameStack>(Cur, Stack);
}
}
void VirtualUnwinder::recordBranchCount(const LBREntry &Branch,
UnwindState &State, uint64_t Repeat) {
if (Branch.Target == ExternalAddr)
return;
if (Branch.Source == ExternalAddr) {
State.getDummyRootPtr()->recordBranchCount(Branch.Source, Branch.Target,
Repeat);
return;
}
if (Binary->usePseudoProbes()) {
State.getParentFrame()->recordBranchCount(Branch.Source, Branch.Target,
Repeat);
} else {
State.CurrentLeafFrame->recordBranchCount(Branch.Source, Branch.Target,
Repeat);
}
}
bool VirtualUnwinder::unwind(const PerfSample *Sample, uint64_t Repeat) {
UnwindState State(Sample, Binary);
if (!State.validateInitialState())
return false;
NumTotalBranches += State.LBRStack.size();
while (State.hasNextLBR()) {
State.checkStateConsistency();
if (!State.IsLastLBR()) {
unwindLinear(State, Repeat);
}
const LBREntry &Branch = State.getCurrentLBR();
if (isCallState(State)) {
unwindCall(State);
} else if (isReturnState(State)) {
unwindReturn(State);
} else if (isValidState(State)) {
unwindBranch(State);
} else {
State.clearCallStack();
State.InstPtr.update(State.getCurrentLBRSource());
State.pushFrame(State.InstPtr.Address);
}
State.advanceLBR();
recordBranchCount(Branch, State, Repeat);
}
collectSamplesFromFrameTrie(State.getDummyRootPtr());
return true;
}
std::unique_ptr<PerfReaderBase>
PerfReaderBase::create(ProfiledBinary *Binary, PerfInputFile &PerfInput,
Optional<uint32_t> PIDFilter) {
std::unique_ptr<PerfReaderBase> PerfReader;
if (PerfInput.Format == PerfFormat::UnsymbolizedProfile) {
PerfReader.reset(
new UnsymbolizedProfileReader(Binary, PerfInput.InputFile));
return PerfReader;
}
if (PerfInput.Format == PerfFormat::PerfData)
PerfInput =
PerfScriptReader::convertPerfDataToTrace(Binary, PerfInput, PIDFilter);
assert((PerfInput.Format == PerfFormat::PerfScript) &&
"Should be a perfscript!");
PerfInput.Content =
PerfScriptReader::checkPerfScriptType(PerfInput.InputFile);
if (PerfInput.Content == PerfContent::LBRStack) {
PerfReader.reset(
new HybridPerfReader(Binary, PerfInput.InputFile, PIDFilter));
} else if (PerfInput.Content == PerfContent::LBR) {
PerfReader.reset(new LBRPerfReader(Binary, PerfInput.InputFile, PIDFilter));
} else {
exitWithError("Unsupported perfscript!");
}
return PerfReader;
}
PerfInputFile PerfScriptReader::convertPerfDataToTrace(
ProfiledBinary *Binary, PerfInputFile &File, Optional<uint32_t> PIDFilter) {
StringRef PerfData = File.InputFile;
auto PerfExecutable = sys::Process::FindInEnvPath("PATH", "perf");
if (!PerfExecutable) {
exitWithError("Perf not found.");
}
std::string PerfPath = *PerfExecutable;
std::string PerfTraceFile = PerfData.str() + ".script.tmp";
StringRef ScriptMMapArgs[] = {PerfPath, "script", "--show-mmap-events",
"-F", "comm,pid", "-i",
PerfData};
Optional<StringRef> Redirects[] = {llvm::None, StringRef(PerfTraceFile), StringRef(PerfTraceFile)}; sys::ExecuteAndWait(PerfPath, ScriptMMapArgs, llvm::None, Redirects);
TraceStream TraceIt(PerfTraceFile);
std::string PIDs;
std::unordered_set<uint32_t> PIDSet;
while (!TraceIt.isAtEoF()) {
MMapEvent MMap;
if (isMMap2Event(TraceIt.getCurrentLine()) &&
extractMMap2EventForBinary(Binary, TraceIt.getCurrentLine(), MMap)) {
auto It = PIDSet.emplace(MMap.PID);
if (It.second && (!PIDFilter || MMap.PID == *PIDFilter)) {
if (!PIDs.empty()) {
PIDs.append(",");
}
PIDs.append(utostr(MMap.PID));
}
}
TraceIt.advance();
}
if (PIDs.empty()) {
exitWithError("No relevant mmap event is found in perf data.");
}
StringRef ScriptSampleArgs[] = {PerfPath, "script", "--show-mmap-events",
"-F", "ip,brstack", "--pid",
PIDs, "-i", PerfData};
sys::ExecuteAndWait(PerfPath, ScriptSampleArgs, llvm::None, Redirects);
return {PerfTraceFile, PerfFormat::PerfScript, PerfContent::UnknownContent};
}
void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) {
StringRef BinaryName = llvm::sys::path::filename(Event.BinaryPath);
if (Binary->getName() != BinaryName)
return;
if (PIDFilter && Event.PID != *PIDFilter)
return;
if (Event.Address == Binary->getBaseAddress()) {
Binary->setIsLoadedByMMap(true);
return;
}
if (Event.Offset == Binary->getTextSegmentOffset()) {
Binary->setBaseAddress(Event.Address);
Binary->setIsLoadedByMMap(true);
} else {
const auto &Offsets = Binary->getTextSegmentOffsets();
auto It = std::lower_bound(Offsets.begin(), Offsets.end(), Event.Offset);
if (It != Offsets.end() && *It == Event.Offset) {
auto I = std::distance(Offsets.begin(), It);
const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses();
if (PreferredAddrs[I] - Binary->getPreferredBaseAddress() !=
Event.Address - Binary->getBaseAddress())
exitWithError("Executable segments not loaded consecutively");
} else {
if (It == Offsets.begin())
exitWithError("File offset not found");
else {
--It;
assert(*It < Event.Offset);
if (Event.Offset - *It != Event.Address - Binary->getBaseAddress())
exitWithError("Segment not loaded by consecutive mmaps");
}
}
}
}
static std::string getContextKeyStr(ContextKey *K,
const ProfiledBinary *Binary) {
if (const auto *CtxKey = dyn_cast<StringBasedCtxKey>(K)) {
return SampleContext::getContextString(CtxKey->Context);
} else if (const auto *CtxKey = dyn_cast<AddrBasedCtxKey>(K)) {
std::ostringstream OContextStr;
for (uint32_t I = 0; I < CtxKey->Context.size(); I++) {
if (OContextStr.str().size())
OContextStr << " @ ";
OContextStr << "0x"
<< utohexstr(
Binary->virtualAddrToOffset(CtxKey->Context[I]),
true);
}
return OContextStr.str();
} else {
llvm_unreachable("unexpected key type");
}
}
void HybridPerfReader::unwindSamples() {
if (Binary->useFSDiscriminator())
exitWithError("FS discriminator is not supported in CS profile.");
VirtualUnwinder Unwinder(&SampleCounters, Binary);
for (const auto &Item : AggregatedSamples) {
const PerfSample *Sample = Item.first.getPtr();
Unwinder.unwind(Sample, Item.second);
}
if (ShowDetailedWarning) {
for (auto Address : Unwinder.getUntrackedCallsites())
WithColor::warning() << "Profile context truncated due to missing probe "
<< "for call instruction at "
<< format("0x%" PRIx64, Address) << "\n";
}
emitWarningSummary(Unwinder.getUntrackedCallsites().size(),
SampleCounters.size(),
"of profiled contexts are truncated due to missing probe "
"for call instruction.");
emitWarningSummary(
Unwinder.NumMismatchedExtCallBranch, Unwinder.NumTotalBranches,
"of branches'source is a call instruction but doesn't match call frame "
"stack, likely due to unwinding error of external frame.");
emitWarningSummary(Unwinder.NumPairedExtAddr * 2, Unwinder.NumTotalBranches,
"of branches containing paired external address.");
emitWarningSummary(Unwinder.NumUnpairedExtAddr, Unwinder.NumTotalBranches,
"of branches containing external address but doesn't have "
"another external address to pair, likely due to "
"interrupt jmp or broken perf script.");
emitWarningSummary(
Unwinder.NumMismatchedProEpiBranch, Unwinder.NumTotalBranches,
"of branches'source is a call instruction but doesn't match call frame "
"stack, likely due to frame in prolog/epilog.");
emitWarningSummary(Unwinder.NumMissingExternalFrame,
Unwinder.NumExtCallBranch,
"of artificial call branches but doesn't have an external "
"frame to match.");
}
bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
SmallVectorImpl<LBREntry> &LBRStack) {
SmallVector<StringRef, 32> Records;
TraceIt.getCurrentLine().split(Records, " ", -1, false);
auto WarnInvalidLBR = [](TraceStream &TraceIt) {
WithColor::warning() << "Invalid address in LBR record at line "
<< TraceIt.getLineNumber() << ": "
<< TraceIt.getCurrentLine() << "\n";
};
size_t Index = 0;
uint64_t LeadingAddr;
if (!Records.empty() && !Records[0].contains('/')) {
if (Records[0].getAsInteger(16, LeadingAddr)) {
WarnInvalidLBR(TraceIt);
TraceIt.advance();
return false;
}
Index = 1;
}
while (Index < Records.size()) {
auto &Token = Records[Index++];
if (Token.size() == 0)
continue;
SmallVector<StringRef, 8> Addresses;
Token.split(Addresses, "/");
uint64_t Src;
uint64_t Dst;
if (Addresses.size() < 2 || Addresses[0].substr(2).getAsInteger(16, Src) ||
Addresses[1].substr(2).getAsInteger(16, Dst)) {
WarnInvalidLBR(TraceIt);
break;
}
bool SrcIsInternal = Binary->addressIsCode(Src);
bool DstIsInternal = Binary->addressIsCode(Dst);
if (!SrcIsInternal)
Src = ExternalAddr;
if (!DstIsInternal)
Dst = ExternalAddr;
if (!SrcIsInternal && !DstIsInternal)
continue;
LBRStack.emplace_back(LBREntry(Src, Dst));
}
TraceIt.advance();
return !LBRStack.empty();
}
bool PerfScriptReader::extractCallstack(TraceStream &TraceIt,
SmallVectorImpl<uint64_t> &CallStack) {
while (!TraceIt.isAtEoF() && !TraceIt.getCurrentLine().startswith(" 0x")) {
StringRef FrameStr = TraceIt.getCurrentLine().ltrim();
uint64_t FrameAddr = 0;
if (FrameStr.getAsInteger(16, FrameAddr)) {
TraceIt.advance();
return false;
}
TraceIt.advance();
if (!Binary->addressIsCode(FrameAddr)) {
if (CallStack.empty())
NumLeafExternalFrame++;
if (CallStack.empty() || CallStack.back() != ExternalAddr)
CallStack.emplace_back(ExternalAddr);
continue;
}
if (!CallStack.empty()) {
auto CallAddr = Binary->getCallAddrFromFrameAddr(FrameAddr);
if (!CallAddr) {
InvalidReturnAddresses.insert(FrameAddr);
break;
}
FrameAddr = CallAddr;
}
CallStack.emplace_back(FrameAddr);
}
if (CallStack.size() > 1 && CallStack.back() == ExternalAddr)
CallStack.pop_back();
while (!TraceIt.isAtEoF() && !TraceIt.getCurrentLine().startswith(" 0x")) {
TraceIt.advance();
}
return !CallStack.empty() &&
!Binary->addressInPrologEpilog(CallStack.front());
}
void PerfScriptReader::warnIfMissingMMap() {
if (!Binary->getMissingMMapWarned() && !Binary->getIsLoadedByMMap()) {
WithColor::warning() << "No relevant mmap event is matched for "
<< Binary->getName()
<< ", will use preferred address ("
<< format("0x%" PRIx64,
Binary->getPreferredBaseAddress())
<< ") as the base loading address!\n";
Binary->setMissingMMapWarned(true);
}
}
void HybridPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) {
std::shared_ptr<PerfSample> Sample = std::make_shared<PerfSample>();
#ifndef NDEBUG
Sample->Linenum = TraceIt.getLineNumber();
#endif
if (!extractCallstack(TraceIt, Sample->CallStack)) {
if (!TraceIt.isAtEoF() && TraceIt.getCurrentLine().startswith(" 0x"))
TraceIt.advance();
return;
}
warnIfMissingMMap();
if (!TraceIt.isAtEoF() && TraceIt.getCurrentLine().startswith(" 0x")) {
if (extractLBRStack(TraceIt, Sample->LBRStack)) {
if (IgnoreStackSamples) {
Sample->CallStack.clear();
} else {
Sample->CallStack.front() = Sample->LBRStack[0].Target;
}
AggregatedSamples[Hashable<PerfSample>(Sample)] += Count;
}
} else {
exitWithError("'Hybrid perf sample is corrupted, No LBR sample line");
}
}
void PerfScriptReader::writeUnsymbolizedProfile(StringRef Filename) {
std::error_code EC;
raw_fd_ostream OS(Filename, EC, llvm::sys::fs::OF_TextWithCRLF);
if (EC)
exitWithError(EC, Filename);
writeUnsymbolizedProfile(OS);
}
using OrderedCounterForPrint = std::map<std::string, SampleCounter *>;
void PerfScriptReader::writeUnsymbolizedProfile(raw_fd_ostream &OS) {
OrderedCounterForPrint OrderedCounters;
for (auto &CI : SampleCounters) {
OrderedCounters[getContextKeyStr(CI.first.getPtr(), Binary)] = &CI.second;
}
auto SCounterPrinter = [&](RangeSample &Counter, StringRef Separator,
uint32_t Indent) {
OS.indent(Indent);
OS << Counter.size() << "\n";
for (auto &I : Counter) {
uint64_t Start = I.first.first;
uint64_t End = I.first.second;
if (!UseOffset || (UseOffset && UseLoadableSegmentAsBase)) {
Start = Binary->offsetToVirtualAddr(Start);
End = Binary->offsetToVirtualAddr(End);
}
if (UseOffset && UseLoadableSegmentAsBase) {
Start -= Binary->getFirstLoadableAddress();
End -= Binary->getFirstLoadableAddress();
}
OS.indent(Indent);
OS << Twine::utohexstr(Start) << Separator << Twine::utohexstr(End) << ":"
<< I.second << "\n";
}
};
for (auto &CI : OrderedCounters) {
uint32_t Indent = 0;
if (ProfileIsCS) {
OS << "[" << CI.first << "]\n";
Indent = 2;
}
SampleCounter &Counter = *CI.second;
SCounterPrinter(Counter.RangeCounter, "-", Indent);
SCounterPrinter(Counter.BranchCounter, "->", Indent);
}
}
void UnsymbolizedProfileReader::readSampleCounters(TraceStream &TraceIt,
SampleCounter &SCounters) {
auto exitWithErrorForTraceLine = [](TraceStream &TraceIt) {
std::string Msg = TraceIt.isAtEoF()
? "Invalid raw profile!"
: "Invalid raw profile at line " +
Twine(TraceIt.getLineNumber()).str() + ": " +
TraceIt.getCurrentLine().str();
exitWithError(Msg);
};
auto ReadNumber = [&](uint64_t &Num) {
if (TraceIt.isAtEoF())
exitWithErrorForTraceLine(TraceIt);
if (TraceIt.getCurrentLine().ltrim().getAsInteger(10, Num))
exitWithErrorForTraceLine(TraceIt);
TraceIt.advance();
};
auto ReadCounter = [&](RangeSample &Counter, StringRef Separator) {
uint64_t Num = 0;
ReadNumber(Num);
while (Num--) {
if (TraceIt.isAtEoF())
exitWithErrorForTraceLine(TraceIt);
StringRef Line = TraceIt.getCurrentLine().ltrim();
uint64_t Count = 0;
auto LineSplit = Line.split(":");
if (LineSplit.second.empty() || LineSplit.second.getAsInteger(10, Count))
exitWithErrorForTraceLine(TraceIt);
uint64_t Source = 0;
uint64_t Target = 0;
auto Range = LineSplit.first.split(Separator);
if (Range.second.empty() || Range.first.getAsInteger(16, Source) ||
Range.second.getAsInteger(16, Target))
exitWithErrorForTraceLine(TraceIt);
if (!UseOffset || (UseOffset && UseLoadableSegmentAsBase)) {
uint64_t BaseAddr = 0;
if (UseOffset && UseLoadableSegmentAsBase)
BaseAddr = Binary->getFirstLoadableAddress();
Source = Binary->virtualAddrToOffset(Source + BaseAddr);
Target = Binary->virtualAddrToOffset(Target + BaseAddr);
}
Counter[{Source, Target}] += Count;
TraceIt.advance();
}
};
ReadCounter(SCounters.RangeCounter, "-");
ReadCounter(SCounters.BranchCounter, "->");
}
void UnsymbolizedProfileReader::readUnsymbolizedProfile(StringRef FileName) {
TraceStream TraceIt(FileName);
while (!TraceIt.isAtEoF()) {
std::shared_ptr<StringBasedCtxKey> Key =
std::make_shared<StringBasedCtxKey>();
StringRef Line = TraceIt.getCurrentLine();
if (Line.startswith("[")) {
ProfileIsCS = true;
auto I = ContextStrSet.insert(Line.str());
SampleContext::createCtxVectorFromStr(*I.first, Key->Context);
TraceIt.advance();
}
auto Ret =
SampleCounters.emplace(Hashable<ContextKey>(Key), SampleCounter());
readSampleCounters(TraceIt, Ret.first->second);
}
}
void UnsymbolizedProfileReader::parsePerfTraces() {
readUnsymbolizedProfile(PerfTraceFile);
}
void PerfScriptReader::computeCounterFromLBR(const PerfSample *Sample,
uint64_t Repeat) {
SampleCounter &Counter = SampleCounters.begin()->second;
uint64_t EndOffeset = 0;
for (const LBREntry &LBR : Sample->LBRStack) {
uint64_t SourceOffset = Binary->virtualAddrToOffset(LBR.Source);
uint64_t TargetOffset = Binary->virtualAddrToOffset(LBR.Target);
if (Binary->offsetIsCode(TargetOffset)) {
Counter.recordBranchCount(SourceOffset, TargetOffset, Repeat);
}
uint64_t StartOffset = TargetOffset;
if (Binary->offsetIsCode(StartOffset) && Binary->offsetIsCode(EndOffeset) &&
isValidFallThroughRange(StartOffset, EndOffeset, Binary))
Counter.recordRangeCount(StartOffset, EndOffeset, Repeat);
EndOffeset = SourceOffset;
}
}
void LBRPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) {
std::shared_ptr<PerfSample> Sample = std::make_shared<PerfSample>();
if (extractLBRStack(TraceIt, Sample->LBRStack)) {
warnIfMissingMMap();
AggregatedSamples[Hashable<PerfSample>(Sample)] += Count;
}
}
void PerfScriptReader::generateUnsymbolizedProfile() {
assert(SampleCounters.empty() &&
"Sample counter map should be empty before raw profile generation");
std::shared_ptr<StringBasedCtxKey> Key =
std::make_shared<StringBasedCtxKey>();
SampleCounters.emplace(Hashable<ContextKey>(Key), SampleCounter());
for (const auto &Item : AggregatedSamples) {
const PerfSample *Sample = Item.first.getPtr();
computeCounterFromLBR(Sample, Item.second);
}
}
uint64_t PerfScriptReader::parseAggregatedCount(TraceStream &TraceIt) {
uint64_t Count = 1;
if (!TraceIt.getCurrentLine().getAsInteger(10, Count))
TraceIt.advance();
return Count;
}
void PerfScriptReader::parseSample(TraceStream &TraceIt) {
NumTotalSample++;
uint64_t Count = parseAggregatedCount(TraceIt);
assert(Count >= 1 && "Aggregated count should be >= 1!");
parseSample(TraceIt, Count);
}
bool PerfScriptReader::extractMMap2EventForBinary(ProfiledBinary *Binary,
StringRef Line,
MMapEvent &MMap) {
constexpr static const char *const Pattern =
"PERF_RECORD_MMAP2 ([0-9]+)/[0-9]+: "
"\\[(0x[a-f0-9]+)\\((0x[a-f0-9]+)\\) @ "
"(0x[a-f0-9]+|0) .*\\]: [-a-z]+ (.*)";
enum EventIndex {
WHOLE_LINE = 0,
PID = 1,
MMAPPED_ADDRESS = 2,
MMAPPED_SIZE = 3,
PAGE_OFFSET = 4,
BINARY_PATH = 5
};
Regex RegMmap2(Pattern);
SmallVector<StringRef, 6> Fields;
bool R = RegMmap2.match(Line, &Fields);
if (!R) {
std::string ErrorMsg = "Cannot parse mmap event: " + Line.str() + " \n";
exitWithError(ErrorMsg);
}
Fields[PID].getAsInteger(10, MMap.PID);
Fields[MMAPPED_ADDRESS].getAsInteger(0, MMap.Address);
Fields[MMAPPED_SIZE].getAsInteger(0, MMap.Size);
Fields[PAGE_OFFSET].getAsInteger(0, MMap.Offset);
MMap.BinaryPath = Fields[BINARY_PATH];
if (ShowMmapEvents) {
outs() << "Mmap: Binary " << MMap.BinaryPath << " loaded at "
<< format("0x%" PRIx64 ":", MMap.Address) << " \n";
}
StringRef BinaryName = llvm::sys::path::filename(MMap.BinaryPath);
return Binary->getName() == BinaryName;
}
void PerfScriptReader::parseMMap2Event(TraceStream &TraceIt) {
MMapEvent MMap;
if (extractMMap2EventForBinary(Binary, TraceIt.getCurrentLine(), MMap))
updateBinaryAddress(MMap);
TraceIt.advance();
}
void PerfScriptReader::parseEventOrSample(TraceStream &TraceIt) {
if (isMMap2Event(TraceIt.getCurrentLine()))
parseMMap2Event(TraceIt);
else
parseSample(TraceIt);
}
void PerfScriptReader::parseAndAggregateTrace() {
TraceStream TraceIt(PerfTraceFile);
while (!TraceIt.isAtEoF())
parseEventOrSample(TraceIt);
}
bool PerfScriptReader::isLBRSample(StringRef Line) {
SmallVector<StringRef, 32> Records;
Line.trim().split(Records, " ", 2, false);
if (Records.size() < 2)
return false;
if (Records[1].startswith("0x") && Records[1].contains('/'))
return true;
return false;
}
bool PerfScriptReader::isMMap2Event(StringRef Line) {
if (Line.empty() || Line.size() < 50)
return false;
if (std::isdigit(Line[0]))
return false;
return Line.contains("PERF_RECORD_MMAP2");
}
PerfContent PerfScriptReader::checkPerfScriptType(StringRef FileName) {
TraceStream TraceIt(FileName);
uint64_t FrameAddr = 0;
while (!TraceIt.isAtEoF()) {
if (!TraceIt.getCurrentLine().getAsInteger(10, FrameAddr))
TraceIt.advance();
int32_t Count = 0;
while (!TraceIt.isAtEoF() &&
!TraceIt.getCurrentLine().ltrim().getAsInteger(16, FrameAddr)) {
Count++;
TraceIt.advance();
}
if (!TraceIt.isAtEoF()) {
if (isLBRSample(TraceIt.getCurrentLine())) {
if (Count > 0)
return PerfContent::LBRStack;
else
return PerfContent::LBR;
}
TraceIt.advance();
}
}
exitWithError("Invalid perf script input!");
return PerfContent::UnknownContent;
}
void HybridPerfReader::generateUnsymbolizedProfile() {
ProfileIsCS = !IgnoreStackSamples;
if (ProfileIsCS)
unwindSamples();
else
PerfScriptReader::generateUnsymbolizedProfile();
}
void PerfScriptReader::warnTruncatedStack() {
if (ShowDetailedWarning) {
for (auto Address : InvalidReturnAddresses) {
WithColor::warning()
<< "Truncated stack sample due to invalid return address at "
<< format("0x%" PRIx64, Address)
<< ", likely caused by frame pointer omission\n";
}
}
emitWarningSummary(
InvalidReturnAddresses.size(), AggregatedSamples.size(),
"of truncated stack samples due to invalid return address, "
"likely caused by frame pointer omission.");
}
void PerfScriptReader::warnInvalidRange() {
std::unordered_map<std::pair<uint64_t, uint64_t>, uint64_t,
pair_hash<uint64_t, uint64_t>>
Ranges;
for (const auto &Item : AggregatedSamples) {
const PerfSample *Sample = Item.first.getPtr();
uint64_t Count = Item.second;
uint64_t EndOffeset = 0;
for (const LBREntry &LBR : Sample->LBRStack) {
uint64_t SourceOffset = Binary->virtualAddrToOffset(LBR.Source);
uint64_t StartOffset = Binary->virtualAddrToOffset(LBR.Target);
if (EndOffeset != 0)
Ranges[{StartOffset, EndOffeset}] += Count;
EndOffeset = SourceOffset;
}
}
if (Ranges.empty()) {
WithColor::warning() << "No samples in perf script!\n";
return;
}
auto WarnInvalidRange =
[&](uint64_t StartOffset, uint64_t EndOffset, StringRef Msg) {
if (!ShowDetailedWarning)
return;
WithColor::warning()
<< "["
<< format("%8" PRIx64, Binary->offsetToVirtualAddr(StartOffset))
<< ","
<< format("%8" PRIx64, Binary->offsetToVirtualAddr(EndOffset))
<< "]: " << Msg << "\n";
};
const char *EndNotBoundaryMsg = "Range is not on instruction boundary, "
"likely due to profile and binary mismatch.";
const char *DanglingRangeMsg = "Range does not belong to any functions, "
"likely from PLT, .init or .fini section.";
const char *RangeCrossFuncMsg =
"Fall through range should not cross function boundaries, likely due to "
"profile and binary mismatch.";
const char *BogusRangeMsg = "Range start is after or too far from range end.";
uint64_t TotalRangeNum = 0;
uint64_t InstNotBoundary = 0;
uint64_t UnmatchedRange = 0;
uint64_t RangeCrossFunc = 0;
uint64_t BogusRange = 0;
for (auto &I : Ranges) {
uint64_t StartOffset = I.first.first;
uint64_t EndOffset = I.first.second;
TotalRangeNum += I.second;
if (!Binary->offsetIsCode(StartOffset) ||
!Binary->offsetIsTransfer(EndOffset)) {
InstNotBoundary += I.second;
WarnInvalidRange(StartOffset, EndOffset, EndNotBoundaryMsg);
}
auto *FRange = Binary->findFuncRangeForOffset(StartOffset);
if (!FRange) {
UnmatchedRange += I.second;
WarnInvalidRange(StartOffset, EndOffset, DanglingRangeMsg);
continue;
}
if (EndOffset >= FRange->EndOffset) {
RangeCrossFunc += I.second;
WarnInvalidRange(StartOffset, EndOffset, RangeCrossFuncMsg);
}
if (!isValidFallThroughRange(StartOffset, EndOffset, Binary)) {
BogusRange += I.second;
WarnInvalidRange(StartOffset, EndOffset, BogusRangeMsg);
}
}
emitWarningSummary(
InstNotBoundary, TotalRangeNum,
"of samples are from ranges that are not on instruction boundary.");
emitWarningSummary(
UnmatchedRange, TotalRangeNum,
"of samples are from ranges that do not belong to any functions.");
emitWarningSummary(
RangeCrossFunc, TotalRangeNum,
"of samples are from ranges that do cross function boundaries.");
emitWarningSummary(
BogusRange, TotalRangeNum,
"of samples are from ranges that have range start after or too far from "
"range end acrossing the unconditinal jmp.");
}
void PerfScriptReader::parsePerfTraces() {
parseAndAggregateTrace();
emitWarningSummary(NumLeafExternalFrame, NumTotalSample,
"of samples have leaf external frame in call stack.");
emitWarningSummary(NumLeadingOutgoingLBR, NumTotalSample,
"of samples have leading external LBR.");
warnTruncatedStack();
warnInvalidRange();
generateUnsymbolizedProfile();
AggregatedSamples.clear();
if (SkipSymbolization)
writeUnsymbolizedProfile(OutputFilename);
}
} }