10b57cec5SDimitry Andric //===-- xray-graph.cpp: XRay Function Call Graph Renderer -----------------===//
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 // Generate a DOT file to represent the function call graph encountered in
100b57cec5SDimitry Andric // the trace.
110b57cec5SDimitry Andric //
120b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
130b57cec5SDimitry Andric
140b57cec5SDimitry Andric #include "xray-graph.h"
150b57cec5SDimitry Andric #include "xray-registry.h"
160b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h"
170b57cec5SDimitry Andric #include "llvm/XRay/InstrumentationMap.h"
180b57cec5SDimitry Andric #include "llvm/XRay/Trace.h"
190b57cec5SDimitry Andric
20bdd1243dSDimitry Andric #include <cmath>
21bdd1243dSDimitry Andric
220b57cec5SDimitry Andric using namespace llvm;
230b57cec5SDimitry Andric using namespace llvm::xray;
240b57cec5SDimitry Andric
250b57cec5SDimitry Andric // Setup llvm-xray graph subcommand and its options.
260b57cec5SDimitry Andric static cl::SubCommand GraphC("graph", "Generate function-call graph");
270b57cec5SDimitry Andric static cl::opt<std::string> GraphInput(cl::Positional,
280b57cec5SDimitry Andric cl::desc("<xray log file>"),
290b57cec5SDimitry Andric cl::Required, cl::sub(GraphC));
300b57cec5SDimitry Andric
310b57cec5SDimitry Andric static cl::opt<bool>
320b57cec5SDimitry Andric GraphKeepGoing("keep-going", cl::desc("Keep going on errors encountered"),
330b57cec5SDimitry Andric cl::sub(GraphC), cl::init(false));
340b57cec5SDimitry Andric static cl::alias GraphKeepGoing2("k", cl::aliasopt(GraphKeepGoing),
35480093f4SDimitry Andric cl::desc("Alias for -keep-going"));
360b57cec5SDimitry Andric
370b57cec5SDimitry Andric static cl::opt<std::string>
380b57cec5SDimitry Andric GraphOutput("output", cl::value_desc("Output file"), cl::init("-"),
390b57cec5SDimitry Andric cl::desc("output file; use '-' for stdout"), cl::sub(GraphC));
400b57cec5SDimitry Andric static cl::alias GraphOutput2("o", cl::aliasopt(GraphOutput),
41480093f4SDimitry Andric cl::desc("Alias for -output"));
420b57cec5SDimitry Andric
430b57cec5SDimitry Andric static cl::opt<std::string>
440b57cec5SDimitry Andric GraphInstrMap("instr_map",
450b57cec5SDimitry Andric cl::desc("binary with the instrumrntation map, or "
460b57cec5SDimitry Andric "a separate instrumentation map"),
470b57cec5SDimitry Andric cl::value_desc("binary with xray_instr_map"), cl::sub(GraphC),
480b57cec5SDimitry Andric cl::init(""));
490b57cec5SDimitry Andric static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap),
50480093f4SDimitry Andric cl::desc("alias for -instr_map"));
510b57cec5SDimitry Andric
520b57cec5SDimitry Andric static cl::opt<bool> GraphDeduceSiblingCalls(
530b57cec5SDimitry Andric "deduce-sibling-calls",
540b57cec5SDimitry Andric cl::desc("Deduce sibling calls when unrolling function call stacks"),
550b57cec5SDimitry Andric cl::sub(GraphC), cl::init(false));
560b57cec5SDimitry Andric static cl::alias
570b57cec5SDimitry Andric GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls),
58480093f4SDimitry Andric cl::desc("Alias for -deduce-sibling-calls"));
590b57cec5SDimitry Andric
600b57cec5SDimitry Andric static cl::opt<GraphRenderer::StatType>
610b57cec5SDimitry Andric GraphEdgeLabel("edge-label",
620b57cec5SDimitry Andric cl::desc("Output graphs with edges labeled with this field"),
630b57cec5SDimitry Andric cl::value_desc("field"), cl::sub(GraphC),
640b57cec5SDimitry Andric cl::init(GraphRenderer::StatType::NONE),
650b57cec5SDimitry Andric cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",
660b57cec5SDimitry Andric "Do not label Edges"),
670b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::COUNT,
680b57cec5SDimitry Andric "count", "function call counts"),
690b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MIN, "min",
700b57cec5SDimitry Andric "minimum function durations"),
710b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MED, "med",
720b57cec5SDimitry Andric "median function durations"),
730b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT90, "90p",
740b57cec5SDimitry Andric "90th percentile durations"),
750b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT99, "99p",
760b57cec5SDimitry Andric "99th percentile durations"),
770b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MAX, "max",
780b57cec5SDimitry Andric "maximum function durations"),
790b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::SUM, "sum",
800b57cec5SDimitry Andric "sum of call durations")));
810b57cec5SDimitry Andric static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel),
82480093f4SDimitry Andric cl::desc("Alias for -edge-label"));
830b57cec5SDimitry Andric
840b57cec5SDimitry Andric static cl::opt<GraphRenderer::StatType> GraphVertexLabel(
850b57cec5SDimitry Andric "vertex-label",
860b57cec5SDimitry Andric cl::desc("Output graphs with vertices labeled with this field"),
870b57cec5SDimitry Andric cl::value_desc("field"), cl::sub(GraphC),
880b57cec5SDimitry Andric cl::init(GraphRenderer::StatType::NONE),
890b57cec5SDimitry Andric cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",
900b57cec5SDimitry Andric "Do not label Vertices"),
910b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::COUNT, "count",
920b57cec5SDimitry Andric "function call counts"),
930b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MIN, "min",
940b57cec5SDimitry Andric "minimum function durations"),
950b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MED, "med",
960b57cec5SDimitry Andric "median function durations"),
970b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT90, "90p",
980b57cec5SDimitry Andric "90th percentile durations"),
990b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT99, "99p",
1000b57cec5SDimitry Andric "99th percentile durations"),
1010b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MAX, "max",
1020b57cec5SDimitry Andric "maximum function durations"),
1030b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::SUM, "sum",
1040b57cec5SDimitry Andric "sum of call durations")));
1050b57cec5SDimitry Andric static cl::alias GraphVertexLabel2("v", cl::aliasopt(GraphVertexLabel),
106480093f4SDimitry Andric cl::desc("Alias for -edge-label"));
1070b57cec5SDimitry Andric
1080b57cec5SDimitry Andric static cl::opt<GraphRenderer::StatType> GraphEdgeColorType(
1090b57cec5SDimitry Andric "color-edges",
1100b57cec5SDimitry Andric cl::desc("Output graphs with edge colors determined by this field"),
1110b57cec5SDimitry Andric cl::value_desc("field"), cl::sub(GraphC),
1120b57cec5SDimitry Andric cl::init(GraphRenderer::StatType::NONE),
1130b57cec5SDimitry Andric cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",
1140b57cec5SDimitry Andric "Do not color Edges"),
1150b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::COUNT, "count",
1160b57cec5SDimitry Andric "function call counts"),
1170b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MIN, "min",
1180b57cec5SDimitry Andric "minimum function durations"),
1190b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MED, "med",
1200b57cec5SDimitry Andric "median function durations"),
1210b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT90, "90p",
1220b57cec5SDimitry Andric "90th percentile durations"),
1230b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT99, "99p",
1240b57cec5SDimitry Andric "99th percentile durations"),
1250b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MAX, "max",
1260b57cec5SDimitry Andric "maximum function durations"),
1270b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::SUM, "sum",
1280b57cec5SDimitry Andric "sum of call durations")));
1290b57cec5SDimitry Andric static cl::alias GraphEdgeColorType2("c", cl::aliasopt(GraphEdgeColorType),
130480093f4SDimitry Andric cl::desc("Alias for -color-edges"));
1310b57cec5SDimitry Andric
1320b57cec5SDimitry Andric static cl::opt<GraphRenderer::StatType> GraphVertexColorType(
1330b57cec5SDimitry Andric "color-vertices",
1340b57cec5SDimitry Andric cl::desc("Output graphs with vertex colors determined by this field"),
1350b57cec5SDimitry Andric cl::value_desc("field"), cl::sub(GraphC),
1360b57cec5SDimitry Andric cl::init(GraphRenderer::StatType::NONE),
1370b57cec5SDimitry Andric cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",
1380b57cec5SDimitry Andric "Do not color vertices"),
1390b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::COUNT, "count",
1400b57cec5SDimitry Andric "function call counts"),
1410b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MIN, "min",
1420b57cec5SDimitry Andric "minimum function durations"),
1430b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MED, "med",
1440b57cec5SDimitry Andric "median function durations"),
1450b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT90, "90p",
1460b57cec5SDimitry Andric "90th percentile durations"),
1470b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::PCT99, "99p",
1480b57cec5SDimitry Andric "99th percentile durations"),
1490b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::MAX, "max",
1500b57cec5SDimitry Andric "maximum function durations"),
1510b57cec5SDimitry Andric clEnumValN(GraphRenderer::StatType::SUM, "sum",
1520b57cec5SDimitry Andric "sum of call durations")));
1530b57cec5SDimitry Andric static cl::alias GraphVertexColorType2("b", cl::aliasopt(GraphVertexColorType),
154480093f4SDimitry Andric cl::desc("Alias for -edge-label"));
1550b57cec5SDimitry Andric
diff(T L,T R)1560b57cec5SDimitry Andric template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); }
1570b57cec5SDimitry Andric
1580b57cec5SDimitry Andric // Updates the statistics for a GraphRenderer::TimeStat
updateStat(GraphRenderer::TimeStat & S,int64_t L)1590b57cec5SDimitry Andric static void updateStat(GraphRenderer::TimeStat &S, int64_t L) {
1600b57cec5SDimitry Andric S.Count++;
1610b57cec5SDimitry Andric if (S.Min > L || S.Min == 0)
1620b57cec5SDimitry Andric S.Min = L;
1630b57cec5SDimitry Andric if (S.Max < L)
1640b57cec5SDimitry Andric S.Max = L;
1650b57cec5SDimitry Andric S.Sum += L;
1660b57cec5SDimitry Andric }
1670b57cec5SDimitry Andric
1685ffd83dbSDimitry Andric // Labels in a DOT graph must be legal XML strings so it's necessary to escape
1695ffd83dbSDimitry Andric // certain characters.
escapeString(StringRef Label)1705ffd83dbSDimitry Andric static std::string escapeString(StringRef Label) {
1715ffd83dbSDimitry Andric std::string Str;
1725ffd83dbSDimitry Andric Str.reserve(Label.size());
1735ffd83dbSDimitry Andric for (const auto C : Label) {
1745ffd83dbSDimitry Andric switch (C) {
1755ffd83dbSDimitry Andric case '&':
1765ffd83dbSDimitry Andric Str.append("&");
1775ffd83dbSDimitry Andric break;
1785ffd83dbSDimitry Andric case '<':
1795ffd83dbSDimitry Andric Str.append("<");
1805ffd83dbSDimitry Andric break;
1815ffd83dbSDimitry Andric case '>':
1825ffd83dbSDimitry Andric Str.append(">");
1835ffd83dbSDimitry Andric break;
1845ffd83dbSDimitry Andric default:
1855ffd83dbSDimitry Andric Str.push_back(C);
1865ffd83dbSDimitry Andric break;
1875ffd83dbSDimitry Andric }
1885ffd83dbSDimitry Andric }
1895ffd83dbSDimitry Andric return Str;
1905ffd83dbSDimitry Andric }
1915ffd83dbSDimitry Andric
1920b57cec5SDimitry Andric // Evaluates an XRay record and performs accounting on it.
1930b57cec5SDimitry Andric //
1940b57cec5SDimitry Andric // If the record is an ENTER record it pushes the FuncID and TSC onto a
1950b57cec5SDimitry Andric // structure representing the call stack for that function.
1960b57cec5SDimitry Andric // If the record is an EXIT record it checks computes computes the ammount of
1970b57cec5SDimitry Andric // time the function took to complete and then stores that information in an
1980b57cec5SDimitry Andric // edge of the graph. If there is no matching ENTER record the function tries
1990b57cec5SDimitry Andric // to recover by assuming that there were EXIT records which were missed, for
2000b57cec5SDimitry Andric // example caused by tail call elimination and if the option is enabled then
2010b57cec5SDimitry Andric // then tries to recover from this.
2020b57cec5SDimitry Andric //
203*06c3fb27SDimitry Andric // This function will also error if the records are out of order, as the trace
2040b57cec5SDimitry Andric // is expected to be sorted.
2050b57cec5SDimitry Andric //
2060b57cec5SDimitry Andric // The graph generated has an immaginary root for functions called by no-one at
2070b57cec5SDimitry Andric // FuncId 0.
2080b57cec5SDimitry Andric //
2090b57cec5SDimitry Andric // FIXME: Refactor this and account subcommand to reduce code duplication.
accountRecord(const XRayRecord & Record)2100b57cec5SDimitry Andric Error GraphRenderer::accountRecord(const XRayRecord &Record) {
2110b57cec5SDimitry Andric using std::make_error_code;
2120b57cec5SDimitry Andric using std::errc;
2130b57cec5SDimitry Andric if (CurrentMaxTSC == 0)
2140b57cec5SDimitry Andric CurrentMaxTSC = Record.TSC;
2150b57cec5SDimitry Andric
2160b57cec5SDimitry Andric if (Record.TSC < CurrentMaxTSC)
2170b57cec5SDimitry Andric return make_error<StringError>("Records not in order",
2180b57cec5SDimitry Andric make_error_code(errc::invalid_argument));
2190b57cec5SDimitry Andric
2200b57cec5SDimitry Andric auto &ThreadStack = PerThreadFunctionStack[Record.TId];
2210b57cec5SDimitry Andric switch (Record.Type) {
2220b57cec5SDimitry Andric case RecordTypes::ENTER:
2230b57cec5SDimitry Andric case RecordTypes::ENTER_ARG: {
2240b57cec5SDimitry Andric if (Record.FuncId != 0 && G.count(Record.FuncId) == 0)
2250b57cec5SDimitry Andric G[Record.FuncId].SymbolName = FuncIdHelper.SymbolOrNumber(Record.FuncId);
2260b57cec5SDimitry Andric ThreadStack.push_back({Record.FuncId, Record.TSC});
2270b57cec5SDimitry Andric break;
2280b57cec5SDimitry Andric }
2290b57cec5SDimitry Andric case RecordTypes::EXIT:
2300b57cec5SDimitry Andric case RecordTypes::TAIL_EXIT: {
2310b57cec5SDimitry Andric // FIXME: Refactor this and the account subcommand to reduce code
2320b57cec5SDimitry Andric // duplication
2330b57cec5SDimitry Andric if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) {
2340b57cec5SDimitry Andric if (!DeduceSiblingCalls)
2350b57cec5SDimitry Andric return make_error<StringError>("No matching ENTRY record",
2360b57cec5SDimitry Andric make_error_code(errc::invalid_argument));
237972a253aSDimitry Andric bool FoundParent =
238972a253aSDimitry Andric llvm::any_of(llvm::reverse(ThreadStack), [&](const FunctionAttr &A) {
239972a253aSDimitry Andric return A.FuncId == Record.FuncId;
240972a253aSDimitry Andric });
241972a253aSDimitry Andric if (!FoundParent)
2420b57cec5SDimitry Andric return make_error<StringError>(
2430b57cec5SDimitry Andric "No matching Entry record in stack",
2440b57cec5SDimitry Andric make_error_code(errc::invalid_argument)); // There is no matching
2450b57cec5SDimitry Andric // Function for this exit.
2460b57cec5SDimitry Andric while (ThreadStack.back().FuncId != Record.FuncId) {
2470b57cec5SDimitry Andric TimestampT D = diff(ThreadStack.back().TSC, Record.TSC);
2480b57cec5SDimitry Andric VertexIdentifier TopFuncId = ThreadStack.back().FuncId;
2490b57cec5SDimitry Andric ThreadStack.pop_back();
2500b57cec5SDimitry Andric assert(ThreadStack.size() != 0);
2510b57cec5SDimitry Andric EdgeIdentifier EI(ThreadStack.back().FuncId, TopFuncId);
2520b57cec5SDimitry Andric auto &EA = G[EI];
2530b57cec5SDimitry Andric EA.Timings.push_back(D);
2540b57cec5SDimitry Andric updateStat(EA.S, D);
2550b57cec5SDimitry Andric updateStat(G[TopFuncId].S, D);
2560b57cec5SDimitry Andric }
2570b57cec5SDimitry Andric }
2580b57cec5SDimitry Andric uint64_t D = diff(ThreadStack.back().TSC, Record.TSC);
2590b57cec5SDimitry Andric ThreadStack.pop_back();
2600b57cec5SDimitry Andric VertexIdentifier VI = ThreadStack.empty() ? 0 : ThreadStack.back().FuncId;
2610b57cec5SDimitry Andric EdgeIdentifier EI(VI, Record.FuncId);
2620b57cec5SDimitry Andric auto &EA = G[EI];
2630b57cec5SDimitry Andric EA.Timings.push_back(D);
2640b57cec5SDimitry Andric updateStat(EA.S, D);
2650b57cec5SDimitry Andric updateStat(G[Record.FuncId].S, D);
2660b57cec5SDimitry Andric break;
2670b57cec5SDimitry Andric }
2680b57cec5SDimitry Andric case RecordTypes::CUSTOM_EVENT:
2690b57cec5SDimitry Andric case RecordTypes::TYPED_EVENT:
2700b57cec5SDimitry Andric // TODO: Support custom and typed events in the graph processing?
2710b57cec5SDimitry Andric break;
2720b57cec5SDimitry Andric }
2730b57cec5SDimitry Andric
2740b57cec5SDimitry Andric return Error::success();
2750b57cec5SDimitry Andric }
2760b57cec5SDimitry Andric
2770b57cec5SDimitry Andric template <typename U>
getStats(U begin,U end,GraphRenderer::TimeStat & S)2780b57cec5SDimitry Andric void GraphRenderer::getStats(U begin, U end, GraphRenderer::TimeStat &S) {
2790b57cec5SDimitry Andric if (begin == end) return;
2800b57cec5SDimitry Andric std::ptrdiff_t MedianOff = S.Count / 2;
2810b57cec5SDimitry Andric std::nth_element(begin, begin + MedianOff, end);
2820b57cec5SDimitry Andric S.Median = *(begin + MedianOff);
2830b57cec5SDimitry Andric std::ptrdiff_t Pct90Off = (S.Count * 9) / 10;
2840b57cec5SDimitry Andric std::nth_element(begin, begin + Pct90Off, end);
2850b57cec5SDimitry Andric S.Pct90 = *(begin + Pct90Off);
2860b57cec5SDimitry Andric std::ptrdiff_t Pct99Off = (S.Count * 99) / 100;
2870b57cec5SDimitry Andric std::nth_element(begin, begin + Pct99Off, end);
2880b57cec5SDimitry Andric S.Pct99 = *(begin + Pct99Off);
2890b57cec5SDimitry Andric }
2900b57cec5SDimitry Andric
updateMaxStats(const GraphRenderer::TimeStat & S,GraphRenderer::TimeStat & M)2910b57cec5SDimitry Andric void GraphRenderer::updateMaxStats(const GraphRenderer::TimeStat &S,
2920b57cec5SDimitry Andric GraphRenderer::TimeStat &M) {
2930b57cec5SDimitry Andric M.Count = std::max(M.Count, S.Count);
2940b57cec5SDimitry Andric M.Min = std::max(M.Min, S.Min);
2950b57cec5SDimitry Andric M.Median = std::max(M.Median, S.Median);
2960b57cec5SDimitry Andric M.Pct90 = std::max(M.Pct90, S.Pct90);
2970b57cec5SDimitry Andric M.Pct99 = std::max(M.Pct99, S.Pct99);
2980b57cec5SDimitry Andric M.Max = std::max(M.Max, S.Max);
2990b57cec5SDimitry Andric M.Sum = std::max(M.Sum, S.Sum);
3000b57cec5SDimitry Andric }
3010b57cec5SDimitry Andric
calculateEdgeStatistics()3020b57cec5SDimitry Andric void GraphRenderer::calculateEdgeStatistics() {
3030b57cec5SDimitry Andric assert(!G.edges().empty());
3040b57cec5SDimitry Andric for (auto &E : G.edges()) {
3050b57cec5SDimitry Andric auto &A = E.second;
3060b57cec5SDimitry Andric assert(!A.Timings.empty());
3070b57cec5SDimitry Andric getStats(A.Timings.begin(), A.Timings.end(), A.S);
3080b57cec5SDimitry Andric updateMaxStats(A.S, G.GraphEdgeMax);
3090b57cec5SDimitry Andric }
3100b57cec5SDimitry Andric }
3110b57cec5SDimitry Andric
calculateVertexStatistics()3120b57cec5SDimitry Andric void GraphRenderer::calculateVertexStatistics() {
3130b57cec5SDimitry Andric std::vector<uint64_t> TempTimings;
3140b57cec5SDimitry Andric for (auto &V : G.vertices()) {
3150b57cec5SDimitry Andric if (V.first != 0) {
3160b57cec5SDimitry Andric for (auto &E : G.inEdges(V.first)) {
3170b57cec5SDimitry Andric auto &A = E.second;
318e8d8bef9SDimitry Andric llvm::append_range(TempTimings, A.Timings);
3190b57cec5SDimitry Andric }
3200b57cec5SDimitry Andric getStats(TempTimings.begin(), TempTimings.end(), G[V.first].S);
3210b57cec5SDimitry Andric updateMaxStats(G[V.first].S, G.GraphVertexMax);
3220b57cec5SDimitry Andric TempTimings.clear();
3230b57cec5SDimitry Andric }
3240b57cec5SDimitry Andric }
3250b57cec5SDimitry Andric }
3260b57cec5SDimitry Andric
3270b57cec5SDimitry Andric // A Helper function for normalizeStatistics which normalises a single
3280b57cec5SDimitry Andric // TimeStat element.
normalizeTimeStat(GraphRenderer::TimeStat & S,double CycleFrequency)3290b57cec5SDimitry Andric static void normalizeTimeStat(GraphRenderer::TimeStat &S,
3300b57cec5SDimitry Andric double CycleFrequency) {
3310b57cec5SDimitry Andric int64_t OldCount = S.Count;
3320b57cec5SDimitry Andric S = S / CycleFrequency;
3330b57cec5SDimitry Andric S.Count = OldCount;
3340b57cec5SDimitry Andric }
3350b57cec5SDimitry Andric
3360b57cec5SDimitry Andric // Normalises the statistics in the graph for a given TSC frequency.
normalizeStatistics(double CycleFrequency)3370b57cec5SDimitry Andric void GraphRenderer::normalizeStatistics(double CycleFrequency) {
3380b57cec5SDimitry Andric for (auto &E : G.edges()) {
3390b57cec5SDimitry Andric auto &S = E.second.S;
3400b57cec5SDimitry Andric normalizeTimeStat(S, CycleFrequency);
3410b57cec5SDimitry Andric }
3420b57cec5SDimitry Andric for (auto &V : G.vertices()) {
3430b57cec5SDimitry Andric auto &S = V.second.S;
3440b57cec5SDimitry Andric normalizeTimeStat(S, CycleFrequency);
3450b57cec5SDimitry Andric }
3460b57cec5SDimitry Andric
3470b57cec5SDimitry Andric normalizeTimeStat(G.GraphEdgeMax, CycleFrequency);
3480b57cec5SDimitry Andric normalizeTimeStat(G.GraphVertexMax, CycleFrequency);
3490b57cec5SDimitry Andric }
3500b57cec5SDimitry Andric
3510b57cec5SDimitry Andric // Returns a string containing the value of statistic field T
3520b57cec5SDimitry Andric std::string
getString(GraphRenderer::StatType T) const3530b57cec5SDimitry Andric GraphRenderer::TimeStat::getString(GraphRenderer::StatType T) const {
3540b57cec5SDimitry Andric std::string St;
3550b57cec5SDimitry Andric raw_string_ostream S{St};
3560b57cec5SDimitry Andric double TimeStat::*DoubleStatPtrs[] = {&TimeStat::Min, &TimeStat::Median,
3570b57cec5SDimitry Andric &TimeStat::Pct90, &TimeStat::Pct99,
3580b57cec5SDimitry Andric &TimeStat::Max, &TimeStat::Sum};
3590b57cec5SDimitry Andric switch (T) {
3600b57cec5SDimitry Andric case GraphRenderer::StatType::NONE:
3610b57cec5SDimitry Andric break;
3620b57cec5SDimitry Andric case GraphRenderer::StatType::COUNT:
3630b57cec5SDimitry Andric S << Count;
3640b57cec5SDimitry Andric break;
3650b57cec5SDimitry Andric default:
3660b57cec5SDimitry Andric S << (*this).*
3670b57cec5SDimitry Andric DoubleStatPtrs[static_cast<int>(T) -
3680b57cec5SDimitry Andric static_cast<int>(GraphRenderer::StatType::MIN)];
3690b57cec5SDimitry Andric break;
3700b57cec5SDimitry Andric }
3710b57cec5SDimitry Andric return S.str();
3720b57cec5SDimitry Andric }
3730b57cec5SDimitry Andric
3740b57cec5SDimitry Andric // Returns the quotient between the property T of this and another TimeStat as
3750b57cec5SDimitry Andric // a double
getDouble(StatType T) const3760b57cec5SDimitry Andric double GraphRenderer::TimeStat::getDouble(StatType T) const {
3770b57cec5SDimitry Andric double retval = 0;
3780b57cec5SDimitry Andric double TimeStat::*DoubleStatPtrs[] = {&TimeStat::Min, &TimeStat::Median,
3790b57cec5SDimitry Andric &TimeStat::Pct90, &TimeStat::Pct99,
3800b57cec5SDimitry Andric &TimeStat::Max, &TimeStat::Sum};
3810b57cec5SDimitry Andric switch (T) {
3820b57cec5SDimitry Andric case GraphRenderer::StatType::NONE:
3830b57cec5SDimitry Andric retval = 0.0;
3840b57cec5SDimitry Andric break;
3850b57cec5SDimitry Andric case GraphRenderer::StatType::COUNT:
3860b57cec5SDimitry Andric retval = static_cast<double>(Count);
3870b57cec5SDimitry Andric break;
3880b57cec5SDimitry Andric default:
3890b57cec5SDimitry Andric retval =
3900b57cec5SDimitry Andric (*this).*DoubleStatPtrs[static_cast<int>(T) -
3910b57cec5SDimitry Andric static_cast<int>(GraphRenderer::StatType::MIN)];
3920b57cec5SDimitry Andric break;
3930b57cec5SDimitry Andric }
3940b57cec5SDimitry Andric return retval;
3950b57cec5SDimitry Andric }
3960b57cec5SDimitry Andric
3970b57cec5SDimitry Andric // Outputs a DOT format version of the Graph embedded in the GraphRenderer
3980b57cec5SDimitry Andric // object on OS. It does this in the expected way by itterating
3990b57cec5SDimitry Andric // through all edges then vertices and then outputting them and their
4000b57cec5SDimitry Andric // annotations.
4010b57cec5SDimitry Andric //
4020b57cec5SDimitry Andric // FIXME: output more information, better presented.
exportGraphAsDOT(raw_ostream & OS,StatType ET,StatType EC,StatType VT,StatType VC)4030b57cec5SDimitry Andric void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, StatType ET, StatType EC,
4040b57cec5SDimitry Andric StatType VT, StatType VC) {
4050b57cec5SDimitry Andric OS << "digraph xray {\n";
4060b57cec5SDimitry Andric
4070b57cec5SDimitry Andric if (VT != StatType::NONE)
4080b57cec5SDimitry Andric OS << "node [shape=record];\n";
4090b57cec5SDimitry Andric
4100b57cec5SDimitry Andric for (const auto &E : G.edges()) {
4110b57cec5SDimitry Andric const auto &S = E.second.S;
4120b57cec5SDimitry Andric OS << "F" << E.first.first << " -> "
4130b57cec5SDimitry Andric << "F" << E.first.second << " [label=\"" << S.getString(ET) << "\"";
4140b57cec5SDimitry Andric if (EC != StatType::NONE)
4150b57cec5SDimitry Andric OS << " color=\""
4160b57cec5SDimitry Andric << CHelper.getColorString(
4170b57cec5SDimitry Andric std::sqrt(S.getDouble(EC) / G.GraphEdgeMax.getDouble(EC)))
4180b57cec5SDimitry Andric << "\"";
4190b57cec5SDimitry Andric OS << "];\n";
4200b57cec5SDimitry Andric }
4210b57cec5SDimitry Andric
4220b57cec5SDimitry Andric for (const auto &V : G.vertices()) {
4230b57cec5SDimitry Andric const auto &VA = V.second;
4240b57cec5SDimitry Andric if (V.first == 0)
4250b57cec5SDimitry Andric continue;
4260b57cec5SDimitry Andric OS << "F" << V.first << " [label=\"" << (VT != StatType::NONE ? "{" : "")
4275ffd83dbSDimitry Andric << escapeString(VA.SymbolName.size() > 40
4285ffd83dbSDimitry Andric ? VA.SymbolName.substr(0, 40) + "..."
4290b57cec5SDimitry Andric : VA.SymbolName);
4300b57cec5SDimitry Andric if (VT != StatType::NONE)
4310b57cec5SDimitry Andric OS << "|" << VA.S.getString(VT) << "}\"";
4320b57cec5SDimitry Andric else
4330b57cec5SDimitry Andric OS << "\"";
4340b57cec5SDimitry Andric if (VC != StatType::NONE)
4350b57cec5SDimitry Andric OS << " color=\""
4360b57cec5SDimitry Andric << CHelper.getColorString(
4370b57cec5SDimitry Andric std::sqrt(VA.S.getDouble(VC) / G.GraphVertexMax.getDouble(VC)))
4380b57cec5SDimitry Andric << "\"";
4390b57cec5SDimitry Andric OS << "];\n";
4400b57cec5SDimitry Andric }
4410b57cec5SDimitry Andric OS << "}\n";
4420b57cec5SDimitry Andric }
4430b57cec5SDimitry Andric
getGraphRenderer()4440b57cec5SDimitry Andric Expected<GraphRenderer> GraphRenderer::Factory::getGraphRenderer() {
4450b57cec5SDimitry Andric InstrumentationMap Map;
4460b57cec5SDimitry Andric if (!GraphInstrMap.empty()) {
4470b57cec5SDimitry Andric auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap);
4480b57cec5SDimitry Andric if (!InstrumentationMapOrError)
4490b57cec5SDimitry Andric return joinErrors(
4500b57cec5SDimitry Andric make_error<StringError>(
4510b57cec5SDimitry Andric Twine("Cannot open instrumentation map '") + GraphInstrMap + "'",
4520b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)),
4530b57cec5SDimitry Andric InstrumentationMapOrError.takeError());
4540b57cec5SDimitry Andric Map = std::move(*InstrumentationMapOrError);
4550b57cec5SDimitry Andric }
4560b57cec5SDimitry Andric
4570b57cec5SDimitry Andric const auto &FunctionAddresses = Map.getFunctionAddresses();
4580b57cec5SDimitry Andric
4590b57cec5SDimitry Andric symbolize::LLVMSymbolizer Symbolizer;
4600b57cec5SDimitry Andric const auto &Header = Trace.getFileHeader();
4610b57cec5SDimitry Andric
4620b57cec5SDimitry Andric llvm::xray::FuncIdConversionHelper FuncIdHelper(InstrMap, Symbolizer,
4630b57cec5SDimitry Andric FunctionAddresses);
4640b57cec5SDimitry Andric
4650b57cec5SDimitry Andric xray::GraphRenderer GR(FuncIdHelper, DeduceSiblingCalls);
4660b57cec5SDimitry Andric for (const auto &Record : Trace) {
4670b57cec5SDimitry Andric auto E = GR.accountRecord(Record);
4680b57cec5SDimitry Andric if (!E)
4690b57cec5SDimitry Andric continue;
4700b57cec5SDimitry Andric
4710b57cec5SDimitry Andric for (const auto &ThreadStack : GR.getPerThreadFunctionStack()) {
4720b57cec5SDimitry Andric errs() << "Thread ID: " << ThreadStack.first << "\n";
4730b57cec5SDimitry Andric auto Level = ThreadStack.second.size();
4740b57cec5SDimitry Andric for (const auto &Entry : llvm::reverse(ThreadStack.second))
4750b57cec5SDimitry Andric errs() << "#" << Level-- << "\t"
4760b57cec5SDimitry Andric << FuncIdHelper.SymbolOrNumber(Entry.FuncId) << '\n';
4770b57cec5SDimitry Andric }
4780b57cec5SDimitry Andric
4790b57cec5SDimitry Andric if (!GraphKeepGoing)
4800b57cec5SDimitry Andric return joinErrors(make_error<StringError>(
4810b57cec5SDimitry Andric "Error encountered generating the call graph.",
4820b57cec5SDimitry Andric std::make_error_code(std::errc::invalid_argument)),
4830b57cec5SDimitry Andric std::move(E));
4840b57cec5SDimitry Andric
4850b57cec5SDimitry Andric handleAllErrors(std::move(E),
4860b57cec5SDimitry Andric [&](const ErrorInfoBase &E) { E.log(errs()); });
4870b57cec5SDimitry Andric }
4880b57cec5SDimitry Andric
4890b57cec5SDimitry Andric GR.G.GraphEdgeMax = {};
4900b57cec5SDimitry Andric GR.G.GraphVertexMax = {};
4910b57cec5SDimitry Andric GR.calculateEdgeStatistics();
4920b57cec5SDimitry Andric GR.calculateVertexStatistics();
4930b57cec5SDimitry Andric
4940b57cec5SDimitry Andric if (Header.CycleFrequency)
4950b57cec5SDimitry Andric GR.normalizeStatistics(Header.CycleFrequency);
4960b57cec5SDimitry Andric
4970b57cec5SDimitry Andric return GR;
4980b57cec5SDimitry Andric }
4990b57cec5SDimitry Andric
5000b57cec5SDimitry Andric // Here we register and implement the llvm-xray graph subcommand.
5010b57cec5SDimitry Andric // The bulk of this code reads in the options, opens the required files, uses
5020b57cec5SDimitry Andric // those files to create a context for analysing the xray trace, then there is a
5030b57cec5SDimitry Andric // short loop which actually analyses the trace, generates the graph and then
5040b57cec5SDimitry Andric // outputs it as a DOT.
5050b57cec5SDimitry Andric //
5060b57cec5SDimitry Andric // FIXME: include additional filtering and annalysis passes to provide more
5070b57cec5SDimitry Andric // specific useful information.
__anonfe2a689a0302() 5080b57cec5SDimitry Andric static CommandRegistration Unused(&GraphC, []() -> Error {
5090b57cec5SDimitry Andric GraphRenderer::Factory F;
5100b57cec5SDimitry Andric
5110b57cec5SDimitry Andric F.KeepGoing = GraphKeepGoing;
5120b57cec5SDimitry Andric F.DeduceSiblingCalls = GraphDeduceSiblingCalls;
5130b57cec5SDimitry Andric F.InstrMap = GraphInstrMap;
5140b57cec5SDimitry Andric
5150b57cec5SDimitry Andric auto TraceOrErr = loadTraceFile(GraphInput, true);
5160b57cec5SDimitry Andric
5170b57cec5SDimitry Andric if (!TraceOrErr)
5180b57cec5SDimitry Andric return make_error<StringError>(
5190b57cec5SDimitry Andric Twine("Failed loading input file '") + GraphInput + "'",
5200b57cec5SDimitry Andric make_error_code(llvm::errc::invalid_argument));
5210b57cec5SDimitry Andric
5220b57cec5SDimitry Andric F.Trace = std::move(*TraceOrErr);
5230b57cec5SDimitry Andric auto GROrError = F.getGraphRenderer();
5240b57cec5SDimitry Andric if (!GROrError)
5250b57cec5SDimitry Andric return GROrError.takeError();
5260b57cec5SDimitry Andric auto &GR = *GROrError;
5270b57cec5SDimitry Andric
5280b57cec5SDimitry Andric std::error_code EC;
529fe6060f1SDimitry Andric raw_fd_ostream OS(GraphOutput, EC, sys::fs::OpenFlags::OF_TextWithCRLF);
5300b57cec5SDimitry Andric if (EC)
5310b57cec5SDimitry Andric return make_error<StringError>(
5320b57cec5SDimitry Andric Twine("Cannot open file '") + GraphOutput + "' for writing.", EC);
5330b57cec5SDimitry Andric
5340b57cec5SDimitry Andric GR.exportGraphAsDOT(OS, GraphEdgeLabel, GraphEdgeColorType, GraphVertexLabel,
5350b57cec5SDimitry Andric GraphVertexColorType);
5360b57cec5SDimitry Andric return Error::success();
5370b57cec5SDimitry Andric });
538