10b57cec5SDimitry Andric //===- xray-stacks.cpp: XRay Function Call Stack Accounting ---------------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric // 90b57cec5SDimitry Andric // This file implements stack-based accounting. It takes XRay traces, and 100b57cec5SDimitry Andric // collates statistics across these traces to show a breakdown of time spent 110b57cec5SDimitry Andric // at various points of the stack to provide insight into which functions 120b57cec5SDimitry Andric // spend the most time in terms of a call stack. We provide a few 130b57cec5SDimitry Andric // sorting/filtering options for zero'ing in on the useful stacks. 140b57cec5SDimitry Andric // 150b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 160b57cec5SDimitry Andric 170b57cec5SDimitry Andric #include <forward_list> 180b57cec5SDimitry Andric #include <numeric> 190b57cec5SDimitry Andric 200b57cec5SDimitry Andric #include "func-id-helper.h" 210b57cec5SDimitry Andric #include "trie-node.h" 220b57cec5SDimitry Andric #include "xray-registry.h" 230b57cec5SDimitry Andric #include "llvm/ADT/StringExtras.h" 240b57cec5SDimitry Andric #include "llvm/Support/CommandLine.h" 250b57cec5SDimitry Andric #include "llvm/Support/Errc.h" 260b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h" 270b57cec5SDimitry Andric #include "llvm/Support/FormatAdapters.h" 280b57cec5SDimitry Andric #include "llvm/Support/FormatVariadic.h" 290b57cec5SDimitry Andric #include "llvm/XRay/Graph.h" 300b57cec5SDimitry Andric #include "llvm/XRay/InstrumentationMap.h" 310b57cec5SDimitry Andric #include "llvm/XRay/Trace.h" 320b57cec5SDimitry Andric 330b57cec5SDimitry Andric using namespace llvm; 340b57cec5SDimitry Andric using namespace llvm::xray; 350b57cec5SDimitry Andric 360b57cec5SDimitry Andric static cl::SubCommand Stack("stack", "Call stack accounting"); 370b57cec5SDimitry Andric static cl::list<std::string> StackInputs(cl::Positional, 380b57cec5SDimitry Andric cl::desc("<xray trace>"), cl::Required, 390b57cec5SDimitry Andric cl::sub(Stack), cl::OneOrMore); 400b57cec5SDimitry Andric 410b57cec5SDimitry Andric static cl::opt<bool> 420b57cec5SDimitry Andric StackKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), 430b57cec5SDimitry Andric cl::sub(Stack), cl::init(false)); 440b57cec5SDimitry Andric static cl::alias StackKeepGoing2("k", cl::aliasopt(StackKeepGoing), 45480093f4SDimitry Andric cl::desc("Alias for -keep-going")); 460b57cec5SDimitry Andric 470b57cec5SDimitry Andric // TODO: Does there need to be an option to deduce tail or sibling calls? 480b57cec5SDimitry Andric 490b57cec5SDimitry Andric static cl::opt<std::string> StacksInstrMap( 500b57cec5SDimitry Andric "instr_map", 510b57cec5SDimitry Andric cl::desc("instrumentation map used to identify function ids. " 520b57cec5SDimitry Andric "Currently supports elf file instrumentation maps."), 530b57cec5SDimitry Andric cl::sub(Stack), cl::init("")); 540b57cec5SDimitry Andric static cl::alias StacksInstrMap2("m", cl::aliasopt(StacksInstrMap), 55480093f4SDimitry Andric cl::desc("Alias for -instr_map")); 560b57cec5SDimitry Andric 570b57cec5SDimitry Andric static cl::opt<bool> 580b57cec5SDimitry Andric SeparateThreadStacks("per-thread-stacks", 590b57cec5SDimitry Andric cl::desc("Report top stacks within each thread id"), 600b57cec5SDimitry Andric cl::sub(Stack), cl::init(false)); 610b57cec5SDimitry Andric 620b57cec5SDimitry Andric static cl::opt<bool> 630b57cec5SDimitry Andric AggregateThreads("aggregate-threads", 640b57cec5SDimitry Andric cl::desc("Aggregate stack times across threads"), 650b57cec5SDimitry Andric cl::sub(Stack), cl::init(false)); 660b57cec5SDimitry Andric 670b57cec5SDimitry Andric static cl::opt<bool> 680b57cec5SDimitry Andric DumpAllStacks("all-stacks", 690b57cec5SDimitry Andric cl::desc("Dump sum of timings for all stacks. " 700b57cec5SDimitry Andric "By default separates stacks per-thread."), 710b57cec5SDimitry Andric cl::sub(Stack), cl::init(false)); 720b57cec5SDimitry Andric static cl::alias DumpAllStacksShort("all", cl::aliasopt(DumpAllStacks), 73480093f4SDimitry Andric cl::desc("Alias for -all-stacks")); 740b57cec5SDimitry Andric 750b57cec5SDimitry Andric // TODO(kpw): Add other interesting formats. Perhaps chrome trace viewer format 760b57cec5SDimitry Andric // possibly with aggregations or just a linear trace of timings. 770b57cec5SDimitry Andric enum StackOutputFormat { HUMAN, FLAMETOOL }; 780b57cec5SDimitry Andric 790b57cec5SDimitry Andric static cl::opt<StackOutputFormat> StacksOutputFormat( 800b57cec5SDimitry Andric "stack-format", 810b57cec5SDimitry Andric cl::desc("The format that output stacks should be " 820b57cec5SDimitry Andric "output in. Only applies with all-stacks."), 830b57cec5SDimitry Andric cl::values( 840b57cec5SDimitry Andric clEnumValN(HUMAN, "human", 850b57cec5SDimitry Andric "Human readable output. Only valid without -all-stacks."), 860b57cec5SDimitry Andric clEnumValN(FLAMETOOL, "flame", 870b57cec5SDimitry Andric "Format consumable by Brendan Gregg's FlameGraph tool. " 880b57cec5SDimitry Andric "Only valid with -all-stacks.")), 890b57cec5SDimitry Andric cl::sub(Stack), cl::init(HUMAN)); 900b57cec5SDimitry Andric 910b57cec5SDimitry Andric // Types of values for each stack in a CallTrie. 920b57cec5SDimitry Andric enum class AggregationType { 930b57cec5SDimitry Andric TOTAL_TIME, // The total time spent in a stack and its callees. 940b57cec5SDimitry Andric INVOCATION_COUNT // The number of times the stack was invoked. 950b57cec5SDimitry Andric }; 960b57cec5SDimitry Andric 970b57cec5SDimitry Andric static cl::opt<AggregationType> RequestedAggregation( 980b57cec5SDimitry Andric "aggregation-type", 990b57cec5SDimitry Andric cl::desc("The type of aggregation to do on call stacks."), 1000b57cec5SDimitry Andric cl::values( 1010b57cec5SDimitry Andric clEnumValN( 1020b57cec5SDimitry Andric AggregationType::TOTAL_TIME, "time", 1030b57cec5SDimitry Andric "Capture the total time spent in an all invocations of a stack."), 1040b57cec5SDimitry Andric clEnumValN(AggregationType::INVOCATION_COUNT, "count", 1050b57cec5SDimitry Andric "Capture the number of times a stack was invoked. " 1060b57cec5SDimitry Andric "In flamegraph mode, this count also includes invocations " 1070b57cec5SDimitry Andric "of all callees.")), 1080b57cec5SDimitry Andric cl::sub(Stack), cl::init(AggregationType::TOTAL_TIME)); 1090b57cec5SDimitry Andric 1100b57cec5SDimitry Andric /// A helper struct to work with formatv and XRayRecords. Makes it easier to 1110b57cec5SDimitry Andric /// use instrumentation map names or addresses in formatted output. 1120b57cec5SDimitry Andric struct format_xray_record : public FormatAdapter<XRayRecord> { 1130b57cec5SDimitry Andric explicit format_xray_record(XRayRecord record, 1140b57cec5SDimitry Andric const FuncIdConversionHelper &conv) 1150b57cec5SDimitry Andric : FormatAdapter<XRayRecord>(std::move(record)), Converter(&conv) {} 1160b57cec5SDimitry Andric void format(raw_ostream &Stream, StringRef Style) override { 1170b57cec5SDimitry Andric Stream << formatv( 1180b57cec5SDimitry Andric "{FuncId: \"{0}\", ThreadId: \"{1}\", RecordType: \"{2}\"}", 1190b57cec5SDimitry Andric Converter->SymbolOrNumber(Item.FuncId), Item.TId, 1200b57cec5SDimitry Andric DecodeRecordType(Item.RecordType)); 1210b57cec5SDimitry Andric } 1220b57cec5SDimitry Andric 1230b57cec5SDimitry Andric private: 1240b57cec5SDimitry Andric Twine DecodeRecordType(uint16_t recordType) { 1250b57cec5SDimitry Andric switch (recordType) { 1260b57cec5SDimitry Andric case 0: 1270b57cec5SDimitry Andric return Twine("Fn Entry"); 1280b57cec5SDimitry Andric case 1: 1290b57cec5SDimitry Andric return Twine("Fn Exit"); 1300b57cec5SDimitry Andric default: 1310b57cec5SDimitry Andric // TODO: Add Tail exit when it is added to llvm/XRay/XRayRecord.h 1320b57cec5SDimitry Andric return Twine("Unknown"); 1330b57cec5SDimitry Andric } 1340b57cec5SDimitry Andric } 1350b57cec5SDimitry Andric 1360b57cec5SDimitry Andric const FuncIdConversionHelper *Converter; 1370b57cec5SDimitry Andric }; 1380b57cec5SDimitry Andric 1390b57cec5SDimitry Andric /// The stack command will take a set of XRay traces as arguments, and collects 1400b57cec5SDimitry Andric /// information about the stacks of instrumented functions that appear in the 1410b57cec5SDimitry Andric /// traces. We track the following pieces of information: 1420b57cec5SDimitry Andric /// 1430b57cec5SDimitry Andric /// - Total time: amount of time/cycles accounted for in the traces. 1440b57cec5SDimitry Andric /// - Stack count: number of times a specific stack appears in the 1450b57cec5SDimitry Andric /// traces. Only instrumented functions show up in stacks. 1460b57cec5SDimitry Andric /// - Cumulative stack time: amount of time spent in a stack accumulated 1470b57cec5SDimitry Andric /// across the invocations in the traces. 1480b57cec5SDimitry Andric /// - Cumulative local time: amount of time spent in each instrumented 1490b57cec5SDimitry Andric /// function showing up in a specific stack, accumulated across the traces. 1500b57cec5SDimitry Andric /// 1510b57cec5SDimitry Andric /// Example output for the kind of data we'd like to provide looks like the 1520b57cec5SDimitry Andric /// following: 1530b57cec5SDimitry Andric /// 1540b57cec5SDimitry Andric /// Total time: 3.33234 s 1550b57cec5SDimitry Andric /// Stack ID: ... 1560b57cec5SDimitry Andric /// Stack Count: 2093 1570b57cec5SDimitry Andric /// # Function Local Time (%) Stack Time (%) 1580b57cec5SDimitry Andric /// 0 main 2.34 ms 0.07% 3.33234 s 100% 1590b57cec5SDimitry Andric /// 1 foo() 3.30000 s 99.02% 3.33 s 99.92% 1600b57cec5SDimitry Andric /// 2 bar() 30 ms 0.90% 30 ms 0.90% 1610b57cec5SDimitry Andric /// 1620b57cec5SDimitry Andric /// We can also show distributions of the function call durations with 1630b57cec5SDimitry Andric /// statistics at each level of the stack. This works by doing the following 1640b57cec5SDimitry Andric /// algorithm: 1650b57cec5SDimitry Andric /// 1660b57cec5SDimitry Andric /// 1. When unwinding, record the duration of each unwound function associated 1670b57cec5SDimitry Andric /// with the path up to which the unwinding stops. For example: 1680b57cec5SDimitry Andric /// 1690b57cec5SDimitry Andric /// Step Duration (? means has start time) 1700b57cec5SDimitry Andric /// 1710b57cec5SDimitry Andric /// push a <start time> a = ? 1720b57cec5SDimitry Andric /// push b <start time> a = ?, a->b = ? 1730b57cec5SDimitry Andric /// push c <start time> a = ?, a->b = ?, a->b->c = ? 1740b57cec5SDimitry Andric /// pop c <end time> a = ?, a->b = ?, emit duration(a->b->c) 1750b57cec5SDimitry Andric /// pop b <end time> a = ?, emit duration(a->b) 1760b57cec5SDimitry Andric /// push c <start time> a = ?, a->c = ? 1770b57cec5SDimitry Andric /// pop c <end time> a = ?, emit duration(a->c) 1780b57cec5SDimitry Andric /// pop a <end time> emit duration(a) 1790b57cec5SDimitry Andric /// 1800b57cec5SDimitry Andric /// 2. We then account for the various stacks we've collected, and for each of 1810b57cec5SDimitry Andric /// them will have measurements that look like the following (continuing 1820b57cec5SDimitry Andric /// with the above simple example): 1830b57cec5SDimitry Andric /// 1840b57cec5SDimitry Andric /// c : [<id("a->b->c"), [durations]>, <id("a->c"), [durations]>] 1850b57cec5SDimitry Andric /// b : [<id("a->b"), [durations]>] 1860b57cec5SDimitry Andric /// a : [<id("a"), [durations]>] 1870b57cec5SDimitry Andric /// 1880b57cec5SDimitry Andric /// This allows us to compute, for each stack id, and each function that 1890b57cec5SDimitry Andric /// shows up in the stack, some important statistics like: 1900b57cec5SDimitry Andric /// 1910b57cec5SDimitry Andric /// - median 1920b57cec5SDimitry Andric /// - 99th percentile 1930b57cec5SDimitry Andric /// - mean + stddev 1940b57cec5SDimitry Andric /// - count 1950b57cec5SDimitry Andric /// 1960b57cec5SDimitry Andric /// 3. For cases where we don't have durations for some of the higher levels 1970b57cec5SDimitry Andric /// of the stack (perhaps instrumentation wasn't activated when the stack was 1980b57cec5SDimitry Andric /// entered), we can mark them appropriately. 1990b57cec5SDimitry Andric /// 2000b57cec5SDimitry Andric /// Computing this data also allows us to implement lookup by call stack nodes, 2010b57cec5SDimitry Andric /// so that we can find functions that show up in multiple stack traces and 2020b57cec5SDimitry Andric /// show the statistical properties of that function in various contexts. We 2030b57cec5SDimitry Andric /// can compute information similar to the following: 2040b57cec5SDimitry Andric /// 2050b57cec5SDimitry Andric /// Function: 'c' 2060b57cec5SDimitry Andric /// Stacks: 2 / 2 2070b57cec5SDimitry Andric /// Stack ID: ... 2080b57cec5SDimitry Andric /// Stack Count: ... 2090b57cec5SDimitry Andric /// # Function ... 2100b57cec5SDimitry Andric /// 0 a ... 2110b57cec5SDimitry Andric /// 1 b ... 2120b57cec5SDimitry Andric /// 2 c ... 2130b57cec5SDimitry Andric /// 2140b57cec5SDimitry Andric /// Stack ID: ... 2150b57cec5SDimitry Andric /// Stack Count: ... 2160b57cec5SDimitry Andric /// # Function ... 2170b57cec5SDimitry Andric /// 0 a ... 2180b57cec5SDimitry Andric /// 1 c ... 2190b57cec5SDimitry Andric /// ----------------... 2200b57cec5SDimitry Andric /// 2210b57cec5SDimitry Andric /// Function: 'b' 2220b57cec5SDimitry Andric /// Stacks: 1 / 2 2230b57cec5SDimitry Andric /// Stack ID: ... 2240b57cec5SDimitry Andric /// Stack Count: ... 2250b57cec5SDimitry Andric /// # Function ... 2260b57cec5SDimitry Andric /// 0 a ... 2270b57cec5SDimitry Andric /// 1 b ... 2280b57cec5SDimitry Andric /// 2 c ... 2290b57cec5SDimitry Andric /// 2300b57cec5SDimitry Andric /// 2310b57cec5SDimitry Andric /// To do this we require a Trie data structure that will allow us to represent 2320b57cec5SDimitry Andric /// all the call stacks of instrumented functions in an easily traversible 2330b57cec5SDimitry Andric /// manner when we do the aggregations and lookups. For instrumented call 2340b57cec5SDimitry Andric /// sequences like the following: 2350b57cec5SDimitry Andric /// 2360b57cec5SDimitry Andric /// a() 2370b57cec5SDimitry Andric /// b() 2380b57cec5SDimitry Andric /// c() 2390b57cec5SDimitry Andric /// d() 2400b57cec5SDimitry Andric /// c() 2410b57cec5SDimitry Andric /// 2420b57cec5SDimitry Andric /// We will have a representation like so: 2430b57cec5SDimitry Andric /// 2440b57cec5SDimitry Andric /// a -> b -> c 2450b57cec5SDimitry Andric /// | | 2460b57cec5SDimitry Andric /// | +--> d 2470b57cec5SDimitry Andric /// | 2480b57cec5SDimitry Andric /// +--> c 2490b57cec5SDimitry Andric /// 2500b57cec5SDimitry Andric /// We maintain a sequence of durations on the leaves and in the internal nodes 2510b57cec5SDimitry Andric /// as we go through and process every record from the XRay trace. We also 2520b57cec5SDimitry Andric /// maintain an index of unique functions, and provide a means of iterating 2530b57cec5SDimitry Andric /// through all the instrumented call stacks which we know about. 2540b57cec5SDimitry Andric 255*e8d8bef9SDimitry Andric namespace { 2560b57cec5SDimitry Andric struct StackDuration { 2570b57cec5SDimitry Andric llvm::SmallVector<int64_t, 4> TerminalDurations; 2580b57cec5SDimitry Andric llvm::SmallVector<int64_t, 4> IntermediateDurations; 2590b57cec5SDimitry Andric }; 260*e8d8bef9SDimitry Andric } // namespace 2610b57cec5SDimitry Andric 262*e8d8bef9SDimitry Andric static StackDuration mergeStackDuration(const StackDuration &Left, 2630b57cec5SDimitry Andric const StackDuration &Right) { 2640b57cec5SDimitry Andric StackDuration Data{}; 2650b57cec5SDimitry Andric Data.TerminalDurations.reserve(Left.TerminalDurations.size() + 2660b57cec5SDimitry Andric Right.TerminalDurations.size()); 2670b57cec5SDimitry Andric Data.IntermediateDurations.reserve(Left.IntermediateDurations.size() + 2680b57cec5SDimitry Andric Right.IntermediateDurations.size()); 2690b57cec5SDimitry Andric // Aggregate the durations. 2700b57cec5SDimitry Andric for (auto duration : Left.TerminalDurations) 2710b57cec5SDimitry Andric Data.TerminalDurations.push_back(duration); 2720b57cec5SDimitry Andric for (auto duration : Right.TerminalDurations) 2730b57cec5SDimitry Andric Data.TerminalDurations.push_back(duration); 2740b57cec5SDimitry Andric 2750b57cec5SDimitry Andric for (auto duration : Left.IntermediateDurations) 2760b57cec5SDimitry Andric Data.IntermediateDurations.push_back(duration); 2770b57cec5SDimitry Andric for (auto duration : Right.IntermediateDurations) 2780b57cec5SDimitry Andric Data.IntermediateDurations.push_back(duration); 2790b57cec5SDimitry Andric return Data; 2800b57cec5SDimitry Andric } 2810b57cec5SDimitry Andric 2820b57cec5SDimitry Andric using StackTrieNode = TrieNode<StackDuration>; 2830b57cec5SDimitry Andric 2840b57cec5SDimitry Andric template <AggregationType AggType> 285*e8d8bef9SDimitry Andric static std::size_t GetValueForStack(const StackTrieNode *Node); 2860b57cec5SDimitry Andric 2870b57cec5SDimitry Andric // When computing total time spent in a stack, we're adding the timings from 2880b57cec5SDimitry Andric // its callees and the timings from when it was a leaf. 2890b57cec5SDimitry Andric template <> 2900b57cec5SDimitry Andric std::size_t 2910b57cec5SDimitry Andric GetValueForStack<AggregationType::TOTAL_TIME>(const StackTrieNode *Node) { 2920b57cec5SDimitry Andric auto TopSum = std::accumulate(Node->ExtraData.TerminalDurations.begin(), 2930b57cec5SDimitry Andric Node->ExtraData.TerminalDurations.end(), 0uLL); 2940b57cec5SDimitry Andric return std::accumulate(Node->ExtraData.IntermediateDurations.begin(), 2950b57cec5SDimitry Andric Node->ExtraData.IntermediateDurations.end(), TopSum); 2960b57cec5SDimitry Andric } 2970b57cec5SDimitry Andric 2980b57cec5SDimitry Andric // Calculates how many times a function was invoked. 2990b57cec5SDimitry Andric // TODO: Hook up option to produce stacks 3000b57cec5SDimitry Andric template <> 3010b57cec5SDimitry Andric std::size_t 3020b57cec5SDimitry Andric GetValueForStack<AggregationType::INVOCATION_COUNT>(const StackTrieNode *Node) { 3030b57cec5SDimitry Andric return Node->ExtraData.TerminalDurations.size() + 3040b57cec5SDimitry Andric Node->ExtraData.IntermediateDurations.size(); 3050b57cec5SDimitry Andric } 3060b57cec5SDimitry Andric 3070b57cec5SDimitry Andric // Make sure there are implementations for each enum value. 3080b57cec5SDimitry Andric template <AggregationType T> struct DependentFalseType : std::false_type {}; 3090b57cec5SDimitry Andric 3100b57cec5SDimitry Andric template <AggregationType AggType> 3110b57cec5SDimitry Andric std::size_t GetValueForStack(const StackTrieNode *Node) { 3120b57cec5SDimitry Andric static_assert(DependentFalseType<AggType>::value, 3130b57cec5SDimitry Andric "No implementation found for aggregation type provided."); 3140b57cec5SDimitry Andric return 0; 3150b57cec5SDimitry Andric } 3160b57cec5SDimitry Andric 3170b57cec5SDimitry Andric class StackTrie { 3180b57cec5SDimitry Andric // Avoid the magic number of 4 propagated through the code with an alias. 3190b57cec5SDimitry Andric // We use this SmallVector to track the root nodes in a call graph. 3200b57cec5SDimitry Andric using RootVector = SmallVector<StackTrieNode *, 4>; 3210b57cec5SDimitry Andric 3220b57cec5SDimitry Andric // We maintain pointers to the roots of the tries we see. 3230b57cec5SDimitry Andric DenseMap<uint32_t, RootVector> Roots; 3240b57cec5SDimitry Andric 3250b57cec5SDimitry Andric // We make sure all the nodes are accounted for in this list. 3260b57cec5SDimitry Andric std::forward_list<StackTrieNode> NodeStore; 3270b57cec5SDimitry Andric 3280b57cec5SDimitry Andric // A map of thread ids to pairs call stack trie nodes and their start times. 3290b57cec5SDimitry Andric DenseMap<uint32_t, SmallVector<std::pair<StackTrieNode *, uint64_t>, 8>> 3300b57cec5SDimitry Andric ThreadStackMap; 3310b57cec5SDimitry Andric 3320b57cec5SDimitry Andric StackTrieNode *createTrieNode(uint32_t ThreadId, int32_t FuncId, 3330b57cec5SDimitry Andric StackTrieNode *Parent) { 3340b57cec5SDimitry Andric NodeStore.push_front(StackTrieNode{FuncId, Parent, {}, {{}, {}}}); 3350b57cec5SDimitry Andric auto I = NodeStore.begin(); 3360b57cec5SDimitry Andric auto *Node = &*I; 3370b57cec5SDimitry Andric if (!Parent) 3380b57cec5SDimitry Andric Roots[ThreadId].push_back(Node); 3390b57cec5SDimitry Andric return Node; 3400b57cec5SDimitry Andric } 3410b57cec5SDimitry Andric 3420b57cec5SDimitry Andric StackTrieNode *findRootNode(uint32_t ThreadId, int32_t FuncId) { 3430b57cec5SDimitry Andric const auto &RootsByThread = Roots[ThreadId]; 3440b57cec5SDimitry Andric auto I = find_if(RootsByThread, 3450b57cec5SDimitry Andric [&](StackTrieNode *N) { return N->FuncId == FuncId; }); 3460b57cec5SDimitry Andric return (I == RootsByThread.end()) ? nullptr : *I; 3470b57cec5SDimitry Andric } 3480b57cec5SDimitry Andric 3490b57cec5SDimitry Andric public: 3500b57cec5SDimitry Andric enum class AccountRecordStatus { 3510b57cec5SDimitry Andric OK, // Successfully processed 3520b57cec5SDimitry Andric ENTRY_NOT_FOUND, // An exit record had no matching call stack entry 3530b57cec5SDimitry Andric UNKNOWN_RECORD_TYPE 3540b57cec5SDimitry Andric }; 3550b57cec5SDimitry Andric 3560b57cec5SDimitry Andric struct AccountRecordState { 3570b57cec5SDimitry Andric // We keep track of whether the call stack is currently unwinding. 3580b57cec5SDimitry Andric bool wasLastRecordExit; 3590b57cec5SDimitry Andric 3600b57cec5SDimitry Andric static AccountRecordState CreateInitialState() { return {false}; } 3610b57cec5SDimitry Andric }; 3620b57cec5SDimitry Andric 3630b57cec5SDimitry Andric AccountRecordStatus accountRecord(const XRayRecord &R, 3640b57cec5SDimitry Andric AccountRecordState *state) { 3650b57cec5SDimitry Andric auto &TS = ThreadStackMap[R.TId]; 3660b57cec5SDimitry Andric switch (R.Type) { 3670b57cec5SDimitry Andric case RecordTypes::CUSTOM_EVENT: 3680b57cec5SDimitry Andric case RecordTypes::TYPED_EVENT: 3690b57cec5SDimitry Andric return AccountRecordStatus::OK; 3700b57cec5SDimitry Andric case RecordTypes::ENTER: 3710b57cec5SDimitry Andric case RecordTypes::ENTER_ARG: { 3720b57cec5SDimitry Andric state->wasLastRecordExit = false; 3730b57cec5SDimitry Andric // When we encounter a new function entry, we want to record the TSC for 3740b57cec5SDimitry Andric // that entry, and the function id. Before doing so we check the top of 3750b57cec5SDimitry Andric // the stack to see if there are callees that already represent this 3760b57cec5SDimitry Andric // function. 3770b57cec5SDimitry Andric if (TS.empty()) { 3780b57cec5SDimitry Andric auto *Root = findRootNode(R.TId, R.FuncId); 3790b57cec5SDimitry Andric TS.emplace_back(Root ? Root : createTrieNode(R.TId, R.FuncId, nullptr), 3800b57cec5SDimitry Andric R.TSC); 3810b57cec5SDimitry Andric return AccountRecordStatus::OK; 3820b57cec5SDimitry Andric } 3830b57cec5SDimitry Andric 3840b57cec5SDimitry Andric auto &Top = TS.back(); 3850b57cec5SDimitry Andric auto I = find_if(Top.first->Callees, 3860b57cec5SDimitry Andric [&](StackTrieNode *N) { return N->FuncId == R.FuncId; }); 3870b57cec5SDimitry Andric if (I == Top.first->Callees.end()) { 3880b57cec5SDimitry Andric // We didn't find the callee in the stack trie, so we're going to 3890b57cec5SDimitry Andric // add to the stack then set up the pointers properly. 3900b57cec5SDimitry Andric auto N = createTrieNode(R.TId, R.FuncId, Top.first); 3910b57cec5SDimitry Andric Top.first->Callees.emplace_back(N); 3920b57cec5SDimitry Andric 3930b57cec5SDimitry Andric // Top may be invalidated after this statement. 3940b57cec5SDimitry Andric TS.emplace_back(N, R.TSC); 3950b57cec5SDimitry Andric } else { 3960b57cec5SDimitry Andric // We found the callee in the stack trie, so we'll use that pointer 3970b57cec5SDimitry Andric // instead, add it to the stack associated with the TSC. 3980b57cec5SDimitry Andric TS.emplace_back(*I, R.TSC); 3990b57cec5SDimitry Andric } 4000b57cec5SDimitry Andric return AccountRecordStatus::OK; 4010b57cec5SDimitry Andric } 4020b57cec5SDimitry Andric case RecordTypes::EXIT: 4030b57cec5SDimitry Andric case RecordTypes::TAIL_EXIT: { 4040b57cec5SDimitry Andric bool wasLastRecordExit = state->wasLastRecordExit; 4050b57cec5SDimitry Andric state->wasLastRecordExit = true; 4060b57cec5SDimitry Andric // The exit case is more interesting, since we want to be able to deduce 4070b57cec5SDimitry Andric // missing exit records. To do that properly, we need to look up the stack 4080b57cec5SDimitry Andric // and see whether the exit record matches any of the entry records. If it 4090b57cec5SDimitry Andric // does match, we attempt to record the durations as we pop the stack to 4100b57cec5SDimitry Andric // where we see the parent. 4110b57cec5SDimitry Andric if (TS.empty()) { 4120b57cec5SDimitry Andric // Short circuit, and say we can't find it. 4130b57cec5SDimitry Andric 4140b57cec5SDimitry Andric return AccountRecordStatus::ENTRY_NOT_FOUND; 4150b57cec5SDimitry Andric } 4160b57cec5SDimitry Andric 4170b57cec5SDimitry Andric auto FunctionEntryMatch = find_if( 4180b57cec5SDimitry Andric reverse(TS), [&](const std::pair<StackTrieNode *, uint64_t> &E) { 4190b57cec5SDimitry Andric return E.first->FuncId == R.FuncId; 4200b57cec5SDimitry Andric }); 4210b57cec5SDimitry Andric auto status = AccountRecordStatus::OK; 4220b57cec5SDimitry Andric if (FunctionEntryMatch == TS.rend()) { 4230b57cec5SDimitry Andric status = AccountRecordStatus::ENTRY_NOT_FOUND; 4240b57cec5SDimitry Andric } else { 4250b57cec5SDimitry Andric // Account for offset of 1 between reverse and forward iterators. We 4260b57cec5SDimitry Andric // want the forward iterator to include the function that is exited. 4270b57cec5SDimitry Andric ++FunctionEntryMatch; 4280b57cec5SDimitry Andric } 4290b57cec5SDimitry Andric auto I = FunctionEntryMatch.base(); 4300b57cec5SDimitry Andric for (auto &E : make_range(I, TS.end() - 1)) 4310b57cec5SDimitry Andric E.first->ExtraData.IntermediateDurations.push_back( 4320b57cec5SDimitry Andric std::max(E.second, R.TSC) - std::min(E.second, R.TSC)); 4330b57cec5SDimitry Andric auto &Deepest = TS.back(); 4340b57cec5SDimitry Andric if (wasLastRecordExit) 4350b57cec5SDimitry Andric Deepest.first->ExtraData.IntermediateDurations.push_back( 4360b57cec5SDimitry Andric std::max(Deepest.second, R.TSC) - std::min(Deepest.second, R.TSC)); 4370b57cec5SDimitry Andric else 4380b57cec5SDimitry Andric Deepest.first->ExtraData.TerminalDurations.push_back( 4390b57cec5SDimitry Andric std::max(Deepest.second, R.TSC) - std::min(Deepest.second, R.TSC)); 4400b57cec5SDimitry Andric TS.erase(I, TS.end()); 4410b57cec5SDimitry Andric return status; 4420b57cec5SDimitry Andric } 4430b57cec5SDimitry Andric } 4440b57cec5SDimitry Andric return AccountRecordStatus::UNKNOWN_RECORD_TYPE; 4450b57cec5SDimitry Andric } 4460b57cec5SDimitry Andric 4470b57cec5SDimitry Andric bool isEmpty() const { return Roots.empty(); } 4480b57cec5SDimitry Andric 4490b57cec5SDimitry Andric void printStack(raw_ostream &OS, const StackTrieNode *Top, 4500b57cec5SDimitry Andric FuncIdConversionHelper &FN) { 4510b57cec5SDimitry Andric // Traverse the pointers up to the parent, noting the sums, then print 4520b57cec5SDimitry Andric // in reverse order (callers at top, callees down bottom). 4530b57cec5SDimitry Andric SmallVector<const StackTrieNode *, 8> CurrentStack; 4540b57cec5SDimitry Andric for (auto *F = Top; F != nullptr; F = F->Parent) 4550b57cec5SDimitry Andric CurrentStack.push_back(F); 4560b57cec5SDimitry Andric int Level = 0; 4570b57cec5SDimitry Andric OS << formatv("{0,-5} {1,-60} {2,+12} {3,+16}\n", "lvl", "function", 4580b57cec5SDimitry Andric "count", "sum"); 459*e8d8bef9SDimitry Andric for (auto *F : reverse(drop_begin(CurrentStack))) { 4600b57cec5SDimitry Andric auto Sum = std::accumulate(F->ExtraData.IntermediateDurations.begin(), 4610b57cec5SDimitry Andric F->ExtraData.IntermediateDurations.end(), 0LL); 4620b57cec5SDimitry Andric auto FuncId = FN.SymbolOrNumber(F->FuncId); 4630b57cec5SDimitry Andric OS << formatv("#{0,-4} {1,-60} {2,+12} {3,+16}\n", Level++, 4640b57cec5SDimitry Andric FuncId.size() > 60 ? FuncId.substr(0, 57) + "..." : FuncId, 4650b57cec5SDimitry Andric F->ExtraData.IntermediateDurations.size(), Sum); 4660b57cec5SDimitry Andric } 4670b57cec5SDimitry Andric auto *Leaf = *CurrentStack.begin(); 4680b57cec5SDimitry Andric auto LeafSum = 4690b57cec5SDimitry Andric std::accumulate(Leaf->ExtraData.TerminalDurations.begin(), 4700b57cec5SDimitry Andric Leaf->ExtraData.TerminalDurations.end(), 0LL); 4710b57cec5SDimitry Andric auto LeafFuncId = FN.SymbolOrNumber(Leaf->FuncId); 4720b57cec5SDimitry Andric OS << formatv("#{0,-4} {1,-60} {2,+12} {3,+16}\n", Level++, 4730b57cec5SDimitry Andric LeafFuncId.size() > 60 ? LeafFuncId.substr(0, 57) + "..." 4740b57cec5SDimitry Andric : LeafFuncId, 4750b57cec5SDimitry Andric Leaf->ExtraData.TerminalDurations.size(), LeafSum); 4760b57cec5SDimitry Andric OS << "\n"; 4770b57cec5SDimitry Andric } 4780b57cec5SDimitry Andric 4790b57cec5SDimitry Andric /// Prints top stacks for each thread. 4800b57cec5SDimitry Andric void printPerThread(raw_ostream &OS, FuncIdConversionHelper &FN) { 4810b57cec5SDimitry Andric for (auto iter : Roots) { 4820b57cec5SDimitry Andric OS << "Thread " << iter.first << ":\n"; 4830b57cec5SDimitry Andric print(OS, FN, iter.second); 4840b57cec5SDimitry Andric OS << "\n"; 4850b57cec5SDimitry Andric } 4860b57cec5SDimitry Andric } 4870b57cec5SDimitry Andric 4880b57cec5SDimitry Andric /// Prints timing sums for each stack in each threads. 4890b57cec5SDimitry Andric template <AggregationType AggType> 4900b57cec5SDimitry Andric void printAllPerThread(raw_ostream &OS, FuncIdConversionHelper &FN, 4910b57cec5SDimitry Andric StackOutputFormat format) { 4920b57cec5SDimitry Andric for (auto iter : Roots) { 4930b57cec5SDimitry Andric uint32_t threadId = iter.first; 4940b57cec5SDimitry Andric RootVector &perThreadRoots = iter.second; 4950b57cec5SDimitry Andric bool reportThreadId = true; 4960b57cec5SDimitry Andric printAll<AggType>(OS, FN, perThreadRoots, threadId, reportThreadId); 4970b57cec5SDimitry Andric } 4980b57cec5SDimitry Andric } 4990b57cec5SDimitry Andric 5000b57cec5SDimitry Andric /// Prints top stacks from looking at all the leaves and ignoring thread IDs. 5010b57cec5SDimitry Andric /// Stacks that consist of the same function IDs but were called in different 5020b57cec5SDimitry Andric /// thread IDs are not considered unique in this printout. 5030b57cec5SDimitry Andric void printIgnoringThreads(raw_ostream &OS, FuncIdConversionHelper &FN) { 5040b57cec5SDimitry Andric RootVector RootValues; 5050b57cec5SDimitry Andric 5060b57cec5SDimitry Andric // Function to pull the values out of a map iterator. 5070b57cec5SDimitry Andric using RootsType = decltype(Roots.begin())::value_type; 5080b57cec5SDimitry Andric auto MapValueFn = [](const RootsType &Value) { return Value.second; }; 5090b57cec5SDimitry Andric 5100b57cec5SDimitry Andric for (const auto &RootNodeRange : 5110b57cec5SDimitry Andric make_range(map_iterator(Roots.begin(), MapValueFn), 5120b57cec5SDimitry Andric map_iterator(Roots.end(), MapValueFn))) { 5130b57cec5SDimitry Andric for (auto *RootNode : RootNodeRange) 5140b57cec5SDimitry Andric RootValues.push_back(RootNode); 5150b57cec5SDimitry Andric } 5160b57cec5SDimitry Andric 5170b57cec5SDimitry Andric print(OS, FN, RootValues); 5180b57cec5SDimitry Andric } 5190b57cec5SDimitry Andric 5200b57cec5SDimitry Andric /// Creates a merged list of Tries for unique stacks that disregards their 5210b57cec5SDimitry Andric /// thread IDs. 5220b57cec5SDimitry Andric RootVector mergeAcrossThreads(std::forward_list<StackTrieNode> &NodeStore) { 5230b57cec5SDimitry Andric RootVector MergedByThreadRoots; 5240b57cec5SDimitry Andric for (auto MapIter : Roots) { 5250b57cec5SDimitry Andric const auto &RootNodeVector = MapIter.second; 5260b57cec5SDimitry Andric for (auto *Node : RootNodeVector) { 5270b57cec5SDimitry Andric auto MaybeFoundIter = 5280b57cec5SDimitry Andric find_if(MergedByThreadRoots, [Node](StackTrieNode *elem) { 5290b57cec5SDimitry Andric return Node->FuncId == elem->FuncId; 5300b57cec5SDimitry Andric }); 5310b57cec5SDimitry Andric if (MaybeFoundIter == MergedByThreadRoots.end()) { 5320b57cec5SDimitry Andric MergedByThreadRoots.push_back(Node); 5330b57cec5SDimitry Andric } else { 5340b57cec5SDimitry Andric MergedByThreadRoots.push_back(mergeTrieNodes( 5350b57cec5SDimitry Andric **MaybeFoundIter, *Node, nullptr, NodeStore, mergeStackDuration)); 5360b57cec5SDimitry Andric MergedByThreadRoots.erase(MaybeFoundIter); 5370b57cec5SDimitry Andric } 5380b57cec5SDimitry Andric } 5390b57cec5SDimitry Andric } 5400b57cec5SDimitry Andric return MergedByThreadRoots; 5410b57cec5SDimitry Andric } 5420b57cec5SDimitry Andric 5430b57cec5SDimitry Andric /// Print timing sums for all stacks merged by Thread ID. 5440b57cec5SDimitry Andric template <AggregationType AggType> 5450b57cec5SDimitry Andric void printAllAggregatingThreads(raw_ostream &OS, FuncIdConversionHelper &FN, 5460b57cec5SDimitry Andric StackOutputFormat format) { 5470b57cec5SDimitry Andric std::forward_list<StackTrieNode> AggregatedNodeStore; 5480b57cec5SDimitry Andric RootVector MergedByThreadRoots = mergeAcrossThreads(AggregatedNodeStore); 5490b57cec5SDimitry Andric bool reportThreadId = false; 5500b57cec5SDimitry Andric printAll<AggType>(OS, FN, MergedByThreadRoots, 5510b57cec5SDimitry Andric /*threadId*/ 0, reportThreadId); 5520b57cec5SDimitry Andric } 5530b57cec5SDimitry Andric 5540b57cec5SDimitry Andric /// Merges the trie by thread id before printing top stacks. 5550b57cec5SDimitry Andric void printAggregatingThreads(raw_ostream &OS, FuncIdConversionHelper &FN) { 5560b57cec5SDimitry Andric std::forward_list<StackTrieNode> AggregatedNodeStore; 5570b57cec5SDimitry Andric RootVector MergedByThreadRoots = mergeAcrossThreads(AggregatedNodeStore); 5580b57cec5SDimitry Andric print(OS, FN, MergedByThreadRoots); 5590b57cec5SDimitry Andric } 5600b57cec5SDimitry Andric 5610b57cec5SDimitry Andric // TODO: Add a format option when more than one are supported. 5620b57cec5SDimitry Andric template <AggregationType AggType> 5630b57cec5SDimitry Andric void printAll(raw_ostream &OS, FuncIdConversionHelper &FN, 5640b57cec5SDimitry Andric RootVector RootValues, uint32_t ThreadId, bool ReportThread) { 5650b57cec5SDimitry Andric SmallVector<const StackTrieNode *, 16> S; 5660b57cec5SDimitry Andric for (const auto *N : RootValues) { 5670b57cec5SDimitry Andric S.clear(); 5680b57cec5SDimitry Andric S.push_back(N); 5690b57cec5SDimitry Andric while (!S.empty()) { 5700b57cec5SDimitry Andric auto *Top = S.pop_back_val(); 5710b57cec5SDimitry Andric printSingleStack<AggType>(OS, FN, ReportThread, ThreadId, Top); 5720b57cec5SDimitry Andric for (const auto *C : Top->Callees) 5730b57cec5SDimitry Andric S.push_back(C); 5740b57cec5SDimitry Andric } 5750b57cec5SDimitry Andric } 5760b57cec5SDimitry Andric } 5770b57cec5SDimitry Andric 5780b57cec5SDimitry Andric /// Prints values for stacks in a format consumable for the flamegraph.pl 5790b57cec5SDimitry Andric /// tool. This is a line based format that lists each level in the stack 5800b57cec5SDimitry Andric /// hierarchy in a semicolon delimited form followed by a space and a numeric 5810b57cec5SDimitry Andric /// value. If breaking down by thread, the thread ID will be added as the 5820b57cec5SDimitry Andric /// root level of the stack. 5830b57cec5SDimitry Andric template <AggregationType AggType> 5840b57cec5SDimitry Andric void printSingleStack(raw_ostream &OS, FuncIdConversionHelper &Converter, 5850b57cec5SDimitry Andric bool ReportThread, uint32_t ThreadId, 5860b57cec5SDimitry Andric const StackTrieNode *Node) { 5870b57cec5SDimitry Andric if (ReportThread) 5880b57cec5SDimitry Andric OS << "thread_" << ThreadId << ";"; 5890b57cec5SDimitry Andric SmallVector<const StackTrieNode *, 5> lineage{}; 5900b57cec5SDimitry Andric lineage.push_back(Node); 5910b57cec5SDimitry Andric while (lineage.back()->Parent != nullptr) 5920b57cec5SDimitry Andric lineage.push_back(lineage.back()->Parent); 5930b57cec5SDimitry Andric while (!lineage.empty()) { 5940b57cec5SDimitry Andric OS << Converter.SymbolOrNumber(lineage.back()->FuncId) << ";"; 5950b57cec5SDimitry Andric lineage.pop_back(); 5960b57cec5SDimitry Andric } 5970b57cec5SDimitry Andric OS << " " << GetValueForStack<AggType>(Node) << "\n"; 5980b57cec5SDimitry Andric } 5990b57cec5SDimitry Andric 6000b57cec5SDimitry Andric void print(raw_ostream &OS, FuncIdConversionHelper &FN, 6010b57cec5SDimitry Andric RootVector RootValues) { 6020b57cec5SDimitry Andric // Go through each of the roots, and traverse the call stack, producing the 6030b57cec5SDimitry Andric // aggregates as you go along. Remember these aggregates and stacks, and 6040b57cec5SDimitry Andric // show summary statistics about: 6050b57cec5SDimitry Andric // 6060b57cec5SDimitry Andric // - Total number of unique stacks 6070b57cec5SDimitry Andric // - Top 10 stacks by count 6080b57cec5SDimitry Andric // - Top 10 stacks by aggregate duration 6090b57cec5SDimitry Andric SmallVector<std::pair<const StackTrieNode *, uint64_t>, 11> 6100b57cec5SDimitry Andric TopStacksByCount; 6110b57cec5SDimitry Andric SmallVector<std::pair<const StackTrieNode *, uint64_t>, 11> TopStacksBySum; 6120b57cec5SDimitry Andric auto greater_second = 6130b57cec5SDimitry Andric [](const std::pair<const StackTrieNode *, uint64_t> &A, 6140b57cec5SDimitry Andric const std::pair<const StackTrieNode *, uint64_t> &B) { 6150b57cec5SDimitry Andric return A.second > B.second; 6160b57cec5SDimitry Andric }; 6170b57cec5SDimitry Andric uint64_t UniqueStacks = 0; 6180b57cec5SDimitry Andric for (const auto *N : RootValues) { 6190b57cec5SDimitry Andric SmallVector<const StackTrieNode *, 16> S; 6200b57cec5SDimitry Andric S.emplace_back(N); 6210b57cec5SDimitry Andric 6220b57cec5SDimitry Andric while (!S.empty()) { 6230b57cec5SDimitry Andric auto *Top = S.pop_back_val(); 6240b57cec5SDimitry Andric 6250b57cec5SDimitry Andric // We only start printing the stack (by walking up the parent pointers) 6260b57cec5SDimitry Andric // when we get to a leaf function. 6270b57cec5SDimitry Andric if (!Top->ExtraData.TerminalDurations.empty()) { 6280b57cec5SDimitry Andric ++UniqueStacks; 6290b57cec5SDimitry Andric auto TopSum = 6300b57cec5SDimitry Andric std::accumulate(Top->ExtraData.TerminalDurations.begin(), 6310b57cec5SDimitry Andric Top->ExtraData.TerminalDurations.end(), 0uLL); 6320b57cec5SDimitry Andric { 6330b57cec5SDimitry Andric auto E = std::make_pair(Top, TopSum); 6340b57cec5SDimitry Andric TopStacksBySum.insert( 6350b57cec5SDimitry Andric llvm::lower_bound(TopStacksBySum, E, greater_second), E); 6360b57cec5SDimitry Andric if (TopStacksBySum.size() == 11) 6370b57cec5SDimitry Andric TopStacksBySum.pop_back(); 6380b57cec5SDimitry Andric } 6390b57cec5SDimitry Andric { 6400b57cec5SDimitry Andric auto E = 6410b57cec5SDimitry Andric std::make_pair(Top, Top->ExtraData.TerminalDurations.size()); 642*e8d8bef9SDimitry Andric TopStacksByCount.insert( 643*e8d8bef9SDimitry Andric llvm::lower_bound(TopStacksByCount, E, greater_second), E); 6440b57cec5SDimitry Andric if (TopStacksByCount.size() == 11) 6450b57cec5SDimitry Andric TopStacksByCount.pop_back(); 6460b57cec5SDimitry Andric } 6470b57cec5SDimitry Andric } 6480b57cec5SDimitry Andric for (const auto *C : Top->Callees) 6490b57cec5SDimitry Andric S.push_back(C); 6500b57cec5SDimitry Andric } 6510b57cec5SDimitry Andric } 6520b57cec5SDimitry Andric 6530b57cec5SDimitry Andric // Now print the statistics in the end. 6540b57cec5SDimitry Andric OS << "\n"; 6550b57cec5SDimitry Andric OS << "Unique Stacks: " << UniqueStacks << "\n"; 6560b57cec5SDimitry Andric OS << "Top 10 Stacks by leaf sum:\n\n"; 6570b57cec5SDimitry Andric for (const auto &P : TopStacksBySum) { 6580b57cec5SDimitry Andric OS << "Sum: " << P.second << "\n"; 6590b57cec5SDimitry Andric printStack(OS, P.first, FN); 6600b57cec5SDimitry Andric } 6610b57cec5SDimitry Andric OS << "\n"; 6620b57cec5SDimitry Andric OS << "Top 10 Stacks by leaf count:\n\n"; 6630b57cec5SDimitry Andric for (const auto &P : TopStacksByCount) { 6640b57cec5SDimitry Andric OS << "Count: " << P.second << "\n"; 6650b57cec5SDimitry Andric printStack(OS, P.first, FN); 6660b57cec5SDimitry Andric } 6670b57cec5SDimitry Andric OS << "\n"; 6680b57cec5SDimitry Andric } 6690b57cec5SDimitry Andric }; 6700b57cec5SDimitry Andric 671*e8d8bef9SDimitry Andric static std::string CreateErrorMessage(StackTrie::AccountRecordStatus Error, 6720b57cec5SDimitry Andric const XRayRecord &Record, 6730b57cec5SDimitry Andric const FuncIdConversionHelper &Converter) { 6740b57cec5SDimitry Andric switch (Error) { 6750b57cec5SDimitry Andric case StackTrie::AccountRecordStatus::ENTRY_NOT_FOUND: 6765ffd83dbSDimitry Andric return std::string( 6775ffd83dbSDimitry Andric formatv("Found record {0} with no matching function entry\n", 6785ffd83dbSDimitry Andric format_xray_record(Record, Converter))); 6790b57cec5SDimitry Andric default: 6805ffd83dbSDimitry Andric return std::string(formatv("Unknown error type for record {0}\n", 6815ffd83dbSDimitry Andric format_xray_record(Record, Converter))); 6820b57cec5SDimitry Andric } 6830b57cec5SDimitry Andric } 6840b57cec5SDimitry Andric 6850b57cec5SDimitry Andric static CommandRegistration Unused(&Stack, []() -> Error { 6860b57cec5SDimitry Andric // Load each file provided as a command-line argument. For each one of them 6870b57cec5SDimitry Andric // account to a single StackTrie, and just print the whole trie for now. 6880b57cec5SDimitry Andric StackTrie ST; 6890b57cec5SDimitry Andric InstrumentationMap Map; 6900b57cec5SDimitry Andric if (!StacksInstrMap.empty()) { 6910b57cec5SDimitry Andric auto InstrumentationMapOrError = loadInstrumentationMap(StacksInstrMap); 6920b57cec5SDimitry Andric if (!InstrumentationMapOrError) 6930b57cec5SDimitry Andric return joinErrors( 6940b57cec5SDimitry Andric make_error<StringError>( 6950b57cec5SDimitry Andric Twine("Cannot open instrumentation map: ") + StacksInstrMap, 6960b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)), 6970b57cec5SDimitry Andric InstrumentationMapOrError.takeError()); 6980b57cec5SDimitry Andric Map = std::move(*InstrumentationMapOrError); 6990b57cec5SDimitry Andric } 7000b57cec5SDimitry Andric 7010b57cec5SDimitry Andric if (SeparateThreadStacks && AggregateThreads) 7020b57cec5SDimitry Andric return make_error<StringError>( 7030b57cec5SDimitry Andric Twine("Can't specify options for per thread reporting and reporting " 7040b57cec5SDimitry Andric "that aggregates threads."), 7050b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)); 7060b57cec5SDimitry Andric 7070b57cec5SDimitry Andric if (!DumpAllStacks && StacksOutputFormat != HUMAN) 7080b57cec5SDimitry Andric return make_error<StringError>( 7090b57cec5SDimitry Andric Twine("Can't specify a non-human format without -all-stacks."), 7100b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)); 7110b57cec5SDimitry Andric 7120b57cec5SDimitry Andric if (DumpAllStacks && StacksOutputFormat == HUMAN) 7130b57cec5SDimitry Andric return make_error<StringError>( 7140b57cec5SDimitry Andric Twine("You must specify a non-human format when reporting with " 7150b57cec5SDimitry Andric "-all-stacks."), 7160b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)); 7170b57cec5SDimitry Andric 7180b57cec5SDimitry Andric symbolize::LLVMSymbolizer Symbolizer; 7190b57cec5SDimitry Andric FuncIdConversionHelper FuncIdHelper(StacksInstrMap, Symbolizer, 7200b57cec5SDimitry Andric Map.getFunctionAddresses()); 7210b57cec5SDimitry Andric // TODO: Someday, support output to files instead of just directly to 7220b57cec5SDimitry Andric // standard output. 7230b57cec5SDimitry Andric for (const auto &Filename : StackInputs) { 7240b57cec5SDimitry Andric auto TraceOrErr = loadTraceFile(Filename); 7250b57cec5SDimitry Andric if (!TraceOrErr) { 7260b57cec5SDimitry Andric if (!StackKeepGoing) 7270b57cec5SDimitry Andric return joinErrors( 7280b57cec5SDimitry Andric make_error<StringError>( 7290b57cec5SDimitry Andric Twine("Failed loading input file '") + Filename + "'", 7300b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)), 7310b57cec5SDimitry Andric TraceOrErr.takeError()); 7320b57cec5SDimitry Andric logAllUnhandledErrors(TraceOrErr.takeError(), errs()); 7330b57cec5SDimitry Andric continue; 7340b57cec5SDimitry Andric } 7350b57cec5SDimitry Andric auto &T = *TraceOrErr; 7360b57cec5SDimitry Andric StackTrie::AccountRecordState AccountRecordState = 7370b57cec5SDimitry Andric StackTrie::AccountRecordState::CreateInitialState(); 7380b57cec5SDimitry Andric for (const auto &Record : T) { 7390b57cec5SDimitry Andric auto error = ST.accountRecord(Record, &AccountRecordState); 7400b57cec5SDimitry Andric if (error != StackTrie::AccountRecordStatus::OK) { 7410b57cec5SDimitry Andric if (!StackKeepGoing) 7420b57cec5SDimitry Andric return make_error<StringError>( 7430b57cec5SDimitry Andric CreateErrorMessage(error, Record, FuncIdHelper), 7440b57cec5SDimitry Andric make_error_code(errc::illegal_byte_sequence)); 7450b57cec5SDimitry Andric errs() << CreateErrorMessage(error, Record, FuncIdHelper); 7460b57cec5SDimitry Andric } 7470b57cec5SDimitry Andric } 7480b57cec5SDimitry Andric } 7490b57cec5SDimitry Andric if (ST.isEmpty()) { 7500b57cec5SDimitry Andric return make_error<StringError>( 7510b57cec5SDimitry Andric "No instrumented calls were accounted in the input file.", 7520b57cec5SDimitry Andric make_error_code(errc::result_out_of_range)); 7530b57cec5SDimitry Andric } 7540b57cec5SDimitry Andric 7550b57cec5SDimitry Andric // Report the stacks in a long form mode for another tool to analyze. 7560b57cec5SDimitry Andric if (DumpAllStacks) { 7570b57cec5SDimitry Andric if (AggregateThreads) { 7580b57cec5SDimitry Andric switch (RequestedAggregation) { 7590b57cec5SDimitry Andric case AggregationType::TOTAL_TIME: 7600b57cec5SDimitry Andric ST.printAllAggregatingThreads<AggregationType::TOTAL_TIME>( 7610b57cec5SDimitry Andric outs(), FuncIdHelper, StacksOutputFormat); 7620b57cec5SDimitry Andric break; 7630b57cec5SDimitry Andric case AggregationType::INVOCATION_COUNT: 7640b57cec5SDimitry Andric ST.printAllAggregatingThreads<AggregationType::INVOCATION_COUNT>( 7650b57cec5SDimitry Andric outs(), FuncIdHelper, StacksOutputFormat); 7660b57cec5SDimitry Andric break; 7670b57cec5SDimitry Andric } 7680b57cec5SDimitry Andric } else { 7690b57cec5SDimitry Andric switch (RequestedAggregation) { 7700b57cec5SDimitry Andric case AggregationType::TOTAL_TIME: 7710b57cec5SDimitry Andric ST.printAllPerThread<AggregationType::TOTAL_TIME>(outs(), FuncIdHelper, 7720b57cec5SDimitry Andric StacksOutputFormat); 7730b57cec5SDimitry Andric break; 7740b57cec5SDimitry Andric case AggregationType::INVOCATION_COUNT: 7750b57cec5SDimitry Andric ST.printAllPerThread<AggregationType::INVOCATION_COUNT>( 7760b57cec5SDimitry Andric outs(), FuncIdHelper, StacksOutputFormat); 7770b57cec5SDimitry Andric break; 7780b57cec5SDimitry Andric } 7790b57cec5SDimitry Andric } 7800b57cec5SDimitry Andric return Error::success(); 7810b57cec5SDimitry Andric } 7820b57cec5SDimitry Andric 7830b57cec5SDimitry Andric // We're only outputting top stacks. 7840b57cec5SDimitry Andric if (AggregateThreads) { 7850b57cec5SDimitry Andric ST.printAggregatingThreads(outs(), FuncIdHelper); 7860b57cec5SDimitry Andric } else if (SeparateThreadStacks) { 7870b57cec5SDimitry Andric ST.printPerThread(outs(), FuncIdHelper); 7880b57cec5SDimitry Andric } else { 7890b57cec5SDimitry Andric ST.printIgnoringThreads(outs(), FuncIdHelper); 7900b57cec5SDimitry Andric } 7910b57cec5SDimitry Andric return Error::success(); 7920b57cec5SDimitry Andric }); 793