aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp
diff options
context:
space:
mode:
authorvitalyisaev <vitalyisaev@yandex-team.com>2023-06-29 10:00:50 +0300
committervitalyisaev <vitalyisaev@yandex-team.com>2023-06-29 10:00:50 +0300
commit6ffe9e53658409f212834330e13564e4952558f6 (patch)
tree85b1e00183517648b228aafa7c8fb07f5276f419 /contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp
parent726057070f9c5a91fc10fde0d5024913d10f1ab9 (diff)
downloadydb-6ffe9e53658409f212834330e13564e4952558f6.tar.gz
YQ Connector: support managed ClickHouse
Со стороны dqrun можно обратиться к инстансу коннектора, который работает на streaming стенде, и извлечь данные из облачного CH.
Diffstat (limited to 'contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp')
-rw-r--r--contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp557
1 files changed, 557 insertions, 0 deletions
diff --git a/contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp b/contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp
new file mode 100644
index 0000000000..3081a04f26
--- /dev/null
+++ b/contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp
@@ -0,0 +1,557 @@
+//===- RawMemProfReader.cpp - Instrumented memory profiling reader --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains support for reading MemProf profiling data.
+//
+//===----------------------------------------------------------------------===//
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
+#include "llvm/DebugInfo/Symbolize/SymbolizableObjectFile.h"
+#include "llvm/Object/Binary.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/ProfileData/InstrProf.h"
+#include "llvm/ProfileData/MemProf.h"
+#include "llvm/ProfileData/MemProfData.inc"
+#include "llvm/ProfileData/RawMemProfReader.h"
+#include "llvm/Support/Endian.h"
+#include "llvm/Support/Path.h"
+
+#define DEBUG_TYPE "memprof"
+
+namespace llvm {
+namespace memprof {
+namespace {
+template <class T = uint64_t> inline T alignedRead(const char *Ptr) {
+ static_assert(std::is_pod<T>::value, "Not a pod type.");
+ assert(reinterpret_cast<size_t>(Ptr) % sizeof(T) == 0 && "Unaligned Read");
+ return *reinterpret_cast<const T *>(Ptr);
+}
+
+Error checkBuffer(const MemoryBuffer &Buffer) {
+ if (!RawMemProfReader::hasFormat(Buffer))
+ return make_error<InstrProfError>(instrprof_error::bad_magic);
+
+ if (Buffer.getBufferSize() == 0)
+ return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
+
+ if (Buffer.getBufferSize() < sizeof(Header)) {
+ return make_error<InstrProfError>(instrprof_error::truncated);
+ }
+
+ // The size of the buffer can be > header total size since we allow repeated
+ // serialization of memprof profiles to the same file.
+ uint64_t TotalSize = 0;
+ const char *Next = Buffer.getBufferStart();
+ while (Next < Buffer.getBufferEnd()) {
+ auto *H = reinterpret_cast<const Header *>(Next);
+ if (H->Version != MEMPROF_RAW_VERSION) {
+ return make_error<InstrProfError>(instrprof_error::unsupported_version);
+ }
+
+ TotalSize += H->TotalSize;
+ Next += H->TotalSize;
+ }
+
+ if (Buffer.getBufferSize() != TotalSize) {
+ return make_error<InstrProfError>(instrprof_error::malformed);
+ }
+ return Error::success();
+}
+
+llvm::SmallVector<SegmentEntry> readSegmentEntries(const char *Ptr) {
+ using namespace support;
+
+ const uint64_t NumItemsToRead =
+ endian::readNext<uint64_t, little, unaligned>(Ptr);
+ llvm::SmallVector<SegmentEntry> Items;
+ for (uint64_t I = 0; I < NumItemsToRead; I++) {
+ Items.push_back(*reinterpret_cast<const SegmentEntry *>(
+ Ptr + I * sizeof(SegmentEntry)));
+ }
+ return Items;
+}
+
+llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
+readMemInfoBlocks(const char *Ptr) {
+ using namespace support;
+
+ const uint64_t NumItemsToRead =
+ endian::readNext<uint64_t, little, unaligned>(Ptr);
+ llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;
+ for (uint64_t I = 0; I < NumItemsToRead; I++) {
+ const uint64_t Id = endian::readNext<uint64_t, little, unaligned>(Ptr);
+ const MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);
+ Items.push_back({Id, MIB});
+ // Only increment by size of MIB since readNext implicitly increments.
+ Ptr += sizeof(MemInfoBlock);
+ }
+ return Items;
+}
+
+CallStackMap readStackInfo(const char *Ptr) {
+ using namespace support;
+
+ const uint64_t NumItemsToRead =
+ endian::readNext<uint64_t, little, unaligned>(Ptr);
+ CallStackMap Items;
+
+ for (uint64_t I = 0; I < NumItemsToRead; I++) {
+ const uint64_t StackId = endian::readNext<uint64_t, little, unaligned>(Ptr);
+ const uint64_t NumPCs = endian::readNext<uint64_t, little, unaligned>(Ptr);
+
+ SmallVector<uint64_t> CallStack;
+ for (uint64_t J = 0; J < NumPCs; J++) {
+ CallStack.push_back(endian::readNext<uint64_t, little, unaligned>(Ptr));
+ }
+
+ Items[StackId] = CallStack;
+ }
+ return Items;
+}
+
+// Merges the contents of stack information in \p From to \p To. Returns true if
+// any stack ids observed previously map to a different set of program counter
+// addresses.
+bool mergeStackMap(const CallStackMap &From, CallStackMap &To) {
+ for (const auto &IdStack : From) {
+ auto I = To.find(IdStack.first);
+ if (I == To.end()) {
+ To[IdStack.first] = IdStack.second;
+ } else {
+ // Check that the PCs are the same (in order).
+ if (IdStack.second != I->second)
+ return true;
+ }
+ }
+ return false;
+}
+
+Error report(Error E, const StringRef Context) {
+ return joinErrors(createStringError(inconvertibleErrorCode(), Context),
+ std::move(E));
+}
+
+bool isRuntimePath(const StringRef Path) {
+ return StringRef(llvm::sys::path::convert_to_slash(Path))
+ .contains("memprof/memprof_");
+}
+
+std::string getBuildIdString(const SegmentEntry &Entry) {
+ constexpr size_t Size = sizeof(Entry.BuildId) / sizeof(uint8_t);
+ constexpr uint8_t Zeros[Size] = {0};
+ // If the build id is unset print a helpful string instead of all zeros.
+ if (memcmp(Entry.BuildId, Zeros, Size) == 0)
+ return "<None>";
+
+ std::string Str;
+ raw_string_ostream OS(Str);
+ for (size_t I = 0; I < Size; I++) {
+ OS << format_hex_no_prefix(Entry.BuildId[I], 2);
+ }
+ return OS.str();
+}
+} // namespace
+
+Expected<std::unique_ptr<RawMemProfReader>>
+RawMemProfReader::create(const Twine &Path, const StringRef ProfiledBinary,
+ bool KeepName) {
+ auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
+ if (std::error_code EC = BufferOr.getError())
+ return report(errorCodeToError(EC), Path.getSingleStringRef());
+
+ std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
+ if (Error E = checkBuffer(*Buffer))
+ return report(std::move(E), Path.getSingleStringRef());
+
+ if (ProfiledBinary.empty())
+ return report(
+ errorCodeToError(make_error_code(std::errc::invalid_argument)),
+ "Path to profiled binary is empty!");
+
+ auto BinaryOr = llvm::object::createBinary(ProfiledBinary);
+ if (!BinaryOr) {
+ return report(BinaryOr.takeError(), ProfiledBinary);
+ }
+
+ // Use new here since constructor is private.
+ std::unique_ptr<RawMemProfReader> Reader(
+ new RawMemProfReader(std::move(BinaryOr.get()), KeepName));
+ if (Error E = Reader->initialize(std::move(Buffer))) {
+ return std::move(E);
+ }
+ return std::move(Reader);
+}
+
+bool RawMemProfReader::hasFormat(const StringRef Path) {
+ auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
+ if (!BufferOr)
+ return false;
+
+ std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
+ return hasFormat(*Buffer);
+}
+
+bool RawMemProfReader::hasFormat(const MemoryBuffer &Buffer) {
+ if (Buffer.getBufferSize() < sizeof(uint64_t))
+ return false;
+ // Aligned read to sanity check that the buffer was allocated with at least 8b
+ // alignment.
+ const uint64_t Magic = alignedRead(Buffer.getBufferStart());
+ return Magic == MEMPROF_RAW_MAGIC_64;
+}
+
+void RawMemProfReader::printYAML(raw_ostream &OS) {
+ uint64_t NumAllocFunctions = 0, NumMibInfo = 0;
+ for (const auto &KV : FunctionProfileData) {
+ const size_t NumAllocSites = KV.second.AllocSites.size();
+ if (NumAllocSites > 0) {
+ NumAllocFunctions++;
+ NumMibInfo += NumAllocSites;
+ }
+ }
+
+ OS << "MemprofProfile:\n";
+ OS << " Summary:\n";
+ OS << " Version: " << MEMPROF_RAW_VERSION << "\n";
+ OS << " NumSegments: " << SegmentInfo.size() << "\n";
+ OS << " NumMibInfo: " << NumMibInfo << "\n";
+ OS << " NumAllocFunctions: " << NumAllocFunctions << "\n";
+ OS << " NumStackOffsets: " << StackMap.size() << "\n";
+ // Print out the segment information.
+ OS << " Segments:\n";
+ for (const auto &Entry : SegmentInfo) {
+ OS << " -\n";
+ OS << " BuildId: " << getBuildIdString(Entry) << "\n";
+ OS << " Start: 0x" << llvm::utohexstr(Entry.Start) << "\n";
+ OS << " End: 0x" << llvm::utohexstr(Entry.End) << "\n";
+ OS << " Offset: 0x" << llvm::utohexstr(Entry.Offset) << "\n";
+ }
+ // Print out the merged contents of the profiles.
+ OS << " Records:\n";
+ for (const auto &Entry : *this) {
+ OS << " -\n";
+ OS << " FunctionGUID: " << Entry.first << "\n";
+ Entry.second.print(OS);
+ }
+}
+
+Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {
+ const StringRef FileName = Binary.getBinary()->getFileName();
+
+ auto *ElfObject = dyn_cast<object::ELFObjectFileBase>(Binary.getBinary());
+ if (!ElfObject) {
+ return report(make_error<StringError>(Twine("Not an ELF file: "),
+ inconvertibleErrorCode()),
+ FileName);
+ }
+
+ // Check whether the profiled binary was built with position independent code
+ // (PIC). For now we provide a error message until symbolization support
+ // is added for pic.
+ auto* Elf64LEObject = llvm::cast<llvm::object::ELF64LEObjectFile>(ElfObject);
+ const llvm::object::ELF64LEFile& ElfFile = Elf64LEObject->getELFFile();
+ auto PHdrsOr = ElfFile.program_headers();
+ if(!PHdrsOr)
+ return report(make_error<StringError>(Twine("Could not read program headers: "),
+ inconvertibleErrorCode()),
+ FileName);
+ auto FirstLoadHeader = PHdrsOr->begin();
+ while (FirstLoadHeader->p_type != llvm::ELF::PT_LOAD)
+ ++FirstLoadHeader;
+ if(FirstLoadHeader->p_vaddr == 0)
+ return report(make_error<StringError>(Twine("Unsupported position independent code"),
+ inconvertibleErrorCode()),
+ FileName);
+
+ auto Triple = ElfObject->makeTriple();
+ if (!Triple.isX86())
+ return report(make_error<StringError>(Twine("Unsupported target: ") +
+ Triple.getArchName(),
+ inconvertibleErrorCode()),
+ FileName);
+
+ auto *Object = cast<object::ObjectFile>(Binary.getBinary());
+ std::unique_ptr<DIContext> Context = DWARFContext::create(
+ *Object, DWARFContext::ProcessDebugRelocations::Process);
+
+ auto SOFOr = symbolize::SymbolizableObjectFile::create(
+ Object, std::move(Context), /*UntagAddresses=*/false);
+ if (!SOFOr)
+ return report(SOFOr.takeError(), FileName);
+ Symbolizer = std::move(SOFOr.get());
+
+ if (Error E = readRawProfile(std::move(DataBuffer)))
+ return E;
+
+ if (Error E = symbolizeAndFilterStackFrames())
+ return E;
+
+ return mapRawProfileToRecords();
+}
+
+Error RawMemProfReader::mapRawProfileToRecords() {
+ // Hold a mapping from function to each callsite location we encounter within
+ // it that is part of some dynamic allocation context. The location is stored
+ // as a pointer to a symbolized list of inline frames.
+ using LocationPtr = const llvm::SmallVector<FrameId> *;
+ llvm::MapVector<GlobalValue::GUID, llvm::SetVector<LocationPtr>>
+ PerFunctionCallSites;
+
+ // Convert the raw profile callstack data into memprof records. While doing so
+ // keep track of related contexts so that we can fill these in later.
+ for (const auto &Entry : CallstackProfileData) {
+ const uint64_t StackId = Entry.first;
+
+ auto It = StackMap.find(StackId);
+ if (It == StackMap.end())
+ return make_error<InstrProfError>(
+ instrprof_error::malformed,
+ "memprof callstack record does not contain id: " + Twine(StackId));
+
+ // Construct the symbolized callstack.
+ llvm::SmallVector<FrameId> Callstack;
+ Callstack.reserve(It->getSecond().size());
+
+ llvm::ArrayRef<uint64_t> Addresses = It->getSecond();
+ for (size_t I = 0; I < Addresses.size(); I++) {
+ const uint64_t Address = Addresses[I];
+ assert(SymbolizedFrame.count(Address) > 0 &&
+ "Address not found in SymbolizedFrame map");
+ const SmallVector<FrameId> &Frames = SymbolizedFrame[Address];
+
+ assert(!idToFrame(Frames.back()).IsInlineFrame &&
+ "The last frame should not be inlined");
+
+ // Record the callsites for each function. Skip the first frame of the
+ // first address since it is the allocation site itself that is recorded
+ // as an alloc site.
+ for (size_t J = 0; J < Frames.size(); J++) {
+ if (I == 0 && J == 0)
+ continue;
+ // We attach the entire bottom-up frame here for the callsite even
+ // though we only need the frames up to and including the frame for
+ // Frames[J].Function. This will enable better deduplication for
+ // compression in the future.
+ const GlobalValue::GUID Guid = idToFrame(Frames[J]).Function;
+ PerFunctionCallSites[Guid].insert(&Frames);
+ }
+
+ // Add all the frames to the current allocation callstack.
+ Callstack.append(Frames.begin(), Frames.end());
+ }
+
+ // We attach the memprof record to each function bottom-up including the
+ // first non-inline frame.
+ for (size_t I = 0; /*Break out using the condition below*/; I++) {
+ const Frame &F = idToFrame(Callstack[I]);
+ auto Result =
+ FunctionProfileData.insert({F.Function, IndexedMemProfRecord()});
+ IndexedMemProfRecord &Record = Result.first->second;
+ Record.AllocSites.emplace_back(Callstack, Entry.second);
+
+ if (!F.IsInlineFrame)
+ break;
+ }
+ }
+
+ // Fill in the related callsites per function.
+ for (const auto &[Id, Locs] : PerFunctionCallSites) {
+ // Some functions may have only callsite data and no allocation data. Here
+ // we insert a new entry for callsite data if we need to.
+ auto Result = FunctionProfileData.insert({Id, IndexedMemProfRecord()});
+ IndexedMemProfRecord &Record = Result.first->second;
+ for (LocationPtr Loc : Locs) {
+ Record.CallSites.push_back(*Loc);
+ }
+ }
+
+ return Error::success();
+}
+
+Error RawMemProfReader::symbolizeAndFilterStackFrames() {
+ // The specifier to use when symbolization is requested.
+ const DILineInfoSpecifier Specifier(
+ DILineInfoSpecifier::FileLineInfoKind::RawValue,
+ DILineInfoSpecifier::FunctionNameKind::LinkageName);
+
+ // For entries where all PCs in the callstack are discarded, we erase the
+ // entry from the stack map.
+ llvm::SmallVector<uint64_t> EntriesToErase;
+ // We keep track of all prior discarded entries so that we can avoid invoking
+ // the symbolizer for such entries.
+ llvm::DenseSet<uint64_t> AllVAddrsToDiscard;
+ for (auto &Entry : StackMap) {
+ for (const uint64_t VAddr : Entry.getSecond()) {
+ // Check if we have already symbolized and cached the result or if we
+ // don't want to attempt symbolization since we know this address is bad.
+ // In this case the address is also removed from the current callstack.
+ if (SymbolizedFrame.count(VAddr) > 0 ||
+ AllVAddrsToDiscard.contains(VAddr))
+ continue;
+
+ Expected<DIInliningInfo> DIOr = Symbolizer->symbolizeInlinedCode(
+ getModuleOffset(VAddr), Specifier, /*UseSymbolTable=*/false);
+ if (!DIOr)
+ return DIOr.takeError();
+ DIInliningInfo DI = DIOr.get();
+
+ // Drop frames which we can't symbolize or if they belong to the runtime.
+ if (DI.getFrame(0).FunctionName == DILineInfo::BadString ||
+ isRuntimePath(DI.getFrame(0).FileName)) {
+ AllVAddrsToDiscard.insert(VAddr);
+ continue;
+ }
+
+ for (size_t I = 0, NumFrames = DI.getNumberOfFrames(); I < NumFrames;
+ I++) {
+ const auto &DIFrame = DI.getFrame(I);
+ const uint64_t Guid =
+ IndexedMemProfRecord::getGUID(DIFrame.FunctionName);
+ const Frame F(Guid, DIFrame.Line - DIFrame.StartLine, DIFrame.Column,
+ // Only the last entry is not an inlined location.
+ I != NumFrames - 1);
+ // Here we retain a mapping from the GUID to symbol name instead of
+ // adding it to the frame object directly to reduce memory overhead.
+ // This is because there can be many unique frames, particularly for
+ // callsite frames.
+ if (KeepSymbolName)
+ GuidToSymbolName.insert({Guid, DIFrame.FunctionName});
+
+ const FrameId Hash = F.hash();
+ IdToFrame.insert({Hash, F});
+ SymbolizedFrame[VAddr].push_back(Hash);
+ }
+ }
+
+ auto &CallStack = Entry.getSecond();
+ llvm::erase_if(CallStack, [&AllVAddrsToDiscard](const uint64_t A) {
+ return AllVAddrsToDiscard.contains(A);
+ });
+ if (CallStack.empty())
+ EntriesToErase.push_back(Entry.getFirst());
+ }
+
+ // Drop the entries where the callstack is empty.
+ for (const uint64_t Id : EntriesToErase) {
+ StackMap.erase(Id);
+ CallstackProfileData.erase(Id);
+ }
+
+ if (StackMap.empty())
+ return make_error<InstrProfError>(
+ instrprof_error::malformed,
+ "no entries in callstack map after symbolization");
+
+ return Error::success();
+}
+
+Error RawMemProfReader::readRawProfile(
+ std::unique_ptr<MemoryBuffer> DataBuffer) {
+ const char *Next = DataBuffer->getBufferStart();
+
+ while (Next < DataBuffer->getBufferEnd()) {
+ auto *Header = reinterpret_cast<const memprof::Header *>(Next);
+
+ // Read in the segment information, check whether its the same across all
+ // profiles in this binary file.
+ const llvm::SmallVector<SegmentEntry> Entries =
+ readSegmentEntries(Next + Header->SegmentOffset);
+ if (!SegmentInfo.empty() && SegmentInfo != Entries) {
+ // We do not expect segment information to change when deserializing from
+ // the same binary profile file. This can happen if dynamic libraries are
+ // loaded/unloaded between profile dumping.
+ return make_error<InstrProfError>(
+ instrprof_error::malformed,
+ "memprof raw profile has different segment information");
+ }
+ SegmentInfo.assign(Entries.begin(), Entries.end());
+
+ // Read in the MemInfoBlocks. Merge them based on stack id - we assume that
+ // raw profiles in the same binary file are from the same process so the
+ // stackdepot ids are the same.
+ for (const auto &Value : readMemInfoBlocks(Next + Header->MIBOffset)) {
+ if (CallstackProfileData.count(Value.first)) {
+ CallstackProfileData[Value.first].Merge(Value.second);
+ } else {
+ CallstackProfileData[Value.first] = Value.second;
+ }
+ }
+
+ // Read in the callstack for each ids. For multiple raw profiles in the same
+ // file, we expect that the callstack is the same for a unique id.
+ const CallStackMap CSM = readStackInfo(Next + Header->StackOffset);
+ if (StackMap.empty()) {
+ StackMap = CSM;
+ } else {
+ if (mergeStackMap(CSM, StackMap))
+ return make_error<InstrProfError>(
+ instrprof_error::malformed,
+ "memprof raw profile got different call stack for same id");
+ }
+
+ Next += Header->TotalSize;
+ }
+
+ return Error::success();
+}
+
+object::SectionedAddress
+RawMemProfReader::getModuleOffset(const uint64_t VirtualAddress) {
+ LLVM_DEBUG({
+ SegmentEntry *ContainingSegment = nullptr;
+ for (auto &SE : SegmentInfo) {
+ if (VirtualAddress > SE.Start && VirtualAddress <= SE.End) {
+ ContainingSegment = &SE;
+ }
+ }
+
+ // Ensure that the virtual address is valid.
+ assert(ContainingSegment && "Could not find a segment entry");
+ });
+
+ // TODO: Compute the file offset based on the maps and program headers. For
+ // now this only works for non PIE binaries.
+ return object::SectionedAddress{VirtualAddress};
+}
+
+Error RawMemProfReader::readNextRecord(GuidMemProfRecordPair &GuidRecord) {
+ if (FunctionProfileData.empty())
+ return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
+
+ if (Iter == FunctionProfileData.end())
+ return make_error<InstrProfError>(instrprof_error::eof);
+
+ auto IdToFrameCallback = [this](const FrameId Id) {
+ Frame F = this->idToFrame(Id);
+ if (!this->KeepSymbolName)
+ return F;
+ auto Iter = this->GuidToSymbolName.find(F.Function);
+ assert(Iter != this->GuidToSymbolName.end());
+ F.SymbolName = Iter->getSecond();
+ return F;
+ };
+
+ const IndexedMemProfRecord &IndexedRecord = Iter->second;
+ GuidRecord = {Iter->first, MemProfRecord(IndexedRecord, IdToFrameCallback)};
+ Iter++;
+ return Error::success();
+}
+} // namespace memprof
+} // namespace llvm