Skip to content

Commit 87299ad

Browse files
committedJan 16, 2017
[XRay] Implement the llvm-xray graph subcommand
Here we define the `graph` subcommand which generates a graph from the function call information and uses it to present the call information graphically with additional annotations. Reviewers: dblaikie, dberris Differential Revision: https://reviews.llvm.org/D27243 llvm-svn: 292156
1 parent 0cd22f9 commit 87299ad

File tree

5 files changed

+616
-0
lines changed

5 files changed

+616
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d \
2+
#RUN: | FileCheck %s -check-prefix=COUNT
3+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e count \
4+
#RUN: | FileCheck %s -check-prefix=COUNT
5+
#
6+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e min \
7+
#RUN: | FileCheck %s -check-prefix=TIME
8+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e med \
9+
#RUN: | FileCheck %s -check-prefix=TIME
10+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e 90p \
11+
#RUN: | FileCheck %s -check-prefix=TIME
12+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e 99p \
13+
#RUN: | FileCheck %s -check-prefix=TIME
14+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e max \
15+
#RUN: | FileCheck %s -check-prefix=TIME
16+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -d -e sum \
17+
#RUN: | FileCheck %s -check-prefix=TIME
18+
#
19+
---
20+
header:
21+
version: 1
22+
type: 0
23+
constant-tsc: true
24+
nonstop-tsc: true
25+
cycle-frequency: 0
26+
records:
27+
# Here we reconstruct the following call trace:
28+
#
29+
# f1()
30+
# f2()
31+
# f3()
32+
#
33+
# But we find that we're missing an exit record for f2() because it's
34+
# tail-called f3(). We make sure that if we see a trace like this that we can
35+
# deduce tail calls, and account the time (potentially wrongly) to f2() when
36+
# f1() exits. That is because we don't go back to f3()'s entry record to
37+
# properly do the math on the timing of f2().
38+
#
39+
# Note that by default, tail/sibling call deduction is disabled, and is enabled
40+
# with a flag "-d" or "-deduce-sibling-calls".
41+
#
42+
- { type: 0, func-id: 1, cpu: 1, thread: 111, kind: function-enter, tsc: 10000 }
43+
- { type: 0, func-id: 2, cpu: 1, thread: 111, kind: function-enter, tsc: 10001 }
44+
- { type: 0, func-id: 3, cpu: 1, thread: 111, kind: function-enter, tsc: 10002 }
45+
- { type: 0, func-id: 3, cpu: 1, thread: 111, kind: function-exit, tsc: 10003 }
46+
- { type: 0, func-id: 1, cpu: 1, thread: 111, kind: function-exit, tsc: 10004 }
47+
...
48+
49+
#EMPTY: digraph xray {
50+
#EMPTY-DAG: F0 -> F1 [label=""];
51+
#EMPTY-DAG: F1 -> F2 [label=""];
52+
#EMPTY-DAG: F2 -> F3 [label=""];
53+
#EMPTY-DAG: F1 [label="@(1)"];
54+
#EMPTY-DAG: F2 [label="@(2)"];
55+
#EMPTY-DAG: F3 [label="@(3)"];
56+
#EMPTY-NEXT: }
57+
58+
#COUNT: digraph xray {
59+
#COUNT-DAG: F0 -> F1 [label="1"];
60+
#COUNT-DAG: F1 -> F2 [label="1"];
61+
#COUNT-DAG: F2 -> F3 [label="1"];
62+
#COUNT-DAG: F1 [label="@(1)"];
63+
#COUNT-DAG: F2 [label="@(2)"];
64+
#COUNT-DAG: F3 [label="@(3)"];
65+
#COUNT-NEXT: }
66+
67+
68+
#TIME: digraph xray {
69+
#TIME-DAG: F0 -> F1 [label="4.{{.*}}"];
70+
#TIME-DAG: F1 -> F2 [label="3.{{.*}}"];
71+
#TIME-DAG: F2 -> F3 [label="1.{{.*}}"];
72+
#TIME-DAG: F1 [label="@(1)"];
73+
#TIME-DAG: F2 [label="@(2)"];
74+
#TIME-DAG: F3 [label="@(3)"];
75+
#TIME-NEXT: }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml \
2+
#RUN: | FileCheck %s -check-prefix=COUNT
3+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e count \
4+
#RUN: | FileCheck %s -check-prefix=COUNT
5+
#
6+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e min \
7+
#RUN: | FileCheck %s -check-prefix=TIME
8+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e med \
9+
#RUN: | FileCheck %s -check-prefix=TIME
10+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e 90p \
11+
#RUN: | FileCheck %s -check-prefix=TIME
12+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e 99p \
13+
#RUN: | FileCheck %s -check-prefix=TIME
14+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e max \
15+
#RUN: | FileCheck %s -check-prefix=TIME
16+
#RUN: llvm-xray graph %s -o - -m %S/Inputs/simple-instrmap.yaml -t yaml -e sum \
17+
#RUN: | FileCheck %s -check-prefix=TIME
18+
---
19+
header:
20+
version: 1
21+
type: 0
22+
constant-tsc: true
23+
nonstop-tsc: true
24+
cycle-frequency: 2601000000
25+
records:
26+
- { type: 0, func-id: 1, cpu: 1, thread: 111, kind: function-enter,
27+
tsc: 10001 }
28+
- { type: 0, func-id: 1, cpu: 1, thread: 111, kind: function-exit,
29+
tsc: 10100 }
30+
...
31+
32+
33+
#EMPTY: digraph xray {
34+
#EMPTY-NEXT: F0 -> F1 [label=""];
35+
#EMPTY-NEXT: F1 [label="@(1)"];
36+
#EMPTY-NEXT: }
37+
38+
#COUNT: digraph xray {
39+
#COUNT-NEXT: F0 -> F1 [label="1"];
40+
#COUNT-NEXT: F1 [label="@(1)"];
41+
#COUNT-NEXT: }
42+
43+
#TIME: digraph xray {
44+
#TIME-NEXT: F0 -> F1 [label="3.8{{.*}}e-08"];
45+
#TIME-NEXT: F1 [label="@(1)"];
46+
#TIME-NEXT: }

