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