blob: 9b17843568006297567cb178080ffac12351f1ee [file] [log] [blame]
David Blaikie87299ad2017-01-16 20:36:26 +00001//===-- xray-graph.c - XRay Function Call Graph Renderer ------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// Generate a DOT file to represent the function call graph encountered in
11// the trace.
12//
13//===----------------------------------------------------------------------===//
14#include <algorithm>
15#include <cassert>
16#include <system_error>
17#include <utility>
18
19#include "xray-extract.h"
20#include "xray-graph.h"
21#include "xray-registry.h"
22#include "llvm/Support/ErrorHandling.h"
23#include "llvm/Support/FormatVariadic.h"
24#include "llvm/XRay/Trace.h"
25#include "llvm/XRay/YAMLXRayRecord.h"
26
27using namespace llvm;
28using namespace xray;
29
30// Setup llvm-xray graph subcommand and its options.
31static cl::SubCommand Graph("graph", "Generate function-call graph");
32static cl::opt<std::string> GraphInput(cl::Positional,
33 cl::desc("<xray log file>"),
34 cl::Required, cl::sub(Graph));
35
36static cl::opt<std::string>
37 GraphOutput("output", cl::value_desc("Output file"), cl::init("-"),
38 cl::desc("output file; use '-' for stdout"), cl::sub(Graph));
39static cl::alias GraphOutput2("o", cl::aliasopt(GraphOutput),
40 cl::desc("Alias for -output"), cl::sub(Graph));
41
42static cl::opt<std::string> GraphInstrMap(
43 "instr_map", cl::desc("binary with the instrumrntation map, or "
44 "a separate instrumentation map"),
45 cl::value_desc("binary with xray_instr_map"), cl::sub(Graph), cl::init(""));
46static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap),
47 cl::desc("alias for -instr_map"),
48 cl::sub(Graph));
49
50static cl::opt<InstrumentationMapExtractor::InputFormats> InstrMapFormat(
51 "instr-map-format", cl::desc("format of instrumentation map"),
52 cl::values(clEnumValN(InstrumentationMapExtractor::InputFormats::ELF, "elf",
53 "instrumentation map in an ELF header"),
54 clEnumValN(InstrumentationMapExtractor::InputFormats::YAML,
55 "yaml", "instrumentation map in YAML")),
56 cl::sub(Graph), cl::init(InstrumentationMapExtractor::InputFormats::ELF));
57static cl::alias InstrMapFormat2("t", cl::aliasopt(InstrMapFormat),
58 cl::desc("Alias for -instr-map-format"),
59 cl::sub(Graph));
60
61static cl::opt<bool> GraphDeduceSiblingCalls(
62 "deduce-sibling-calls",
63 cl::desc("Deduce sibling calls when unrolling function call stacks"),
64 cl::sub(Graph), cl::init(false));
65static cl::alias
66 GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls),
67 cl::desc("Alias for -deduce-sibling-calls"),
68 cl::sub(Graph));
69
70static cl::opt<GraphRenderer::StatType>
71 GraphEdgeLabel("edge-label",
72 cl::desc("Output graphs with edges labeled with this field"),
73 cl::value_desc("field"), cl::sub(Graph),
74 cl::init(GraphRenderer::StatType::COUNT),
75 cl::values(clEnumValN(GraphRenderer::StatType::COUNT,
76 "count", "function call counts"),
77 clEnumValN(GraphRenderer::StatType::MIN, "min",
78 "minimum function durations"),
79 clEnumValN(GraphRenderer::StatType::MED, "med",
80 "median function durations"),
81 clEnumValN(GraphRenderer::StatType::PCT90, "90p",
82 "90th percentile durations"),
83 clEnumValN(GraphRenderer::StatType::PCT99, "99p",
84 "99th percentile durations"),
85 clEnumValN(GraphRenderer::StatType::MAX, "max",
86 "maximum function durations"),
87 clEnumValN(GraphRenderer::StatType::SUM, "sum",
88 "sum of call durations")));
89static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel),
90 cl::desc("Alias for -edge-label"),
91 cl::sub(Graph));
92
93namespace {
94template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); }
95
96void updateStat(GraphRenderer::TimeStat &S, int64_t lat) {
97 S.Count++;
98 if (S.Min > lat || S.Min == 0)
99 S.Min = lat;
100 if (S.Max < lat)
101 S.Max = lat;
102 S.Sum += lat;
103}
104}
105
106// Evaluates an XRay record and performs accounting on it, creating and
107// decorating a function call graph as it does so. It does this by maintaining
108// a call stack on a per-thread basis and adding edges and verticies to the
109// graph as they are seen for the first time.
110//
111// There is an immaginary root for functions at the top of their stack with
112// FuncId 0.
113//
114// FIXME: make more robust to errors and
115// Decorate Graph More Heavily.
116// FIXME: Refactor this and account subcommand to reduce code duplication.
117bool GraphRenderer::accountRecord(const XRayRecord &Record) {
118 if (CurrentMaxTSC == 0)
119 CurrentMaxTSC = Record.TSC;
120
121 if (Record.TSC < CurrentMaxTSC)
122 return false;
123
124 auto &ThreadStack = PerThreadFunctionStack[Record.TId];
125 switch (Record.Type) {
126 case RecordTypes::ENTER: {
127 if (VertexAttrs.count(Record.FuncId) == 0)
128 VertexAttrs[Record.FuncId].SymbolName =
129 FuncIdHelper.SymbolOrNumber(Record.FuncId);
130 ThreadStack.push_back({Record.FuncId, Record.TSC});
131 break;
132 }
133 case RecordTypes::EXIT: {
134 // FIXME: Refactor this and the account subcommand to reducr code
135 // duplication
136 if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) {
137 if (!DeduceSiblingCalls)
138 return false;
139 auto Parent = std::find_if(
140 ThreadStack.rbegin(), ThreadStack.rend(),
141 [&](const FunctionAttr &A) { return A.FuncId == Record.FuncId; });
142 if (Parent == ThreadStack.rend())
143 return false; // There is no matching Function for this exit.
144 while (ThreadStack.back().FuncId != Record.FuncId) {
145 uint64_t D = diff(ThreadStack.back().TSC, Record.TSC);
146 int32_t TopFuncId = ThreadStack.back().FuncId;
147 ThreadStack.pop_back();
148 assert(ThreadStack.size() != 0);
149 auto &EA = Graph[ThreadStack.back().FuncId][TopFuncId];
150 EA.Timings.push_back(D);
151 updateStat(EA.S, D);
152 updateStat(VertexAttrs[TopFuncId].S, D);
153 }
154 }
155 uint64_t D = diff(ThreadStack.back().TSC, Record.TSC);
156 ThreadStack.pop_back();
157 auto &V = Graph[ThreadStack.empty() ? 0 : ThreadStack.back().FuncId];
158 auto &EA = V[Record.FuncId];
159 EA.Timings.push_back(D);
160 updateStat(EA.S, D);
161 updateStat(VertexAttrs[Record.FuncId].S, D);
162 break;
163 }
164 }
165
166 return true;
167}
168
169template <typename U>
170void GraphRenderer::getStats(U begin, U end, GraphRenderer::TimeStat &S) {
171 assert(begin != end);
172 std::ptrdiff_t MedianOff = S.Count / 2;
173 std::nth_element(begin, begin + MedianOff, end);
174 S.Median = *(begin + MedianOff);
175 std::ptrdiff_t Pct90Off = (S.Count * 9) / 10;
176 std::nth_element(begin, begin + Pct90Off, end);
177 S.Pct90 = *(begin + Pct90Off);
178 std::ptrdiff_t Pct99Off = (S.Count * 99) / 100;
179 std::nth_element(begin, begin + Pct99Off, end);
180 S.Pct99 = *(begin + Pct99Off);
181}
182
183void GraphRenderer::calculateEdgeStatistics() {
184 for (auto &V : Graph) {
185 for (auto &E : V.second) {
186 auto &A = E.second;
187 getStats(A.Timings.begin(), A.Timings.end(), A.S);
188 }
189 }
190}
191
192void GraphRenderer::calculateVertexStatistics() {
193 DenseMap<int32_t, std::pair<uint64_t, SmallVector<EdgeAttribute *, 4>>>
194 IncommingEdges;
195 uint64_t MaxCount = 0;
196 for (auto &V : Graph) {
197 for (auto &E : V.second) {
198 auto &IEV = IncommingEdges[E.first];
199 IEV.second.push_back(&E.second);
200 IEV.first += E.second.S.Count;
201 if (IEV.first > MaxCount)
202 MaxCount = IEV.first;
203 }
204 }
205 std::vector<uint64_t> TempTimings;
206 TempTimings.reserve(MaxCount);
207 for (auto &V : IncommingEdges) {
208 for (auto &P : V.second.second) {
209 TempTimings.insert(TempTimings.end(), P->Timings.begin(),
210 P->Timings.end());
211 }
212 getStats(TempTimings.begin(), TempTimings.end(), VertexAttrs[V.first].S);
213 TempTimings.clear();
214 }
215}
216
217void GraphRenderer::normaliseStatistics(double CycleFrequency) {
218 for (auto &V : Graph) {
219 for (auto &E : V.second) {
220 auto &S = E.second.S;
221 S.Min /= CycleFrequency;
222 S.Median /= CycleFrequency;
223 S.Max /= CycleFrequency;
224 S.Sum /= CycleFrequency;
225 S.Pct90 /= CycleFrequency;
226 S.Pct99 /= CycleFrequency;
227 }
228 }
229 for (auto &V : VertexAttrs) {
230 auto &S = V.second.S;
231 S.Min /= CycleFrequency;
232 S.Median /= CycleFrequency;
233 S.Max /= CycleFrequency;
234 S.Sum /= CycleFrequency;
235 S.Pct90 /= CycleFrequency;
236 S.Pct99 /= CycleFrequency;
237 }
238}
239
240namespace {
241void outputEdgeInfo(const GraphRenderer::TimeStat &S, GraphRenderer::StatType T,
242 raw_ostream &OS) {
243 switch (T) {
244 case GraphRenderer::StatType::COUNT:
245 OS << S.Count;
246 break;
247 case GraphRenderer::StatType::MIN:
248 OS << S.Min;
249 break;
250 case GraphRenderer::StatType::MED:
251 OS << S.Median;
252 break;
253 case GraphRenderer::StatType::PCT90:
254 OS << S.Pct90;
255 break;
256 case GraphRenderer::StatType::PCT99:
257 OS << S.Pct99;
258 break;
259 case GraphRenderer::StatType::MAX:
260 OS << S.Max;
261 break;
262 case GraphRenderer::StatType::SUM:
263 OS << S.Sum;
264 break;
265 }
266}
267}
268
269// Outputs a DOT format version of the Graph embedded in the GraphRenderer
270// object on OS. It does this in the expected way by itterating
271// through all edges then vertices and then outputting them and their
272// annotations.
273//
274// FIXME: output more information, better presented.
275void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H,
276 StatType T) {
277 calculateEdgeStatistics();
278 calculateVertexStatistics();
279 if (H.CycleFrequency)
280 normaliseStatistics(H.CycleFrequency);
281
282 OS << "digraph xray {\n";
283
284 for (const auto &V : Graph)
285 for (const auto &E : V.second) {
286 OS << "F" << V.first << " -> "
287 << "F" << E.first << " [label=\"";
288 outputEdgeInfo(E.second.S, T, OS);
289 OS << "\"];\n";
290 }
291
292 for (const auto &V : VertexAttrs)
293 OS << "F" << V.first << " [label=\""
294 << (V.second.SymbolName.size() > 40
295 ? V.second.SymbolName.substr(0, 40) + "..."
296 : V.second.SymbolName)
297 << "\"];\n";
298
299 OS << "}\n";
300}
301
302// Here we register and implement the llvm-xray graph subcommand.
303// The bulk of this code reads in the options, opens the required files, uses
304// those files to create a context for analysing the xray trace, then there is a
305// short loop which actually analyses the trace, generates the graph and then
306// outputs it as a DOT.
307//
308// FIXME: include additional filtering and annalysis passes to provide more
309// specific useful information.
310static CommandRegistration Unused(&Graph, []() -> Error {
311 int Fd;
312 auto EC = sys::fs::openFileForRead(GraphInput, Fd);
313 if (EC)
314 return make_error<StringError>(
315 Twine("Cannot open file '") + GraphInput + "'", EC);
316
317 Error Err = Error::success();
318 xray::InstrumentationMapExtractor Extractor(GraphInstrMap, InstrMapFormat,
319 Err);
320 handleAllErrors(std::move(Err),
321 [&](const ErrorInfoBase &E) { E.log(errs()); });
322
323 const auto &FunctionAddresses = Extractor.getFunctionAddresses();
324
325 symbolize::LLVMSymbolizer::Options Opts(
326 symbolize::FunctionNameKind::LinkageName, true, true, false, "");
327
328 symbolize::LLVMSymbolizer Symbolizer(Opts);
329
330 llvm::xray::FuncIdConversionHelper FuncIdHelper(GraphInstrMap, Symbolizer,
331 FunctionAddresses);
332
333 xray::GraphRenderer GR(FuncIdHelper, GraphDeduceSiblingCalls);
334
335 raw_fd_ostream OS(GraphOutput, EC, sys::fs::OpenFlags::F_Text);
336
337 if (EC)
338 return make_error<StringError>(
339 Twine("Cannot open file '") + GraphOutput + "' for writing.", EC);
340
341 auto TraceOrErr = loadTraceFile(GraphInput, true);
342
343 if (!TraceOrErr) {
344 return joinErrors(
345 make_error<StringError>(
346 Twine("Failed loading input file '") + GraphInput + "'",
347 std::make_error_code(std::errc::protocol_error)),
348 std::move(Err));
349 }
350
351 auto &Trace = *TraceOrErr;
352 const auto &Header = Trace.getFileHeader();
353 for (const auto &Record : Trace) {
354 // Generate graph, FIXME: better error recovery.
355 if (!GR.accountRecord(Record)) {
356 return make_error<StringError>(
357 Twine("Failed accounting function calls in file '") + GraphInput +
358 "'.",
359 std::make_error_code(std::errc::bad_message));
360 }
361 }
362
363 GR.exportGraphAsDOT(OS, Header, GraphEdgeLabel);
364 return Error::success();
365});