‎llvm/tools/llvm-xray/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(LLVM_XRAY_TOOLS
1212
xray-converter.cc
1313
xray-extract.cc
1414
xray-extract.cc
15+
xray-graph.cc
1516
xray-registry.cc)
1617

1718
add_llvm_tool(llvm-xray llvm-xray.cc ${LLVM_XRAY_TOOLS})

‎llvm/tools/llvm-xray/xray-graph.cc

+365
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
//===-- 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+
27+
using namespace llvm;
28+
using namespace xray;
29+
30+
// Setup llvm-xray graph subcommand and its options.
31+
static cl::SubCommand Graph("graph", "Generate function-call graph");
32+
static cl::opt<std::string> GraphInput(cl::Positional,
33+
cl::desc("<xray log file>"),
34+
cl::Required, cl::sub(Graph));
35+
36+
static 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));
39+
static cl::alias GraphOutput2("o", cl::aliasopt(GraphOutput),
40+
cl::desc("Alias for -output"), cl::sub(Graph));
41+
42+
static 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(""));
46+
static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap),
47+
cl::desc("alias for -instr_map"),
48+
cl::sub(Graph));
49+
50+
static 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));
57+
static cl::alias InstrMapFormat2("t", cl::aliasopt(InstrMapFormat),
58+
cl::desc("Alias for -instr-map-format"),
59+
cl::sub(Graph));
60+
61+
static 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));
65+
static cl::alias
66+
GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls),
67+
cl::desc("Alias for -deduce-sibling-calls"),
68+
cl::sub(Graph));
69+
70+
static 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")));
89+
static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel),
90+
cl::desc("Alias for -edge-label"),
91+
cl::sub(Graph));
92+
93+
namespace {
94+
template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); }
95+
96+
void 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.
117+
bool 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+
169+
template <typename U>
170+
void 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+
183+
void 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+
192+
void 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+
217+
void 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+
240+
namespace {
241+
void 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.
275+
void 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.
310+
static 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+
});

