2447 lines · cpp
1//===- bolt/Profile/DataAggregator.cpp - Perf data aggregator -------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This family of functions reads profile data written by perf record,10// aggregate it and then write it back to an output file.11//12//===----------------------------------------------------------------------===//13 14#include "bolt/Profile/DataAggregator.h"15#include "bolt/Core/BinaryContext.h"16#include "bolt/Core/BinaryFunction.h"17#include "bolt/Passes/BinaryPasses.h"18#include "bolt/Profile/BoltAddressTranslation.h"19#include "bolt/Profile/Heatmap.h"20#include "bolt/Profile/YAMLProfileWriter.h"21#include "bolt/Utils/CommandLineOpts.h"22#include "bolt/Utils/Utils.h"23#include "llvm/ADT/STLExtras.h"24#include "llvm/ADT/ScopeExit.h"25#include "llvm/Support/CommandLine.h"26#include "llvm/Support/Compiler.h"27#include "llvm/Support/Debug.h"28#include "llvm/Support/Errc.h"29#include "llvm/Support/FileSystem.h"30#include "llvm/Support/Process.h"31#include "llvm/Support/Program.h"32#include "llvm/Support/Regex.h"33#include "llvm/Support/Timer.h"34#include "llvm/Support/raw_ostream.h"35#include <map>36#include <optional>37#include <unordered_map>38#include <utility>39 40#define DEBUG_TYPE "aggregator"41 42using namespace llvm;43using namespace bolt;44 45namespace opts {46 47static cl::opt<bool>48 BasicAggregation("basic-events",49 cl::desc("aggregate basic events (without brstack info)"),50 cl::cat(AggregatorCategory));51 52static cl::alias BasicAggregationAlias("ba",53 cl::desc("Alias for --basic-events"),54 cl::aliasopt(BasicAggregation));55 56static cl::opt<bool> DeprecatedBasicAggregationNl(57 "nl", cl::desc("Alias for --basic-events (deprecated. Use --ba)"),58 cl::cat(AggregatorCategory), cl::ReallyHidden,59 cl::callback([](const bool &Enabled) {60 errs()61 << "BOLT-WARNING: '-nl' is deprecated, please use '--ba' instead.\n";62 BasicAggregation = Enabled;63 }));64 65cl::opt<bool> ArmSPE("spe", cl::desc("Enable Arm SPE mode."),66 cl::cat(AggregatorCategory));67 68static cl::opt<std::string> ITraceAggregation(69 "itrace", cl::desc("Generate brstack info with perf itrace argument"),70 cl::cat(AggregatorCategory));71 72static cl::opt<bool>73FilterMemProfile("filter-mem-profile",74 cl::desc("if processing a memory profile, filter out stack or heap accesses "75 "that won't be useful for BOLT to reduce profile file size"),76 cl::init(true),77 cl::cat(AggregatorCategory));78 79static cl::opt<bool> ParseMemProfile(80 "parse-mem-profile",81 cl::desc("enable memory profile parsing if it's present in the input data, "82 "on by default unless `--itrace` is set."),83 cl::init(true), cl::cat(AggregatorCategory));84 85static cl::opt<unsigned long long>86FilterPID("pid",87 cl::desc("only use samples from process with specified PID"),88 cl::init(0),89 cl::Optional,90 cl::cat(AggregatorCategory));91 92static cl::opt<bool> ImputeTraceFallthrough(93 "impute-trace-fall-through",94 cl::desc("impute missing fall-throughs for branch-only traces"),95 cl::Optional, cl::cat(AggregatorCategory));96 97static cl::opt<bool>98IgnoreBuildID("ignore-build-id",99 cl::desc("continue even if build-ids in input binary and perf.data mismatch"),100 cl::init(false),101 cl::cat(AggregatorCategory));102 103static cl::opt<bool> IgnoreInterruptLBR(104 "ignore-interrupt-lbr",105 cl::desc("ignore kernel interrupt LBR that happens asynchronously"),106 cl::init(true), cl::cat(AggregatorCategory));107 108static cl::opt<unsigned long long>109MaxSamples("max-samples",110 cl::init(-1ULL),111 cl::desc("maximum number of samples to read from LBR profile"),112 cl::Optional,113 cl::Hidden,114 cl::cat(AggregatorCategory));115 116extern cl::opt<opts::ProfileFormatKind> ProfileFormat;117extern cl::opt<bool> ProfileWritePseudoProbes;118extern cl::opt<std::string> SaveProfile;119 120cl::opt<bool> ReadPreAggregated(121 "pa", cl::desc("skip perf and read data from a pre-aggregated file format"),122 cl::cat(AggregatorCategory));123 124cl::opt<std::string>125 ReadPerfEvents("perf-script-events",126 cl::desc("skip perf event collection by supplying a "127 "perf-script output in a textual format"),128 cl::ReallyHidden, cl::init(""), cl::cat(AggregatorCategory));129 130static cl::opt<bool>131TimeAggregator("time-aggr",132 cl::desc("time BOLT aggregator"),133 cl::init(false),134 cl::ZeroOrMore,135 cl::cat(AggregatorCategory));136 137} // namespace opts138 139namespace {140 141const char TimerGroupName[] = "aggregator";142const char TimerGroupDesc[] = "Aggregator";143 144std::vector<SectionNameAndRange> getTextSections(const BinaryContext *BC) {145 std::vector<SectionNameAndRange> sections;146 for (BinarySection &Section : BC->sections()) {147 if (!Section.isText())148 continue;149 if (Section.getSize() == 0)150 continue;151 sections.push_back(152 {Section.getName(), Section.getAddress(), Section.getEndAddress()});153 }154 llvm::sort(sections,155 [](const SectionNameAndRange &A, const SectionNameAndRange &B) {156 return A.BeginAddress < B.BeginAddress;157 });158 return sections;159}160}161 162DataAggregator::~DataAggregator() { deleteTempFiles(); }163 164namespace {165void deleteTempFile(const std::string &FileName) {166 if (std::error_code Errc = sys::fs::remove(FileName.c_str()))167 errs() << "PERF2BOLT: failed to delete temporary file " << FileName168 << " with error " << Errc.message() << "\n";169}170}171 172void DataAggregator::deleteTempFiles() {173 for (std::string &FileName : TempFiles)174 deleteTempFile(FileName);175 TempFiles.clear();176}177 178void DataAggregator::findPerfExecutable() {179 std::optional<std::string> PerfExecutable =180 sys::Process::FindInEnvPath("PATH", "perf");181 if (!PerfExecutable) {182 outs() << "PERF2BOLT: No perf executable found!\n";183 exit(1);184 }185 PerfPath = *PerfExecutable;186}187 188void DataAggregator::start() {189 outs() << "PERF2BOLT: Starting data aggregation job for " << Filename << "\n";190 191 // Turn on heatmap building if requested by --heatmap flag.192 if (!opts::HeatmapMode && opts::HeatmapOutput.getNumOccurrences())193 opts::HeatmapMode = opts::HeatmapModeKind::HM_Optional;194 195 // Don't launch perf for pre-aggregated files or when perf input is specified196 // by the user.197 if (opts::ReadPreAggregated || !opts::ReadPerfEvents.empty())198 return;199 200 findPerfExecutable();201 202 if (opts::ArmSPE) {203 // pid from_ip to_ip flags204 // where flags could be:205 // P/M: whether branch was Predicted or Mispredicted.206 // N: optionally appears when the branch was Not-Taken (ie fall-through)207 // 12345 0x123/0x456/PN/-/-/8/RET/-208 opts::ITraceAggregation = "bl";209 opts::ParseMemProfile = true;210 opts::BasicAggregation = false;211 }212 213 if (opts::BasicAggregation) {214 launchPerfProcess("events without brstack", MainEventsPPI,215 "script -F pid,event,ip");216 } else if (!opts::ITraceAggregation.empty()) {217 // Disable parsing memory profile from trace data, unless requested by user.218 if (!opts::ParseMemProfile.getNumOccurrences())219 opts::ParseMemProfile = false;220 launchPerfProcess("branch events with itrace", MainEventsPPI,221 "script -F pid,brstack --itrace=" +222 opts::ITraceAggregation);223 } else {224 launchPerfProcess("branch events", MainEventsPPI, "script -F pid,brstack");225 }226 227 if (opts::ParseMemProfile)228 launchPerfProcess("mem events", MemEventsPPI,229 "script -F pid,event,addr,ip");230 231 launchPerfProcess("process events", MMapEventsPPI,232 "script --show-mmap-events --no-itrace");233 234 launchPerfProcess("task events", TaskEventsPPI,235 "script --show-task-events --no-itrace");236}237 238void DataAggregator::abort() {239 if (opts::ReadPreAggregated)240 return;241 242 std::string Error;243 244 // Kill subprocesses in case they are not finished245 sys::Wait(TaskEventsPPI.PI, 1, &Error);246 sys::Wait(MMapEventsPPI.PI, 1, &Error);247 sys::Wait(MainEventsPPI.PI, 1, &Error);248 if (opts::ParseMemProfile)249 sys::Wait(MemEventsPPI.PI, 1, &Error);250 251 deleteTempFiles();252 253 exit(1);254}255 256void DataAggregator::launchPerfProcess(StringRef Name, PerfProcessInfo &PPI,257 StringRef Args) {258 SmallVector<StringRef, 4> Argv;259 260 outs() << "PERF2BOLT: spawning perf job to read " << Name << '\n';261 Argv.push_back(PerfPath.data());262 263 Args.split(Argv, ' ');264 Argv.push_back("-f");265 Argv.push_back("-i");266 Argv.push_back(Filename.c_str());267 268 if (std::error_code Errc =269 sys::fs::createTemporaryFile("perf.script", "out", PPI.StdoutPath)) {270 errs() << "PERF2BOLT: failed to create temporary file " << PPI.StdoutPath271 << " with error " << Errc.message() << "\n";272 exit(1);273 }274 TempFiles.push_back(PPI.StdoutPath.data());275 276 if (std::error_code Errc =277 sys::fs::createTemporaryFile("perf.script", "err", PPI.StderrPath)) {278 errs() << "PERF2BOLT: failed to create temporary file " << PPI.StderrPath279 << " with error " << Errc.message() << "\n";280 exit(1);281 }282 TempFiles.push_back(PPI.StderrPath.data());283 284 std::optional<StringRef> Redirects[] = {285 std::nullopt, // Stdin286 StringRef(PPI.StdoutPath.data()), // Stdout287 StringRef(PPI.StderrPath.data())}; // Stderr288 289 LLVM_DEBUG({290 dbgs() << "Launching perf: ";291 for (StringRef Arg : Argv)292 dbgs() << Arg << " ";293 dbgs() << " 1> " << PPI.StdoutPath.data() << " 2> " << PPI.StderrPath.data()294 << "\n";295 });296 297 PPI.PI = sys::ExecuteNoWait(PerfPath.data(), Argv, /*envp*/ std::nullopt,298 Redirects);299}300 301void DataAggregator::processFileBuildID(StringRef FileBuildID) {302 auto WarningCallback = [](int ReturnCode, StringRef ErrBuf) {303 errs() << "PERF-ERROR: return code " << ReturnCode << "\n" << ErrBuf;304 };305 306 PerfProcessInfo BuildIDProcessInfo;307 launchPerfProcess("buildid list", BuildIDProcessInfo, "buildid-list");308 if (prepareToParse("buildid", BuildIDProcessInfo, WarningCallback))309 return;310 311 std::optional<StringRef> FileName = getFileNameForBuildID(FileBuildID);312 if (FileName && *FileName == sys::path::filename(BC->getFilename())) {313 outs() << "PERF2BOLT: matched build-id and file name\n";314 return;315 }316 317 if (FileName) {318 errs() << "PERF2BOLT-WARNING: build-id matched a different file name\n";319 BuildIDBinaryName = std::string(*FileName);320 return;321 }322 323 if (!hasAllBuildIDs()) {324 errs() << "PERF2BOLT-WARNING: build-id will not be checked because perf "325 "data was recorded without it\n";326 return;327 }328 329 errs() << "PERF2BOLT-ERROR: failed to match build-id from perf output. "330 "This indicates the input binary supplied for data aggregation "331 "is not the same recorded by perf when collecting profiling "332 "data, or there were no samples recorded for the binary. "333 "Use -ignore-build-id option to override.\n";334 if (!opts::IgnoreBuildID)335 abort();336}337 338bool DataAggregator::checkPerfDataMagic(StringRef FileName) {339 if (opts::ReadPreAggregated)340 return true;341 342 Expected<sys::fs::file_t> FD = sys::fs::openNativeFileForRead(FileName);343 if (!FD) {344 consumeError(FD.takeError());345 return false;346 }347 348 char Buf[7] = {0, 0, 0, 0, 0, 0, 0};349 350 auto Close = make_scope_exit([&] { sys::fs::closeFile(*FD); });351 Expected<size_t> BytesRead = sys::fs::readNativeFileSlice(352 *FD, MutableArrayRef(Buf, sizeof(Buf)), 0);353 if (!BytesRead) {354 consumeError(BytesRead.takeError());355 return false;356 }357 358 if (*BytesRead != 7)359 return false;360 361 if (strncmp(Buf, "PERFILE", 7) == 0)362 return true;363 return false;364}365 366void DataAggregator::parsePreAggregated() {367 ErrorOr<std::unique_ptr<MemoryBuffer>> MB =368 MemoryBuffer::getFileOrSTDIN(Filename);369 if (std::error_code EC = MB.getError()) {370 errs() << "PERF2BOLT-ERROR: cannot open " << Filename << ": "371 << EC.message() << "\n";372 exit(1);373 }374 375 FileBuf = std::move(*MB);376 ParsingBuf = FileBuf->getBuffer();377 Col = 0;378 Line = 1;379 if (parsePreAggregatedLBRSamples()) {380 errs() << "PERF2BOLT: failed to parse samples\n";381 exit(1);382 }383}384 385void DataAggregator::filterBinaryMMapInfo() {386 if (opts::FilterPID) {387 auto MMapInfoIter = BinaryMMapInfo.find(opts::FilterPID);388 if (MMapInfoIter != BinaryMMapInfo.end()) {389 MMapInfo MMap = MMapInfoIter->second;390 BinaryMMapInfo.clear();391 BinaryMMapInfo.insert(std::make_pair(MMap.PID, MMap));392 } else {393 if (errs().has_colors())394 errs().changeColor(raw_ostream::RED);395 errs() << "PERF2BOLT-ERROR: could not find a profile matching PID \""396 << opts::FilterPID << "\""397 << " for binary \"" << BC->getFilename() << "\".";398 assert(!BinaryMMapInfo.empty() && "No memory map for matching binary");399 errs() << " Profile for the following process is available:\n";400 for (std::pair<const uint64_t, MMapInfo> &MMI : BinaryMMapInfo)401 outs() << " " << MMI.second.PID402 << (MMI.second.Forked ? " (forked)\n" : "\n");403 404 if (errs().has_colors())405 errs().resetColor();406 407 exit(1);408 }409 }410}411 412int DataAggregator::prepareToParse(StringRef Name, PerfProcessInfo &Process,413 PerfProcessErrorCallbackTy Callback) {414 if (!opts::ReadPerfEvents.empty()) {415 outs() << "PERF2BOLT: using pre-processed perf events for '" << Name416 << "' (perf-script-events)\n";417 ParsingBuf = opts::ReadPerfEvents;418 return 0;419 }420 421 std::string Error;422 outs() << "PERF2BOLT: waiting for perf " << Name423 << " collection to finish...\n";424 std::optional<sys::ProcessStatistics> PS;425 sys::ProcessInfo PI = sys::Wait(Process.PI, std::nullopt, &Error, &PS);426 427 if (!Error.empty()) {428 errs() << "PERF-ERROR: " << PerfPath << ": " << Error << "\n";429 deleteTempFiles();430 exit(1);431 }432 433 LLVM_DEBUG({434 const float UserSec = 1.f * PS->UserTime.count() / 1e6;435 const float TotalSec = 1.f * PS->TotalTime.count() / 1e6;436 const float PeakGiB = 1.f * PS->PeakMemory / (1 << 20);437 dbgs() << formatv("Finished in {0:f2}s user time, {1:f2}s total time, "438 "{2:f2} GiB peak RSS\n",439 UserSec, TotalSec, PeakGiB);440 });441 442 if (PI.ReturnCode != 0) {443 ErrorOr<std::unique_ptr<MemoryBuffer>> ErrorMB =444 MemoryBuffer::getFileOrSTDIN(Process.StderrPath.data());445 StringRef ErrBuf = (*ErrorMB)->getBuffer();446 447 deleteTempFiles();448 Callback(PI.ReturnCode, ErrBuf);449 return PI.ReturnCode;450 }451 452 ErrorOr<std::unique_ptr<MemoryBuffer>> MB =453 MemoryBuffer::getFileOrSTDIN(Process.StdoutPath.data());454 if (std::error_code EC = MB.getError()) {455 errs() << "Cannot open " << Process.StdoutPath.data() << ": "456 << EC.message() << "\n";457 deleteTempFiles();458 exit(1);459 }460 461 FileBuf = std::move(*MB);462 ParsingBuf = FileBuf->getBuffer();463 Col = 0;464 Line = 1;465 return PI.ReturnCode;466}467 468void DataAggregator::parsePerfData(BinaryContext &BC) {469 auto ErrorCallback = [](int ReturnCode, StringRef ErrBuf) {470 errs() << "PERF-ERROR: return code " << ReturnCode << "\n" << ErrBuf;471 exit(1);472 };473 474 auto MemEventsErrorCallback = [&](int ReturnCode, StringRef ErrBuf) {475 Regex NoData("Samples for '.*' event do not have ADDR attribute set. "476 "Cannot print 'addr' field.");477 if (!NoData.match(ErrBuf))478 ErrorCallback(ReturnCode, ErrBuf);479 };480 481 if (std::optional<StringRef> FileBuildID = BC.getFileBuildID()) {482 outs() << "BOLT-INFO: binary build-id is: " << *FileBuildID << "\n";483 processFileBuildID(*FileBuildID);484 } else {485 errs() << "BOLT-WARNING: build-id will not be checked because we could "486 "not read one from input binary\n";487 }488 489 if (BC.IsLinuxKernel) {490 // Current MMap parsing logic does not work with linux kernel.491 // MMap entries for linux kernel uses PERF_RECORD_MMAP492 // format instead of typical PERF_RECORD_MMAP2 format.493 // Since linux kernel address mapping is absolute (same as494 // in the ELF file), we avoid parsing MMap in linux kernel mode.495 // While generating optimized linux kernel binary, we may need496 // to parse MMap entries.497 498 // In linux kernel mode, we analyze and optimize499 // all linux kernel binary instructions, irrespective500 // of whether they are due to system calls or due to501 // interrupts. Therefore, we cannot ignore interrupt502 // in Linux kernel mode.503 opts::IgnoreInterruptLBR = false;504 } else {505 prepareToParse("mmap events", MMapEventsPPI, ErrorCallback);506 if (parseMMapEvents())507 errs() << "PERF2BOLT: failed to parse mmap events\n";508 }509 510 prepareToParse("task events", TaskEventsPPI, ErrorCallback);511 if (parseTaskEvents())512 errs() << "PERF2BOLT: failed to parse task events\n";513 514 filterBinaryMMapInfo();515 prepareToParse("events", MainEventsPPI, ErrorCallback);516 517 if ((!opts::BasicAggregation && parseBranchEvents()) ||518 (opts::BasicAggregation && parseBasicEvents()))519 errs() << "PERF2BOLT: failed to parse samples\n";520 521 // Special handling for memory events522 if (opts::ParseMemProfile &&523 !prepareToParse("mem events", MemEventsPPI, MemEventsErrorCallback))524 if (const std::error_code EC = parseMemEvents())525 errs() << "PERF2BOLT: failed to parse memory events: " << EC.message()526 << '\n';527 528 deleteTempFiles();529}530 531void DataAggregator::imputeFallThroughs() {532 if (Traces.empty())533 return;534 535 std::pair PrevBranch(Trace::EXTERNAL, Trace::EXTERNAL);536 uint64_t AggregateCount = 0;537 uint64_t AggregateFallthroughSize = 0;538 uint64_t InferredTraces = 0;539 540 // Helper map with whether the instruction is a call/ret/unconditional branch541 std::unordered_map<uint64_t, bool> IsUncondCTMap;542 auto checkUnconditionalControlTransfer = [&](const uint64_t Addr) {543 auto isUncondCT = [&](const MCInst &MI) -> bool {544 return BC->MIB->isUnconditionalControlTransfer(MI);545 };546 return testAndSet<bool>(Addr, isUncondCT, IsUncondCTMap).value_or(true);547 };548 549 // Traces are sorted by their component addresses (Branch, From, To).550 // assert(is_sorted(Traces));551 552 // Traces corresponding to the top-of-stack branch entry with a missing553 // fall-through have BR_ONLY(-1ULL/UINT64_MAX) in To field, meaning that for554 // fixed values of Branch and From branch-only traces are stored after all555 // traces with valid fall-through.556 //557 // Group traces by (Branch, From) and compute weighted average fall-through558 // length for the top-of-stack trace (closing the group) by accumulating the559 // fall-through lengths of traces with valid fall-throughs earlier in the560 // group.561 for (auto &[Trace, Info] : Traces) {562 // Skip fall-throughs in external code.563 if (Trace.From == Trace::EXTERNAL)564 continue;565 if (std::pair CurrentBranch(Trace.Branch, Trace.From);566 CurrentBranch != PrevBranch) {567 // New group: reset aggregates.568 AggregateCount = AggregateFallthroughSize = 0;569 PrevBranch = CurrentBranch;570 }571 // BR_ONLY must be the last trace in the group572 if (Trace.To == Trace::BR_ONLY) {573 // If the group is not empty, use aggregate values, otherwise 0-length574 // for unconditional jumps (call/ret/uncond branch) or 1-length for others575 uint64_t InferredBytes =576 AggregateFallthroughSize577 ? AggregateFallthroughSize / AggregateCount578 : !checkUnconditionalControlTransfer(Trace.From);579 Trace.To = Trace.From + InferredBytes;580 LLVM_DEBUG(dbgs() << "imputed " << Trace << " (" << InferredBytes581 << " bytes)\n");582 ++InferredTraces;583 } else {584 // Only use valid fall-through lengths585 if (Trace.To != Trace::EXTERNAL)586 AggregateFallthroughSize += (Trace.To - Trace.From) * Info.TakenCount;587 AggregateCount += Info.TakenCount;588 }589 }590 if (opts::Verbosity >= 1)591 outs() << "BOLT-INFO: imputed " << InferredTraces << " traces\n";592}593 594Error DataAggregator::preprocessProfile(BinaryContext &BC) {595 this->BC = &BC;596 597 if (opts::ReadPreAggregated) {598 parsePreAggregated();599 } else {600 parsePerfData(BC);601 }602 603 // Sort parsed traces for faster processing.604 llvm::sort(Traces, llvm::less_first());605 606 if (opts::ImputeTraceFallthrough)607 imputeFallThroughs();608 609 if (opts::HeatmapMode) {610 if (std::error_code EC = printLBRHeatMap())611 return errorCodeToError(EC);612 if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive)613 exit(0);614 }615 616 return Error::success();617}618 619Error DataAggregator::readProfile(BinaryContext &BC) {620 processProfile(BC);621 622 for (auto &BFI : BC.getBinaryFunctions()) {623 BinaryFunction &Function = BFI.second;624 convertBranchData(Function);625 }626 627 if (opts::AggregateOnly) {628 if (opts::ProfileFormat == opts::ProfileFormatKind::PF_Fdata)629 if (std::error_code EC = writeAggregatedFile(opts::OutputFilename))630 report_error("cannot create output data file", EC);631 632 // BAT YAML is handled by DataAggregator since normal YAML output requires633 // CFG which is not available in BAT mode.634 if (usesBAT()) {635 if (opts::ProfileFormat == opts::ProfileFormatKind::PF_YAML)636 if (std::error_code EC = writeBATYAML(BC, opts::OutputFilename))637 report_error("cannot create output data file", EC);638 if (!opts::SaveProfile.empty())639 if (std::error_code EC = writeBATYAML(BC, opts::SaveProfile))640 report_error("cannot create output data file", EC);641 }642 }643 644 return Error::success();645}646 647bool DataAggregator::mayHaveProfileData(const BinaryFunction &Function) {648 return Function.hasProfileAvailable();649}650 651void DataAggregator::processProfile(BinaryContext &BC) {652 if (opts::BasicAggregation)653 processBasicEvents();654 else655 processBranchEvents();656 657 processMemEvents();658 659 // Mark all functions with registered events as having a valid profile.660 for (auto &BFI : BC.getBinaryFunctions()) {661 BinaryFunction &BF = BFI.second;662 if (FuncBranchData *FBD = getBranchData(BF)) {663 BF.markProfiled(BinaryFunction::PF_BRANCH);664 BF.RawSampleCount = FBD->getNumExecutedBranches();665 } else if (FuncBasicSampleData *FSD =666 getFuncBasicSampleData(BF.getNames())) {667 BF.markProfiled(BinaryFunction::PF_BASIC);668 BF.RawSampleCount = FSD->getSamples();669 }670 }671 672 for (auto &FuncBranches : NamesToBranches) {673 llvm::stable_sort(FuncBranches.second.Data);674 llvm::stable_sort(FuncBranches.second.EntryData);675 }676 677 for (auto &MemEvents : NamesToMemEvents)678 llvm::stable_sort(MemEvents.second.Data);679 680 // Release intermediate storage.681 clear(Traces);682 clear(BasicSamples);683 clear(MemSamples);684}685 686BinaryFunction *687DataAggregator::getBinaryFunctionContainingAddress(uint64_t Address) const {688 if (!BC->containsAddress(Address))689 return nullptr;690 691 return BC->getBinaryFunctionContainingAddress(Address, /*CheckPastEnd=*/false,692 /*UseMaxSize=*/true);693}694 695BinaryFunction *696DataAggregator::getBATParentFunction(const BinaryFunction &Func) const {697 if (BAT)698 if (const uint64_t HotAddr = BAT->fetchParentAddress(Func.getAddress()))699 return getBinaryFunctionContainingAddress(HotAddr);700 return nullptr;701}702 703StringRef DataAggregator::getLocationName(const BinaryFunction &Func,704 bool BAT) {705 if (!BAT)706 return Func.getOneName();707 708 const BinaryFunction *OrigFunc = &Func;709 // If it is a local function, prefer the name containing the file name where710 // the local function was declared711 for (StringRef AlternativeName : OrigFunc->getNames()) {712 size_t FileNameIdx = AlternativeName.find('/');713 // Confirm the alternative name has the pattern Symbol/FileName/1 before714 // using it715 if (FileNameIdx == StringRef::npos ||716 AlternativeName.find('/', FileNameIdx + 1) == StringRef::npos)717 continue;718 return AlternativeName;719 }720 return OrigFunc->getOneName();721}722 723bool DataAggregator::doBasicSample(BinaryFunction &OrigFunc, uint64_t Address,724 uint64_t Count) {725 // To record executed bytes, use basic block size as is regardless of BAT.726 uint64_t BlockSize = 0;727 if (BinaryBasicBlock *BB = OrigFunc.getBasicBlockContainingOffset(728 Address - OrigFunc.getAddress()))729 BlockSize = BB->getOriginalSize();730 731 BinaryFunction *ParentFunc = getBATParentFunction(OrigFunc);732 BinaryFunction &Func = ParentFunc ? *ParentFunc : OrigFunc;733 // Attach executed bytes to parent function in case of cold fragment.734 Func.SampleCountInBytes += Count * BlockSize;735 736 auto I = NamesToBasicSamples.find(Func.getOneName());737 if (I == NamesToBasicSamples.end()) {738 bool Success;739 StringRef LocName = getLocationName(Func, BAT);740 std::tie(I, Success) = NamesToBasicSamples.insert(std::make_pair(741 Func.getOneName(),742 FuncBasicSampleData(LocName, FuncBasicSampleData::ContainerTy())));743 }744 745 Address -= Func.getAddress();746 if (BAT)747 Address = BAT->translate(Func.getAddress(), Address, /*IsBranchSrc=*/false);748 749 I->second.bumpCount(Address, Count);750 return true;751}752 753bool DataAggregator::doIntraBranch(BinaryFunction &Func, uint64_t From,754 uint64_t To, uint64_t Count,755 uint64_t Mispreds) {756 FuncBranchData *AggrData = getBranchData(Func);757 if (!AggrData) {758 AggrData = &NamesToBranches[Func.getOneName()];759 AggrData->Name = getLocationName(Func, BAT);760 setBranchData(Func, AggrData);761 }762 763 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: bumpBranchCount: "764 << formatv("{0} @ {1:x} -> {0} @ {2:x}\n", Func, From, To));765 AggrData->bumpBranchCount(From, To, Count, Mispreds);766 return true;767}768 769bool DataAggregator::doInterBranch(BinaryFunction *FromFunc,770 BinaryFunction *ToFunc, uint64_t From,771 uint64_t To, uint64_t Count,772 uint64_t Mispreds) {773 FuncBranchData *FromAggrData = nullptr;774 FuncBranchData *ToAggrData = nullptr;775 StringRef SrcFunc;776 StringRef DstFunc;777 if (FromFunc) {778 SrcFunc = getLocationName(*FromFunc, BAT);779 FromAggrData = getBranchData(*FromFunc);780 if (!FromAggrData) {781 FromAggrData = &NamesToBranches[FromFunc->getOneName()];782 FromAggrData->Name = SrcFunc;783 setBranchData(*FromFunc, FromAggrData);784 }785 786 recordExit(*FromFunc, From, Mispreds, Count);787 }788 if (ToFunc) {789 DstFunc = getLocationName(*ToFunc, BAT);790 ToAggrData = getBranchData(*ToFunc);791 if (!ToAggrData) {792 ToAggrData = &NamesToBranches[ToFunc->getOneName()];793 ToAggrData->Name = DstFunc;794 setBranchData(*ToFunc, ToAggrData);795 }796 797 recordEntry(*ToFunc, To, Mispreds, Count);798 }799 800 if (FromAggrData)801 FromAggrData->bumpCallCount(From, Location(!DstFunc.empty(), DstFunc, To),802 Count, Mispreds);803 if (ToAggrData)804 ToAggrData->bumpEntryCount(Location(!SrcFunc.empty(), SrcFunc, From), To,805 Count, Mispreds);806 return true;807}808 809bool DataAggregator::checkReturn(uint64_t Addr) {810 auto isReturn = [&](const MCInst &MI) -> bool {811 return BC->MIB->isReturn(MI);812 };813 return testAndSet<bool>(Addr, isReturn, Returns).value_or(false);814}815 816bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count,817 uint64_t Mispreds) {818 // Mutates \p Addr to an offset into the containing function, performing BAT819 // offset translation and parent lookup.820 //821 // Returns the containing function (or BAT parent).822 auto handleAddress = [&](uint64_t &Addr, bool IsFrom) {823 BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr);824 if (!Func) {825 Addr = 0;826 return Func;827 }828 829 Addr -= Func->getAddress();830 831 if (BAT)832 Addr = BAT->translate(Func->getAddress(), Addr, IsFrom);833 834 if (BinaryFunction *ParentFunc = getBATParentFunction(*Func))835 return ParentFunc;836 837 return Func;838 };839 840 BinaryFunction *FromFunc = handleAddress(From, /*IsFrom*/ true);841 BinaryFunction *ToFunc = handleAddress(To, /*IsFrom*/ false);842 if (!FromFunc && !ToFunc)843 return false;844 845 // Treat recursive control transfers as inter-branches.846 if (FromFunc == ToFunc && To != 0) {847 recordBranch(*FromFunc, From, To, Count, Mispreds);848 return doIntraBranch(*FromFunc, From, To, Count, Mispreds);849 }850 851 return doInterBranch(FromFunc, ToFunc, From, To, Count, Mispreds);852}853 854bool DataAggregator::doTrace(const Trace &Trace, uint64_t Count,855 bool IsReturn) {856 const uint64_t From = Trace.From, To = Trace.To;857 BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(From);858 BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(To);859 NumTraces += Count;860 if (!FromFunc || !ToFunc) {861 LLVM_DEBUG(dbgs() << "Out of range trace " << Trace << '\n');862 NumLongRangeTraces += Count;863 return false;864 }865 if (FromFunc != ToFunc) {866 LLVM_DEBUG(dbgs() << "Invalid trace " << Trace << '\n');867 NumInvalidTraces += Count;868 return false;869 }870 871 // Set ParentFunc to BAT parent function or FromFunc itself.872 BinaryFunction *ParentFunc = getBATParentFunction(*FromFunc);873 if (!ParentFunc)874 ParentFunc = FromFunc;875 ParentFunc->SampleCountInBytes += Count * (To - From);876 877 const uint64_t FuncAddress = FromFunc->getAddress();878 std::optional<BoltAddressTranslation::FallthroughListTy> FTs =879 BAT && BAT->isBATFunction(FuncAddress)880 ? BAT->getFallthroughsInTrace(FuncAddress, From - IsReturn, To)881 : getFallthroughsInTrace(*FromFunc, Trace, Count, IsReturn);882 if (!FTs) {883 LLVM_DEBUG(dbgs() << "Invalid trace " << Trace << '\n');884 NumInvalidTraces += Count;885 return false;886 }887 888 LLVM_DEBUG(dbgs() << "Processing " << FTs->size() << " fallthroughs for "889 << FromFunc->getPrintName() << ":" << Trace << '\n');890 for (const auto &[From, To] : *FTs)891 doIntraBranch(*ParentFunc, From, To, Count, false);892 893 return true;894}895 896std::optional<SmallVector<std::pair<uint64_t, uint64_t>, 16>>897DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, const Trace &Trace,898 uint64_t Count, bool IsReturn) const {899 SmallVector<std::pair<uint64_t, uint64_t>, 16> Branches;900 901 BinaryContext &BC = BF.getBinaryContext();902 903 // Offsets of the trace within this function.904 const uint64_t From = Trace.From - BF.getAddress();905 const uint64_t To = Trace.To - BF.getAddress();906 907 if (From > To)908 return std::nullopt;909 910 // Accept fall-throughs inside pseudo functions (PLT/thunks).911 // This check has to be above BF.empty as pseudo functions would pass it:912 // pseudo => ignored => CFG not built => empty.913 // If we return nullopt, trace would be reported as mismatching disassembled914 // function contents which it is not. To avoid this, return an empty915 // fall-through list instead.916 if (BF.isPseudo())917 return Branches;918 919 // Can only record traces in CFG state920 if (!BF.hasCFG())921 return std::nullopt;922 923 const BinaryBasicBlock *FromBB = BF.getBasicBlockContainingOffset(From);924 const BinaryBasicBlock *ToBB = BF.getBasicBlockContainingOffset(To);925 926 if (!FromBB || !ToBB)927 return std::nullopt;928 929 // Adjust FromBB if the first LBR is a return from the last instruction in930 // the previous block (that instruction should be a call).931 if (Trace.Branch != Trace::FT_ONLY && !BF.containsAddress(Trace.Branch) &&932 From == FromBB->getOffset() &&933 (IsReturn ? From : !(FromBB->isEntryPoint() || FromBB->isLandingPad()))) {934 const BinaryBasicBlock *PrevBB =935 BF.getLayout().getBlock(FromBB->getIndex() - 1);936 if (PrevBB->getSuccessor(FromBB->getLabel())) {937 const MCInst *Instr = PrevBB->getLastNonPseudoInstr();938 if (Instr && BC.MIB->isCall(*Instr))939 FromBB = PrevBB;940 else941 LLVM_DEBUG(dbgs() << "invalid trace (no call): " << Trace << '\n');942 } else {943 LLVM_DEBUG(dbgs() << "invalid trace: " << Trace << '\n');944 }945 }946 947 // Fill out information for fall-through edges. The From and To could be948 // within the same basic block, e.g. when two call instructions are in the949 // same block. In this case we skip the processing.950 if (FromBB == ToBB)951 return Branches;952 953 // Process blocks in the original layout order.954 BinaryBasicBlock *BB = BF.getLayout().getBlock(FromBB->getIndex());955 assert(BB == FromBB && "index mismatch");956 while (BB != ToBB) {957 BinaryBasicBlock *NextBB = BF.getLayout().getBlock(BB->getIndex() + 1);958 assert((NextBB && NextBB->getOffset() > BB->getOffset()) && "bad layout");959 960 // Check for bad LBRs.961 if (!BB->getSuccessor(NextBB->getLabel())) {962 LLVM_DEBUG(dbgs() << "no fall-through for the trace: " << Trace << '\n');963 return std::nullopt;964 }965 966 const MCInst *Instr = BB->getLastNonPseudoInstr();967 uint64_t Offset = 0;968 if (Instr)969 Offset = BC.MIB->getOffsetWithDefault(*Instr, 0);970 else971 Offset = BB->getOffset();972 973 Branches.emplace_back(Offset, NextBB->getOffset());974 975 BB = NextBB;976 }977 978 // Record fall-through jumps979 for (const auto &[FromOffset, ToOffset] : Branches) {980 BinaryBasicBlock *FromBB = BF.getBasicBlockContainingOffset(FromOffset);981 BinaryBasicBlock *ToBB = BF.getBasicBlockAtOffset(ToOffset);982 assert(FromBB && ToBB);983 BinaryBasicBlock::BinaryBranchInfo &BI = FromBB->getBranchInfo(*ToBB);984 BI.Count += Count;985 }986 987 return Branches;988}989 990bool DataAggregator::recordEntry(BinaryFunction &BF, uint64_t To, bool Mispred,991 uint64_t Count) const {992 if (To > BF.getSize())993 return false;994 995 if (!BF.hasProfile())996 BF.ExecutionCount = 0;997 998 BinaryBasicBlock *EntryBB = nullptr;999 if (To == 0) {1000 BF.ExecutionCount += Count;1001 if (!BF.empty())1002 EntryBB = &BF.front();1003 } else if (BinaryBasicBlock *BB = BF.getBasicBlockAtOffset(To)) {1004 if (BB->isEntryPoint())1005 EntryBB = BB;1006 }1007 1008 if (EntryBB)1009 EntryBB->setExecutionCount(EntryBB->getKnownExecutionCount() + Count);1010 1011 return true;1012}1013 1014bool DataAggregator::recordExit(BinaryFunction &BF, uint64_t From, bool Mispred,1015 uint64_t Count) const {1016 if (!BF.isSimple() || From > BF.getSize())1017 return false;1018 1019 if (!BF.hasProfile())1020 BF.ExecutionCount = 0;1021 1022 return true;1023}1024 1025ErrorOr<DataAggregator::LBREntry> DataAggregator::parseLBREntry() {1026 LBREntry Res;1027 ErrorOr<StringRef> FromStrRes = parseString('/');1028 if (std::error_code EC = FromStrRes.getError())1029 return EC;1030 StringRef OffsetStr = FromStrRes.get();1031 if (OffsetStr.getAsInteger(0, Res.From)) {1032 reportError("expected hexadecimal number with From address");1033 Diag << "Found: " << OffsetStr << "\n";1034 return make_error_code(llvm::errc::io_error);1035 }1036 1037 ErrorOr<StringRef> ToStrRes = parseString('/');1038 if (std::error_code EC = ToStrRes.getError())1039 return EC;1040 OffsetStr = ToStrRes.get();1041 if (OffsetStr.getAsInteger(0, Res.To)) {1042 reportError("expected hexadecimal number with To address");1043 Diag << "Found: " << OffsetStr << "\n";1044 return make_error_code(llvm::errc::io_error);1045 }1046 1047 ErrorOr<StringRef> MispredStrRes = parseString('/');1048 if (std::error_code EC = MispredStrRes.getError())1049 return EC;1050 StringRef MispredStr = MispredStrRes.get();1051 // SPE brstack mispredicted flags might be up to two characters long:1052 // 'PN' or 'MN'. Where 'N' optionally appears.1053 bool ValidStrSize = opts::ArmSPE1054 ? MispredStr.size() >= 1 && MispredStr.size() <= 21055 : MispredStr.size() == 1;1056 bool SpeTakenBitErr =1057 (opts::ArmSPE && MispredStr.size() == 2 && MispredStr[1] != 'N');1058 bool PredictionBitErr =1059 !ValidStrSize ||1060 (MispredStr[0] != 'P' && MispredStr[0] != 'M' && MispredStr[0] != '-');1061 if (SpeTakenBitErr)1062 reportError("expected 'N' as SPE prediction bit for a not-taken branch");1063 if (PredictionBitErr)1064 reportError("expected 'P', 'M' or '-' char as a prediction bit");1065 1066 if (SpeTakenBitErr || PredictionBitErr) {1067 Diag << "Found: " << MispredStr << "\n";1068 return make_error_code(llvm::errc::io_error);1069 }1070 Res.Mispred = MispredStr[0] == 'M';1071 1072 static bool MispredWarning = true;1073 if (MispredStr[0] == '-' && MispredWarning) {1074 errs() << "PERF2BOLT-WARNING: misprediction bit is missing in profile\n";1075 MispredWarning = false;1076 }1077 1078 ErrorOr<StringRef> Rest = parseString(FieldSeparator, true);1079 if (std::error_code EC = Rest.getError())1080 return EC;1081 if (Rest.get().size() < 5) {1082 reportError("expected rest of brstack entry");1083 Diag << "Found: " << Rest.get() << "\n";1084 return make_error_code(llvm::errc::io_error);1085 }1086 return Res;1087}1088 1089bool DataAggregator::checkAndConsumeFS() {1090 if (ParsingBuf[0] != FieldSeparator)1091 return false;1092 1093 ParsingBuf = ParsingBuf.drop_front(1);1094 Col += 1;1095 return true;1096}1097 1098void DataAggregator::consumeRestOfLine() {1099 size_t LineEnd = ParsingBuf.find_first_of('\n');1100 if (LineEnd == StringRef::npos) {1101 ParsingBuf = StringRef();1102 Col = 0;1103 Line += 1;1104 return;1105 }1106 ParsingBuf = ParsingBuf.drop_front(LineEnd + 1);1107 Col = 0;1108 Line += 1;1109}1110 1111bool DataAggregator::checkNewLine() {1112 return ParsingBuf[0] == '\n';1113}1114 1115ErrorOr<DataAggregator::PerfBranchSample> DataAggregator::parseBranchSample() {1116 PerfBranchSample Res;1117 1118 while (checkAndConsumeFS()) {1119 }1120 1121 ErrorOr<int64_t> PIDRes = parseNumberField(FieldSeparator, true);1122 if (std::error_code EC = PIDRes.getError())1123 return EC;1124 auto MMapInfoIter = BinaryMMapInfo.find(*PIDRes);1125 if (!BC->IsLinuxKernel && MMapInfoIter == BinaryMMapInfo.end()) {1126 consumeRestOfLine();1127 return make_error_code(errc::no_such_process);1128 }1129 1130 if (checkAndConsumeNewLine())1131 return Res;1132 1133 while (!checkAndConsumeNewLine()) {1134 checkAndConsumeFS();1135 1136 ErrorOr<LBREntry> LBRRes = parseLBREntry();1137 if (std::error_code EC = LBRRes.getError())1138 return EC;1139 LBREntry LBR = LBRRes.get();1140 if (ignoreKernelInterrupt(LBR))1141 continue;1142 if (!BC->HasFixedLoadAddress)1143 adjustLBR(LBR, MMapInfoIter->second);1144 Res.LBR.push_back(LBR);1145 }1146 1147 return Res;1148}1149 1150ErrorOr<DataAggregator::PerfBasicSample> DataAggregator::parseBasicSample() {1151 while (checkAndConsumeFS()) {1152 }1153 1154 ErrorOr<int64_t> PIDRes = parseNumberField(FieldSeparator, true);1155 if (std::error_code EC = PIDRes.getError())1156 return EC;1157 1158 auto MMapInfoIter = BinaryMMapInfo.find(*PIDRes);1159 if (MMapInfoIter == BinaryMMapInfo.end()) {1160 consumeRestOfLine();1161 return PerfBasicSample{StringRef(), 0};1162 }1163 1164 while (checkAndConsumeFS()) {1165 }1166 1167 ErrorOr<StringRef> Event = parseString(FieldSeparator);1168 if (std::error_code EC = Event.getError())1169 return EC;1170 1171 while (checkAndConsumeFS()) {1172 }1173 1174 ErrorOr<uint64_t> AddrRes = parseHexField(FieldSeparator, true);1175 if (std::error_code EC = AddrRes.getError())1176 return EC;1177 1178 if (!checkAndConsumeNewLine()) {1179 reportError("expected end of line");1180 return make_error_code(llvm::errc::io_error);1181 }1182 1183 uint64_t Address = *AddrRes;1184 if (!BC->HasFixedLoadAddress)1185 adjustAddress(Address, MMapInfoIter->second);1186 1187 return PerfBasicSample{Event.get(), Address};1188}1189 1190ErrorOr<DataAggregator::PerfMemSample> DataAggregator::parseMemSample() {1191 PerfMemSample Res{0, 0};1192 1193 while (checkAndConsumeFS()) {1194 }1195 1196 ErrorOr<int64_t> PIDRes = parseNumberField(FieldSeparator, true);1197 if (std::error_code EC = PIDRes.getError())1198 return EC;1199 1200 auto MMapInfoIter = BinaryMMapInfo.find(*PIDRes);1201 if (MMapInfoIter == BinaryMMapInfo.end()) {1202 consumeRestOfLine();1203 return Res;1204 }1205 1206 while (checkAndConsumeFS()) {1207 }1208 1209 ErrorOr<StringRef> Event = parseString(FieldSeparator);1210 if (std::error_code EC = Event.getError())1211 return EC;1212 if (!Event.get().contains("mem-loads")) {1213 consumeRestOfLine();1214 return Res;1215 }1216 1217 while (checkAndConsumeFS()) {1218 }1219 1220 ErrorOr<uint64_t> AddrRes = parseHexField(FieldSeparator);1221 if (std::error_code EC = AddrRes.getError())1222 return EC;1223 1224 while (checkAndConsumeFS()) {1225 }1226 1227 ErrorOr<uint64_t> PCRes = parseHexField(FieldSeparator, true);1228 if (std::error_code EC = PCRes.getError()) {1229 consumeRestOfLine();1230 return EC;1231 }1232 1233 if (!checkAndConsumeNewLine()) {1234 reportError("expected end of line");1235 return make_error_code(llvm::errc::io_error);1236 }1237 1238 uint64_t Address = *AddrRes;1239 if (!BC->HasFixedLoadAddress)1240 adjustAddress(Address, MMapInfoIter->second);1241 1242 return PerfMemSample{PCRes.get(), Address};1243}1244 1245ErrorOr<Location> DataAggregator::parseLocationOrOffset() {1246 auto parseOffset = [this]() -> ErrorOr<Location> {1247 ErrorOr<uint64_t> Res = parseHexField(FieldSeparator);1248 if (std::error_code EC = Res.getError())1249 return EC;1250 return Location(Res.get());1251 };1252 1253 size_t Sep = ParsingBuf.find_first_of(" \n");1254 if (Sep == StringRef::npos)1255 return parseOffset();1256 StringRef LookAhead = ParsingBuf.substr(0, Sep);1257 if (!LookAhead.contains(':'))1258 return parseOffset();1259 1260 ErrorOr<StringRef> BuildID = parseString(':');1261 if (std::error_code EC = BuildID.getError())1262 return EC;1263 ErrorOr<uint64_t> Offset = parseHexField(FieldSeparator);1264 if (std::error_code EC = Offset.getError())1265 return EC;1266 return Location(true, BuildID.get(), Offset.get());1267}1268 1269std::error_code DataAggregator::parseAggregatedLBREntry() {1270 enum AggregatedLBREntry : char {1271 INVALID = 0,1272 EVENT_NAME, // E1273 TRACE, // T1274 RETURN, // R1275 SAMPLE, // S1276 BRANCH, // B1277 FT, // F1278 FT_EXTERNAL_ORIGIN, // f1279 FT_EXTERNAL_RETURN // r1280 } Type = INVALID;1281 1282 /// The number of fields to parse, set based on \p Type.1283 int AddrNum = 0;1284 int CounterNum = 0;1285 /// Storage for parsed fields.1286 StringRef EventName;1287 std::optional<Location> Addr[3];1288 int64_t Counters[2] = {0};1289 1290 /// Parse strings: record type and optionally an event name.1291 while (Type == INVALID || Type == EVENT_NAME) {1292 while (checkAndConsumeFS()) {1293 }1294 ErrorOr<StringRef> StrOrErr =1295 parseString(FieldSeparator, Type == EVENT_NAME);1296 if (std::error_code EC = StrOrErr.getError())1297 return EC;1298 StringRef Str = StrOrErr.get();1299 1300 if (Type == EVENT_NAME) {1301 EventName = Str;1302 break;1303 }1304 1305 Type = StringSwitch<AggregatedLBREntry>(Str)1306 .Case("T", TRACE)1307 .Case("R", RETURN)1308 .Case("S", SAMPLE)1309 .Case("E", EVENT_NAME)1310 .Case("B", BRANCH)1311 .Case("F", FT)1312 .Case("f", FT_EXTERNAL_ORIGIN)1313 .Case("r", FT_EXTERNAL_RETURN)1314 .Default(INVALID);1315 1316 if (Type == INVALID) {1317 reportError("expected T, R, S, E, B, F, f or r");1318 return make_error_code(llvm::errc::io_error);1319 }1320 1321 using SSI = StringSwitch<int>;1322 AddrNum =1323 SSI(Str).Cases({"T", "R"}, 3).Case("S", 1).Case("E", 0).Default(2);1324 CounterNum = SSI(Str).Case("B", 2).Case("E", 0).Default(1);1325 }1326 1327 /// Parse locations depending on entry type, recording them in \p Addr array.1328 for (int I = 0; I < AddrNum; ++I) {1329 while (checkAndConsumeFS()) {1330 }1331 ErrorOr<Location> AddrOrErr = parseLocationOrOffset();1332 if (std::error_code EC = AddrOrErr.getError())1333 return EC;1334 Addr[I] = AddrOrErr.get();1335 }1336 1337 /// Parse counters depending on entry type.1338 for (int I = 0; I < CounterNum; ++I) {1339 while (checkAndConsumeFS()) {1340 }1341 ErrorOr<int64_t> CountOrErr =1342 parseNumberField(FieldSeparator, I + 1 == CounterNum);1343 if (std::error_code EC = CountOrErr.getError())1344 return EC;1345 Counters[I] = CountOrErr.get();1346 }1347 1348 /// Expect end of line here.1349 if (!checkAndConsumeNewLine()) {1350 reportError("expected end of line");1351 return make_error_code(llvm::errc::io_error);1352 }1353 1354 /// Record event name into \p EventNames and return.1355 if (Type == EVENT_NAME) {1356 EventNames.insert(EventName);1357 return std::error_code();1358 }1359 1360 const uint64_t FromOffset = Addr[0]->Offset;1361 BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(FromOffset);1362 if (FromFunc)1363 FromFunc->setHasProfileAvailable();1364 1365 int64_t Count = Counters[0];1366 int64_t Mispreds = Counters[1];1367 1368 /// Record basic IP sample into \p BasicSamples and return.1369 if (Type == SAMPLE) {1370 BasicSamples[FromOffset] += Count;1371 NumTotalSamples += Count;1372 return std::error_code();1373 }1374 1375 const uint64_t ToOffset = Addr[1]->Offset;1376 BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(ToOffset);1377 if (ToFunc)1378 ToFunc->setHasProfileAvailable();1379 1380 /// For fall-through types, adjust locations to match Trace container.1381 if (Type == FT || Type == FT_EXTERNAL_ORIGIN || Type == FT_EXTERNAL_RETURN) {1382 Addr[2] = Location(Addr[1]->Offset); // Trace To1383 Addr[1] = Location(Addr[0]->Offset); // Trace From1384 // Put a magic value into Trace Branch to differentiate from a full trace:1385 if (Type == FT)1386 Addr[0] = Location(Trace::FT_ONLY);1387 else if (Type == FT_EXTERNAL_ORIGIN)1388 Addr[0] = Location(Trace::FT_EXTERNAL_ORIGIN);1389 else if (Type == FT_EXTERNAL_RETURN)1390 Addr[0] = Location(Trace::FT_EXTERNAL_RETURN);1391 else1392 llvm_unreachable("Unexpected fall-through type");1393 }1394 1395 /// For branch type, mark Trace To to differentiate from a full trace.1396 if (Type == BRANCH)1397 Addr[2] = Location(Trace::BR_ONLY);1398 1399 if (Type == RETURN) {1400 if (!Addr[0]->Offset)1401 Addr[0]->Offset = Trace::FT_EXTERNAL_RETURN;1402 else1403 Returns.emplace(Addr[0]->Offset, true);1404 }1405 1406 /// Record a trace.1407 Trace T{Addr[0]->Offset, Addr[1]->Offset, Addr[2]->Offset};1408 TakenBranchInfo TI{(uint64_t)Count, (uint64_t)Mispreds};1409 Traces.emplace_back(T, TI);1410 1411 NumTotalSamples += Count;1412 1413 return std::error_code();1414}1415 1416bool DataAggregator::ignoreKernelInterrupt(LBREntry &LBR) const {1417 return opts::IgnoreInterruptLBR &&1418 (LBR.From >= KernelBaseAddr || LBR.To >= KernelBaseAddr);1419}1420 1421std::error_code DataAggregator::printLBRHeatMap() {1422 outs() << "PERF2BOLT: parse branch events...\n";1423 NamedRegionTimer T("buildHeatmap", "Building heatmap", TimerGroupName,1424 TimerGroupDesc, opts::TimeAggregator);1425 1426 if (BC->IsLinuxKernel) {1427 opts::HeatmapMaxAddress = 0xffffffffffffffff;1428 opts::HeatmapMinAddress = KernelBaseAddr;1429 }1430 opts::HeatmapBlockSizes &HMBS = opts::HeatmapBlock;1431 Heatmap HM(HMBS[0], opts::HeatmapMinAddress, opts::HeatmapMaxAddress,1432 getTextSections(BC));1433 auto getSymbolValue = [&](const MCSymbol *Symbol) -> uint64_t {1434 if (Symbol)1435 if (ErrorOr<uint64_t> SymValue = BC->getSymbolValue(*Symbol))1436 return SymValue.get();1437 return 0;1438 };1439 HM.HotStart = getSymbolValue(BC->getHotTextStartSymbol());1440 HM.HotEnd = getSymbolValue(BC->getHotTextEndSymbol());1441 1442 if (!NumTotalSamples) {1443 if (opts::BasicAggregation) {1444 errs() << "HEATMAP-ERROR: no basic event samples detected in profile. "1445 "Cannot build heatmap.";1446 } else {1447 errs() << "HEATMAP-ERROR: no brstack traces detected in profile. "1448 "Cannot build heatmap. Use -ba for building heatmap from "1449 "basic events.\n";1450 }1451 exit(1);1452 }1453 1454 outs() << "HEATMAP: building heat map...\n";1455 1456 // Register basic samples and perf LBR addresses not covered by fallthroughs.1457 for (const auto &[PC, Hits] : BasicSamples)1458 HM.registerAddress(PC, Hits);1459 for (const auto &[Trace, Info] : Traces)1460 if (Trace.To != Trace::BR_ONLY)1461 HM.registerAddressRange(Trace.From, Trace.To, Info.TakenCount);1462 1463 if (HM.getNumInvalidRanges())1464 outs() << "HEATMAP: invalid traces: " << HM.getNumInvalidRanges() << '\n';1465 1466 if (!HM.size()) {1467 errs() << "HEATMAP-ERROR: no valid traces registered\n";1468 exit(1);1469 }1470 1471 HM.print(opts::HeatmapOutput);1472 if (opts::HeatmapOutput == "-") {1473 HM.printCDF(opts::HeatmapOutput);1474 HM.printSectionHotness(opts::HeatmapOutput);1475 } else {1476 HM.printCDF(opts::HeatmapOutput + ".csv");1477 HM.printSectionHotness(opts::HeatmapOutput + "-section-hotness.csv");1478 }1479 // Provide coarse-grained heatmaps if requested via zoom-out scales1480 for (const uint64_t NewBucketSize : ArrayRef(HMBS).drop_front()) {1481 HM.resizeBucket(NewBucketSize);1482 if (opts::HeatmapOutput == "-")1483 HM.print(opts::HeatmapOutput);1484 else1485 HM.print(formatv("{0}-{1}", opts::HeatmapOutput, NewBucketSize).str());1486 }1487 1488 return std::error_code();1489}1490 1491void DataAggregator::parseLBRSample(const PerfBranchSample &Sample,1492 bool NeedsSkylakeFix) {1493 // LBRs are stored in reverse execution order. NextLBR refers to the next1494 // executed branch record.1495 const LBREntry *NextLBR = nullptr;1496 uint32_t NumEntry = 0;1497 for (const LBREntry &LBR : Sample.LBR) {1498 ++NumEntry;1499 // Hardware bug workaround: Intel Skylake (which has 32 LBR entries)1500 // sometimes record entry 32 as an exact copy of entry 31. This will cause1501 // us to likely record an invalid trace and generate a stale function for1502 // BAT mode (non BAT disassembles the function and is able to ignore this1503 // trace at aggregation time). Drop first 2 entries (last two, in1504 // chronological order)1505 if (NeedsSkylakeFix && NumEntry <= 2)1506 continue;1507 uint64_t TraceTo = NextLBR ? NextLBR->From : Trace::BR_ONLY;1508 NextLBR = &LBR;1509 1510 TakenBranchInfo &Info = TraceMap[Trace{LBR.From, LBR.To, TraceTo}];1511 ++Info.TakenCount;1512 Info.MispredCount += LBR.Mispred;1513 }1514 // Record LBR addresses not covered by fallthroughs (bottom-of-stack source1515 // and top-of-stack target) as basic samples for heatmap.1516 if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive &&1517 !Sample.LBR.empty()) {1518 ++BasicSamples[Sample.LBR.front().To];1519 ++BasicSamples[Sample.LBR.back().From];1520 }1521}1522 1523void DataAggregator::printLongRangeTracesDiagnostic() const {1524 outs() << "PERF2BOLT: out of range traces involving unknown regions: "1525 << NumLongRangeTraces;1526 if (NumTraces > 0)1527 outs() << format(" (%.1f%%)", NumLongRangeTraces * 100.0f / NumTraces);1528 outs() << "\n";1529}1530 1531static float printColoredPct(uint64_t Numerator, uint64_t Denominator, float T1,1532 float T2) {1533 if (Denominator == 0) {1534 outs() << "\n";1535 return 0;1536 }1537 float Percent = Numerator * 100.0f / Denominator;1538 outs() << " (";1539 if (outs().has_colors()) {1540 if (Percent > T2)1541 outs().changeColor(raw_ostream::RED);1542 else if (Percent > T1)1543 outs().changeColor(raw_ostream::YELLOW);1544 else1545 outs().changeColor(raw_ostream::GREEN);1546 }1547 outs() << format("%.1f%%", Percent);1548 if (outs().has_colors())1549 outs().resetColor();1550 outs() << ")\n";1551 return Percent;1552}1553 1554void DataAggregator::printBranchSamplesDiagnostics() const {1555 outs() << "PERF2BOLT: traces mismatching disassembled function contents: "1556 << NumInvalidTraces;1557 if (printColoredPct(NumInvalidTraces, NumTraces, 5, 10) > 10)1558 outs() << "\n !! WARNING !! This high mismatch ratio indicates the input "1559 "binary is probably not the same binary used during profiling "1560 "collection. The generated data may be ineffective for improving "1561 "performance\n\n";1562 printLongRangeTracesDiagnostic();1563}1564 1565void DataAggregator::printBasicSamplesDiagnostics(1566 uint64_t OutOfRangeSamples) const {1567 outs() << "PERF2BOLT: out of range samples recorded in unknown regions: "1568 << OutOfRangeSamples;1569 if (printColoredPct(OutOfRangeSamples, NumTotalSamples, 40, 60) > 80)1570 outs() << "\n !! WARNING !! This high mismatch ratio indicates the input "1571 "binary is probably not the same binary used during profiling "1572 "collection. The generated data may be ineffective for improving "1573 "performance\n\n";1574}1575 1576void DataAggregator::printBranchStacksDiagnostics(1577 uint64_t IgnoredSamples) const {1578 outs() << "PERF2BOLT: ignored samples: " << IgnoredSamples;1579 if (printColoredPct(IgnoredSamples, NumTotalSamples, 20, 50) > 50)1580 errs() << "PERF2BOLT-WARNING: less than 50% of all recorded samples "1581 "were attributed to the input binary\n";1582}1583 1584std::error_code DataAggregator::parseBranchEvents() {1585 std::string BranchEventTypeStr =1586 opts::ArmSPE ? "SPE branch events in brstack-format" : "branch events";1587 outs() << "PERF2BOLT: parse " << BranchEventTypeStr << "...\n";1588 NamedRegionTimer T("parseBranch", "Parsing branch events", TimerGroupName,1589 TimerGroupDesc, opts::TimeAggregator);1590 1591 uint64_t NumEntries = 0;1592 uint64_t NumSamples = 0;1593 uint64_t NumSamplesNoLBR = 0;1594 bool NeedsSkylakeFix = false;1595 1596 while (hasData() && NumTotalSamples < opts::MaxSamples) {1597 ++NumTotalSamples;1598 1599 ErrorOr<PerfBranchSample> SampleRes = parseBranchSample();1600 if (std::error_code EC = SampleRes.getError()) {1601 if (EC == errc::no_such_process)1602 continue;1603 return EC;1604 }1605 ++NumSamples;1606 1607 PerfBranchSample &Sample = SampleRes.get();1608 1609 if (Sample.LBR.empty()) {1610 ++NumSamplesNoLBR;1611 continue;1612 }1613 1614 NumEntries += Sample.LBR.size();1615 if (this->BC->isX86() && BAT && Sample.LBR.size() == 32 &&1616 !NeedsSkylakeFix) {1617 errs() << "PERF2BOLT-WARNING: using Intel Skylake bug workaround\n";1618 NeedsSkylakeFix = true;1619 }1620 1621 parseLBRSample(Sample, NeedsSkylakeFix);1622 }1623 1624 Traces.reserve(TraceMap.size());1625 for (const auto &[Trace, Info] : TraceMap) {1626 Traces.emplace_back(Trace, Info);1627 for (const uint64_t Addr : {Trace.Branch, Trace.From})1628 if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Addr))1629 BF->setHasProfileAvailable();1630 }1631 clear(TraceMap);1632 1633 outs() << "PERF2BOLT: read " << NumSamples << " samples and " << NumEntries1634 << " brstack entries\n";1635 if (NumTotalSamples) {1636 if (NumSamples && NumSamplesNoLBR == NumSamples) {1637 // Note: we don't know if perf2bolt is being used to parse memory samples1638 // at this point. In this case, it is OK to parse zero LBRs.1639 if (!opts::ArmSPE)1640 errs()1641 << "PERF2BOLT-WARNING: all recorded samples for this binary lack "1642 "brstack. Record profile with perf record -j any or run "1643 "perf2bolt "1644 "in non-brstack mode with -ba (the performance improvement in "1645 "-ba "1646 "mode may be limited)\n";1647 else1648 errs()1649 << "PERF2BOLT-WARNING: All recorded samples for this binary lack "1650 "SPE brstack entries. Make sure you are running Linux perf 6.14 "1651 "or later, otherwise you get zero samples. Record the profile "1652 "with: perf record -e 'arm_spe_0/branch_filter=1/'.";1653 } else {1654 printBranchStacksDiagnostics(NumTotalSamples - NumSamples);1655 }1656 }1657 1658 return std::error_code();1659}1660 1661void DataAggregator::processBranchEvents() {1662 outs() << "PERF2BOLT: processing branch events...\n";1663 NamedRegionTimer T("processBranch", "Processing branch events",1664 TimerGroupName, TimerGroupDesc, opts::TimeAggregator);1665 1666 Returns.emplace(Trace::FT_EXTERNAL_RETURN, true);1667 for (const auto &[Trace, Info] : Traces) {1668 bool IsReturn = checkReturn(Trace.Branch);1669 // Ignore returns.1670 if (!IsReturn && Trace.Branch != Trace::FT_ONLY &&1671 Trace.Branch != Trace::FT_EXTERNAL_ORIGIN)1672 doBranch(Trace.Branch, Trace.From, Info.TakenCount, Info.MispredCount);1673 if (Trace.To != Trace::BR_ONLY)1674 doTrace(Trace, Info.TakenCount, IsReturn);1675 }1676 printBranchSamplesDiagnostics();1677}1678 1679std::error_code DataAggregator::parseBasicEvents() {1680 outs() << "PERF2BOLT: parsing basic events (without brstack)...\n";1681 NamedRegionTimer T("parseBasic", "Parsing basic events", TimerGroupName,1682 TimerGroupDesc, opts::TimeAggregator);1683 while (hasData()) {1684 ErrorOr<PerfBasicSample> Sample = parseBasicSample();1685 if (std::error_code EC = Sample.getError())1686 return EC;1687 1688 if (!Sample->PC)1689 continue;1690 ++NumTotalSamples;1691 1692 if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Sample->PC))1693 BF->setHasProfileAvailable();1694 1695 ++BasicSamples[Sample->PC];1696 EventNames.insert(Sample->EventName);1697 }1698 outs() << "PERF2BOLT: read " << NumTotalSamples << " basic samples\n";1699 1700 return std::error_code();1701}1702 1703void DataAggregator::processBasicEvents() {1704 outs() << "PERF2BOLT: processing basic events (without brstack)...\n";1705 NamedRegionTimer T("processBasic", "Processing basic events", TimerGroupName,1706 TimerGroupDesc, opts::TimeAggregator);1707 uint64_t OutOfRangeSamples = 0;1708 for (auto &Sample : BasicSamples) {1709 const uint64_t PC = Sample.first;1710 const uint64_t HitCount = Sample.second;1711 BinaryFunction *Func = getBinaryFunctionContainingAddress(PC);1712 if (!Func) {1713 OutOfRangeSamples += HitCount;1714 continue;1715 }1716 1717 doBasicSample(*Func, PC, HitCount);1718 }1719 1720 printBasicSamplesDiagnostics(OutOfRangeSamples);1721}1722 1723std::error_code DataAggregator::parseMemEvents() {1724 outs() << "PERF2BOLT: parsing memory events...\n";1725 NamedRegionTimer T("parseMemEvents", "Parsing mem events", TimerGroupName,1726 TimerGroupDesc, opts::TimeAggregator);1727 while (hasData()) {1728 ErrorOr<PerfMemSample> Sample = parseMemSample();1729 if (std::error_code EC = Sample.getError())1730 return EC;1731 1732 if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Sample->PC))1733 BF->setHasProfileAvailable();1734 1735 MemSamples.emplace_back(std::move(Sample.get()));1736 }1737 1738 return std::error_code();1739}1740 1741void DataAggregator::processMemEvents() {1742 NamedRegionTimer T("ProcessMemEvents", "Processing mem events",1743 TimerGroupName, TimerGroupDesc, opts::TimeAggregator);1744 for (const PerfMemSample &Sample : MemSamples) {1745 uint64_t PC = Sample.PC;1746 uint64_t Addr = Sample.Addr;1747 StringRef FuncName;1748 StringRef MemName;1749 1750 // Try to resolve symbol for PC1751 BinaryFunction *Func = getBinaryFunctionContainingAddress(PC);1752 if (!Func) {1753 LLVM_DEBUG(if (PC != 0) {1754 dbgs() << formatv("Skipped mem event: {0:x} => {1:x}\n", PC, Addr);1755 });1756 continue;1757 }1758 1759 FuncName = Func->getOneName();1760 PC -= Func->getAddress();1761 1762 // Try to resolve symbol for memory load1763 if (BinaryData *BD = BC->getBinaryDataContainingAddress(Addr)) {1764 MemName = BD->getName();1765 Addr -= BD->getAddress();1766 } else if (opts::FilterMemProfile) {1767 // Filter out heap/stack accesses1768 continue;1769 }1770 1771 const Location FuncLoc(!FuncName.empty(), FuncName, PC);1772 const Location AddrLoc(!MemName.empty(), MemName, Addr);1773 1774 FuncMemData *MemData = &NamesToMemEvents[FuncName];1775 MemData->Name = FuncName;1776 setMemData(*Func, MemData);1777 MemData->update(FuncLoc, AddrLoc);1778 LLVM_DEBUG(dbgs() << "Mem event: " << FuncLoc << " = " << AddrLoc << "\n");1779 }1780}1781 1782std::error_code DataAggregator::parsePreAggregatedLBRSamples() {1783 outs() << "PERF2BOLT: parsing pre-aggregated profile...\n";1784 NamedRegionTimer T("parseAggregated", "Parsing aggregated branch events",1785 TimerGroupName, TimerGroupDesc, opts::TimeAggregator);1786 size_t AggregatedLBRs = 0;1787 while (hasData()) {1788 if (std::error_code EC = parseAggregatedLBREntry())1789 return EC;1790 ++AggregatedLBRs;1791 }1792 1793 outs() << "PERF2BOLT: read " << AggregatedLBRs1794 << " aggregated brstack entries\n";1795 1796 return std::error_code();1797}1798 1799std::optional<int32_t> DataAggregator::parseCommExecEvent() {1800 size_t LineEnd = ParsingBuf.find_first_of("\n");1801 if (LineEnd == StringRef::npos) {1802 reportError("expected rest of line");1803 Diag << "Found: " << ParsingBuf << "\n";1804 return std::nullopt;1805 }1806 StringRef Line = ParsingBuf.substr(0, LineEnd);1807 1808 size_t Pos = Line.find("PERF_RECORD_COMM exec");1809 if (Pos == StringRef::npos)1810 return std::nullopt;1811 Line = Line.drop_front(Pos);1812 1813 // Line:1814 // PERF_RECORD_COMM exec: <name>:<pid>/<tid>"1815 StringRef PIDStr = Line.rsplit(':').second.split('/').first;1816 int32_t PID;1817 if (PIDStr.getAsInteger(10, PID)) {1818 reportError("expected PID");1819 Diag << "Found: " << PIDStr << "in '" << Line << "'\n";1820 return std::nullopt;1821 }1822 1823 return PID;1824}1825 1826namespace {1827std::optional<uint64_t> parsePerfTime(const StringRef TimeStr) {1828 const StringRef SecTimeStr = TimeStr.split('.').first;1829 const StringRef USecTimeStr = TimeStr.split('.').second;1830 uint64_t SecTime;1831 uint64_t USecTime;1832 if (SecTimeStr.getAsInteger(10, SecTime) ||1833 USecTimeStr.getAsInteger(10, USecTime))1834 return std::nullopt;1835 return SecTime * 1000000ULL + USecTime;1836}1837}1838 1839std::optional<DataAggregator::ForkInfo> DataAggregator::parseForkEvent() {1840 while (checkAndConsumeFS()) {1841 }1842 1843 size_t LineEnd = ParsingBuf.find_first_of("\n");1844 if (LineEnd == StringRef::npos) {1845 reportError("expected rest of line");1846 Diag << "Found: " << ParsingBuf << "\n";1847 return std::nullopt;1848 }1849 StringRef Line = ParsingBuf.substr(0, LineEnd);1850 1851 size_t Pos = Line.find("PERF_RECORD_FORK");1852 if (Pos == StringRef::npos) {1853 consumeRestOfLine();1854 return std::nullopt;1855 }1856 1857 ForkInfo FI;1858 1859 const StringRef TimeStr =1860 Line.substr(0, Pos).rsplit(':').first.rsplit(FieldSeparator).second;1861 if (std::optional<uint64_t> TimeRes = parsePerfTime(TimeStr)) {1862 FI.Time = *TimeRes;1863 }1864 1865 Line = Line.drop_front(Pos);1866 1867 // Line:1868 // PERF_RECORD_FORK(<child_pid>:<child_tid>):(<parent_pid>:<parent_tid>)1869 const StringRef ChildPIDStr = Line.split('(').second.split(':').first;1870 if (ChildPIDStr.getAsInteger(10, FI.ChildPID)) {1871 reportError("expected PID");1872 Diag << "Found: " << ChildPIDStr << "in '" << Line << "'\n";1873 return std::nullopt;1874 }1875 1876 const StringRef ParentPIDStr = Line.rsplit('(').second.split(':').first;1877 if (ParentPIDStr.getAsInteger(10, FI.ParentPID)) {1878 reportError("expected PID");1879 Diag << "Found: " << ParentPIDStr << "in '" << Line << "'\n";1880 return std::nullopt;1881 }1882 1883 consumeRestOfLine();1884 1885 return FI;1886}1887 1888ErrorOr<std::pair<StringRef, DataAggregator::MMapInfo>>1889DataAggregator::parseMMapEvent() {1890 while (checkAndConsumeFS()) {1891 }1892 1893 MMapInfo ParsedInfo;1894 1895 size_t LineEnd = ParsingBuf.find_first_of("\n");1896 if (LineEnd == StringRef::npos) {1897 reportError("expected rest of line");1898 Diag << "Found: " << ParsingBuf << "\n";1899 return make_error_code(llvm::errc::io_error);1900 }1901 StringRef Line = ParsingBuf.substr(0, LineEnd);1902 1903 size_t Pos = Line.find("PERF_RECORD_MMAP2");1904 if (Pos == StringRef::npos) {1905 consumeRestOfLine();1906 return std::make_pair(StringRef(), ParsedInfo);1907 }1908 1909 // Line:1910 // {<name> .* <sec>.<usec>: }PERF_RECORD_MMAP2 <pid>/<tid>: .* <file_name>1911 1912 const StringRef TimeStr =1913 Line.substr(0, Pos).rsplit(':').first.rsplit(FieldSeparator).second;1914 if (std::optional<uint64_t> TimeRes = parsePerfTime(TimeStr))1915 ParsedInfo.Time = *TimeRes;1916 1917 Line = Line.drop_front(Pos);1918 1919 // Line:1920 // PERF_RECORD_MMAP2 <pid>/<tid>: [<hexbase>(<hexsize>) .*]: .* <file_name>1921 1922 StringRef FileName = Line.rsplit(FieldSeparator).second;1923 if (FileName.starts_with("//") || FileName.starts_with("[")) {1924 consumeRestOfLine();1925 return std::make_pair(StringRef(), ParsedInfo);1926 }1927 FileName = sys::path::filename(FileName);1928 1929 const StringRef PIDStr = Line.split(FieldSeparator).second.split('/').first;1930 if (PIDStr.getAsInteger(10, ParsedInfo.PID)) {1931 reportError("expected PID");1932 Diag << "Found: " << PIDStr << "in '" << Line << "'\n";1933 return make_error_code(llvm::errc::io_error);1934 }1935 1936 const StringRef BaseAddressStr = Line.split('[').second.split('(').first;1937 if (BaseAddressStr.getAsInteger(0, ParsedInfo.MMapAddress)) {1938 reportError("expected base address");1939 Diag << "Found: " << BaseAddressStr << "in '" << Line << "'\n";1940 return make_error_code(llvm::errc::io_error);1941 }1942 1943 const StringRef SizeStr = Line.split('(').second.split(')').first;1944 if (SizeStr.getAsInteger(0, ParsedInfo.Size)) {1945 reportError("expected mmaped size");1946 Diag << "Found: " << SizeStr << "in '" << Line << "'\n";1947 return make_error_code(llvm::errc::io_error);1948 }1949 1950 const StringRef OffsetStr =1951 Line.split('@').second.ltrim().split(FieldSeparator).first;1952 if (OffsetStr.getAsInteger(0, ParsedInfo.Offset)) {1953 reportError("expected mmaped page-aligned offset");1954 Diag << "Found: " << OffsetStr << "in '" << Line << "'\n";1955 return make_error_code(llvm::errc::io_error);1956 }1957 1958 consumeRestOfLine();1959 1960 return std::make_pair(FileName, ParsedInfo);1961}1962 1963std::error_code DataAggregator::parseMMapEvents() {1964 outs() << "PERF2BOLT: parsing perf-script mmap events output\n";1965 NamedRegionTimer T("parseMMapEvents", "Parsing mmap events", TimerGroupName,1966 TimerGroupDesc, opts::TimeAggregator);1967 1968 std::multimap<StringRef, MMapInfo> GlobalMMapInfo;1969 while (hasData()) {1970 ErrorOr<std::pair<StringRef, MMapInfo>> FileMMapInfoRes = parseMMapEvent();1971 if (std::error_code EC = FileMMapInfoRes.getError())1972 return EC;1973 1974 std::pair<StringRef, MMapInfo> FileMMapInfo = FileMMapInfoRes.get();1975 if (FileMMapInfo.second.PID == -1)1976 continue;1977 if (FileMMapInfo.first == "(deleted)")1978 continue;1979 1980 GlobalMMapInfo.insert(FileMMapInfo);1981 }1982 1983 LLVM_DEBUG({1984 dbgs() << "FileName -> mmap info:\n"1985 << " Filename : PID [MMapAddr, Size, Offset]\n";1986 for (const auto &[Name, MMap] : GlobalMMapInfo)1987 dbgs() << formatv(" {0} : {1} [{2:x}, {3:x} @ {4:x}]\n", Name, MMap.PID,1988 MMap.MMapAddress, MMap.Size, MMap.Offset);1989 });1990 1991 StringRef NameToUse = llvm::sys::path::filename(BC->getFilename());1992 if (GlobalMMapInfo.count(NameToUse) == 0 && !BuildIDBinaryName.empty()) {1993 errs() << "PERF2BOLT-WARNING: using \"" << BuildIDBinaryName1994 << "\" for profile matching\n";1995 NameToUse = BuildIDBinaryName;1996 }1997 1998 auto Range = GlobalMMapInfo.equal_range(NameToUse);1999 for (MMapInfo &MMapInfo : llvm::make_second_range(make_range(Range))) {2000 if (BC->HasFixedLoadAddress && MMapInfo.MMapAddress) {2001 // Check that the binary mapping matches one of the segments.2002 bool MatchFound = llvm::any_of(2003 llvm::make_second_range(BC->SegmentMapInfo),2004 [&](SegmentInfo &SegInfo) {2005 // The mapping is page-aligned and hence the MMapAddress could be2006 // different from the segment start address. We cannot know the page2007 // size of the mapping, but we know it should not exceed the segment2008 // alignment value. Hence we are performing an approximate check.2009 return SegInfo.Address >= MMapInfo.MMapAddress &&2010 SegInfo.Address - MMapInfo.MMapAddress < SegInfo.Alignment &&2011 SegInfo.IsExecutable;2012 });2013 if (!MatchFound) {2014 errs() << "PERF2BOLT-WARNING: ignoring mapping of " << NameToUse2015 << " at 0x" << Twine::utohexstr(MMapInfo.MMapAddress) << '\n';2016 continue;2017 }2018 }2019 2020 // Set base address for shared objects.2021 if (!BC->HasFixedLoadAddress) {2022 std::optional<uint64_t> BaseAddress =2023 BC->getBaseAddressForMapping(MMapInfo.MMapAddress, MMapInfo.Offset);2024 if (!BaseAddress) {2025 errs() << "PERF2BOLT-WARNING: unable to find base address of the "2026 "binary when memory mapped at 0x"2027 << Twine::utohexstr(MMapInfo.MMapAddress)2028 << " using file offset 0x" << Twine::utohexstr(MMapInfo.Offset)2029 << ". Ignoring profile data for this mapping\n";2030 continue;2031 }2032 MMapInfo.BaseAddress = *BaseAddress;2033 }2034 2035 // Try to add MMapInfo to the map and update its size. Large binaries may2036 // span to multiple text segments, so the mapping is inserted only on the2037 // first occurrence.2038 if (!BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo)).second)2039 assert(MMapInfo.BaseAddress == BinaryMMapInfo[MMapInfo.PID].BaseAddress &&2040 "Base address on multiple segment mappings should match");2041 2042 // Update mapping size.2043 const uint64_t EndAddress = MMapInfo.MMapAddress + MMapInfo.Size;2044 const uint64_t Size = EndAddress - BinaryMMapInfo[MMapInfo.PID].BaseAddress;2045 if (Size > BinaryMMapInfo[MMapInfo.PID].Size)2046 BinaryMMapInfo[MMapInfo.PID].Size = Size;2047 }2048 2049 if (BinaryMMapInfo.empty()) {2050 if (errs().has_colors())2051 errs().changeColor(raw_ostream::RED);2052 errs() << "PERF2BOLT-ERROR: could not find a profile matching binary \""2053 << BC->getFilename() << "\".";2054 if (!GlobalMMapInfo.empty()) {2055 errs() << " Profile for the following binary name(s) is available:\n";2056 for (auto I = GlobalMMapInfo.begin(), IE = GlobalMMapInfo.end(); I != IE;2057 I = GlobalMMapInfo.upper_bound(I->first))2058 errs() << " " << I->first << '\n';2059 errs() << "Please rename the input binary.\n";2060 } else {2061 errs() << " Failed to extract any binary name from a profile.\n";2062 }2063 if (errs().has_colors())2064 errs().resetColor();2065 2066 exit(1);2067 }2068 2069 return std::error_code();2070}2071 2072std::error_code DataAggregator::parseTaskEvents() {2073 outs() << "PERF2BOLT: parsing perf-script task events output\n";2074 NamedRegionTimer T("parseTaskEvents", "Parsing task events", TimerGroupName,2075 TimerGroupDesc, opts::TimeAggregator);2076 2077 while (hasData()) {2078 if (std::optional<int32_t> CommInfo = parseCommExecEvent()) {2079 // Remove forked child that ran execve2080 auto MMapInfoIter = BinaryMMapInfo.find(*CommInfo);2081 if (MMapInfoIter != BinaryMMapInfo.end() && MMapInfoIter->second.Forked)2082 BinaryMMapInfo.erase(MMapInfoIter);2083 consumeRestOfLine();2084 continue;2085 }2086 2087 std::optional<ForkInfo> ForkInfo = parseForkEvent();2088 if (!ForkInfo)2089 continue;2090 2091 if (ForkInfo->ParentPID == ForkInfo->ChildPID)2092 continue;2093 2094 if (ForkInfo->Time == 0) {2095 // Process was forked and mmaped before perf ran. In this case the child2096 // should have its own mmap entry unless it was execve'd.2097 continue;2098 }2099 2100 auto MMapInfoIter = BinaryMMapInfo.find(ForkInfo->ParentPID);2101 if (MMapInfoIter == BinaryMMapInfo.end())2102 continue;2103 2104 MMapInfo MMapInfo = MMapInfoIter->second;2105 MMapInfo.PID = ForkInfo->ChildPID;2106 MMapInfo.Forked = true;2107 BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo));2108 }2109 2110 outs() << "PERF2BOLT: input binary is associated with "2111 << BinaryMMapInfo.size() << " PID(s)\n";2112 2113 LLVM_DEBUG({2114 for (const MMapInfo &MMI : llvm::make_second_range(BinaryMMapInfo))2115 outs() << formatv(" {0}{1}: ({2:x}: {3:x})\n", MMI.PID,2116 (MMI.Forked ? " (forked)" : ""), MMI.MMapAddress,2117 MMI.Size);2118 });2119 2120 return std::error_code();2121}2122 2123std::optional<std::pair<StringRef, StringRef>>2124DataAggregator::parseNameBuildIDPair() {2125 while (checkAndConsumeFS()) {2126 }2127 2128 ErrorOr<StringRef> BuildIDStr = parseString(FieldSeparator, true);2129 if (std::error_code EC = BuildIDStr.getError())2130 return std::nullopt;2131 2132 // If one of the strings is missing, don't issue a parsing error, but still2133 // do not return a value.2134 consumeAllRemainingFS();2135 if (checkNewLine())2136 return std::nullopt;2137 2138 ErrorOr<StringRef> NameStr = parseString(FieldSeparator, true);2139 if (std::error_code EC = NameStr.getError())2140 return std::nullopt;2141 2142 consumeRestOfLine();2143 return std::make_pair(NameStr.get(), BuildIDStr.get());2144}2145 2146bool DataAggregator::hasAllBuildIDs() {2147 const StringRef SavedParsingBuf = ParsingBuf;2148 2149 if (!hasData())2150 return false;2151 2152 bool HasInvalidEntries = false;2153 while (hasData()) {2154 if (!parseNameBuildIDPair()) {2155 HasInvalidEntries = true;2156 break;2157 }2158 }2159 2160 ParsingBuf = SavedParsingBuf;2161 2162 return !HasInvalidEntries;2163}2164 2165std::optional<StringRef>2166DataAggregator::getFileNameForBuildID(StringRef FileBuildID) {2167 const StringRef SavedParsingBuf = ParsingBuf;2168 2169 StringRef FileName;2170 while (hasData()) {2171 std::optional<std::pair<StringRef, StringRef>> IDPair =2172 parseNameBuildIDPair();2173 if (!IDPair) {2174 consumeRestOfLine();2175 continue;2176 }2177 2178 if (IDPair->second.starts_with(FileBuildID)) {2179 FileName = sys::path::filename(IDPair->first);2180 break;2181 }2182 }2183 2184 ParsingBuf = SavedParsingBuf;2185 2186 if (!FileName.empty())2187 return FileName;2188 2189 return std::nullopt;2190}2191 2192std::error_code2193DataAggregator::writeAggregatedFile(StringRef OutputFilename) const {2194 std::error_code EC;2195 raw_fd_ostream OutFile(OutputFilename, EC, sys::fs::OpenFlags::OF_None);2196 if (EC)2197 return EC;2198 2199 bool WriteMemLocs = false;2200 2201 auto writeLocation = [&OutFile, &WriteMemLocs](const Location &Loc) {2202 if (WriteMemLocs)2203 OutFile << (Loc.IsSymbol ? "4 " : "3 ");2204 else2205 OutFile << (Loc.IsSymbol ? "1 " : "0 ");2206 OutFile << (Loc.Name.empty() ? "[unknown]" : getEscapedName(Loc.Name))2207 << " " << Twine::utohexstr(Loc.Offset) << FieldSeparator;2208 };2209 2210 uint64_t BranchValues = 0;2211 uint64_t MemValues = 0;2212 2213 if (BAT)2214 OutFile << "boltedcollection\n";2215 if (opts::BasicAggregation) {2216 OutFile << "no_lbr";2217 for (const StringMapEntry<EmptyStringSetTag> &Entry : EventNames)2218 OutFile << " " << Entry.getKey();2219 OutFile << "\n";2220 2221 for (const auto &KV : NamesToBasicSamples) {2222 const FuncBasicSampleData &FSD = KV.second;2223 for (const BasicSampleInfo &SI : FSD.Data) {2224 writeLocation(SI.Loc);2225 OutFile << SI.Hits << "\n";2226 ++BranchValues;2227 }2228 }2229 } else {2230 for (const auto &KV : NamesToBranches) {2231 const FuncBranchData &FBD = KV.second;2232 for (const BranchInfo &BI : FBD.Data) {2233 writeLocation(BI.From);2234 writeLocation(BI.To);2235 OutFile << BI.Mispreds << " " << BI.Branches << "\n";2236 ++BranchValues;2237 }2238 for (const BranchInfo &BI : FBD.EntryData) {2239 // Do not output if source is a known symbol, since this was already2240 // accounted for in the source function2241 if (BI.From.IsSymbol)2242 continue;2243 writeLocation(BI.From);2244 writeLocation(BI.To);2245 OutFile << BI.Mispreds << " " << BI.Branches << "\n";2246 ++BranchValues;2247 }2248 }2249 2250 WriteMemLocs = true;2251 for (const auto &KV : NamesToMemEvents) {2252 const FuncMemData &FMD = KV.second;2253 for (const MemInfo &MemEvent : FMD.Data) {2254 writeLocation(MemEvent.Offset);2255 writeLocation(MemEvent.Addr);2256 OutFile << MemEvent.Count << "\n";2257 ++MemValues;2258 }2259 }2260 }2261 2262 outs() << "PERF2BOLT: wrote " << BranchValues << " objects and " << MemValues2263 << " memory objects to " << OutputFilename << "\n";2264 2265 return std::error_code();2266}2267 2268std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,2269 StringRef OutputFilename) const {2270 std::error_code EC;2271 raw_fd_ostream OutFile(OutputFilename, EC, sys::fs::OpenFlags::OF_None);2272 if (EC)2273 return EC;2274 2275 yaml::bolt::BinaryProfile BP;2276 2277 const MCPseudoProbeDecoder *PseudoProbeDecoder =2278 opts::ProfileWritePseudoProbes ? BC.getPseudoProbeDecoder() : nullptr;2279 2280 // Fill out the header info.2281 BP.Header.Version = 1;2282 BP.Header.FileName = std::string(BC.getFilename());2283 std::optional<StringRef> BuildID = BC.getFileBuildID();2284 BP.Header.Id = BuildID ? std::string(*BuildID) : "<unknown>";2285 BP.Header.Origin = std::string(getReaderName());2286 // Only the input binary layout order is supported.2287 BP.Header.IsDFSOrder = false;2288 // FIXME: Need to match hash function used to produce BAT hashes.2289 BP.Header.HashFunction = HashFunction::Default;2290 2291 ListSeparator LS(",");2292 raw_string_ostream EventNamesOS(BP.Header.EventNames);2293 for (const StringMapEntry<EmptyStringSetTag> &EventEntry : EventNames)2294 EventNamesOS << LS << EventEntry.first().str();2295 2296 BP.Header.Flags = opts::BasicAggregation ? BinaryFunction::PF_BASIC2297 : BinaryFunction::PF_BRANCH;2298 2299 // Add probe inline tree nodes.2300 YAMLProfileWriter::InlineTreeDesc InlineTree;2301 if (PseudoProbeDecoder)2302 std::tie(BP.PseudoProbeDesc, InlineTree) =2303 YAMLProfileWriter::convertPseudoProbeDesc(*PseudoProbeDecoder);2304 2305 if (!opts::BasicAggregation) {2306 // Convert profile for functions not covered by BAT2307 for (auto &BFI : BC.getBinaryFunctions()) {2308 BinaryFunction &Function = BFI.second;2309 if (!Function.hasProfile())2310 continue;2311 if (BAT->isBATFunction(Function.getAddress()))2312 continue;2313 BP.Functions.emplace_back(YAMLProfileWriter::convert(2314 Function, /*UseDFS=*/false, InlineTree, BAT));2315 }2316 2317 for (const auto &KV : NamesToBranches) {2318 const StringRef FuncName = KV.first;2319 const FuncBranchData &Branches = KV.second;2320 yaml::bolt::BinaryFunctionProfile YamlBF;2321 BinaryData *BD = BC.getBinaryDataByName(FuncName);2322 assert(BD);2323 uint64_t FuncAddress = BD->getAddress();2324 if (!BAT->isBATFunction(FuncAddress))2325 continue;2326 BinaryFunction *BF = BC.getBinaryFunctionAtAddress(FuncAddress);2327 assert(BF);2328 YamlBF.Name = getLocationName(*BF, BAT);2329 YamlBF.Id = BF->getFunctionNumber();2330 YamlBF.Hash = BAT->getBFHash(FuncAddress);2331 YamlBF.ExecCount = BF->getKnownExecutionCount();2332 YamlBF.ExternEntryCount = BF->getExternEntryCount();2333 YamlBF.NumBasicBlocks = BAT->getNumBasicBlocks(FuncAddress);2334 const BoltAddressTranslation::BBHashMapTy &BlockMap =2335 BAT->getBBHashMap(FuncAddress);2336 YamlBF.Blocks.resize(YamlBF.NumBasicBlocks);2337 2338 for (auto &&[Entry, YamlBB] : llvm::zip(BlockMap, YamlBF.Blocks)) {2339 const auto &Block = Entry.second;2340 YamlBB.Hash = Block.Hash;2341 YamlBB.Index = Block.Index;2342 }2343 2344 // Lookup containing basic block offset and index2345 auto getBlock = [&BlockMap](uint32_t Offset) {2346 auto BlockIt = BlockMap.upper_bound(Offset);2347 if (LLVM_UNLIKELY(BlockIt == BlockMap.begin())) {2348 errs() << "BOLT-ERROR: invalid BAT section\n";2349 exit(1);2350 }2351 --BlockIt;2352 return std::pair(BlockIt->first, BlockIt->second.Index);2353 };2354 2355 for (const BranchInfo &BI : Branches.Data) {2356 using namespace yaml::bolt;2357 const auto &[BlockOffset, BlockIndex] = getBlock(BI.From.Offset);2358 BinaryBasicBlockProfile &YamlBB = YamlBF.Blocks[BlockIndex];2359 if (BI.To.IsSymbol && BI.To.Name == BI.From.Name && BI.To.Offset != 0) {2360 // Internal branch2361 const unsigned SuccIndex = getBlock(BI.To.Offset).second;2362 auto &SI = YamlBB.Successors.emplace_back(SuccessorInfo{SuccIndex});2363 SI.Count = BI.Branches;2364 SI.Mispreds = BI.Mispreds;2365 } else {2366 // Call2367 const uint32_t Offset = BI.From.Offset - BlockOffset;2368 auto &CSI = YamlBB.CallSites.emplace_back(CallSiteInfo{Offset});2369 CSI.Count = BI.Branches;2370 CSI.Mispreds = BI.Mispreds;2371 if (const BinaryData *BD = BC.getBinaryDataByName(BI.To.Name))2372 YAMLProfileWriter::setCSIDestination(BC, CSI, BD->getSymbol(), BAT,2373 BI.To.Offset);2374 }2375 }2376 // Set entry counts, similar to DataReader::readProfile.2377 for (const BranchInfo &BI : Branches.EntryData) {2378 if (!BlockMap.isInputBlock(BI.To.Offset)) {2379 if (opts::Verbosity >= 1)2380 errs() << "BOLT-WARNING: Unexpected EntryData in " << FuncName2381 << " at 0x" << Twine::utohexstr(BI.To.Offset) << '\n';2382 continue;2383 }2384 const unsigned BlockIndex = BlockMap.getBBIndex(BI.To.Offset);2385 YamlBF.Blocks[BlockIndex].ExecCount += BI.Branches;2386 }2387 if (PseudoProbeDecoder) {2388 DenseMap<const MCDecodedPseudoProbeInlineTree *, uint32_t>2389 InlineTreeNodeId;2390 if (BF->getGUID()) {2391 std::tie(YamlBF.InlineTree, InlineTreeNodeId) =2392 YAMLProfileWriter::convertBFInlineTree(*PseudoProbeDecoder,2393 InlineTree, BF->getGUID());2394 }2395 // Fetch probes belonging to all fragments2396 const AddressProbesMap &ProbeMap =2397 PseudoProbeDecoder->getAddress2ProbesMap();2398 BinaryFunction::FragmentsSetTy Fragments(BF->Fragments);2399 Fragments.insert(BF);2400 DenseMap<uint32_t, YAMLProfileWriter::BlockProbeCtx> BlockCtx;2401 for (const BinaryFunction *F : Fragments) {2402 const uint64_t FuncAddr = F->getAddress();2403 for (const MCDecodedPseudoProbe &Probe :2404 ProbeMap.find(FuncAddr, FuncAddr + F->getSize())) {2405 const uint32_t OutputAddress = Probe.getAddress();2406 const uint32_t InputOffset = BAT->translate(2407 FuncAddr, OutputAddress - FuncAddr, /*IsBranchSrc=*/true);2408 const auto &[BlockOffset, BlockIndex] = getBlock(InputOffset);2409 BlockCtx[BlockIndex].addBlockProbe(InlineTreeNodeId, Probe,2410 InputOffset - BlockOffset);2411 }2412 }2413 2414 for (auto &[Block, Ctx] : BlockCtx)2415 Ctx.finalize(YamlBF.Blocks[Block]);2416 }2417 // Skip printing if there's no profile data2418 llvm::erase_if(2419 YamlBF.Blocks, [](const yaml::bolt::BinaryBasicBlockProfile &YamlBB) {2420 auto HasCount = [](const auto &SI) { return SI.Count; };2421 bool HasAnyCount = YamlBB.ExecCount ||2422 llvm::any_of(YamlBB.Successors, HasCount) ||2423 llvm::any_of(YamlBB.CallSites, HasCount);2424 return !HasAnyCount;2425 });2426 BP.Functions.emplace_back(YamlBF);2427 }2428 }2429 2430 // Write the profile.2431 yaml::Output Out(OutFile, nullptr, 0);2432 Out << BP;2433 return std::error_code();2434}2435 2436void DataAggregator::dump() const { DataReader::dump(); }2437 2438void DataAggregator::dump(const PerfBranchSample &Sample) const {2439 Diag << "Sample brstack entries: " << Sample.LBR.size() << "\n";2440 for (const LBREntry &LBR : Sample.LBR)2441 Diag << LBR << '\n';2442}2443 2444void DataAggregator::dump(const PerfMemSample &Sample) const {2445 Diag << "Sample mem entries: " << Sample.PC << ": " << Sample.Addr << "\n";2446}2447