xref: /llvm-project/llvm/tools/llvm-xray/xray-account.cpp (revision 9aae408d551083bbbac96ecc512d45c30358e4b9)
1a0e3ae4cSDean Michael Berris //===- xray-account.h - XRay Function Call Accounting ---------------------===//
2a0e3ae4cSDean Michael Berris //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6a0e3ae4cSDean Michael Berris //
7a0e3ae4cSDean Michael Berris //===----------------------------------------------------------------------===//
8a0e3ae4cSDean Michael Berris //
9a0e3ae4cSDean Michael Berris // This file implements basic function call accounting from an XRay trace.
10a0e3ae4cSDean Michael Berris //
11a0e3ae4cSDean Michael Berris //===----------------------------------------------------------------------===//
12a0e3ae4cSDean Michael Berris 
13a0e3ae4cSDean Michael Berris #include <algorithm>
14a0e3ae4cSDean Michael Berris #include <cassert>
15a0e3ae4cSDean Michael Berris #include <numeric>
16a0e3ae4cSDean Michael Berris #include <system_error>
17a0e3ae4cSDean Michael Berris #include <utility>
18a0e3ae4cSDean Michael Berris 
19a0e3ae4cSDean Michael Berris #include "xray-account.h"
20a0e3ae4cSDean Michael Berris #include "xray-registry.h"
21a0e3ae4cSDean Michael Berris #include "llvm/Support/ErrorHandling.h"
22a0e3ae4cSDean Michael Berris #include "llvm/Support/FormatVariadic.h"
23a0e3ae4cSDean Michael Berris #include "llvm/XRay/InstrumentationMap.h"
24a0e3ae4cSDean Michael Berris #include "llvm/XRay/Trace.h"
25a0e3ae4cSDean Michael Berris 
2616544cbeSserge-sans-paille #include <cmath>
2716544cbeSserge-sans-paille 
28a0e3ae4cSDean Michael Berris using namespace llvm;
29a0e3ae4cSDean Michael Berris using namespace llvm::xray;
30a0e3ae4cSDean Michael Berris 
31a0e3ae4cSDean Michael Berris static cl::SubCommand Account("account", "Function call accounting");
32a0e3ae4cSDean Michael Berris static cl::opt<std::string> AccountInput(cl::Positional,
33a0e3ae4cSDean Michael Berris                                          cl::desc("<xray log file>"),
34a0e3ae4cSDean Michael Berris                                          cl::Required, cl::sub(Account));
35a0e3ae4cSDean Michael Berris static cl::opt<bool>
36a0e3ae4cSDean Michael Berris     AccountKeepGoing("keep-going", cl::desc("Keep going on errors encountered"),
37a0e3ae4cSDean Michael Berris                      cl::sub(Account), cl::init(false));
38a0e3ae4cSDean Michael Berris static cl::alias AccountKeepGoing2("k", cl::aliasopt(AccountKeepGoing),
39995c18fcSShoaib Meenai                                    cl::desc("Alias for -keep_going"));
40f2ab2134SRoman Lebedev static cl::opt<bool> AccountRecursiveCallsOnly(
41f2ab2134SRoman Lebedev     "recursive-calls-only", cl::desc("Only count the calls that are recursive"),
42f2ab2134SRoman Lebedev     cl::sub(Account), cl::init(false));
43a0e3ae4cSDean Michael Berris static cl::opt<bool> AccountDeduceSiblingCalls(
44a0e3ae4cSDean Michael Berris     "deduce-sibling-calls",
45a0e3ae4cSDean Michael Berris     cl::desc("Deduce sibling calls when unrolling function call stacks"),
46a0e3ae4cSDean Michael Berris     cl::sub(Account), cl::init(false));
47a0e3ae4cSDean Michael Berris static cl::alias
48a0e3ae4cSDean Michael Berris     AccountDeduceSiblingCalls2("d", cl::aliasopt(AccountDeduceSiblingCalls),
49995c18fcSShoaib Meenai                                cl::desc("Alias for -deduce_sibling_calls"));
50a0e3ae4cSDean Michael Berris static cl::opt<std::string>
51a0e3ae4cSDean Michael Berris     AccountOutput("output", cl::value_desc("output file"), cl::init("-"),
52a0e3ae4cSDean Michael Berris                   cl::desc("output file; use '-' for stdout"),
53a0e3ae4cSDean Michael Berris                   cl::sub(Account));
54a0e3ae4cSDean Michael Berris static cl::alias AccountOutput2("o", cl::aliasopt(AccountOutput),
55995c18fcSShoaib Meenai                                 cl::desc("Alias for -output"));
56a0e3ae4cSDean Michael Berris enum class AccountOutputFormats { TEXT, CSV };
57a0e3ae4cSDean Michael Berris static cl::opt<AccountOutputFormats>
58a0e3ae4cSDean Michael Berris     AccountOutputFormat("format", cl::desc("output format"),
59a0e3ae4cSDean Michael Berris                         cl::values(clEnumValN(AccountOutputFormats::TEXT,
60a0e3ae4cSDean Michael Berris                                               "text", "report stats in text"),
61a0e3ae4cSDean Michael Berris                                    clEnumValN(AccountOutputFormats::CSV, "csv",
62a0e3ae4cSDean Michael Berris                                               "report stats in csv")),
63a0e3ae4cSDean Michael Berris                         cl::sub(Account));
64a0e3ae4cSDean Michael Berris static cl::alias AccountOutputFormat2("f", cl::desc("Alias of -format"),
65995c18fcSShoaib Meenai                                       cl::aliasopt(AccountOutputFormat));
66a0e3ae4cSDean Michael Berris 
67a0e3ae4cSDean Michael Berris enum class SortField {
68a0e3ae4cSDean Michael Berris   FUNCID,
69a0e3ae4cSDean Michael Berris   COUNT,
70a0e3ae4cSDean Michael Berris   MIN,
71a0e3ae4cSDean Michael Berris   MED,
72a0e3ae4cSDean Michael Berris   PCT90,
73a0e3ae4cSDean Michael Berris   PCT99,
74a0e3ae4cSDean Michael Berris   MAX,
75a0e3ae4cSDean Michael Berris   SUM,
76a0e3ae4cSDean Michael Berris   FUNC,
77a0e3ae4cSDean Michael Berris };
78a0e3ae4cSDean Michael Berris 
79a0e3ae4cSDean Michael Berris static cl::opt<SortField> AccountSortOutput(
80a0e3ae4cSDean Michael Berris     "sort", cl::desc("sort output by this field"), cl::value_desc("field"),
81a0e3ae4cSDean Michael Berris     cl::sub(Account), cl::init(SortField::FUNCID),
82a0e3ae4cSDean Michael Berris     cl::values(clEnumValN(SortField::FUNCID, "funcid", "function id"),
83*9aae408dSYuanfang Chen                clEnumValN(SortField::COUNT, "count", "function call counts"),
84a0e3ae4cSDean Michael Berris                clEnumValN(SortField::MIN, "min", "minimum function durations"),
85a0e3ae4cSDean Michael Berris                clEnumValN(SortField::MED, "med", "median function durations"),
86a0e3ae4cSDean Michael Berris                clEnumValN(SortField::PCT90, "90p", "90th percentile durations"),
87a0e3ae4cSDean Michael Berris                clEnumValN(SortField::PCT99, "99p", "99th percentile durations"),
88a0e3ae4cSDean Michael Berris                clEnumValN(SortField::MAX, "max", "maximum function durations"),
89a0e3ae4cSDean Michael Berris                clEnumValN(SortField::SUM, "sum", "sum of call durations"),
90a0e3ae4cSDean Michael Berris                clEnumValN(SortField::FUNC, "func", "function names")));
91a0e3ae4cSDean Michael Berris static cl::alias AccountSortOutput2("s", cl::aliasopt(AccountSortOutput),
92995c18fcSShoaib Meenai                                     cl::desc("Alias for -sort"));
93a0e3ae4cSDean Michael Berris 
94a0e3ae4cSDean Michael Berris enum class SortDirection {
95a0e3ae4cSDean Michael Berris   ASCENDING,
96a0e3ae4cSDean Michael Berris   DESCENDING,
97a0e3ae4cSDean Michael Berris };
98a0e3ae4cSDean Michael Berris static cl::opt<SortDirection> AccountSortOrder(
99a0e3ae4cSDean Michael Berris     "sortorder", cl::desc("sort ordering"), cl::init(SortDirection::ASCENDING),
100a0e3ae4cSDean Michael Berris     cl::values(clEnumValN(SortDirection::ASCENDING, "asc", "ascending"),
101a0e3ae4cSDean Michael Berris                clEnumValN(SortDirection::DESCENDING, "dsc", "descending")),
102a0e3ae4cSDean Michael Berris     cl::sub(Account));
103a0e3ae4cSDean Michael Berris static cl::alias AccountSortOrder2("r", cl::aliasopt(AccountSortOrder),
104995c18fcSShoaib Meenai                                    cl::desc("Alias for -sortorder"));
105a0e3ae4cSDean Michael Berris 
106a0e3ae4cSDean Michael Berris static cl::opt<int> AccountTop("top", cl::desc("only show the top N results"),
107a0e3ae4cSDean Michael Berris                                cl::value_desc("N"), cl::sub(Account),
108a0e3ae4cSDean Michael Berris                                cl::init(-1));
109a0e3ae4cSDean Michael Berris static cl::alias AccountTop2("p", cl::desc("Alias for -top"),
110995c18fcSShoaib Meenai                              cl::aliasopt(AccountTop));
111a0e3ae4cSDean Michael Berris 
112a0e3ae4cSDean Michael Berris static cl::opt<std::string>
113a0e3ae4cSDean Michael Berris     AccountInstrMap("instr_map",
114a0e3ae4cSDean Michael Berris                     cl::desc("binary with the instrumentation map, or "
115a0e3ae4cSDean Michael Berris                              "a separate instrumentation map"),
116a0e3ae4cSDean Michael Berris                     cl::value_desc("binary with xray_instr_map"),
117a0e3ae4cSDean Michael Berris                     cl::sub(Account), cl::init(""));
118a0e3ae4cSDean Michael Berris static cl::alias AccountInstrMap2("m", cl::aliasopt(AccountInstrMap),
119995c18fcSShoaib Meenai                                   cl::desc("Alias for -instr_map"));
120a0e3ae4cSDean Michael Berris 
121a0e3ae4cSDean Michael Berris namespace {
122a0e3ae4cSDean Michael Berris 
setMinMax(std::pair<T,T> & MM,U && V)123a0e3ae4cSDean Michael Berris template <class T, class U> void setMinMax(std::pair<T, T> &MM, U &&V) {
124a0e3ae4cSDean Michael Berris   if (MM.first == 0 || MM.second == 0)
125a0e3ae4cSDean Michael Berris     MM = std::make_pair(std::forward<U>(V), std::forward<U>(V));
126a0e3ae4cSDean Michael Berris   else
127a0e3ae4cSDean Michael Berris     MM = std::make_pair(std::min(MM.first, V), std::max(MM.second, V));
128a0e3ae4cSDean Michael Berris }
129a0e3ae4cSDean Michael Berris 
diff(T L,T R)130a0e3ae4cSDean Michael Berris template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); }
131a0e3ae4cSDean Michael Berris 
132a0e3ae4cSDean Michael Berris } // namespace
133a0e3ae4cSDean Michael Berris 
134f2ab2134SRoman Lebedev using RecursionStatus = LatencyAccountant::FunctionStack::RecursionStatus;
operator ++()135f2ab2134SRoman Lebedev RecursionStatus &RecursionStatus::operator++() {
136f2ab2134SRoman Lebedev   auto Depth = Bitfield::get<RecursionStatus::Depth>(Storage);
137f2ab2134SRoman Lebedev   assert(Depth >= 0 && Depth < std::numeric_limits<decltype(Depth)>::max());
138f2ab2134SRoman Lebedev   ++Depth;
139f2ab2134SRoman Lebedev   Bitfield::set<RecursionStatus::Depth>(Storage, Depth); // ++Storage
140f2ab2134SRoman Lebedev   // Did this function just (maybe indirectly) call itself the first time?
141f2ab2134SRoman Lebedev   if (!isRecursive() && Depth == 2) // Storage == 2  /  Storage s> 1
142f2ab2134SRoman Lebedev     Bitfield::set<RecursionStatus::IsRecursive>(Storage,
143f2ab2134SRoman Lebedev                                                 true); // Storage |= INT_MIN
144f2ab2134SRoman Lebedev   return *this;
145f2ab2134SRoman Lebedev }
operator --()146f2ab2134SRoman Lebedev RecursionStatus &RecursionStatus::operator--() {
147f2ab2134SRoman Lebedev   auto Depth = Bitfield::get<RecursionStatus::Depth>(Storage);
148f2ab2134SRoman Lebedev   assert(Depth > 0);
149f2ab2134SRoman Lebedev   --Depth;
150f2ab2134SRoman Lebedev   Bitfield::set<RecursionStatus::Depth>(Storage, Depth); // --Storage
151f2ab2134SRoman Lebedev   // Did we leave a function that previouly (maybe indirectly) called itself?
152f2ab2134SRoman Lebedev   if (isRecursive() && Depth == 0) // Storage == INT_MIN
153f2ab2134SRoman Lebedev     Bitfield::set<RecursionStatus::IsRecursive>(Storage, false); // Storage = 0
154f2ab2134SRoman Lebedev   return *this;
155f2ab2134SRoman Lebedev }
isRecursive() const156f2ab2134SRoman Lebedev bool RecursionStatus::isRecursive() const {
157f2ab2134SRoman Lebedev   return Bitfield::get<RecursionStatus::IsRecursive>(Storage); // Storage s< 0
158f2ab2134SRoman Lebedev }
159f2ab2134SRoman Lebedev 
accountRecord(const XRayRecord & Record)160a0e3ae4cSDean Michael Berris bool LatencyAccountant::accountRecord(const XRayRecord &Record) {
161a0e3ae4cSDean Michael Berris   setMinMax(PerThreadMinMaxTSC[Record.TId], Record.TSC);
162a0e3ae4cSDean Michael Berris   setMinMax(PerCPUMinMaxTSC[Record.CPU], Record.TSC);
163a0e3ae4cSDean Michael Berris 
164a0e3ae4cSDean Michael Berris   if (CurrentMaxTSC == 0)
165a0e3ae4cSDean Michael Berris     CurrentMaxTSC = Record.TSC;
166a0e3ae4cSDean Michael Berris 
167a0e3ae4cSDean Michael Berris   if (Record.TSC < CurrentMaxTSC)
168a0e3ae4cSDean Michael Berris     return false;
169a0e3ae4cSDean Michael Berris 
170a0e3ae4cSDean Michael Berris   auto &ThreadStack = PerThreadFunctionStack[Record.TId];
171f2ab2134SRoman Lebedev   if (RecursiveCallsOnly && !ThreadStack.RecursionDepth)
172f2ab2134SRoman Lebedev     ThreadStack.RecursionDepth.emplace();
173a0e3ae4cSDean Michael Berris   switch (Record.Type) {
17425f8d204SDean Michael Berris   case RecordTypes::CUSTOM_EVENT:
17525f8d204SDean Michael Berris   case RecordTypes::TYPED_EVENT:
17625f8d204SDean Michael Berris     // TODO: Support custom and typed event accounting in the future.
17725f8d204SDean Michael Berris     return true;
178a0e3ae4cSDean Michael Berris   case RecordTypes::ENTER:
179a0e3ae4cSDean Michael Berris   case RecordTypes::ENTER_ARG: {
180f2ab2134SRoman Lebedev     ThreadStack.Stack.emplace_back(Record.FuncId, Record.TSC);
181f2ab2134SRoman Lebedev     if (ThreadStack.RecursionDepth)
182f2ab2134SRoman Lebedev       ++(*ThreadStack.RecursionDepth)[Record.FuncId];
183a0e3ae4cSDean Michael Berris     break;
184a0e3ae4cSDean Michael Berris   }
185a0e3ae4cSDean Michael Berris   case RecordTypes::EXIT:
186a0e3ae4cSDean Michael Berris   case RecordTypes::TAIL_EXIT: {
187f2ab2134SRoman Lebedev     if (ThreadStack.Stack.empty())
188a0e3ae4cSDean Michael Berris       return false;
189a0e3ae4cSDean Michael Berris 
190f2ab2134SRoman Lebedev     if (ThreadStack.Stack.back().first == Record.FuncId) {
191f2ab2134SRoman Lebedev       const auto &Top = ThreadStack.Stack.back();
192f2ab2134SRoman Lebedev       if (!ThreadStack.RecursionDepth ||
193f2ab2134SRoman Lebedev           (*ThreadStack.RecursionDepth)[Top.first].isRecursive())
194a0e3ae4cSDean Michael Berris         recordLatency(Top.first, diff(Top.second, Record.TSC));
195f2ab2134SRoman Lebedev       if (ThreadStack.RecursionDepth)
196f2ab2134SRoman Lebedev         --(*ThreadStack.RecursionDepth)[Top.first];
197f2ab2134SRoman Lebedev       ThreadStack.Stack.pop_back();
198a0e3ae4cSDean Michael Berris       break;
199a0e3ae4cSDean Michael Berris     }
200a0e3ae4cSDean Michael Berris 
201a0e3ae4cSDean Michael Berris     if (!DeduceSiblingCalls)
202a0e3ae4cSDean Michael Berris       return false;
203a0e3ae4cSDean Michael Berris 
204a0e3ae4cSDean Michael Berris     // Look for the parent up the stack.
205a0e3ae4cSDean Michael Berris     auto Parent =
206ce9f007cSKazu Hirata         llvm::find_if(llvm::reverse(ThreadStack.Stack),
207a0e3ae4cSDean Michael Berris                       [&](const std::pair<const int32_t, uint64_t> &E) {
208a0e3ae4cSDean Michael Berris                         return E.first == Record.FuncId;
209a0e3ae4cSDean Michael Berris                       });
210f2ab2134SRoman Lebedev     if (Parent == ThreadStack.Stack.rend())
211a0e3ae4cSDean Michael Berris       return false;
212a0e3ae4cSDean Michael Berris 
213a0e3ae4cSDean Michael Berris     // Account time for this apparently sibling call exit up the stack.
214a0e3ae4cSDean Michael Berris     // Considering the following case:
215a0e3ae4cSDean Michael Berris     //
216a0e3ae4cSDean Michael Berris     //   f()
217a0e3ae4cSDean Michael Berris     //    g()
218a0e3ae4cSDean Michael Berris     //      h()
219a0e3ae4cSDean Michael Berris     //
220a0e3ae4cSDean Michael Berris     // We might only ever see the following entries:
221a0e3ae4cSDean Michael Berris     //
222a0e3ae4cSDean Michael Berris     //   -> f()
223a0e3ae4cSDean Michael Berris     //   -> g()
224a0e3ae4cSDean Michael Berris     //   -> h()
225a0e3ae4cSDean Michael Berris     //   <- h()
226a0e3ae4cSDean Michael Berris     //   <- f()
227a0e3ae4cSDean Michael Berris     //
228a0e3ae4cSDean Michael Berris     // Now we don't see the exit to g() because some older version of the XRay
229a0e3ae4cSDean Michael Berris     // runtime wasn't instrumenting tail exits. If we don't deduce tail calls,
230a0e3ae4cSDean Michael Berris     // we may potentially never account time for g() -- and this code would have
231a0e3ae4cSDean Michael Berris     // already bailed out, because `<- f()` doesn't match the current "top" of
232a0e3ae4cSDean Michael Berris     // stack where we're waiting for the exit to `g()` instead. This is not
233a0e3ae4cSDean Michael Berris     // ideal and brittle -- so instead we provide a potentially inaccurate
234a0e3ae4cSDean Michael Berris     // accounting of g() instead, computing it from the exit of f().
235a0e3ae4cSDean Michael Berris     //
236a0e3ae4cSDean Michael Berris     // While it might be better that we account the time between `-> g()` and
237a0e3ae4cSDean Michael Berris     // `-> h()` as the proper accounting of time for g() here, this introduces
238a0e3ae4cSDean Michael Berris     // complexity to do correctly (need to backtrack, etc.).
239a0e3ae4cSDean Michael Berris     //
240a0e3ae4cSDean Michael Berris     // FIXME: Potentially implement the more complex deduction algorithm?
241f2ab2134SRoman Lebedev     auto R = make_range(std::next(Parent).base(), ThreadStack.Stack.end());
242f2ab2134SRoman Lebedev     for (auto &E : R) {
243f2ab2134SRoman Lebedev       if (!ThreadStack.RecursionDepth ||
244f2ab2134SRoman Lebedev           (*ThreadStack.RecursionDepth)[E.first].isRecursive())
245a0e3ae4cSDean Michael Berris         recordLatency(E.first, diff(E.second, Record.TSC));
246a0e3ae4cSDean Michael Berris     }
247f2ab2134SRoman Lebedev     for (auto &Top : reverse(R)) {
248f2ab2134SRoman Lebedev       if (ThreadStack.RecursionDepth)
249f2ab2134SRoman Lebedev         --(*ThreadStack.RecursionDepth)[Top.first];
250f2ab2134SRoman Lebedev       ThreadStack.Stack.pop_back();
251f2ab2134SRoman Lebedev     }
252a0e3ae4cSDean Michael Berris     break;
253a0e3ae4cSDean Michael Berris   }
254a0e3ae4cSDean Michael Berris   }
255a0e3ae4cSDean Michael Berris 
256a0e3ae4cSDean Michael Berris   return true;
257a0e3ae4cSDean Michael Berris }
258a0e3ae4cSDean Michael Berris 
259a0e3ae4cSDean Michael Berris namespace {
260a0e3ae4cSDean Michael Berris 
261a0e3ae4cSDean Michael Berris // We consolidate the data into a struct which we can output in various forms.
262a0e3ae4cSDean Michael Berris struct ResultRow {
263a0e3ae4cSDean Michael Berris   uint64_t Count;
264a0e3ae4cSDean Michael Berris   double Min;
265a0e3ae4cSDean Michael Berris   double Median;
266a0e3ae4cSDean Michael Berris   double Pct90;
267a0e3ae4cSDean Michael Berris   double Pct99;
268a0e3ae4cSDean Michael Berris   double Max;
269a0e3ae4cSDean Michael Berris   double Sum;
270a0e3ae4cSDean Michael Berris   std::string DebugInfo;
271a0e3ae4cSDean Michael Berris   std::string Function;
272a0e3ae4cSDean Michael Berris };
273a0e3ae4cSDean Michael Berris 
getStats(MutableArrayRef<uint64_t> Timings)274ed5a6b93SRoman Lebedev ResultRow getStats(MutableArrayRef<uint64_t> Timings) {
275a0e3ae4cSDean Michael Berris   assert(!Timings.empty());
276a0e3ae4cSDean Michael Berris   ResultRow R;
277a0e3ae4cSDean Michael Berris   R.Sum = std::accumulate(Timings.begin(), Timings.end(), 0.0);
278a0e3ae4cSDean Michael Berris   auto MinMax = std::minmax_element(Timings.begin(), Timings.end());
279a0e3ae4cSDean Michael Berris   R.Min = *MinMax.first;
280a0e3ae4cSDean Michael Berris   R.Max = *MinMax.second;
281a0e3ae4cSDean Michael Berris   R.Count = Timings.size();
282a0e3ae4cSDean Michael Berris 
283a0e3ae4cSDean Michael Berris   auto MedianOff = Timings.size() / 2;
284a0e3ae4cSDean Michael Berris   std::nth_element(Timings.begin(), Timings.begin() + MedianOff, Timings.end());
285a0e3ae4cSDean Michael Berris   R.Median = Timings[MedianOff];
286a0e3ae4cSDean Michael Berris 
287a0e3ae4cSDean Michael Berris   auto Pct90Off = std::floor(Timings.size() * 0.9);
288ed5a6b93SRoman Lebedev   std::nth_element(Timings.begin(), Timings.begin() + (uint64_t)Pct90Off,
289ed5a6b93SRoman Lebedev                    Timings.end());
290a0e3ae4cSDean Michael Berris   R.Pct90 = Timings[Pct90Off];
291a0e3ae4cSDean Michael Berris 
292a0e3ae4cSDean Michael Berris   auto Pct99Off = std::floor(Timings.size() * 0.99);
293ed5a6b93SRoman Lebedev   std::nth_element(Timings.begin(), Timings.begin() + (uint64_t)Pct99Off,
294ed5a6b93SRoman Lebedev                    Timings.end());
295a0e3ae4cSDean Michael Berris   R.Pct99 = Timings[Pct99Off];
296a0e3ae4cSDean Michael Berris   return R;
297a0e3ae4cSDean Michael Berris }
298a0e3ae4cSDean Michael Berris 
299a0e3ae4cSDean Michael Berris } // namespace
300a0e3ae4cSDean Michael Berris 
3019569523aSFangrui Song using TupleType = std::tuple<int32_t, uint64_t, ResultRow>;
3029569523aSFangrui Song 
3039569523aSFangrui Song template <typename F>
sortByKey(std::vector<TupleType> & Results,F Fn)3049569523aSFangrui Song static void sortByKey(std::vector<TupleType> &Results, F Fn) {
3059569523aSFangrui Song   bool ASC = AccountSortOrder == SortDirection::ASCENDING;
3069569523aSFangrui Song   llvm::sort(Results, [=](const TupleType &L, const TupleType &R) {
3079569523aSFangrui Song     return ASC ? Fn(L) < Fn(R) : Fn(L) > Fn(R);
3089569523aSFangrui Song   });
3099569523aSFangrui Song }
3109569523aSFangrui Song 
311a0e3ae4cSDean Michael Berris template <class F>
exportStats(const XRayFileHeader & Header,F Fn) const312a0e3ae4cSDean Michael Berris void LatencyAccountant::exportStats(const XRayFileHeader &Header, F Fn) const {
313a0e3ae4cSDean Michael Berris   std::vector<TupleType> Results;
314a0e3ae4cSDean Michael Berris   Results.reserve(FunctionLatencies.size());
315a0e3ae4cSDean Michael Berris   for (auto FT : FunctionLatencies) {
316a0e3ae4cSDean Michael Berris     const auto &FuncId = FT.first;
317a0e3ae4cSDean Michael Berris     auto &Timings = FT.second;
318a0e3ae4cSDean Michael Berris     Results.emplace_back(FuncId, Timings.size(), getStats(Timings));
319a0e3ae4cSDean Michael Berris     auto &Row = std::get<2>(Results.back());
320a0e3ae4cSDean Michael Berris     if (Header.CycleFrequency) {
321a0e3ae4cSDean Michael Berris       double CycleFrequency = Header.CycleFrequency;
322a0e3ae4cSDean Michael Berris       Row.Min /= CycleFrequency;
323a0e3ae4cSDean Michael Berris       Row.Median /= CycleFrequency;
324a0e3ae4cSDean Michael Berris       Row.Pct90 /= CycleFrequency;
325a0e3ae4cSDean Michael Berris       Row.Pct99 /= CycleFrequency;
326a0e3ae4cSDean Michael Berris       Row.Max /= CycleFrequency;
327a0e3ae4cSDean Michael Berris       Row.Sum /= CycleFrequency;
328a0e3ae4cSDean Michael Berris     }
329a0e3ae4cSDean Michael Berris 
330a0e3ae4cSDean Michael Berris     Row.Function = FuncIdHelper.SymbolOrNumber(FuncId);
331a0e3ae4cSDean Michael Berris     Row.DebugInfo = FuncIdHelper.FileLineAndColumn(FuncId);
332a0e3ae4cSDean Michael Berris   }
333a0e3ae4cSDean Michael Berris 
334a0e3ae4cSDean Michael Berris   // Sort the data according to user-provided flags.
335a0e3ae4cSDean Michael Berris   switch (AccountSortOutput) {
336a0e3ae4cSDean Michael Berris   case SortField::FUNCID:
3379569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<0>(X); });
338a0e3ae4cSDean Michael Berris     break;
339a0e3ae4cSDean Michael Berris   case SortField::COUNT:
3409569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<1>(X); });
341a0e3ae4cSDean Michael Berris     break;
342a0e3ae4cSDean Michael Berris   case SortField::MIN:
3439569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Min; });
344a0e3ae4cSDean Michael Berris     break;
3459569523aSFangrui Song   case SortField::MED:
3469569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Median; });
3479569523aSFangrui Song     break;
3489569523aSFangrui Song   case SortField::PCT90:
3499569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct90; });
3509569523aSFangrui Song     break;
3519569523aSFangrui Song   case SortField::PCT99:
3529569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Pct99; });
3539569523aSFangrui Song     break;
3549569523aSFangrui Song   case SortField::MAX:
3559569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Max; });
3569569523aSFangrui Song     break;
3579569523aSFangrui Song   case SortField::SUM:
3589569523aSFangrui Song     sortByKey(Results, [](const TupleType &X) { return std::get<2>(X).Sum; });
3599569523aSFangrui Song     break;
3609569523aSFangrui Song   case SortField::FUNC:
3619569523aSFangrui Song     llvm_unreachable("Not implemented");
362a0e3ae4cSDean Michael Berris   }
363a0e3ae4cSDean Michael Berris 
36438a20c2aSDavid Carlier   if (AccountTop > 0) {
36538a20c2aSDavid Carlier     auto MaxTop =
36638a20c2aSDavid Carlier         std::min(AccountTop.getValue(), static_cast<int>(Results.size()));
36738a20c2aSDavid Carlier     Results.erase(Results.begin() + MaxTop, Results.end());
36838a20c2aSDavid Carlier   }
369a0e3ae4cSDean Michael Berris 
370a0e3ae4cSDean Michael Berris   for (const auto &R : Results)
371a0e3ae4cSDean Michael Berris     Fn(std::get<0>(R), std::get<1>(R), std::get<2>(R));
372a0e3ae4cSDean Michael Berris }
373a0e3ae4cSDean Michael Berris 
exportStatsAsText(raw_ostream & OS,const XRayFileHeader & Header) const374a0e3ae4cSDean Michael Berris void LatencyAccountant::exportStatsAsText(raw_ostream &OS,
375a0e3ae4cSDean Michael Berris                                           const XRayFileHeader &Header) const {
376a0e3ae4cSDean Michael Berris   OS << "Functions with latencies: " << FunctionLatencies.size() << "\n";
377a0e3ae4cSDean Michael Berris 
378a0e3ae4cSDean Michael Berris   // We spend some effort to make the text output more readable, so we do the
379a0e3ae4cSDean Michael Berris   // following formatting decisions for each of the fields:
380a0e3ae4cSDean Michael Berris   //
381a0e3ae4cSDean Michael Berris   //   - funcid: 32-bit, but we can determine the largest number and be
382a0e3ae4cSDean Michael Berris   //   between
383a0e3ae4cSDean Michael Berris   //     a minimum of 5 characters, up to 9 characters, right aligned.
384a0e3ae4cSDean Michael Berris   //   - count:  64-bit, but we can determine the largest number and be
385a0e3ae4cSDean Michael Berris   //   between
386a0e3ae4cSDean Michael Berris   //     a minimum of 5 characters, up to 9 characters, right aligned.
387a0e3ae4cSDean Michael Berris   //   - min, median, 90pct, 99pct, max: double precision, but we want to keep
388a0e3ae4cSDean Michael Berris   //     the values in seconds, with microsecond precision (0.000'001), so we
389a0e3ae4cSDean Michael Berris   //     have at most 6 significant digits, with the whole number part to be
390a0e3ae4cSDean Michael Berris   //     at
391a0e3ae4cSDean Michael Berris   //     least 1 character. For readability we'll right-align, with full 9
392a0e3ae4cSDean Michael Berris   //     characters each.
393a0e3ae4cSDean Michael Berris   //   - debug info, function name: we format this as a concatenation of the
394a0e3ae4cSDean Michael Berris   //     debug info and the function name.
395a0e3ae4cSDean Michael Berris   //
396a0e3ae4cSDean Michael Berris   static constexpr char StatsHeaderFormat[] =
397a0e3ae4cSDean Michael Berris       "{0,+9} {1,+10} [{2,+9}, {3,+9}, {4,+9}, {5,+9}, {6,+9}] {7,+9}";
398a0e3ae4cSDean Michael Berris   static constexpr char StatsFormat[] =
399a0e3ae4cSDean Michael Berris       R"({0,+9} {1,+10} [{2,+9:f6}, {3,+9:f6}, {4,+9:f6}, {5,+9:f6}, {6,+9:f6}] {7,+9:f6})";
400a0e3ae4cSDean Michael Berris   OS << llvm::formatv(StatsHeaderFormat, "funcid", "count", "min", "med", "90p",
401a0e3ae4cSDean Michael Berris                       "99p", "max", "sum")
402a0e3ae4cSDean Michael Berris      << llvm::formatv("  {0,-12}\n", "function");
403a0e3ae4cSDean Michael Berris   exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) {
404a0e3ae4cSDean Michael Berris     OS << llvm::formatv(StatsFormat, FuncId, Count, Row.Min, Row.Median,
405a0e3ae4cSDean Michael Berris                         Row.Pct90, Row.Pct99, Row.Max, Row.Sum)
406a0e3ae4cSDean Michael Berris        << "  " << Row.DebugInfo << ": " << Row.Function << "\n";
407a0e3ae4cSDean Michael Berris   });
408a0e3ae4cSDean Michael Berris }
409a0e3ae4cSDean Michael Berris 
exportStatsAsCSV(raw_ostream & OS,const XRayFileHeader & Header) const410a0e3ae4cSDean Michael Berris void LatencyAccountant::exportStatsAsCSV(raw_ostream &OS,
411a0e3ae4cSDean Michael Berris                                          const XRayFileHeader &Header) const {
412a0e3ae4cSDean Michael Berris   OS << "funcid,count,min,median,90%ile,99%ile,max,sum,debug,function\n";
413a0e3ae4cSDean Michael Berris   exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) {
414a0e3ae4cSDean Michael Berris     OS << FuncId << ',' << Count << ',' << Row.Min << ',' << Row.Median << ','
415a0e3ae4cSDean Michael Berris        << Row.Pct90 << ',' << Row.Pct99 << ',' << Row.Max << "," << Row.Sum
416a0e3ae4cSDean Michael Berris        << ",\"" << Row.DebugInfo << "\",\"" << Row.Function << "\"\n";
417a0e3ae4cSDean Michael Berris   });
418a0e3ae4cSDean Michael Berris }
419a0e3ae4cSDean Michael Berris 
420a0e3ae4cSDean Michael Berris using namespace llvm::xray;
421a0e3ae4cSDean Michael Berris 
422a0e3ae4cSDean Michael Berris namespace llvm {
423a0e3ae4cSDean Michael Berris template <> struct format_provider<llvm::xray::RecordTypes> {
formatllvm::format_provider424a0e3ae4cSDean Michael Berris   static void format(const llvm::xray::RecordTypes &T, raw_ostream &Stream,
425a0e3ae4cSDean Michael Berris                      StringRef Style) {
426a0e3ae4cSDean Michael Berris     switch (T) {
427a0e3ae4cSDean Michael Berris     case RecordTypes::ENTER:
428a0e3ae4cSDean Michael Berris       Stream << "enter";
429a0e3ae4cSDean Michael Berris       break;
430a0e3ae4cSDean Michael Berris     case RecordTypes::ENTER_ARG:
431a0e3ae4cSDean Michael Berris       Stream << "enter-arg";
432a0e3ae4cSDean Michael Berris       break;
433a0e3ae4cSDean Michael Berris     case RecordTypes::EXIT:
434a0e3ae4cSDean Michael Berris       Stream << "exit";
435a0e3ae4cSDean Michael Berris       break;
436a0e3ae4cSDean Michael Berris     case RecordTypes::TAIL_EXIT:
437a0e3ae4cSDean Michael Berris       Stream << "tail-exit";
438a0e3ae4cSDean Michael Berris       break;
43925f8d204SDean Michael Berris     case RecordTypes::CUSTOM_EVENT:
44025f8d204SDean Michael Berris       Stream << "custom-event";
44125f8d204SDean Michael Berris       break;
44225f8d204SDean Michael Berris     case RecordTypes::TYPED_EVENT:
44325f8d204SDean Michael Berris       Stream << "typed-event";
44425f8d204SDean Michael Berris       break;
445a0e3ae4cSDean Michael Berris     }
446a0e3ae4cSDean Michael Berris   }
447a0e3ae4cSDean Michael Berris };
448a0e3ae4cSDean Michael Berris } // namespace llvm
449a0e3ae4cSDean Michael Berris 
__anon09c816560f02() 450a0e3ae4cSDean Michael Berris static CommandRegistration Unused(&Account, []() -> Error {
451a0e3ae4cSDean Michael Berris   InstrumentationMap Map;
452a0e3ae4cSDean Michael Berris   if (!AccountInstrMap.empty()) {
453a0e3ae4cSDean Michael Berris     auto InstrumentationMapOrError = loadInstrumentationMap(AccountInstrMap);
454a0e3ae4cSDean Michael Berris     if (!InstrumentationMapOrError)
455a0e3ae4cSDean Michael Berris       return joinErrors(make_error<StringError>(
456a0e3ae4cSDean Michael Berris                             Twine("Cannot open instrumentation map '") +
457a0e3ae4cSDean Michael Berris                                 AccountInstrMap + "'",
458a0e3ae4cSDean Michael Berris                             std::make_error_code(std::errc::invalid_argument)),
459a0e3ae4cSDean Michael Berris                         InstrumentationMapOrError.takeError());
460a0e3ae4cSDean Michael Berris     Map = std::move(*InstrumentationMapOrError);
461a0e3ae4cSDean Michael Berris   }
462a0e3ae4cSDean Michael Berris 
463a0e3ae4cSDean Michael Berris   std::error_code EC;
46482b3e28eSAbhina Sreeskantharajan   raw_fd_ostream OS(AccountOutput, EC, sys::fs::OpenFlags::OF_TextWithCRLF);
465a0e3ae4cSDean Michael Berris   if (EC)
466a0e3ae4cSDean Michael Berris     return make_error<StringError>(
467a0e3ae4cSDean Michael Berris         Twine("Cannot open file '") + AccountOutput + "' for writing.", EC);
468a0e3ae4cSDean Michael Berris 
469a0e3ae4cSDean Michael Berris   const auto &FunctionAddresses = Map.getFunctionAddresses();
470a2048f86SPeter Collingbourne   symbolize::LLVMSymbolizer Symbolizer;
471a0e3ae4cSDean Michael Berris   llvm::xray::FuncIdConversionHelper FuncIdHelper(AccountInstrMap, Symbolizer,
472a0e3ae4cSDean Michael Berris                                                   FunctionAddresses);
473f2ab2134SRoman Lebedev   xray::LatencyAccountant FCA(FuncIdHelper, AccountRecursiveCallsOnly,
474f2ab2134SRoman Lebedev                               AccountDeduceSiblingCalls);
475a0e3ae4cSDean Michael Berris   auto TraceOrErr = loadTraceFile(AccountInput);
476a0e3ae4cSDean Michael Berris   if (!TraceOrErr)
477a0e3ae4cSDean Michael Berris     return joinErrors(
478a0e3ae4cSDean Michael Berris         make_error<StringError>(
479a0e3ae4cSDean Michael Berris             Twine("Failed loading input file '") + AccountInput + "'",
480a0e3ae4cSDean Michael Berris             std::make_error_code(std::errc::executable_format_error)),
481a0e3ae4cSDean Michael Berris         TraceOrErr.takeError());
482a0e3ae4cSDean Michael Berris 
483a0e3ae4cSDean Michael Berris   auto &T = *TraceOrErr;
484a0e3ae4cSDean Michael Berris   for (const auto &Record : T) {
485a0e3ae4cSDean Michael Berris     if (FCA.accountRecord(Record))
486a0e3ae4cSDean Michael Berris       continue;
487a0e3ae4cSDean Michael Berris     errs()
488a0e3ae4cSDean Michael Berris         << "Error processing record: "
489a0e3ae4cSDean Michael Berris         << llvm::formatv(
49010141261SDean Michael Berris                R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}; process-id: {6}}})",
491a0e3ae4cSDean Michael Berris                Record.RecordType, Record.CPU, Record.Type, Record.FuncId,
49210141261SDean Michael Berris                Record.TSC, Record.TId, Record.PId)
493a0e3ae4cSDean Michael Berris         << '\n';
494a0e3ae4cSDean Michael Berris     for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) {
495a0e3ae4cSDean Michael Berris       errs() << "Thread ID: " << ThreadStack.first << "\n";
496f2ab2134SRoman Lebedev       if (ThreadStack.second.Stack.empty()) {
497a0e3ae4cSDean Michael Berris         errs() << "  (empty stack)\n";
498a0e3ae4cSDean Michael Berris         continue;
499a0e3ae4cSDean Michael Berris       }
500f2ab2134SRoman Lebedev       auto Level = ThreadStack.second.Stack.size();
501f2ab2134SRoman Lebedev       for (const auto &Entry : llvm::reverse(ThreadStack.second.Stack))
502a0e3ae4cSDean Michael Berris         errs() << "  #" << Level-- << "\t"
503a0e3ae4cSDean Michael Berris                << FuncIdHelper.SymbolOrNumber(Entry.first) << '\n';
504a0e3ae4cSDean Michael Berris     }
505a0e3ae4cSDean Michael Berris     if (!AccountKeepGoing)
506a0e3ae4cSDean Michael Berris       return make_error<StringError>(
507a0e3ae4cSDean Michael Berris           Twine("Failed accounting function calls in file '") + AccountInput +
508a0e3ae4cSDean Michael Berris               "'.",
509a0e3ae4cSDean Michael Berris           std::make_error_code(std::errc::executable_format_error));
510a0e3ae4cSDean Michael Berris   }
511a0e3ae4cSDean Michael Berris   switch (AccountOutputFormat) {
512a0e3ae4cSDean Michael Berris   case AccountOutputFormats::TEXT:
513a0e3ae4cSDean Michael Berris     FCA.exportStatsAsText(OS, T.getFileHeader());
514a0e3ae4cSDean Michael Berris     break;
515a0e3ae4cSDean Michael Berris   case AccountOutputFormats::CSV:
516a0e3ae4cSDean Michael Berris     FCA.exportStatsAsCSV(OS, T.getFileHeader());
517a0e3ae4cSDean Michael Berris     break;
518a0e3ae4cSDean Michael Berris   }
519a0e3ae4cSDean Michael Berris 
520a0e3ae4cSDean Michael Berris   return Error::success();
521a0e3ae4cSDean Michael Berris });
522