‎llvm/tools/llvm-xray/xray-graph.h

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//===-- xray-graph.h - XRay Function Call Graph Renderer --------*- C++ -*-===//
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+
15+
#ifndef XRAY_GRAPH_H
16+
#define XRAY_GRAPH_H
17+
18+
#include <vector>
19+
20+
#include "func-id-helper.h"
21+
#include "llvm/ADT/DenseMap.h"
22+
#include "llvm/ADT/SmallVector.h"
23+
#include "llvm/Support/raw_ostream.h"
24+
#include "llvm/XRay/Trace.h"
25+
#include "llvm/XRay/XRayRecord.h"
26+
27+
namespace llvm {
28+
namespace xray {
29+
30+
/// A class encapsulating the logic related to analyzing XRay traces, producting
31+
/// Graphs from them and then exporting those graphs for review.
32+
class GraphRenderer {
33+
public:
34+
/// An inner struct for common timing statistics information
35+
struct TimeStat {
36+
uint64_t Count;
37+
double Min;
38+
double Median;
39+
double Pct90;
40+
double Pct99;
41+
double Max;
42+
double Sum;
43+
};
44+
45+
/// An inner struct for storing edge attributes for our graph. Here the
46+
/// attributes are mainly function call statistics.
47+
///
48+
/// FIXME: expand to contain more information eg call latencies.
49+
struct EdgeAttribute {
50+
TimeStat S;
51+
std::vector<uint64_t> Timings;
52+
};
53+
54+
/// An Inner Struct for storing vertex attributes, at the moment just
55+
/// SymbolNames, however in future we could store bulk function statistics.
56+
///
57+
/// FIXME: Store more attributes based on instrumentation map.
58+
struct VertexAttribute {
59+
std::string SymbolName;
60+
TimeStat S;
61+
};
62+
63+
private:
64+
/// The Graph stored in an edge-list like format, with the edges also having
65+
/// An attached set of attributes.
66+
DenseMap<int32_t, DenseMap<int32_t, EdgeAttribute>> Graph;
67+
68+
/// Graph Vertex Attributes. These are presently stored seperate from the
69+
/// main graph.
70+
DenseMap<int32_t, VertexAttribute> VertexAttrs;
71+
72+
struct FunctionAttr {
73+
int32_t FuncId;
74+
uint64_t TSC;
75+
};
76+
77+
/// Use a Map to store the Function stack for each thread whilst building the
78+
/// graph.
79+
///
80+
/// FIXME: Perhaps we can Build this into LatencyAccountant? or vise versa?
81+
DenseMap<pid_t, SmallVector<FunctionAttr, 4>> PerThreadFunctionStack;
82+
83+
/// Usefull object for getting human readable Symbol Names.
84+
FuncIdConversionHelper &FuncIdHelper;
85+
bool DeduceSiblingCalls = false;
86+
uint64_t CurrentMaxTSC = 0;
87+
88+
/// A private function to help implement the statistic generation functions;
89+
template <typename U>
90+
void getStats(U begin, U end, GraphRenderer::TimeStat &S);
91+
92+
/// Calculates latency statistics for each edge and stores the data in the
93+
/// Graph
94+
void calculateEdgeStatistics();
95+
96+
/// Calculates latency statistics for each vertex and stores the data in the
97+
/// Graph
98+
void calculateVertexStatistics();
99+
100+
/// Normalises latency statistics for each edge and vertex by CycleFrequency;
101+
void normaliseStatistics(double CycleFrequency);
102+
103+
public:
104+
/// Takes in a reference to a FuncIdHelper in order to have ready access to
105+
/// Symbol names.
106+
explicit GraphRenderer(FuncIdConversionHelper &FuncIdHelper, bool DSC)
107+
: FuncIdHelper(FuncIdHelper), DeduceSiblingCalls(DSC) {}
108+
109+
/// Process an Xray record and expand the graph.
110+
///
111+
/// This Function will return true on success, or false if records are not
112+
/// presented in per-thread call-tree DFS order. (That is for each thread the
113+
/// Records should be in order runtime on an ideal system.)
114+
///
115+
/// FIXME: Make this more robust against small irregularities.
116+
bool accountRecord(const XRayRecord &Record);
117+
118+
/// An enum for enumerating the various statistics gathered on latencies
119+
enum class StatType { COUNT, MIN, MED, PCT90, PCT99, MAX, SUM };
120+
121+
/// Output the Embedded graph in DOT format on \p OS, labeling the edges by
122+
/// \p T
123+
void exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H,
124+
StatType T = StatType::COUNT);
125+
};
126+
}
127+
}
128+
129+
#endif // XRAY_GRAPH_H

0 commit comments

Comments
 (0)
Please sign in to comment.