diff options
author | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
---|---|---|
committer | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
commit | 6ffe9e53658409f212834330e13564e4952558f6 (patch) | |
tree | 85b1e00183517648b228aafa7c8fb07f5276f419 /contrib/libs/llvm16/lib/ProfileData/RawMemProfReader.cpp | |
parent | 726057070f9c5a91fc10fde0d5024913d10f1ab9 (diff) | |
download | ydb-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.cpp | 557 |
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 |