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 
26bdd1243dSDimitry Andric #include <cmath>
27bdd1243dSDimitry Andric 
280b57cec5SDimitry Andric using namespace llvm;
290b57cec5SDimitry Andric using namespace llvm::xray;
300b57cec5SDimitry Andric 
310b57cec5SDimitry Andric static cl::SubCommand Account("account", "Function call accounting");
320b57cec5SDimitry Andric static cl::opt<std::string> AccountInput(cl::Positional,
330b57cec5SDimitry Andric                                          cl::desc("<xray log file>"),
340b57cec5SDimitry Andric                                          cl::Required, cl::sub(Account));
350b57cec5SDimitry Andric static cl::opt<bool>
360b57cec5SDimitry Andric     AccountKeepGoing("keep-going", cl::desc("Keep going on errors encountered"),
370b57cec5SDimitry Andric                      cl::sub(Account), cl::init(false));
380b57cec5SDimitry Andric static cl::alias AccountKeepGoing2("k", cl::aliasopt(AccountKeepGoing),
39480093f4SDimitry Andric                                    cl::desc("Alias for -keep_going"));
40e8d8bef9SDimitry Andric static cl::opt<bool> AccountRecursiveCallsOnly(
41e8d8bef9SDimitry Andric     "recursive-calls-only", cl::desc("Only count the calls that are recursive"),
42e8d8bef9SDimitry Andric     cl::sub(Account), cl::init(false));
430b57cec5SDimitry Andric static cl::opt<bool> AccountDeduceSiblingCalls(
440b57cec5SDimitry Andric     "deduce-sibling-calls",
450b57cec5SDimitry Andric     cl::desc("Deduce sibling calls when unrolling function call stacks"),
460b57cec5SDimitry Andric     cl::sub(Account), cl::init(false));
470b57cec5SDimitry Andric static cl::alias
480b57cec5SDimitry Andric     AccountDeduceSiblingCalls2("d", cl::aliasopt(AccountDeduceSiblingCalls),
49480093f4SDimitry Andric                                cl::desc("Alias for -deduce_sibling_calls"));
500b57cec5SDimitry Andric static cl::opt<std::string>
510b57cec5SDimitry Andric     AccountOutput("output", cl::value_desc("output file"), cl::init("-"),
520b57cec5SDimitry Andric                   cl::desc("output file; use '-' for stdout"),
530b57cec5SDimitry Andric                   cl::sub(Account));
540b57cec5SDimitry Andric static cl::alias AccountOutput2("o", cl::aliasopt(AccountOutput),
55480093f4SDimitry Andric                                 cl::desc("Alias for -output"));
560b57cec5SDimitry Andric enum class AccountOutputFormats { TEXT, CSV };
570b57cec5SDimitry Andric static cl::opt<AccountOutputFormats>
580b57cec5SDimitry Andric     AccountOutputFormat("format", cl::desc("output format"),
590b57cec5SDimitry Andric                         cl::values(clEnumValN(AccountOutputFormats::TEXT,
600b57cec5SDimitry Andric                                               "text", "report stats in text"),
610b57cec5SDimitry Andric                                    clEnumValN(AccountOutputFormats::CSV, "csv",
620b57cec5SDimitry Andric                                               "report stats in csv")),
630b57cec5SDimitry Andric                         cl::sub(Account));
640b57cec5SDimitry Andric static cl::alias AccountOutputFormat2("f", cl::desc("Alias of -format"),
65480093f4SDimitry Andric                                       cl::aliasopt(AccountOutputFormat));
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric enum class SortField {
680b57cec5SDimitry Andric   FUNCID,
690b57cec5SDimitry Andric   COUNT,
700b57cec5SDimitry Andric   MIN,
710b57cec5SDimitry Andric   MED,
720b57cec5SDimitry Andric   PCT90,
730b57cec5SDimitry Andric   PCT99,
740b57cec5SDimitry Andric   MAX,
750b57cec5SDimitry Andric   SUM,
760b57cec5SDimitry Andric   FUNC,
770b57cec5SDimitry Andric };
780b57cec5SDimitry Andric 
790b57cec5SDimitry Andric static cl::opt<SortField> AccountSortOutput(
800b57cec5SDimitry Andric     "sort", cl::desc("sort output by this field"), cl::value_desc("field"),
810b57cec5SDimitry Andric     cl::sub(Account), cl::init(SortField::FUNCID),
820b57cec5SDimitry Andric     cl::values(clEnumValN(SortField::FUNCID, "funcid", "function id"),
8306c3fb27SDimitry Andric                clEnumValN(SortField::COUNT, "count", "function call counts"),
840b57cec5SDimitry Andric                clEnumValN(SortField::MIN, "min", "minimum function durations"),
850b57cec5SDimitry Andric                clEnumValN(SortField::MED, "med", "median function durations"),
860b57cec5SDimitry Andric                clEnumValN(SortField::PCT90, "90p", "90th percentile durations"),
870b57cec5SDimitry Andric                clEnumValN(SortField::PCT99, "99p", "99th percentile durations"),
880b57cec5SDimitry Andric                clEnumValN(SortField::MAX, "max", "maximum function durations"),
890b57cec5SDimitry Andric                clEnumValN(SortField::SUM, "sum", "sum of call durations"),
900b57cec5SDimitry Andric                clEnumValN(SortField::FUNC, "func", "function names")));
910b57cec5SDimitry Andric static cl::alias AccountSortOutput2("s", cl::aliasopt(AccountSortOutput),
92480093f4SDimitry Andric                                     cl::desc("Alias for -sort"));
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),
104480093f4SDimitry Andric                                    cl::desc("Alias for -sortorder"));
1050b57cec5SDimitry Andric 
1060b57cec5SDimitry Andric static cl::opt<int> AccountTop("top", cl::desc("only show the top N results"),
1070b57cec5SDimitry Andric                                cl::value_desc("N"), cl::sub(Account),
1080b57cec5SDimitry Andric                                cl::init(-1));
1090b57cec5SDimitry Andric static cl::alias AccountTop2("p", cl::desc("Alias for -top"),
110480093f4SDimitry Andric                              cl::aliasopt(AccountTop));
1110b57cec5SDimitry Andric 
1120b57cec5SDimitry Andric static cl::opt<std::string>
1130b57cec5SDimitry Andric     AccountInstrMap("instr_map",
1140b57cec5SDimitry Andric                     cl::desc("binary with the instrumentation map, or "
1150b57cec5SDimitry Andric                              "a separate instrumentation map"),
1160b57cec5SDimitry Andric                     cl::value_desc("binary with xray_instr_map"),
1170b57cec5SDimitry Andric                     cl::sub(Account), cl::init(""));
1180b57cec5SDimitry Andric static cl::alias AccountInstrMap2("m", cl::aliasopt(AccountInstrMap),
119480093f4SDimitry Andric                                   cl::desc("Alias for -instr_map"));
1200b57cec5SDimitry Andric 
1210b57cec5SDimitry Andric namespace {
1220b57cec5SDimitry Andric 
setMinMax(std::pair<T,T> & MM,U && V)1230b57cec5SDimitry Andric template <class T, class U> void setMinMax(std::pair<T, T> &MM, U &&V) {
1240b57cec5SDimitry Andric   if (MM.first == 0 || MM.second == 0)
1250b57cec5SDimitry Andric     MM = std::make_pair(std::forward<U>(V), std::forward<U>(V));
1260b57cec5SDimitry Andric   else
1270b57cec5SDimitry Andric     MM = std::make_pair(std::min(MM.first, V), std::max(MM.second, V));
1280b57cec5SDimitry Andric }
1290b57cec5SDimitry Andric 
diff(T L,T R)1300b57cec5SDimitry Andric template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); }
1310b57cec5SDimitry Andric 
1320b57cec5SDimitry Andric } // namespace
1330b57cec5SDimitry Andric 
134e8d8bef9SDimitry Andric using RecursionStatus = LatencyAccountant::FunctionStack::RecursionStatus;
operator ++()135e8d8bef9SDimitry Andric RecursionStatus &RecursionStatus::operator++() {
136e8d8bef9SDimitry Andric   auto Depth = Bitfield::get<RecursionStatus::Depth>(Storage);
137e8d8bef9SDimitry Andric   assert(Depth >= 0 && Depth < std::numeric_limits<decltype(Depth)>::max());
138e8d8bef9SDimitry Andric   ++Depth;
139e8d8bef9SDimitry Andric   Bitfield::set<RecursionStatus::Depth>(Storage, Depth); // ++Storage
140e8d8bef9SDimitry Andric   // Did this function just (maybe indirectly) call itself the first time?
141e8d8bef9SDimitry Andric   if (!isRecursive() && Depth == 2) // Storage == 2  /  Storage s> 1
142e8d8bef9SDimitry Andric     Bitfield::set<RecursionStatus::IsRecursive>(Storage,
143e8d8bef9SDimitry Andric                                                 true); // Storage |= INT_MIN
144e8d8bef9SDimitry Andric   return *this;
145e8d8bef9SDimitry Andric }
operator --()146e8d8bef9SDimitry Andric RecursionStatus &RecursionStatus::operator--() {
147e8d8bef9SDimitry Andric   auto Depth = Bitfield::get<RecursionStatus::Depth>(Storage);
148e8d8bef9SDimitry Andric   assert(Depth > 0);
149e8d8bef9SDimitry Andric   --Depth;
150e8d8bef9SDimitry Andric   Bitfield::set<RecursionStatus::Depth>(Storage, Depth); // --Storage
151e8d8bef9SDimitry Andric   // Did we leave a function that previouly (maybe indirectly) called itself?
152e8d8bef9SDimitry Andric   if (isRecursive() && Depth == 0) // Storage == INT_MIN
153e8d8bef9SDimitry Andric     Bitfield::set<RecursionStatus::IsRecursive>(Storage, false); // Storage = 0
154e8d8bef9SDimitry Andric   return *this;
155e8d8bef9SDimitry Andric }
isRecursive() const156e8d8bef9SDimitry Andric bool RecursionStatus::isRecursive() const {
157e8d8bef9SDimitry Andric   return Bitfield::get<RecursionStatus::IsRecursive>(Storage); // Storage s< 0
158e8d8bef9SDimitry Andric }
159e8d8bef9SDimitry Andric 
accountRecord(const XRayRecord & Record)1600b57cec5SDimitry Andric bool LatencyAccountant::accountRecord(const XRayRecord &Record) {
1610b57cec5SDimitry Andric   setMinMax(PerThreadMinMaxTSC[Record.TId], Record.TSC);
1620b57cec5SDimitry Andric   setMinMax(PerCPUMinMaxTSC[Record.CPU], Record.TSC);
1630b57cec5SDimitry Andric 
1640b57cec5SDimitry Andric   if (CurrentMaxTSC == 0)
1650b57cec5SDimitry Andric     CurrentMaxTSC = Record.TSC;
1660b57cec5SDimitry Andric 
1670b57cec5SDimitry Andric   if (Record.TSC < CurrentMaxTSC)
1680b57cec5SDimitry Andric     return false;
1690b57cec5SDimitry Andric 
1700b57cec5SDimitry Andric   auto &ThreadStack = PerThreadFunctionStack[Record.TId];
171e8d8bef9SDimitry Andric   if (RecursiveCallsOnly && !ThreadStack.RecursionDepth)
172e8d8bef9SDimitry Andric     ThreadStack.RecursionDepth.emplace();
1730b57cec5SDimitry Andric   switch (Record.Type) {
1740b57cec5SDimitry Andric   case RecordTypes::CUSTOM_EVENT:
1750b57cec5SDimitry Andric   case RecordTypes::TYPED_EVENT:
1760b57cec5SDimitry Andric     // TODO: Support custom and typed event accounting in the future.
1770b57cec5SDimitry Andric     return true;
1780b57cec5SDimitry Andric   case RecordTypes::ENTER:
1790b57cec5SDimitry Andric   case RecordTypes::ENTER_ARG: {
180e8d8bef9SDimitry Andric     ThreadStack.Stack.emplace_back(Record.FuncId, Record.TSC);
181e8d8bef9SDimitry Andric     if (ThreadStack.RecursionDepth)
182e8d8bef9SDimitry Andric       ++(*ThreadStack.RecursionDepth)[Record.FuncId];
1830b57cec5SDimitry Andric     break;
1840b57cec5SDimitry Andric   }
1850b57cec5SDimitry Andric   case RecordTypes::EXIT:
1860b57cec5SDimitry Andric   case RecordTypes::TAIL_EXIT: {
187e8d8bef9SDimitry Andric     if (ThreadStack.Stack.empty())
1880b57cec5SDimitry Andric       return false;
1890b57cec5SDimitry Andric 
190e8d8bef9SDimitry Andric     if (ThreadStack.Stack.back().first == Record.FuncId) {
191e8d8bef9SDimitry Andric       const auto &Top = ThreadStack.Stack.back();
192e8d8bef9SDimitry Andric       if (!ThreadStack.RecursionDepth ||
193e8d8bef9SDimitry Andric           (*ThreadStack.RecursionDepth)[Top.first].isRecursive())
1940b57cec5SDimitry Andric         recordLatency(Top.first, diff(Top.second, Record.TSC));
195e8d8bef9SDimitry Andric       if (ThreadStack.RecursionDepth)
196e8d8bef9SDimitry Andric         --(*ThreadStack.RecursionDepth)[Top.first];
197e8d8bef9SDimitry Andric       ThreadStack.Stack.pop_back();
1980b57cec5SDimitry Andric       break;
1990b57cec5SDimitry Andric     }
2000b57cec5SDimitry Andric 
2010b57cec5SDimitry Andric     if (!DeduceSiblingCalls)
2020b57cec5SDimitry Andric       return false;
2030b57cec5SDimitry Andric 
2040b57cec5SDimitry Andric     // Look for the parent up the stack.
2050b57cec5SDimitry Andric     auto Parent =
206bdd1243dSDimitry Andric         llvm::find_if(llvm::reverse(ThreadStack.Stack),
2070b57cec5SDimitry Andric                       [&](const std::pair<const int32_t, uint64_t> &E) {
2080b57cec5SDimitry Andric                         return E.first == Record.FuncId;
2090b57cec5SDimitry Andric                       });
210e8d8bef9SDimitry Andric     if (Parent == ThreadStack.Stack.rend())
2110b57cec5SDimitry Andric       return false;
2120b57cec5SDimitry Andric 
2130b57cec5SDimitry Andric     // Account time for this apparently sibling call exit up the stack.
2140b57cec5SDimitry Andric     // Considering the following case:
2150b57cec5SDimitry Andric     //
2160b57cec5SDimitry Andric     //   f()
2170b57cec5SDimitry Andric     //    g()
2180b57cec5SDimitry Andric     //      h()
2190b57cec5SDimitry Andric     //
2200b57cec5SDimitry Andric     // We might only ever see the following entries:
2210b57cec5SDimitry Andric     //
2220b57cec5SDimitry Andric     //   -> f()
2230b57cec5SDimitry Andric     //   -> g()
2240b57cec5SDimitry Andric     //   -> h()
2250b57cec5SDimitry Andric     //   <- h()
2260b57cec5SDimitry Andric     //   <- f()
2270b57cec5SDimitry Andric     //
2280b57cec5SDimitry Andric     // Now we don't see the exit to g() because some older version of the XRay
2290b57cec5SDimitry Andric     // runtime wasn't instrumenting tail exits. If we don't deduce tail calls,
2300b57cec5SDimitry Andric     // we may potentially never account time for g() -- and this code would have
2310b57cec5SDimitry Andric     // already bailed out, because `<- f()` doesn't match the current "top" of
2320b57cec5SDimitry Andric     // stack where we're waiting for the exit to `g()` instead. This is not
2330b57cec5SDimitry Andric     // ideal and brittle -- so instead we provide a potentially inaccurate
2340b57cec5SDimitry Andric     // accounting of g() instead, computing it from the exit of f().
2350b57cec5SDimitry Andric     //
2360b57cec5SDimitry Andric     // While it might be better that we account the time between `-> g()` and
2370b57cec5SDimitry Andric     // `-> h()` as the proper accounting of time for g() here, this introduces
2380b57cec5SDimitry Andric     // complexity to do correctly (need to backtrack, etc.).
2390b57cec5SDimitry Andric     //
2400b57cec5SDimitry Andric     // FIXME: Potentially implement the more complex deduction algorithm?
241e8d8bef9SDimitry Andric     auto R = make_range(std::next(Parent).base(), ThreadStack.Stack.end());
242e8d8bef9SDimitry Andric     for (auto &E : R) {
243e8d8bef9SDimitry Andric       if (!ThreadStack.RecursionDepth ||
244e8d8bef9SDimitry Andric           (*ThreadStack.RecursionDepth)[E.first].isRecursive())
2450b57cec5SDimitry Andric         recordLatency(E.first, diff(E.second, Record.TSC));
2460b57cec5SDimitry Andric     }
247e8d8bef9SDimitry Andric     for (auto &Top : reverse(R)) {
248e8d8bef9SDimitry Andric       if (ThreadStack.RecursionDepth)
249e8d8bef9SDimitry Andric         --(*ThreadStack.RecursionDepth)[Top.first];
250e8d8bef9SDimitry Andric       ThreadStack.Stack.pop_back();
251e8d8bef9SDimitry Andric     }
2520b57cec5SDimitry Andric     break;
2530b57cec5SDimitry Andric   }
2540b57cec5SDimitry Andric   }
2550b57cec5SDimitry Andric 
2560b57cec5SDimitry Andric   return true;
2570b57cec5SDimitry Andric }
2580b57cec5SDimitry Andric 
2590b57cec5SDimitry Andric namespace {
2600b57cec5SDimitry Andric 
2610b57cec5SDimitry Andric // We consolidate the data into a struct which we can output in various forms.
2620b57cec5SDimitry Andric struct ResultRow {
2630b57cec5SDimitry Andric   uint64_t Count;
2640b57cec5SDimitry Andric   double Min;
2650b57cec5SDimitry Andric   double Median;
2660b57cec5SDimitry Andric   double Pct90;
2670b57cec5SDimitry Andric   double Pct99;
2680b57cec5SDimitry Andric   double Max;
2690b57cec5SDimitry Andric   double Sum;
2700b57cec5SDimitry Andric   std::string DebugInfo;
2710b57cec5SDimitry Andric   std::string Function;
2720b57cec5SDimitry Andric };
2730b57cec5SDimitry Andric 
getStats(MutableArrayRef<uint64_t> Timings)274e8d8bef9SDimitry Andric ResultRow getStats(MutableArrayRef<uint64_t> Timings) {
2750b57cec5SDimitry Andric   assert(!Timings.empty());
2760b57cec5SDimitry Andric   ResultRow R;
2770b57cec5SDimitry Andric   R.Sum = std::accumulate(Timings.begin(), Timings.end(), 0.0);
2780b57cec5SDimitry Andric   auto MinMax = std::minmax_element(Timings.begin(), Timings.end());
2790b57cec5SDimitry Andric   R.Min = *MinMax.first;
2800b57cec5SDimitry Andric   R.Max = *MinMax.second;
2810b57cec5SDimitry Andric   R.Count = Timings.size();
2820b57cec5SDimitry Andric 
2830b57cec5SDimitry Andric   auto MedianOff = Timings.size() / 2;
2840b57cec5SDimitry Andric   std::nth_element(Timings.begin(), Timings.begin() + MedianOff, Timings.end());
2850b57cec5SDimitry Andric   R.Median = Timings[MedianOff];
2860b57cec5SDimitry Andric 
2870b57cec5SDimitry Andric   auto Pct90Off = std::floor(Timings.size() * 0.9);
288e8d8bef9SDimitry Andric   std::nth_element(Timings.begin(), Timings.begin() + (uint64_t)Pct90Off,
289e8d8bef9SDimitry Andric                    Timings.end());
2900b57cec5SDimitry Andric   R.Pct90 = Timings[Pct90Off];
2910b57cec5SDimitry Andric 
2920b57cec5SDimitry Andric   auto Pct99Off = std::floor(Timings.size() * 0.99);
293e8d8bef9SDimitry Andric   std::nth_element(Timings.begin(), Timings.begin() + (uint64_t)Pct99Off,
294e8d8bef9SDimitry Andric                    Timings.end());
2950b57cec5SDimitry Andric   R.Pct99 = Timings[Pct99Off];
2960b57cec5SDimitry Andric   return R;
2970b57cec5SDimitry Andric }
2980b57cec5SDimitry Andric 
2990b57cec5SDimitry Andric } // namespace
3000b57cec5SDimitry Andric 
3010b57cec5SDimitry Andric using TupleType = std::tuple<int32_t, uint64_t, ResultRow>;
3020b57cec5SDimitry Andric 
3030b57cec5SDimitry Andric template <typename F>
sortByKey(std::vector<TupleType> & Results,F Fn)3040b57cec5SDimitry Andric static void sortByKey(std::vector<TupleType> &Results, F Fn) {
3050b57cec5SDimitry Andric   bool ASC = AccountSortOrder == SortDirection::ASCENDING;
3060b57cec5SDimitry Andric   llvm::sort(Results, [=](const TupleType &L, const TupleType &R) {
3070b57cec5SDimitry Andric     return ASC ? Fn(L) < Fn(R) : Fn(L) > Fn(R);
3080b57cec5SDimitry Andric   });
3090b57cec5SDimitry Andric }
3100b57cec5SDimitry Andric 
3110b57cec5SDimitry Andric template <class F>
exportStats(const XRayFileHeader & Header,F Fn) const3120b57cec5SDimitry Andric void LatencyAccountant::exportStats(const XRayFileHeader &Header, F Fn) const {
3130b57cec5SDimitry Andric   std::vector<TupleType> Results;
3140b57cec5SDimitry Andric   Results.reserve(FunctionLatencies.size());
3150b57cec5SDimitry Andric   for (auto FT : FunctionLatencies) {
3160b57cec5SDimitry Andric     const auto &FuncId = FT.first;
3170b57cec5SDimitry Andric     auto &Timings = FT.second;
3180b57cec5SDimitry Andric     Results.emplace_back(FuncId, Timings.size(), getStats(Timings));
3190b57cec5SDimitry Andric     auto &Row = std::get<2>(Results.back());
3200b57cec5SDimitry Andric     if (Header.CycleFrequency) {
3210b57cec5SDimitry Andric       double CycleFrequency = Header.CycleFrequency;
3220b57cec5SDimitry Andric       Row.Min /= CycleFrequency;
3230b57cec5SDimitry Andric       Row.Median /= CycleFrequency;
3240b57cec5SDimitry Andric       Row.Pct90 /= CycleFrequency;
3250b57cec5SDimitry Andric       Row.Pct99 /= CycleFrequency;
3260b57cec5SDimitry Andric       Row.Max /= CycleFrequency;
3270b57cec5SDimitry Andric       Row.Sum /= CycleFrequency;
3280b57cec5SDimitry Andric     }
3290b57cec5SDimitry Andric 
3300b57cec5SDimitry Andric     Row.Function = FuncIdHelper.SymbolOrNumber(FuncId);
3310b57cec5SDimitry Andric     Row.DebugInfo = FuncIdHelper.FileLineAndColumn(FuncId);
3320b57cec5SDimitry Andric   }
3330b57cec5SDimitry Andric 
3340b57cec5SDimitry Andric   // Sort the data according to user-provided flags.
3350b57cec5SDimitry Andric   switch (AccountSortOutput) {
3360b57cec5SDimitry Andric   case SortField::FUNCID:
3370b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<0>(X); });
3380b57cec5SDimitry Andric     break;
3390b57cec5SDimitry Andric   case SortField::COUNT:
3400b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<1>(X); });
3410b57cec5SDimitry Andric     break;
3420b57cec5SDimitry Andric   case SortField::MIN:
3430b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Min; });
3440b57cec5SDimitry Andric     break;
3450b57cec5SDimitry Andric   case SortField::MED:
3460b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Median; });
3470b57cec5SDimitry Andric     break;
3480b57cec5SDimitry Andric   case SortField::PCT90:
3490b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct90; });
3500b57cec5SDimitry Andric     break;
3510b57cec5SDimitry Andric   case SortField::PCT99:
3520b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct99; });
3530b57cec5SDimitry Andric     break;
3540b57cec5SDimitry Andric   case SortField::MAX:
3550b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Max; });
3560b57cec5SDimitry Andric     break;
3570b57cec5SDimitry Andric   case SortField::SUM:
3580b57cec5SDimitry Andric     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Sum; });
3590b57cec5SDimitry Andric     break;
3600b57cec5SDimitry Andric   case SortField::FUNC:
3610b57cec5SDimitry Andric     llvm_unreachable("Not implemented");
3620b57cec5SDimitry Andric   }
3630b57cec5SDimitry Andric 
3640b57cec5SDimitry Andric   if (AccountTop > 0) {
3650b57cec5SDimitry Andric     auto MaxTop =
3660b57cec5SDimitry Andric         std::min(AccountTop.getValue(), static_cast<int>(Results.size()));
3670b57cec5SDimitry Andric     Results.erase(Results.begin() + MaxTop, Results.end());
3680b57cec5SDimitry Andric   }
3690b57cec5SDimitry Andric 
3700b57cec5SDimitry Andric   for (const auto &R : Results)
3710b57cec5SDimitry Andric     Fn(std::get<0>(R), std::get<1>(R), std::get<2>(R));
3720b57cec5SDimitry Andric }
3730b57cec5SDimitry Andric 
exportStatsAsText(raw_ostream & OS,const XRayFileHeader & Header) const3740b57cec5SDimitry Andric void LatencyAccountant::exportStatsAsText(raw_ostream &OS,
3750b57cec5SDimitry Andric                                           const XRayFileHeader &Header) const {
3760b57cec5SDimitry Andric   OS << "Functions with latencies: " << FunctionLatencies.size() << "\n";
3770b57cec5SDimitry Andric 
3780b57cec5SDimitry Andric   // We spend some effort to make the text output more readable, so we do the
3790b57cec5SDimitry Andric   // following formatting decisions for each of the fields:
3800b57cec5SDimitry Andric   //
3810b57cec5SDimitry Andric   //   - funcid: 32-bit, but we can determine the largest number and be
3820b57cec5SDimitry Andric   //   between
3830b57cec5SDimitry Andric   //     a minimum of 5 characters, up to 9 characters, right aligned.
3840b57cec5SDimitry Andric   //   - count:  64-bit, but we can determine the largest number and be
3850b57cec5SDimitry Andric   //   between
3860b57cec5SDimitry Andric   //     a minimum of 5 characters, up to 9 characters, right aligned.
3870b57cec5SDimitry Andric   //   - min, median, 90pct, 99pct, max: double precision, but we want to keep
3880b57cec5SDimitry Andric   //     the values in seconds, with microsecond precision (0.000'001), so we
3890b57cec5SDimitry Andric   //     have at most 6 significant digits, with the whole number part to be
3900b57cec5SDimitry Andric   //     at
3910b57cec5SDimitry Andric   //     least 1 character. For readability we'll right-align, with full 9
3920b57cec5SDimitry Andric   //     characters each.
3930b57cec5SDimitry Andric   //   - debug info, function name: we format this as a concatenation of the
3940b57cec5SDimitry Andric   //     debug info and the function name.
3950b57cec5SDimitry Andric   //
3960b57cec5SDimitry Andric   static constexpr char StatsHeaderFormat[] =
3970b57cec5SDimitry Andric       "{0,+9} {1,+10} [{2,+9}, {3,+9}, {4,+9}, {5,+9}, {6,+9}] {7,+9}";
3980b57cec5SDimitry Andric   static constexpr char StatsFormat[] =
3990b57cec5SDimitry Andric       R"({0,+9} {1,+10} [{2,+9:f6}, {3,+9:f6}, {4,+9:f6}, {5,+9:f6}, {6,+9:f6}] {7,+9:f6})";
4000b57cec5SDimitry Andric   OS << llvm::formatv(StatsHeaderFormat, "funcid", "count", "min", "med", "90p",
4010b57cec5SDimitry Andric                       "99p", "max", "sum")
4020b57cec5SDimitry Andric      << llvm::formatv("  {0,-12}\n", "function");
4030b57cec5SDimitry Andric   exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) {
4040b57cec5SDimitry Andric     OS << llvm::formatv(StatsFormat, FuncId, Count, Row.Min, Row.Median,
4050b57cec5SDimitry Andric                         Row.Pct90, Row.Pct99, Row.Max, Row.Sum)
4060b57cec5SDimitry Andric        << "  " << Row.DebugInfo << ": " << Row.Function << "\n";
4070b57cec5SDimitry Andric   });
4080b57cec5SDimitry Andric }
4090b57cec5SDimitry Andric 
exportStatsAsCSV(raw_ostream & OS,const XRayFileHeader & Header) const4100b57cec5SDimitry Andric void LatencyAccountant::exportStatsAsCSV(raw_ostream &OS,
4110b57cec5SDimitry Andric                                          const XRayFileHeader &Header) const {
4120b57cec5SDimitry Andric   OS << "funcid,count,min,median,90%ile,99%ile,max,sum,debug,function\n";
4130b57cec5SDimitry Andric   exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) {
4140b57cec5SDimitry Andric     OS << FuncId << ',' << Count << ',' << Row.Min << ',' << Row.Median << ','
4150b57cec5SDimitry Andric        << Row.Pct90 << ',' << Row.Pct99 << ',' << Row.Max << "," << Row.Sum
4160b57cec5SDimitry Andric        << ",\"" << Row.DebugInfo << "\",\"" << Row.Function << "\"\n";
4170b57cec5SDimitry Andric   });
4180b57cec5SDimitry Andric }
4190b57cec5SDimitry Andric 
4200b57cec5SDimitry Andric using namespace llvm::xray;
4210b57cec5SDimitry Andric 
4220b57cec5SDimitry Andric namespace llvm {
4230b57cec5SDimitry Andric template <> struct format_provider<llvm::xray::RecordTypes> {
formatllvm::format_provider4240b57cec5SDimitry Andric   static void format(const llvm::xray::RecordTypes &T, raw_ostream &Stream,
4250b57cec5SDimitry Andric                      StringRef Style) {
4260b57cec5SDimitry Andric     switch (T) {
4270b57cec5SDimitry Andric     case RecordTypes::ENTER:
4280b57cec5SDimitry Andric       Stream << "enter";
4290b57cec5SDimitry Andric       break;
4300b57cec5SDimitry Andric     case RecordTypes::ENTER_ARG:
4310b57cec5SDimitry Andric       Stream << "enter-arg";
4320b57cec5SDimitry Andric       break;
4330b57cec5SDimitry Andric     case RecordTypes::EXIT:
4340b57cec5SDimitry Andric       Stream << "exit";
4350b57cec5SDimitry Andric       break;
4360b57cec5SDimitry Andric     case RecordTypes::TAIL_EXIT:
4370b57cec5SDimitry Andric       Stream << "tail-exit";
4380b57cec5SDimitry Andric       break;
4390b57cec5SDimitry Andric     case RecordTypes::CUSTOM_EVENT:
4400b57cec5SDimitry Andric       Stream << "custom-event";
4410b57cec5SDimitry Andric       break;
4420b57cec5SDimitry Andric     case RecordTypes::TYPED_EVENT:
4430b57cec5SDimitry Andric       Stream << "typed-event";
4440b57cec5SDimitry Andric       break;
4450b57cec5SDimitry Andric     }
4460b57cec5SDimitry Andric   }
4470b57cec5SDimitry Andric };
4480b57cec5SDimitry Andric } // namespace llvm
4490b57cec5SDimitry Andric 
__anonc21121eb0f02() 4500b57cec5SDimitry Andric static CommandRegistration Unused(&Account, []() -> Error {
4510b57cec5SDimitry Andric   InstrumentationMap Map;
4520b57cec5SDimitry Andric   if (!AccountInstrMap.empty()) {
4530b57cec5SDimitry Andric     auto InstrumentationMapOrError = loadInstrumentationMap(AccountInstrMap);
4540b57cec5SDimitry Andric     if (!InstrumentationMapOrError)
4550b57cec5SDimitry Andric       return joinErrors(make_error<StringError>(
4560b57cec5SDimitry Andric                             Twine("Cannot open instrumentation map '") +
4570b57cec5SDimitry Andric                                 AccountInstrMap + "'",
4580b57cec5SDimitry Andric                             std::make_error_code(std::errc::invalid_argument)),
4590b57cec5SDimitry Andric                         InstrumentationMapOrError.takeError());
4600b57cec5SDimitry Andric     Map = std::move(*InstrumentationMapOrError);
4610b57cec5SDimitry Andric   }
4620b57cec5SDimitry Andric 
4630b57cec5SDimitry Andric   std::error_code EC;
464fe6060f1SDimitry Andric   raw_fd_ostream OS(AccountOutput, EC, sys::fs::OpenFlags::OF_TextWithCRLF);
4650b57cec5SDimitry Andric   if (EC)
4660b57cec5SDimitry Andric     return make_error<StringError>(
4670b57cec5SDimitry Andric         Twine("Cannot open file '") + AccountOutput + "' for writing.", EC);
4680b57cec5SDimitry Andric 
4690b57cec5SDimitry Andric   const auto &FunctionAddresses = Map.getFunctionAddresses();
4700b57cec5SDimitry Andric   symbolize::LLVMSymbolizer Symbolizer;
4710b57cec5SDimitry Andric   llvm::xray::FuncIdConversionHelper FuncIdHelper(AccountInstrMap, Symbolizer,
4720b57cec5SDimitry Andric                                                   FunctionAddresses);
473e8d8bef9SDimitry Andric   xray::LatencyAccountant FCA(FuncIdHelper, AccountRecursiveCallsOnly,
474e8d8bef9SDimitry Andric                               AccountDeduceSiblingCalls);
4750b57cec5SDimitry Andric   auto TraceOrErr = loadTraceFile(AccountInput);
4760b57cec5SDimitry Andric   if (!TraceOrErr)
4770b57cec5SDimitry Andric     return joinErrors(
4780b57cec5SDimitry Andric         make_error<StringError>(
4790b57cec5SDimitry Andric             Twine("Failed loading input file '") + AccountInput + "'",
4800b57cec5SDimitry Andric             std::make_error_code(std::errc::executable_format_error)),
4810b57cec5SDimitry Andric         TraceOrErr.takeError());
4820b57cec5SDimitry Andric 
4830b57cec5SDimitry Andric   auto &T = *TraceOrErr;
4840b57cec5SDimitry Andric   for (const auto &Record : T) {
4850b57cec5SDimitry Andric     if (FCA.accountRecord(Record))
4860b57cec5SDimitry Andric       continue;
4870b57cec5SDimitry Andric     errs()
4880b57cec5SDimitry Andric         << "Error processing record: "
4890b57cec5SDimitry Andric         << llvm::formatv(
4900b57cec5SDimitry Andric                R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}; process-id: {6}}})",
4910b57cec5SDimitry Andric                Record.RecordType, Record.CPU, Record.Type, Record.FuncId,
4920b57cec5SDimitry Andric                Record.TSC, Record.TId, Record.PId)
4930b57cec5SDimitry Andric         << '\n';
4940b57cec5SDimitry Andric     for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) {
4950b57cec5SDimitry Andric       errs() << "Thread ID: " << ThreadStack.first << "\n";
496e8d8bef9SDimitry Andric       if (ThreadStack.second.Stack.empty()) {
4970b57cec5SDimitry Andric         errs() << "  (empty stack)\n";
4980b57cec5SDimitry Andric         continue;
4990b57cec5SDimitry Andric       }
500e8d8bef9SDimitry Andric       auto Level = ThreadStack.second.Stack.size();
501e8d8bef9SDimitry Andric       for (const auto &Entry : llvm::reverse(ThreadStack.second.Stack))
5020b57cec5SDimitry Andric         errs() << "  #" << Level-- << "\t"
5030b57cec5SDimitry Andric                << FuncIdHelper.SymbolOrNumber(Entry.first) << '\n';
5040b57cec5SDimitry Andric     }
5050b57cec5SDimitry Andric     if (!AccountKeepGoing)
5060b57cec5SDimitry Andric       return make_error<StringError>(
5070b57cec5SDimitry Andric           Twine("Failed accounting function calls in file '") + AccountInput +
5080b57cec5SDimitry Andric               "'.",
5090b57cec5SDimitry Andric           std::make_error_code(std::errc::executable_format_error));
5100b57cec5SDimitry Andric   }
5110b57cec5SDimitry Andric   switch (AccountOutputFormat) {
5120b57cec5SDimitry Andric   case AccountOutputFormats::TEXT:
5130b57cec5SDimitry Andric     FCA.exportStatsAsText(OS, T.getFileHeader());
5140b57cec5SDimitry Andric     break;
5150b57cec5SDimitry Andric   case AccountOutputFormats::CSV:
5160b57cec5SDimitry Andric     FCA.exportStatsAsCSV(OS, T.getFileHeader());
5170b57cec5SDimitry Andric     break;
5180b57cec5SDimitry Andric   }
5190b57cec5SDimitry Andric 
5200b57cec5SDimitry Andric   return Error::success();
5210b57cec5SDimitry Andric });
522