Path: blob/main/contrib/llvm-project/llvm/lib/XRay/Profile.cpp
35234 views
//===- Profile.cpp - XRay Profile Abstraction -----------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Defines the XRay Profile class representing the latency profile generated by9// XRay's profiling mode.10//11//===----------------------------------------------------------------------===//12#include "llvm/XRay/Profile.h"1314#include "llvm/Support/DataExtractor.h"15#include "llvm/Support/Error.h"16#include "llvm/Support/FileSystem.h"17#include "llvm/XRay/Trace.h"18#include <deque>19#include <memory>2021namespace llvm {22namespace xray {2324Profile::Profile(const Profile &O) {25// We need to re-create all the tries from the original (O), into the current26// Profile being initialized, through the Block instances we see.27for (const auto &Block : O) {28Blocks.push_back({Block.Thread, {}});29auto &B = Blocks.back();30for (const auto &PathData : Block.PathData)31B.PathData.push_back({internPath(cantFail(O.expandPath(PathData.first))),32PathData.second});33}34}3536Profile &Profile::operator=(const Profile &O) {37Profile P = O;38*this = std::move(P);39return *this;40}4142namespace {4344struct BlockHeader {45uint32_t Size;46uint32_t Number;47uint64_t Thread;48};4950static Expected<BlockHeader> readBlockHeader(DataExtractor &Extractor,51uint64_t &Offset) {52BlockHeader H;53uint64_t CurrentOffset = Offset;54H.Size = Extractor.getU32(&Offset);55if (Offset == CurrentOffset)56return make_error<StringError>(57Twine("Error parsing block header size at offset '") +58Twine(CurrentOffset) + "'",59std::make_error_code(std::errc::invalid_argument));60CurrentOffset = Offset;61H.Number = Extractor.getU32(&Offset);62if (Offset == CurrentOffset)63return make_error<StringError>(64Twine("Error parsing block header number at offset '") +65Twine(CurrentOffset) + "'",66std::make_error_code(std::errc::invalid_argument));67CurrentOffset = Offset;68H.Thread = Extractor.getU64(&Offset);69if (Offset == CurrentOffset)70return make_error<StringError>(71Twine("Error parsing block header thread id at offset '") +72Twine(CurrentOffset) + "'",73std::make_error_code(std::errc::invalid_argument));74return H;75}7677static Expected<std::vector<Profile::FuncID>> readPath(DataExtractor &Extractor,78uint64_t &Offset) {79// We're reading a sequence of int32_t's until we find a 0.80std::vector<Profile::FuncID> Path;81auto CurrentOffset = Offset;82int32_t FuncId;83do {84FuncId = Extractor.getSigned(&Offset, 4);85if (CurrentOffset == Offset)86return make_error<StringError>(87Twine("Error parsing path at offset '") + Twine(CurrentOffset) + "'",88std::make_error_code(std::errc::invalid_argument));89CurrentOffset = Offset;90Path.push_back(FuncId);91} while (FuncId != 0);92return std::move(Path);93}9495static Expected<Profile::Data> readData(DataExtractor &Extractor,96uint64_t &Offset) {97// We expect a certain number of elements for Data:98// - A 64-bit CallCount99// - A 64-bit CumulativeLocalTime counter100Profile::Data D;101auto CurrentOffset = Offset;102D.CallCount = Extractor.getU64(&Offset);103if (CurrentOffset == Offset)104return make_error<StringError>(105Twine("Error parsing call counts at offset '") + Twine(CurrentOffset) +106"'",107std::make_error_code(std::errc::invalid_argument));108CurrentOffset = Offset;109D.CumulativeLocalTime = Extractor.getU64(&Offset);110if (CurrentOffset == Offset)111return make_error<StringError>(112Twine("Error parsing cumulative local time at offset '") +113Twine(CurrentOffset) + "'",114std::make_error_code(std::errc::invalid_argument));115return D;116}117118} // namespace119120Error Profile::addBlock(Block &&B) {121if (B.PathData.empty())122return make_error<StringError>(123"Block may not have empty path data.",124std::make_error_code(std::errc::invalid_argument));125126Blocks.emplace_back(std::move(B));127return Error::success();128}129130Expected<std::vector<Profile::FuncID>> Profile::expandPath(PathID P) const {131auto It = PathIDMap.find(P);132if (It == PathIDMap.end())133return make_error<StringError>(134Twine("PathID not found: ") + Twine(P),135std::make_error_code(std::errc::invalid_argument));136std::vector<Profile::FuncID> Path;137for (auto Node = It->second; Node; Node = Node->Caller)138Path.push_back(Node->Func);139return std::move(Path);140}141142Profile::PathID Profile::internPath(ArrayRef<FuncID> P) {143if (P.empty())144return 0;145146auto RootToLeafPath = reverse(P);147148// Find the root.149auto It = RootToLeafPath.begin();150auto PathRoot = *It++;151auto RootIt =152find_if(Roots, [PathRoot](TrieNode *N) { return N->Func == PathRoot; });153154// If we've not seen this root before, remember it.155TrieNode *Node = nullptr;156if (RootIt == Roots.end()) {157NodeStorage.emplace_back();158Node = &NodeStorage.back();159Node->Func = PathRoot;160Roots.push_back(Node);161} else {162Node = *RootIt;163}164165// Now traverse the path, re-creating if necessary.166while (It != RootToLeafPath.end()) {167auto NodeFuncID = *It++;168auto CalleeIt = find_if(Node->Callees, [NodeFuncID](TrieNode *N) {169return N->Func == NodeFuncID;170});171if (CalleeIt == Node->Callees.end()) {172NodeStorage.emplace_back();173auto NewNode = &NodeStorage.back();174NewNode->Func = NodeFuncID;175NewNode->Caller = Node;176Node->Callees.push_back(NewNode);177Node = NewNode;178} else {179Node = *CalleeIt;180}181}182183// At this point, Node *must* be pointing at the leaf.184assert(Node->Func == P.front());185if (Node->ID == 0) {186Node->ID = NextID++;187PathIDMap.insert({Node->ID, Node});188}189return Node->ID;190}191192Profile mergeProfilesByThread(const Profile &L, const Profile &R) {193Profile Merged;194using PathDataMap = DenseMap<Profile::PathID, Profile::Data>;195using PathDataMapPtr = std::unique_ptr<PathDataMap>;196using PathDataVector = decltype(Profile::Block::PathData);197using ThreadProfileIndexMap = DenseMap<Profile::ThreadID, PathDataMapPtr>;198ThreadProfileIndexMap ThreadProfileIndex;199200for (const auto &P : {std::ref(L), std::ref(R)})201for (const auto &Block : P.get()) {202ThreadProfileIndexMap::iterator It;203std::tie(It, std::ignore) = ThreadProfileIndex.insert(204{Block.Thread, std::make_unique<PathDataMap>()});205for (const auto &PathAndData : Block.PathData) {206auto &PathID = PathAndData.first;207auto &Data = PathAndData.second;208auto NewPathID =209Merged.internPath(cantFail(P.get().expandPath(PathID)));210PathDataMap::iterator PathDataIt;211bool Inserted;212std::tie(PathDataIt, Inserted) = It->second->insert({NewPathID, Data});213if (!Inserted) {214auto &ExistingData = PathDataIt->second;215ExistingData.CallCount += Data.CallCount;216ExistingData.CumulativeLocalTime += Data.CumulativeLocalTime;217}218}219}220221for (const auto &IndexedThreadBlock : ThreadProfileIndex) {222PathDataVector PathAndData;223PathAndData.reserve(IndexedThreadBlock.second->size());224copy(*IndexedThreadBlock.second, std::back_inserter(PathAndData));225cantFail(226Merged.addBlock({IndexedThreadBlock.first, std::move(PathAndData)}));227}228return Merged;229}230231Profile mergeProfilesByStack(const Profile &L, const Profile &R) {232Profile Merged;233using PathDataMap = DenseMap<Profile::PathID, Profile::Data>;234PathDataMap PathData;235using PathDataVector = decltype(Profile::Block::PathData);236for (const auto &P : {std::ref(L), std::ref(R)})237for (const auto &Block : P.get())238for (const auto &PathAndData : Block.PathData) {239auto &PathId = PathAndData.first;240auto &Data = PathAndData.second;241auto NewPathID =242Merged.internPath(cantFail(P.get().expandPath(PathId)));243PathDataMap::iterator PathDataIt;244bool Inserted;245std::tie(PathDataIt, Inserted) = PathData.insert({NewPathID, Data});246if (!Inserted) {247auto &ExistingData = PathDataIt->second;248ExistingData.CallCount += Data.CallCount;249ExistingData.CumulativeLocalTime += Data.CumulativeLocalTime;250}251}252253// In the end there's a single Block, for thread 0.254PathDataVector Block;255Block.reserve(PathData.size());256copy(PathData, std::back_inserter(Block));257cantFail(Merged.addBlock({0, std::move(Block)}));258return Merged;259}260261Expected<Profile> loadProfile(StringRef Filename) {262Expected<sys::fs::file_t> FdOrErr = sys::fs::openNativeFileForRead(Filename);263if (!FdOrErr)264return FdOrErr.takeError();265266uint64_t FileSize;267if (auto EC = sys::fs::file_size(Filename, FileSize))268return make_error<StringError>(269Twine("Cannot get filesize of '") + Filename + "'", EC);270271std::error_code EC;272sys::fs::mapped_file_region MappedFile(273*FdOrErr, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0,274EC);275sys::fs::closeFile(*FdOrErr);276if (EC)277return make_error<StringError>(278Twine("Cannot mmap profile '") + Filename + "'", EC);279StringRef Data(MappedFile.data(), MappedFile.size());280281Profile P;282uint64_t Offset = 0;283DataExtractor Extractor(Data, true, 8);284285// For each block we get from the file:286while (Offset != MappedFile.size()) {287auto HeaderOrError = readBlockHeader(Extractor, Offset);288if (!HeaderOrError)289return HeaderOrError.takeError();290291// TODO: Maybe store this header information for each block, even just for292// debugging?293const auto &Header = HeaderOrError.get();294295// Read in the path data.296auto PathOrError = readPath(Extractor, Offset);297if (!PathOrError)298return PathOrError.takeError();299const auto &Path = PathOrError.get();300301// For each path we encounter, we should intern it to get a PathID.302auto DataOrError = readData(Extractor, Offset);303if (!DataOrError)304return DataOrError.takeError();305auto &Data = DataOrError.get();306307if (auto E =308P.addBlock(Profile::Block{Profile::ThreadID{Header.Thread},309{{P.internPath(Path), std::move(Data)}}}))310return std::move(E);311}312313return P;314}315316namespace {317318struct StackEntry {319uint64_t Timestamp;320Profile::FuncID FuncId;321};322323} // namespace324325Expected<Profile> profileFromTrace(const Trace &T) {326Profile P;327328// The implementation of the algorithm re-creates the execution of329// the functions based on the trace data. To do this, we set up a number of330// data structures to track the execution context of every thread in the331// Trace.332DenseMap<Profile::ThreadID, std::vector<StackEntry>> ThreadStacks;333DenseMap<Profile::ThreadID, DenseMap<Profile::PathID, Profile::Data>>334ThreadPathData;335336// We then do a pass through the Trace to account data on a per-thread-basis.337for (const auto &E : T) {338auto &TSD = ThreadStacks[E.TId];339switch (E.Type) {340case RecordTypes::ENTER:341case RecordTypes::ENTER_ARG:342343// Push entries into the function call stack.344TSD.push_back({E.TSC, E.FuncId});345break;346347case RecordTypes::EXIT:348case RecordTypes::TAIL_EXIT:349350// Exits cause some accounting to happen, based on the state of the stack.351// For each function we pop off the stack, we take note of the path and352// record the cumulative state for this path. As we're doing this, we353// intern the path into the Profile.354while (!TSD.empty()) {355auto Top = TSD.back();356auto FunctionLocalTime = AbsoluteDifference(Top.Timestamp, E.TSC);357SmallVector<Profile::FuncID, 16> Path;358transform(reverse(TSD), std::back_inserter(Path),359std::mem_fn(&StackEntry::FuncId));360auto InternedPath = P.internPath(Path);361auto &TPD = ThreadPathData[E.TId][InternedPath];362++TPD.CallCount;363TPD.CumulativeLocalTime += FunctionLocalTime;364TSD.pop_back();365366// If we've matched the corresponding entry event for this function,367// then we exit the loop.368if (Top.FuncId == E.FuncId)369break;370371// FIXME: Consider the intermediate times and the cumulative tree time372// as well.373}374375break;376377case RecordTypes::CUSTOM_EVENT:378case RecordTypes::TYPED_EVENT:379// TODO: Support an extension point to allow handling of custom and typed380// events in profiles.381break;382}383}384385// Once we've gone through the Trace, we now create one Block per thread in386// the Profile.387for (const auto &ThreadPaths : ThreadPathData) {388const auto &TID = ThreadPaths.first;389const auto &PathsData = ThreadPaths.second;390if (auto E = P.addBlock({391TID,392std::vector<std::pair<Profile::PathID, Profile::Data>>(393PathsData.begin(), PathsData.end()),394}))395return std::move(E);396}397398return P;399}400401} // namespace xray402} // namespace llvm403404405