10b57cec5SDimitry Andric //===- xray-account.h - XRay Function Call 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 basic function call accounting from an XRay trace. 100b57cec5SDimitry Andric // 110b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 120b57cec5SDimitry Andric 130b57cec5SDimitry Andric #include <algorithm> 140b57cec5SDimitry Andric #include <cassert> 150b57cec5SDimitry Andric #include <numeric> 160b57cec5SDimitry Andric #include <system_error> 170b57cec5SDimitry Andric #include <utility> 180b57cec5SDimitry Andric 190b57cec5SDimitry Andric #include "xray-account.h" 200b57cec5SDimitry Andric #include "xray-registry.h" 210b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h" 220b57cec5SDimitry Andric #include "llvm/Support/FormatVariadic.h" 230b57cec5SDimitry Andric #include "llvm/XRay/InstrumentationMap.h" 240b57cec5SDimitry Andric #include "llvm/XRay/Trace.h" 250b57cec5SDimitry Andric 260b57cec5SDimitry Andric using namespace llvm; 270b57cec5SDimitry Andric using namespace llvm::xray; 280b57cec5SDimitry Andric 290b57cec5SDimitry Andric static cl::SubCommand Account("account", "Function call accounting"); 300b57cec5SDimitry Andric static cl::opt<std::string> AccountInput(cl::Positional, 310b57cec5SDimitry Andric cl::desc("<xray log file>"), 320b57cec5SDimitry Andric cl::Required, cl::sub(Account)); 330b57cec5SDimitry Andric static cl::opt<bool> 340b57cec5SDimitry Andric AccountKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), 350b57cec5SDimitry Andric cl::sub(Account), cl::init(false)); 360b57cec5SDimitry Andric static cl::alias AccountKeepGoing2("k", cl::aliasopt(AccountKeepGoing), 370b57cec5SDimitry Andric cl::desc("Alias for -keep_going"), 380b57cec5SDimitry Andric cl::sub(Account)); 390b57cec5SDimitry Andric static cl::opt<bool> AccountDeduceSiblingCalls( 400b57cec5SDimitry Andric "deduce-sibling-calls", 410b57cec5SDimitry Andric cl::desc("Deduce sibling calls when unrolling function call stacks"), 420b57cec5SDimitry Andric cl::sub(Account), cl::init(false)); 430b57cec5SDimitry Andric static cl::alias 440b57cec5SDimitry Andric AccountDeduceSiblingCalls2("d", cl::aliasopt(AccountDeduceSiblingCalls), 450b57cec5SDimitry Andric cl::desc("Alias for -deduce_sibling_calls"), 460b57cec5SDimitry Andric cl::sub(Account)); 470b57cec5SDimitry Andric static cl::opt<std::string> 480b57cec5SDimitry Andric AccountOutput("output", cl::value_desc("output file"), cl::init("-"), 490b57cec5SDimitry Andric cl::desc("output file; use '-' for stdout"), 500b57cec5SDimitry Andric cl::sub(Account)); 510b57cec5SDimitry Andric static cl::alias AccountOutput2("o", cl::aliasopt(AccountOutput), 520b57cec5SDimitry Andric cl::desc("Alias for -output"), 530b57cec5SDimitry Andric cl::sub(Account)); 540b57cec5SDimitry Andric enum class AccountOutputFormats { TEXT, CSV }; 550b57cec5SDimitry Andric static cl::opt<AccountOutputFormats> 560b57cec5SDimitry Andric AccountOutputFormat("format", cl::desc("output format"), 570b57cec5SDimitry Andric cl::values(clEnumValN(AccountOutputFormats::TEXT, 580b57cec5SDimitry Andric "text", "report stats in text"), 590b57cec5SDimitry Andric clEnumValN(AccountOutputFormats::CSV, "csv", 600b57cec5SDimitry Andric "report stats in csv")), 610b57cec5SDimitry Andric cl::sub(Account)); 620b57cec5SDimitry Andric static cl::alias AccountOutputFormat2("f", cl::desc("Alias of -format"), 630b57cec5SDimitry Andric cl::aliasopt(AccountOutputFormat), 640b57cec5SDimitry Andric cl::sub(Account)); 650b57cec5SDimitry Andric 660b57cec5SDimitry Andric enum class SortField { 670b57cec5SDimitry Andric FUNCID, 680b57cec5SDimitry Andric COUNT, 690b57cec5SDimitry Andric MIN, 700b57cec5SDimitry Andric MED, 710b57cec5SDimitry Andric PCT90, 720b57cec5SDimitry Andric PCT99, 730b57cec5SDimitry Andric MAX, 740b57cec5SDimitry Andric SUM, 750b57cec5SDimitry Andric FUNC, 760b57cec5SDimitry Andric }; 770b57cec5SDimitry Andric 780b57cec5SDimitry Andric static cl::opt<SortField> AccountSortOutput( 790b57cec5SDimitry Andric "sort", cl::desc("sort output by this field"), cl::value_desc("field"), 800b57cec5SDimitry Andric cl::sub(Account), cl::init(SortField::FUNCID), 810b57cec5SDimitry Andric cl::values(clEnumValN(SortField::FUNCID, "funcid", "function id"), 820b57cec5SDimitry Andric clEnumValN(SortField::COUNT, "count", "funciton call counts"), 830b57cec5SDimitry Andric clEnumValN(SortField::MIN, "min", "minimum function durations"), 840b57cec5SDimitry Andric clEnumValN(SortField::MED, "med", "median function durations"), 850b57cec5SDimitry Andric clEnumValN(SortField::PCT90, "90p", "90th percentile durations"), 860b57cec5SDimitry Andric clEnumValN(SortField::PCT99, "99p", "99th percentile durations"), 870b57cec5SDimitry Andric clEnumValN(SortField::MAX, "max", "maximum function durations"), 880b57cec5SDimitry Andric clEnumValN(SortField::SUM, "sum", "sum of call durations"), 890b57cec5SDimitry Andric clEnumValN(SortField::FUNC, "func", "function names"))); 900b57cec5SDimitry Andric static cl::alias AccountSortOutput2("s", cl::aliasopt(AccountSortOutput), 910b57cec5SDimitry Andric cl::desc("Alias for -sort"), 920b57cec5SDimitry Andric cl::sub(Account)); 930b57cec5SDimitry Andric 940b57cec5SDimitry Andric enum class SortDirection { 950b57cec5SDimitry Andric ASCENDING, 960b57cec5SDimitry Andric DESCENDING, 970b57cec5SDimitry Andric }; 980b57cec5SDimitry Andric static cl::opt<SortDirection> AccountSortOrder( 990b57cec5SDimitry Andric "sortorder", cl::desc("sort ordering"), cl::init(SortDirection::ASCENDING), 1000b57cec5SDimitry Andric cl::values(clEnumValN(SortDirection::ASCENDING, "asc", "ascending"), 1010b57cec5SDimitry Andric clEnumValN(SortDirection::DESCENDING, "dsc", "descending")), 1020b57cec5SDimitry Andric cl::sub(Account)); 1030b57cec5SDimitry Andric static cl::alias AccountSortOrder2("r", cl::aliasopt(AccountSortOrder), 1040b57cec5SDimitry Andric cl::desc("Alias for -sortorder"), 1050b57cec5SDimitry Andric cl::sub(Account)); 1060b57cec5SDimitry Andric 1070b57cec5SDimitry Andric static cl::opt<int> AccountTop("top", cl::desc("only show the top N results"), 1080b57cec5SDimitry Andric cl::value_desc("N"), cl::sub(Account), 1090b57cec5SDimitry Andric cl::init(-1)); 1100b57cec5SDimitry Andric static cl::alias AccountTop2("p", cl::desc("Alias for -top"), 1110b57cec5SDimitry Andric cl::aliasopt(AccountTop), cl::sub(Account)); 1120b57cec5SDimitry Andric 1130b57cec5SDimitry Andric static cl::opt<std::string> 1140b57cec5SDimitry Andric AccountInstrMap("instr_map", 1150b57cec5SDimitry Andric cl::desc("binary with the instrumentation map, or " 1160b57cec5SDimitry Andric "a separate instrumentation map"), 1170b57cec5SDimitry Andric cl::value_desc("binary with xray_instr_map"), 1180b57cec5SDimitry Andric cl::sub(Account), cl::init("")); 1190b57cec5SDimitry Andric static cl::alias AccountInstrMap2("m", cl::aliasopt(AccountInstrMap), 1200b57cec5SDimitry Andric cl::desc("Alias for -instr_map"), 1210b57cec5SDimitry Andric cl::sub(Account)); 1220b57cec5SDimitry Andric 1230b57cec5SDimitry Andric namespace { 1240b57cec5SDimitry Andric 1250b57cec5SDimitry Andric template <class T, class U> void setMinMax(std::pair<T, T> &MM, U &&V) { 1260b57cec5SDimitry Andric if (MM.first == 0 || MM.second == 0) 1270b57cec5SDimitry Andric MM = std::make_pair(std::forward<U>(V), std::forward<U>(V)); 1280b57cec5SDimitry Andric else 1290b57cec5SDimitry Andric MM = std::make_pair(std::min(MM.first, V), std::max(MM.second, V)); 1300b57cec5SDimitry Andric } 1310b57cec5SDimitry Andric 1320b57cec5SDimitry Andric template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); } 1330b57cec5SDimitry Andric 1340b57cec5SDimitry Andric } // namespace 1350b57cec5SDimitry Andric 1360b57cec5SDimitry Andric bool LatencyAccountant::accountRecord(const XRayRecord &Record) { 1370b57cec5SDimitry Andric setMinMax(PerThreadMinMaxTSC[Record.TId], Record.TSC); 1380b57cec5SDimitry Andric setMinMax(PerCPUMinMaxTSC[Record.CPU], Record.TSC); 1390b57cec5SDimitry Andric 1400b57cec5SDimitry Andric if (CurrentMaxTSC == 0) 1410b57cec5SDimitry Andric CurrentMaxTSC = Record.TSC; 1420b57cec5SDimitry Andric 1430b57cec5SDimitry Andric if (Record.TSC < CurrentMaxTSC) 1440b57cec5SDimitry Andric return false; 1450b57cec5SDimitry Andric 1460b57cec5SDimitry Andric auto &ThreadStack = PerThreadFunctionStack[Record.TId]; 1470b57cec5SDimitry Andric switch (Record.Type) { 1480b57cec5SDimitry Andric case RecordTypes::CUSTOM_EVENT: 1490b57cec5SDimitry Andric case RecordTypes::TYPED_EVENT: 1500b57cec5SDimitry Andric // TODO: Support custom and typed event accounting in the future. 1510b57cec5SDimitry Andric return true; 1520b57cec5SDimitry Andric case RecordTypes::ENTER: 1530b57cec5SDimitry Andric case RecordTypes::ENTER_ARG: { 1540b57cec5SDimitry Andric ThreadStack.emplace_back(Record.FuncId, Record.TSC); 1550b57cec5SDimitry Andric break; 1560b57cec5SDimitry Andric } 1570b57cec5SDimitry Andric case RecordTypes::EXIT: 1580b57cec5SDimitry Andric case RecordTypes::TAIL_EXIT: { 1590b57cec5SDimitry Andric if (ThreadStack.empty()) 1600b57cec5SDimitry Andric return false; 1610b57cec5SDimitry Andric 1620b57cec5SDimitry Andric if (ThreadStack.back().first == Record.FuncId) { 1630b57cec5SDimitry Andric const auto &Top = ThreadStack.back(); 1640b57cec5SDimitry Andric recordLatency(Top.first, diff(Top.second, Record.TSC)); 1650b57cec5SDimitry Andric ThreadStack.pop_back(); 1660b57cec5SDimitry Andric break; 1670b57cec5SDimitry Andric } 1680b57cec5SDimitry Andric 1690b57cec5SDimitry Andric if (!DeduceSiblingCalls) 1700b57cec5SDimitry Andric return false; 1710b57cec5SDimitry Andric 1720b57cec5SDimitry Andric // Look for the parent up the stack. 1730b57cec5SDimitry Andric auto Parent = 1740b57cec5SDimitry Andric std::find_if(ThreadStack.rbegin(), ThreadStack.rend(), 1750b57cec5SDimitry Andric [&](const std::pair<const int32_t, uint64_t> &E) { 1760b57cec5SDimitry Andric return E.first == Record.FuncId; 1770b57cec5SDimitry Andric }); 1780b57cec5SDimitry Andric if (Parent == ThreadStack.rend()) 1790b57cec5SDimitry Andric return false; 1800b57cec5SDimitry Andric 1810b57cec5SDimitry Andric // Account time for this apparently sibling call exit up the stack. 1820b57cec5SDimitry Andric // Considering the following case: 1830b57cec5SDimitry Andric // 1840b57cec5SDimitry Andric // f() 1850b57cec5SDimitry Andric // g() 1860b57cec5SDimitry Andric // h() 1870b57cec5SDimitry Andric // 1880b57cec5SDimitry Andric // We might only ever see the following entries: 1890b57cec5SDimitry Andric // 1900b57cec5SDimitry Andric // -> f() 1910b57cec5SDimitry Andric // -> g() 1920b57cec5SDimitry Andric // -> h() 1930b57cec5SDimitry Andric // <- h() 1940b57cec5SDimitry Andric // <- f() 1950b57cec5SDimitry Andric // 1960b57cec5SDimitry Andric // Now we don't see the exit to g() because some older version of the XRay 1970b57cec5SDimitry Andric // runtime wasn't instrumenting tail exits. If we don't deduce tail calls, 1980b57cec5SDimitry Andric // we may potentially never account time for g() -- and this code would have 1990b57cec5SDimitry Andric // already bailed out, because `<- f()` doesn't match the current "top" of 2000b57cec5SDimitry Andric // stack where we're waiting for the exit to `g()` instead. This is not 2010b57cec5SDimitry Andric // ideal and brittle -- so instead we provide a potentially inaccurate 2020b57cec5SDimitry Andric // accounting of g() instead, computing it from the exit of f(). 2030b57cec5SDimitry Andric // 2040b57cec5SDimitry Andric // While it might be better that we account the time between `-> g()` and 2050b57cec5SDimitry Andric // `-> h()` as the proper accounting of time for g() here, this introduces 2060b57cec5SDimitry Andric // complexity to do correctly (need to backtrack, etc.). 2070b57cec5SDimitry Andric // 2080b57cec5SDimitry Andric // FIXME: Potentially implement the more complex deduction algorithm? 2090b57cec5SDimitry Andric auto I = std::next(Parent).base(); 2100b57cec5SDimitry Andric for (auto &E : make_range(I, ThreadStack.end())) { 2110b57cec5SDimitry Andric recordLatency(E.first, diff(E.second, Record.TSC)); 2120b57cec5SDimitry Andric } 2130b57cec5SDimitry Andric ThreadStack.erase(I, ThreadStack.end()); 2140b57cec5SDimitry Andric break; 2150b57cec5SDimitry Andric } 2160b57cec5SDimitry Andric } 2170b57cec5SDimitry Andric 2180b57cec5SDimitry Andric return true; 2190b57cec5SDimitry Andric } 2200b57cec5SDimitry Andric 2210b57cec5SDimitry Andric namespace { 2220b57cec5SDimitry Andric 2230b57cec5SDimitry Andric // We consolidate the data into a struct which we can output in various forms. 2240b57cec5SDimitry Andric struct ResultRow { 2250b57cec5SDimitry Andric uint64_t Count; 2260b57cec5SDimitry Andric double Min; 2270b57cec5SDimitry Andric double Median; 2280b57cec5SDimitry Andric double Pct90; 2290b57cec5SDimitry Andric double Pct99; 2300b57cec5SDimitry Andric double Max; 2310b57cec5SDimitry Andric double Sum; 2320b57cec5SDimitry Andric std::string DebugInfo; 2330b57cec5SDimitry Andric std::string Function; 2340b57cec5SDimitry Andric }; 2350b57cec5SDimitry Andric 2360b57cec5SDimitry Andric ResultRow getStats(std::vector<uint64_t> &Timings) { 2370b57cec5SDimitry Andric assert(!Timings.empty()); 2380b57cec5SDimitry Andric ResultRow R; 2390b57cec5SDimitry Andric R.Sum = std::accumulate(Timings.begin(), Timings.end(), 0.0); 2400b57cec5SDimitry Andric auto MinMax = std::minmax_element(Timings.begin(), Timings.end()); 2410b57cec5SDimitry Andric R.Min = *MinMax.first; 2420b57cec5SDimitry Andric R.Max = *MinMax.second; 2430b57cec5SDimitry Andric R.Count = Timings.size(); 2440b57cec5SDimitry Andric 2450b57cec5SDimitry Andric auto MedianOff = Timings.size() / 2; 2460b57cec5SDimitry Andric std::nth_element(Timings.begin(), Timings.begin() + MedianOff, Timings.end()); 2470b57cec5SDimitry Andric R.Median = Timings[MedianOff]; 2480b57cec5SDimitry Andric 2490b57cec5SDimitry Andric auto Pct90Off = std::floor(Timings.size() * 0.9); 2500b57cec5SDimitry Andric std::nth_element(Timings.begin(), Timings.begin() + Pct90Off, Timings.end()); 2510b57cec5SDimitry Andric R.Pct90 = Timings[Pct90Off]; 2520b57cec5SDimitry Andric 2530b57cec5SDimitry Andric auto Pct99Off = std::floor(Timings.size() * 0.99); 2540b57cec5SDimitry Andric std::nth_element(Timings.begin(), Timings.begin() + Pct99Off, Timings.end()); 2550b57cec5SDimitry Andric R.Pct99 = Timings[Pct99Off]; 2560b57cec5SDimitry Andric return R; 2570b57cec5SDimitry Andric } 2580b57cec5SDimitry Andric 2590b57cec5SDimitry Andric } // namespace 2600b57cec5SDimitry Andric 2610b57cec5SDimitry Andric using TupleType = std::tuple<int32_t, uint64_t, ResultRow>; 2620b57cec5SDimitry Andric 2630b57cec5SDimitry Andric template <typename F> 2640b57cec5SDimitry Andric static void sortByKey(std::vector<TupleType> &Results, F Fn) { 2650b57cec5SDimitry Andric bool ASC = AccountSortOrder == SortDirection::ASCENDING; 2660b57cec5SDimitry Andric llvm::sort(Results, [=](const TupleType &L, const TupleType &R) { 2670b57cec5SDimitry Andric return ASC ? Fn(L) < Fn(R) : Fn(L) > Fn(R); 2680b57cec5SDimitry Andric }); 2690b57cec5SDimitry Andric } 2700b57cec5SDimitry Andric 2710b57cec5SDimitry Andric template <class F> 2720b57cec5SDimitry Andric void LatencyAccountant::exportStats(const XRayFileHeader &Header, F Fn) const { 2730b57cec5SDimitry Andric std::vector<TupleType> Results; 2740b57cec5SDimitry Andric Results.reserve(FunctionLatencies.size()); 2750b57cec5SDimitry Andric for (auto FT : FunctionLatencies) { 2760b57cec5SDimitry Andric const auto &FuncId = FT.first; 2770b57cec5SDimitry Andric auto &Timings = FT.second; 2780b57cec5SDimitry Andric Results.emplace_back(FuncId, Timings.size(), getStats(Timings)); 2790b57cec5SDimitry Andric auto &Row = std::get<2>(Results.back()); 2800b57cec5SDimitry Andric if (Header.CycleFrequency) { 2810b57cec5SDimitry Andric double CycleFrequency = Header.CycleFrequency; 2820b57cec5SDimitry Andric Row.Min /= CycleFrequency; 2830b57cec5SDimitry Andric Row.Median /= CycleFrequency; 2840b57cec5SDimitry Andric Row.Pct90 /= CycleFrequency; 2850b57cec5SDimitry Andric Row.Pct99 /= CycleFrequency; 2860b57cec5SDimitry Andric Row.Max /= CycleFrequency; 2870b57cec5SDimitry Andric Row.Sum /= CycleFrequency; 2880b57cec5SDimitry Andric } 2890b57cec5SDimitry Andric 2900b57cec5SDimitry Andric Row.Function = FuncIdHelper.SymbolOrNumber(FuncId); 2910b57cec5SDimitry Andric Row.DebugInfo = FuncIdHelper.FileLineAndColumn(FuncId); 2920b57cec5SDimitry Andric } 2930b57cec5SDimitry Andric 2940b57cec5SDimitry Andric // Sort the data according to user-provided flags. 2950b57cec5SDimitry Andric switch (AccountSortOutput) { 2960b57cec5SDimitry Andric case SortField::FUNCID: 2970b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<0>(X); }); 2980b57cec5SDimitry Andric break; 2990b57cec5SDimitry Andric case SortField::COUNT: 3000b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<1>(X); }); 3010b57cec5SDimitry Andric break; 3020b57cec5SDimitry Andric case SortField::MIN: 3030b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Min; }); 3040b57cec5SDimitry Andric break; 3050b57cec5SDimitry Andric case SortField::MED: 3060b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Median; }); 3070b57cec5SDimitry Andric break; 3080b57cec5SDimitry Andric case SortField::PCT90: 3090b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct90; }); 3100b57cec5SDimitry Andric break; 3110b57cec5SDimitry Andric case SortField::PCT99: 3120b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct99; }); 3130b57cec5SDimitry Andric break; 3140b57cec5SDimitry Andric case SortField::MAX: 3150b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Max; }); 3160b57cec5SDimitry Andric break; 3170b57cec5SDimitry Andric case SortField::SUM: 3180b57cec5SDimitry Andric sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Sum; }); 3190b57cec5SDimitry Andric break; 3200b57cec5SDimitry Andric case SortField::FUNC: 3210b57cec5SDimitry Andric llvm_unreachable("Not implemented"); 3220b57cec5SDimitry Andric } 3230b57cec5SDimitry Andric 3240b57cec5SDimitry Andric if (AccountTop > 0) { 3250b57cec5SDimitry Andric auto MaxTop = 3260b57cec5SDimitry Andric std::min(AccountTop.getValue(), static_cast<int>(Results.size())); 3270b57cec5SDimitry Andric Results.erase(Results.begin() + MaxTop, Results.end()); 3280b57cec5SDimitry Andric } 3290b57cec5SDimitry Andric 3300b57cec5SDimitry Andric for (const auto &R : Results) 3310b57cec5SDimitry Andric Fn(std::get<0>(R), std::get<1>(R), std::get<2>(R)); 3320b57cec5SDimitry Andric } 3330b57cec5SDimitry Andric 3340b57cec5SDimitry Andric void LatencyAccountant::exportStatsAsText(raw_ostream &OS, 3350b57cec5SDimitry Andric const XRayFileHeader &Header) const { 3360b57cec5SDimitry Andric OS << "Functions with latencies: " << FunctionLatencies.size() << "\n"; 3370b57cec5SDimitry Andric 3380b57cec5SDimitry Andric // We spend some effort to make the text output more readable, so we do the 3390b57cec5SDimitry Andric // following formatting decisions for each of the fields: 3400b57cec5SDimitry Andric // 3410b57cec5SDimitry Andric // - funcid: 32-bit, but we can determine the largest number and be 3420b57cec5SDimitry Andric // between 3430b57cec5SDimitry Andric // a minimum of 5 characters, up to 9 characters, right aligned. 3440b57cec5SDimitry Andric // - count: 64-bit, but we can determine the largest number and be 3450b57cec5SDimitry Andric // between 3460b57cec5SDimitry Andric // a minimum of 5 characters, up to 9 characters, right aligned. 3470b57cec5SDimitry Andric // - min, median, 90pct, 99pct, max: double precision, but we want to keep 3480b57cec5SDimitry Andric // the values in seconds, with microsecond precision (0.000'001), so we 3490b57cec5SDimitry Andric // have at most 6 significant digits, with the whole number part to be 3500b57cec5SDimitry Andric // at 3510b57cec5SDimitry Andric // least 1 character. For readability we'll right-align, with full 9 3520b57cec5SDimitry Andric // characters each. 3530b57cec5SDimitry Andric // - debug info, function name: we format this as a concatenation of the 3540b57cec5SDimitry Andric // debug info and the function name. 3550b57cec5SDimitry Andric // 3560b57cec5SDimitry Andric static constexpr char StatsHeaderFormat[] = 3570b57cec5SDimitry Andric "{0,+9} {1,+10} [{2,+9}, {3,+9}, {4,+9}, {5,+9}, {6,+9}] {7,+9}"; 3580b57cec5SDimitry Andric static constexpr char StatsFormat[] = 3590b57cec5SDimitry Andric R"({0,+9} {1,+10} [{2,+9:f6}, {3,+9:f6}, {4,+9:f6}, {5,+9:f6}, {6,+9:f6}] {7,+9:f6})"; 3600b57cec5SDimitry Andric OS << llvm::formatv(StatsHeaderFormat, "funcid", "count", "min", "med", "90p", 3610b57cec5SDimitry Andric "99p", "max", "sum") 3620b57cec5SDimitry Andric << llvm::formatv(" {0,-12}\n", "function"); 3630b57cec5SDimitry Andric exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) { 3640b57cec5SDimitry Andric OS << llvm::formatv(StatsFormat, FuncId, Count, Row.Min, Row.Median, 3650b57cec5SDimitry Andric Row.Pct90, Row.Pct99, Row.Max, Row.Sum) 3660b57cec5SDimitry Andric << " " << Row.DebugInfo << ": " << Row.Function << "\n"; 3670b57cec5SDimitry Andric }); 3680b57cec5SDimitry Andric } 3690b57cec5SDimitry Andric 3700b57cec5SDimitry Andric void LatencyAccountant::exportStatsAsCSV(raw_ostream &OS, 3710b57cec5SDimitry Andric const XRayFileHeader &Header) const { 3720b57cec5SDimitry Andric OS << "funcid,count,min,median,90%ile,99%ile,max,sum,debug,function\n"; 3730b57cec5SDimitry Andric exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) { 3740b57cec5SDimitry Andric OS << FuncId << ',' << Count << ',' << Row.Min << ',' << Row.Median << ',' 3750b57cec5SDimitry Andric << Row.Pct90 << ',' << Row.Pct99 << ',' << Row.Max << "," << Row.Sum 3760b57cec5SDimitry Andric << ",\"" << Row.DebugInfo << "\",\"" << Row.Function << "\"\n"; 3770b57cec5SDimitry Andric }); 3780b57cec5SDimitry Andric } 3790b57cec5SDimitry Andric 3800b57cec5SDimitry Andric using namespace llvm::xray; 3810b57cec5SDimitry Andric 3820b57cec5SDimitry Andric namespace llvm { 3830b57cec5SDimitry Andric template <> struct format_provider<llvm::xray::RecordTypes> { 3840b57cec5SDimitry Andric static void format(const llvm::xray::RecordTypes &T, raw_ostream &Stream, 3850b57cec5SDimitry Andric StringRef Style) { 3860b57cec5SDimitry Andric switch (T) { 3870b57cec5SDimitry Andric case RecordTypes::ENTER: 3880b57cec5SDimitry Andric Stream << "enter"; 3890b57cec5SDimitry Andric break; 3900b57cec5SDimitry Andric case RecordTypes::ENTER_ARG: 3910b57cec5SDimitry Andric Stream << "enter-arg"; 3920b57cec5SDimitry Andric break; 3930b57cec5SDimitry Andric case RecordTypes::EXIT: 3940b57cec5SDimitry Andric Stream << "exit"; 3950b57cec5SDimitry Andric break; 3960b57cec5SDimitry Andric case RecordTypes::TAIL_EXIT: 3970b57cec5SDimitry Andric Stream << "tail-exit"; 3980b57cec5SDimitry Andric break; 3990b57cec5SDimitry Andric case RecordTypes::CUSTOM_EVENT: 4000b57cec5SDimitry Andric Stream << "custom-event"; 4010b57cec5SDimitry Andric break; 4020b57cec5SDimitry Andric case RecordTypes::TYPED_EVENT: 4030b57cec5SDimitry Andric Stream << "typed-event"; 4040b57cec5SDimitry Andric break; 4050b57cec5SDimitry Andric } 4060b57cec5SDimitry Andric } 4070b57cec5SDimitry Andric }; 4080b57cec5SDimitry Andric } // namespace llvm 4090b57cec5SDimitry Andric 4100b57cec5SDimitry Andric static CommandRegistration Unused(&Account, []() -> Error { 4110b57cec5SDimitry Andric InstrumentationMap Map; 4120b57cec5SDimitry Andric if (!AccountInstrMap.empty()) { 4130b57cec5SDimitry Andric auto InstrumentationMapOrError = loadInstrumentationMap(AccountInstrMap); 4140b57cec5SDimitry Andric if (!InstrumentationMapOrError) 4150b57cec5SDimitry Andric return joinErrors(make_error<StringError>( 4160b57cec5SDimitry Andric Twine("Cannot open instrumentation map '") + 4170b57cec5SDimitry Andric AccountInstrMap + "'", 4180b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)), 4190b57cec5SDimitry Andric InstrumentationMapOrError.takeError()); 4200b57cec5SDimitry Andric Map = std::move(*InstrumentationMapOrError); 4210b57cec5SDimitry Andric } 4220b57cec5SDimitry Andric 4230b57cec5SDimitry Andric std::error_code EC; 4240b57cec5SDimitry Andric raw_fd_ostream OS(AccountOutput, EC, sys::fs::OpenFlags::F_Text); 4250b57cec5SDimitry Andric if (EC) 4260b57cec5SDimitry Andric return make_error<StringError>( 4270b57cec5SDimitry Andric Twine("Cannot open file '") + AccountOutput + "' for writing.", EC); 4280b57cec5SDimitry Andric 4290b57cec5SDimitry Andric const auto &FunctionAddresses = Map.getFunctionAddresses(); 4300b57cec5SDimitry Andric symbolize::LLVMSymbolizer Symbolizer; 4310b57cec5SDimitry Andric llvm::xray::FuncIdConversionHelper FuncIdHelper(AccountInstrMap, Symbolizer, 4320b57cec5SDimitry Andric FunctionAddresses); 4330b57cec5SDimitry Andric xray::LatencyAccountant FCA(FuncIdHelper, AccountDeduceSiblingCalls); 4340b57cec5SDimitry Andric auto TraceOrErr = loadTraceFile(AccountInput); 4350b57cec5SDimitry Andric if (!TraceOrErr) 4360b57cec5SDimitry Andric return joinErrors( 4370b57cec5SDimitry Andric make_error<StringError>( 4380b57cec5SDimitry Andric Twine("Failed loading input file '") + AccountInput + "'", 4390b57cec5SDimitry Andric std::make_error_code(std::errc::executable_format_error)), 4400b57cec5SDimitry Andric TraceOrErr.takeError()); 4410b57cec5SDimitry Andric 4420b57cec5SDimitry Andric auto &T = *TraceOrErr; 4430b57cec5SDimitry Andric for (const auto &Record : T) { 4440b57cec5SDimitry Andric if (FCA.accountRecord(Record)) 4450b57cec5SDimitry Andric continue; 4460b57cec5SDimitry Andric errs() 4470b57cec5SDimitry Andric << "Error processing record: " 4480b57cec5SDimitry Andric << llvm::formatv( 4490b57cec5SDimitry Andric R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}; process-id: {6}}})", 4500b57cec5SDimitry Andric Record.RecordType, Record.CPU, Record.Type, Record.FuncId, 4510b57cec5SDimitry Andric Record.TSC, Record.TId, Record.PId) 4520b57cec5SDimitry Andric << '\n'; 4530b57cec5SDimitry Andric for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) { 4540b57cec5SDimitry Andric errs() << "Thread ID: " << ThreadStack.first << "\n"; 4550b57cec5SDimitry Andric if (ThreadStack.second.empty()) { 4560b57cec5SDimitry Andric errs() << " (empty stack)\n"; 4570b57cec5SDimitry Andric continue; 4580b57cec5SDimitry Andric } 4590b57cec5SDimitry Andric auto Level = ThreadStack.second.size(); 4600b57cec5SDimitry Andric for (const auto &Entry : llvm::reverse(ThreadStack.second)) 4610b57cec5SDimitry Andric errs() << " #" << Level-- << "\t" 4620b57cec5SDimitry Andric << FuncIdHelper.SymbolOrNumber(Entry.first) << '\n'; 4630b57cec5SDimitry Andric } 4640b57cec5SDimitry Andric if (!AccountKeepGoing) 4650b57cec5SDimitry Andric return make_error<StringError>( 4660b57cec5SDimitry Andric Twine("Failed accounting function calls in file '") + AccountInput + 4670b57cec5SDimitry Andric "'.", 4680b57cec5SDimitry Andric std::make_error_code(std::errc::executable_format_error)); 4690b57cec5SDimitry Andric } 4700b57cec5SDimitry Andric switch (AccountOutputFormat) { 4710b57cec5SDimitry Andric case AccountOutputFormats::TEXT: 4720b57cec5SDimitry Andric FCA.exportStatsAsText(OS, T.getFileHeader()); 4730b57cec5SDimitry Andric break; 4740b57cec5SDimitry Andric case AccountOutputFormats::CSV: 4750b57cec5SDimitry Andric FCA.exportStatsAsCSV(OS, T.getFileHeader()); 4760b57cec5SDimitry Andric break; 4770b57cec5SDimitry Andric } 4780b57cec5SDimitry Andric 4790b57cec5SDimitry Andric return Error::success(); 4800b57cec5SDimitry Andric }); 481