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