diff options
author | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
---|---|---|
committer | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
commit | 06e5c21a835c0e923506c4ff27929f34e00761c2 (patch) | |
tree | 75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/libs/llvm12/lib/XRay/Profile.cpp | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'contrib/libs/llvm12/lib/XRay/Profile.cpp')
-rw-r--r-- | contrib/libs/llvm12/lib/XRay/Profile.cpp | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/contrib/libs/llvm12/lib/XRay/Profile.cpp b/contrib/libs/llvm12/lib/XRay/Profile.cpp new file mode 100644 index 0000000000..c1a43632b6 --- /dev/null +++ b/contrib/libs/llvm12/lib/XRay/Profile.cpp @@ -0,0 +1,403 @@ +//===- Profile.cpp - XRay Profile Abstraction -----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the XRay Profile class representing the latency profile generated by +// XRay's profiling mode. +// +//===----------------------------------------------------------------------===// +#include "llvm/XRay/Profile.h" + +#include "llvm/Support/DataExtractor.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/XRay/Trace.h" +#include <deque> +#include <memory> + +namespace llvm { +namespace xray { + +Profile::Profile(const Profile &O) { + // We need to re-create all the tries from the original (O), into the current + // Profile being initialized, through the Block instances we see. + for (const auto &Block : O) { + Blocks.push_back({Block.Thread, {}}); + auto &B = Blocks.back(); + for (const auto &PathData : Block.PathData) + B.PathData.push_back({internPath(cantFail(O.expandPath(PathData.first))), + PathData.second}); + } +} + +Profile &Profile::operator=(const Profile &O) { + Profile P = O; + *this = std::move(P); + return *this; +} + +namespace { + +struct BlockHeader { + uint32_t Size; + uint32_t Number; + uint64_t Thread; +}; + +static Expected<BlockHeader> readBlockHeader(DataExtractor &Extractor, + uint64_t &Offset) { + BlockHeader H; + uint64_t CurrentOffset = Offset; + H.Size = Extractor.getU32(&Offset); + if (Offset == CurrentOffset) + return make_error<StringError>( + Twine("Error parsing block header size at offset '") + + Twine(CurrentOffset) + "'", + std::make_error_code(std::errc::invalid_argument)); + CurrentOffset = Offset; + H.Number = Extractor.getU32(&Offset); + if (Offset == CurrentOffset) + return make_error<StringError>( + Twine("Error parsing block header number at offset '") + + Twine(CurrentOffset) + "'", + std::make_error_code(std::errc::invalid_argument)); + CurrentOffset = Offset; + H.Thread = Extractor.getU64(&Offset); + if (Offset == CurrentOffset) + return make_error<StringError>( + Twine("Error parsing block header thread id at offset '") + + Twine(CurrentOffset) + "'", + std::make_error_code(std::errc::invalid_argument)); + return H; +} + +static Expected<std::vector<Profile::FuncID>> readPath(DataExtractor &Extractor, + uint64_t &Offset) { + // We're reading a sequence of int32_t's until we find a 0. + std::vector<Profile::FuncID> Path; + auto CurrentOffset = Offset; + int32_t FuncId; + do { + FuncId = Extractor.getSigned(&Offset, 4); + if (CurrentOffset == Offset) + return make_error<StringError>( + Twine("Error parsing path at offset '") + Twine(CurrentOffset) + "'", + std::make_error_code(std::errc::invalid_argument)); + CurrentOffset = Offset; + Path.push_back(FuncId); + } while (FuncId != 0); + return std::move(Path); +} + +static Expected<Profile::Data> readData(DataExtractor &Extractor, + uint64_t &Offset) { + // We expect a certain number of elements for Data: + // - A 64-bit CallCount + // - A 64-bit CumulativeLocalTime counter + Profile::Data D; + auto CurrentOffset = Offset; + D.CallCount = Extractor.getU64(&Offset); + if (CurrentOffset == Offset) + return make_error<StringError>( + Twine("Error parsing call counts at offset '") + Twine(CurrentOffset) + + "'", + std::make_error_code(std::errc::invalid_argument)); + CurrentOffset = Offset; + D.CumulativeLocalTime = Extractor.getU64(&Offset); + if (CurrentOffset == Offset) + return make_error<StringError>( + Twine("Error parsing cumulative local time at offset '") + + Twine(CurrentOffset) + "'", + std::make_error_code(std::errc::invalid_argument)); + return D; +} + +} // namespace + +Error Profile::addBlock(Block &&B) { + if (B.PathData.empty()) + return make_error<StringError>( + "Block may not have empty path data.", + std::make_error_code(std::errc::invalid_argument)); + + Blocks.emplace_back(std::move(B)); + return Error::success(); +} + +Expected<std::vector<Profile::FuncID>> Profile::expandPath(PathID P) const { + auto It = PathIDMap.find(P); + if (It == PathIDMap.end()) + return make_error<StringError>( + Twine("PathID not found: ") + Twine(P), + std::make_error_code(std::errc::invalid_argument)); + std::vector<Profile::FuncID> Path; + for (auto Node = It->second; Node; Node = Node->Caller) + Path.push_back(Node->Func); + return std::move(Path); +} + +Profile::PathID Profile::internPath(ArrayRef<FuncID> P) { + if (P.empty()) + return 0; + + auto RootToLeafPath = reverse(P); + + // Find the root. + auto It = RootToLeafPath.begin(); + auto PathRoot = *It++; + auto RootIt = + find_if(Roots, [PathRoot](TrieNode *N) { return N->Func == PathRoot; }); + + // If we've not seen this root before, remember it. + TrieNode *Node = nullptr; + if (RootIt == Roots.end()) { + NodeStorage.emplace_back(); + Node = &NodeStorage.back(); + Node->Func = PathRoot; + Roots.push_back(Node); + } else { + Node = *RootIt; + } + + // Now traverse the path, re-creating if necessary. + while (It != RootToLeafPath.end()) { + auto NodeFuncID = *It++; + auto CalleeIt = find_if(Node->Callees, [NodeFuncID](TrieNode *N) { + return N->Func == NodeFuncID; + }); + if (CalleeIt == Node->Callees.end()) { + NodeStorage.emplace_back(); + auto NewNode = &NodeStorage.back(); + NewNode->Func = NodeFuncID; + NewNode->Caller = Node; + Node->Callees.push_back(NewNode); + Node = NewNode; + } else { + Node = *CalleeIt; + } + } + + // At this point, Node *must* be pointing at the leaf. + assert(Node->Func == P.front()); + if (Node->ID == 0) { + Node->ID = NextID++; + PathIDMap.insert({Node->ID, Node}); + } + return Node->ID; +} + +Profile mergeProfilesByThread(const Profile &L, const Profile &R) { + Profile Merged; + using PathDataMap = DenseMap<Profile::PathID, Profile::Data>; + using PathDataMapPtr = std::unique_ptr<PathDataMap>; + using PathDataVector = decltype(Profile::Block::PathData); + using ThreadProfileIndexMap = DenseMap<Profile::ThreadID, PathDataMapPtr>; + ThreadProfileIndexMap ThreadProfileIndex; + + for (const auto &P : {std::ref(L), std::ref(R)}) + for (const auto &Block : P.get()) { + ThreadProfileIndexMap::iterator It; + std::tie(It, std::ignore) = ThreadProfileIndex.insert( + {Block.Thread, PathDataMapPtr{new PathDataMap()}}); + for (const auto &PathAndData : Block.PathData) { + auto &PathID = PathAndData.first; + auto &Data = PathAndData.second; + auto NewPathID = + Merged.internPath(cantFail(P.get().expandPath(PathID))); + PathDataMap::iterator PathDataIt; + bool Inserted; + std::tie(PathDataIt, Inserted) = It->second->insert({NewPathID, Data}); + if (!Inserted) { + auto &ExistingData = PathDataIt->second; + ExistingData.CallCount += Data.CallCount; + ExistingData.CumulativeLocalTime += Data.CumulativeLocalTime; + } + } + } + + for (const auto &IndexedThreadBlock : ThreadProfileIndex) { + PathDataVector PathAndData; + PathAndData.reserve(IndexedThreadBlock.second->size()); + copy(*IndexedThreadBlock.second, std::back_inserter(PathAndData)); + cantFail( + Merged.addBlock({IndexedThreadBlock.first, std::move(PathAndData)})); + } + return Merged; +} + +Profile mergeProfilesByStack(const Profile &L, const Profile &R) { + Profile Merged; + using PathDataMap = DenseMap<Profile::PathID, Profile::Data>; + PathDataMap PathData; + using PathDataVector = decltype(Profile::Block::PathData); + for (const auto &P : {std::ref(L), std::ref(R)}) + for (const auto &Block : P.get()) + for (const auto &PathAndData : Block.PathData) { + auto &PathId = PathAndData.first; + auto &Data = PathAndData.second; + auto NewPathID = + Merged.internPath(cantFail(P.get().expandPath(PathId))); + PathDataMap::iterator PathDataIt; + bool Inserted; + std::tie(PathDataIt, Inserted) = PathData.insert({NewPathID, Data}); + if (!Inserted) { + auto &ExistingData = PathDataIt->second; + ExistingData.CallCount += Data.CallCount; + ExistingData.CumulativeLocalTime += Data.CumulativeLocalTime; + } + } + + // In the end there's a single Block, for thread 0. + PathDataVector Block; + Block.reserve(PathData.size()); + copy(PathData, std::back_inserter(Block)); + cantFail(Merged.addBlock({0, std::move(Block)})); + return Merged; +} + +Expected<Profile> loadProfile(StringRef Filename) { + Expected<sys::fs::file_t> FdOrErr = sys::fs::openNativeFileForRead(Filename); + if (!FdOrErr) + return FdOrErr.takeError(); + + uint64_t FileSize; + if (auto EC = sys::fs::file_size(Filename, FileSize)) + return make_error<StringError>( + Twine("Cannot get filesize of '") + Filename + "'", EC); + + std::error_code EC; + sys::fs::mapped_file_region MappedFile( + *FdOrErr, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0, + EC); + sys::fs::closeFile(*FdOrErr); + if (EC) + return make_error<StringError>( + Twine("Cannot mmap profile '") + Filename + "'", EC); + StringRef Data(MappedFile.data(), MappedFile.size()); + + Profile P; + uint64_t Offset = 0; + DataExtractor Extractor(Data, true, 8); + + // For each block we get from the file: + while (Offset != MappedFile.size()) { + auto HeaderOrError = readBlockHeader(Extractor, Offset); + if (!HeaderOrError) + return HeaderOrError.takeError(); + + // TODO: Maybe store this header information for each block, even just for + // debugging? + const auto &Header = HeaderOrError.get(); + + // Read in the path data. + auto PathOrError = readPath(Extractor, Offset); + if (!PathOrError) + return PathOrError.takeError(); + const auto &Path = PathOrError.get(); + + // For each path we encounter, we should intern it to get a PathID. + auto DataOrError = readData(Extractor, Offset); + if (!DataOrError) + return DataOrError.takeError(); + auto &Data = DataOrError.get(); + + if (auto E = + P.addBlock(Profile::Block{Profile::ThreadID{Header.Thread}, + {{P.internPath(Path), std::move(Data)}}})) + return std::move(E); + } + + return P; +} + +namespace { + +struct StackEntry { + uint64_t Timestamp; + Profile::FuncID FuncId; +}; + +} // namespace + +Expected<Profile> profileFromTrace(const Trace &T) { + Profile P; + + // The implementation of the algorithm re-creates the execution of + // the functions based on the trace data. To do this, we set up a number of + // data structures to track the execution context of every thread in the + // Trace. + DenseMap<Profile::ThreadID, std::vector<StackEntry>> ThreadStacks; + DenseMap<Profile::ThreadID, DenseMap<Profile::PathID, Profile::Data>> + ThreadPathData; + + // We then do a pass through the Trace to account data on a per-thread-basis. + for (const auto &E : T) { + auto &TSD = ThreadStacks[E.TId]; + switch (E.Type) { + case RecordTypes::ENTER: + case RecordTypes::ENTER_ARG: + + // Push entries into the function call stack. + TSD.push_back({E.TSC, E.FuncId}); + break; + + case RecordTypes::EXIT: + case RecordTypes::TAIL_EXIT: + + // Exits cause some accounting to happen, based on the state of the stack. + // For each function we pop off the stack, we take note of the path and + // record the cumulative state for this path. As we're doing this, we + // intern the path into the Profile. + while (!TSD.empty()) { + auto Top = TSD.back(); + auto FunctionLocalTime = AbsoluteDifference(Top.Timestamp, E.TSC); + SmallVector<Profile::FuncID, 16> Path; + transform(reverse(TSD), std::back_inserter(Path), + std::mem_fn(&StackEntry::FuncId)); + auto InternedPath = P.internPath(Path); + auto &TPD = ThreadPathData[E.TId][InternedPath]; + ++TPD.CallCount; + TPD.CumulativeLocalTime += FunctionLocalTime; + TSD.pop_back(); + + // If we've matched the corresponding entry event for this function, + // then we exit the loop. + if (Top.FuncId == E.FuncId) + break; + + // FIXME: Consider the intermediate times and the cumulative tree time + // as well. + } + + break; + + case RecordTypes::CUSTOM_EVENT: + case RecordTypes::TYPED_EVENT: + // TODO: Support an extension point to allow handling of custom and typed + // events in profiles. + break; + } + } + + // Once we've gone through the Trace, we now create one Block per thread in + // the Profile. + for (const auto &ThreadPaths : ThreadPathData) { + const auto &TID = ThreadPaths.first; + const auto &PathsData = ThreadPaths.second; + if (auto E = P.addBlock({ + TID, + std::vector<std::pair<Profile::PathID, Profile::Data>>( + PathsData.begin(), PathsData.end()), + })) + return std::move(E); + } + + return P; +} + +} // namespace xray +} // namespace llvm |