aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/llvm12/lib/XRay/Profile.cpp
diff options
context:
space:
mode:
authormonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
committermonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
commit06e5c21a835c0e923506c4ff27929f34e00761c2 (patch)
tree75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/libs/llvm12/lib/XRay/Profile.cpp
parent03f024c4412e3aa613bb543cf1660176320ba8f4 (diff)
downloadydb-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.cpp403
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