Changeset View
Changeset View
Standalone View
Standalone View
tools/llvm-xray/xray-graph.cc
//===-- xray-graph.c - XRay Function Call Graph Renderer ------------------===// | //===-- xray-graph.cc - XRay Function Call Graph Renderer -----------------===// | ||||
// | // | ||||
// The LLVM Compiler Infrastructure | // The LLVM Compiler Infrastructure | ||||
// | // | ||||
// This file is distributed under the University of Illinois Open Source | // This file is distributed under the University of Illinois Open Source | ||||
// License. See LICENSE.TXT for details. | // License. See LICENSE.TXT for details. | ||||
// | // | ||||
//===----------------------------------------------------------------------===// | //===----------------------------------------------------------------------===// | ||||
// | // | ||||
Show All 15 Lines | |||||
#include "llvm/XRay/InstrumentationMap.h" | #include "llvm/XRay/InstrumentationMap.h" | ||||
#include "llvm/XRay/Trace.h" | #include "llvm/XRay/Trace.h" | ||||
#include "llvm/XRay/YAMLXRayRecord.h" | #include "llvm/XRay/YAMLXRayRecord.h" | ||||
using namespace llvm; | using namespace llvm; | ||||
using namespace llvm::xray; | using namespace llvm::xray; | ||||
// Setup llvm-xray graph subcommand and its options. | // Setup llvm-xray graph subcommand and its options. | ||||
static cl::SubCommand Graph("graph", "Generate function-call graph"); | static cl::SubCommand GraphC("graph", "Generate function-call graph"); | ||||
static cl::opt<std::string> GraphInput(cl::Positional, | static cl::opt<std::string> GraphInput(cl::Positional, | ||||
cl::desc("<xray log file>"), | cl::desc("<xray log file>"), | ||||
cl::Required, cl::sub(Graph)); | cl::Required, cl::sub(GraphC)); | ||||
static cl::opt<bool> | static cl::opt<bool> | ||||
GraphKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), | GraphKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), | ||||
cl::sub(Graph), cl::init(false)); | cl::sub(GraphC), cl::init(false)); | ||||
static cl::alias GraphKeepGoing2("k", cl::aliasopt(GraphKeepGoing), | static cl::alias GraphKeepGoing2("k", cl::aliasopt(GraphKeepGoing), | ||||
cl::desc("Alias for -keep-going"), | cl::desc("Alias for -keep-going"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<std::string> | static cl::opt<std::string> | ||||
GraphOutput("output", cl::value_desc("Output file"), cl::init("-"), | GraphOutput("output", cl::value_desc("Output file"), cl::init("-"), | ||||
cl::desc("output file; use '-' for stdout"), cl::sub(Graph)); | cl::desc("output file; use '-' for stdout"), cl::sub(GraphC)); | ||||
static cl::alias GraphOutput2("o", cl::aliasopt(GraphOutput), | static cl::alias GraphOutput2("o", cl::aliasopt(GraphOutput), | ||||
cl::desc("Alias for -output"), cl::sub(Graph)); | cl::desc("Alias for -output"), cl::sub(GraphC)); | ||||
static cl::opt<std::string> GraphInstrMap( | static cl::opt<std::string> GraphInstrMap( | ||||
"instr_map", cl::desc("binary with the instrumrntation map, or " | "instr_map", cl::desc("binary with the instrumrntation map, or " | ||||
"a separate instrumentation map"), | "a separate instrumentation map"), | ||||
cl::value_desc("binary with xray_instr_map"), cl::sub(Graph), cl::init("")); | cl::value_desc("binary with xray_instr_map"), cl::sub(GraphC), cl::init("")); | ||||
static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap), | static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap), | ||||
cl::desc("alias for -instr_map"), | cl::desc("alias for -instr_map"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<bool> GraphDeduceSiblingCalls( | static cl::opt<bool> GraphDeduceSiblingCalls( | ||||
"deduce-sibling-calls", | "deduce-sibling-calls", | ||||
cl::desc("Deduce sibling calls when unrolling function call stacks"), | cl::desc("Deduce sibling calls when unrolling function call stacks"), | ||||
cl::sub(Graph), cl::init(false)); | cl::sub(GraphC), cl::init(false)); | ||||
static cl::alias | static cl::alias | ||||
GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls), | GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls), | ||||
cl::desc("Alias for -deduce-sibling-calls"), | cl::desc("Alias for -deduce-sibling-calls"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<GraphRenderer::StatType> | static cl::opt<GraphRenderer::StatType> | ||||
GraphEdgeLabel("edge-label", | GraphEdgeLabel("edge-label", | ||||
cl::desc("Output graphs with edges labeled with this field"), | cl::desc("Output graphs with edges labeled with this field"), | ||||
cl::value_desc("field"), cl::sub(Graph), | cl::value_desc("field"), cl::sub(GraphC), | ||||
cl::init(GraphRenderer::StatType::NONE), | cl::init(GraphRenderer::StatType::NONE), | ||||
cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | ||||
"Do not label Edges"), | "Do not label Edges"), | ||||
clEnumValN(GraphRenderer::StatType::COUNT, | clEnumValN(GraphRenderer::StatType::COUNT, | ||||
"count", "function call counts"), | "count", "function call counts"), | ||||
clEnumValN(GraphRenderer::StatType::MIN, "min", | clEnumValN(GraphRenderer::StatType::MIN, "min", | ||||
"minimum function durations"), | "minimum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::MED, "med", | clEnumValN(GraphRenderer::StatType::MED, "med", | ||||
"median function durations"), | "median function durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT90, "90p", | clEnumValN(GraphRenderer::StatType::PCT90, "90p", | ||||
"90th percentile durations"), | "90th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT99, "99p", | clEnumValN(GraphRenderer::StatType::PCT99, "99p", | ||||
"99th percentile durations"), | "99th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::MAX, "max", | clEnumValN(GraphRenderer::StatType::MAX, "max", | ||||
"maximum function durations"), | "maximum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::SUM, "sum", | clEnumValN(GraphRenderer::StatType::SUM, "sum", | ||||
"sum of call durations"))); | "sum of call durations"))); | ||||
static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel), | static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel), | ||||
cl::desc("Alias for -edge-label"), | cl::desc("Alias for -edge-label"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<GraphRenderer::StatType> GraphVertexLabel( | static cl::opt<GraphRenderer::StatType> GraphVertexLabel( | ||||
"vertex-label", | "vertex-label", | ||||
cl::desc("Output graphs with vertices labeled with this field"), | cl::desc("Output graphs with vertices labeled with this field"), | ||||
cl::value_desc("field"), cl::sub(Graph), | cl::value_desc("field"), cl::sub(GraphC), | ||||
cl::init(GraphRenderer::StatType::NONE), | cl::init(GraphRenderer::StatType::NONE), | ||||
cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | ||||
"Do not label Edges"), | "Do not label Edges"), | ||||
clEnumValN(GraphRenderer::StatType::COUNT, "count", | clEnumValN(GraphRenderer::StatType::COUNT, "count", | ||||
"function call counts"), | "function call counts"), | ||||
clEnumValN(GraphRenderer::StatType::MIN, "min", | clEnumValN(GraphRenderer::StatType::MIN, "min", | ||||
"minimum function durations"), | "minimum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::MED, "med", | clEnumValN(GraphRenderer::StatType::MED, "med", | ||||
"median function durations"), | "median function durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT90, "90p", | clEnumValN(GraphRenderer::StatType::PCT90, "90p", | ||||
"90th percentile durations"), | "90th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT99, "99p", | clEnumValN(GraphRenderer::StatType::PCT99, "99p", | ||||
"99th percentile durations"), | "99th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::MAX, "max", | clEnumValN(GraphRenderer::StatType::MAX, "max", | ||||
"maximum function durations"), | "maximum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::SUM, "sum", | clEnumValN(GraphRenderer::StatType::SUM, "sum", | ||||
"sum of call durations"))); | "sum of call durations"))); | ||||
static cl::alias GraphVertexLabel2("v", cl::aliasopt(GraphVertexLabel), | static cl::alias GraphVertexLabel2("v", cl::aliasopt(GraphVertexLabel), | ||||
cl::desc("Alias for -edge-label"), | cl::desc("Alias for -edge-label"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<GraphRenderer::StatType> GraphEdgeColorType( | static cl::opt<GraphRenderer::StatType> GraphEdgeColorType( | ||||
"color-edges", | "color-edges", | ||||
cl::desc("Output graphs with edge colors determined by this field"), | cl::desc("Output graphs with edge colors determined by this field"), | ||||
cl::value_desc("field"), cl::sub(Graph), | cl::value_desc("field"), cl::sub(GraphC), | ||||
cl::init(GraphRenderer::StatType::NONE), | cl::init(GraphRenderer::StatType::NONE), | ||||
cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | ||||
"Do not label Edges"), | "Do not label Edges"), | ||||
clEnumValN(GraphRenderer::StatType::COUNT, "count", | clEnumValN(GraphRenderer::StatType::COUNT, "count", | ||||
"function call counts"), | "function call counts"), | ||||
clEnumValN(GraphRenderer::StatType::MIN, "min", | clEnumValN(GraphRenderer::StatType::MIN, "min", | ||||
"minimum function durations"), | "minimum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::MED, "med", | clEnumValN(GraphRenderer::StatType::MED, "med", | ||||
"median function durations"), | "median function durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT90, "90p", | clEnumValN(GraphRenderer::StatType::PCT90, "90p", | ||||
"90th percentile durations"), | "90th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT99, "99p", | clEnumValN(GraphRenderer::StatType::PCT99, "99p", | ||||
"99th percentile durations"), | "99th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::MAX, "max", | clEnumValN(GraphRenderer::StatType::MAX, "max", | ||||
"maximum function durations"), | "maximum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::SUM, "sum", | clEnumValN(GraphRenderer::StatType::SUM, "sum", | ||||
"sum of call durations"))); | "sum of call durations"))); | ||||
static cl::alias GraphEdgeColorType2("c", cl::aliasopt(GraphEdgeColorType), | static cl::alias GraphEdgeColorType2("c", cl::aliasopt(GraphEdgeColorType), | ||||
cl::desc("Alias for -color-edges"), | cl::desc("Alias for -color-edges"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
static cl::opt<GraphRenderer::StatType> GraphVertexColorType( | static cl::opt<GraphRenderer::StatType> GraphVertexColorType( | ||||
"color-vertices", | "color-vertices", | ||||
cl::desc("Output graphs with vertex colors determined by this field"), | cl::desc("Output graphs with vertex colors determined by this field"), | ||||
cl::value_desc("field"), cl::sub(Graph), | cl::value_desc("field"), cl::sub(GraphC), | ||||
cl::init(GraphRenderer::StatType::NONE), | cl::init(GraphRenderer::StatType::NONE), | ||||
cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", | ||||
"Do not label Edges"), | "Do not label Edges"), | ||||
clEnumValN(GraphRenderer::StatType::COUNT, "count", | clEnumValN(GraphRenderer::StatType::COUNT, "count", | ||||
"function call counts"), | "function call counts"), | ||||
clEnumValN(GraphRenderer::StatType::MIN, "min", | clEnumValN(GraphRenderer::StatType::MIN, "min", | ||||
"minimum function durations"), | "minimum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::MED, "med", | clEnumValN(GraphRenderer::StatType::MED, "med", | ||||
"median function durations"), | "median function durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT90, "90p", | clEnumValN(GraphRenderer::StatType::PCT90, "90p", | ||||
"90th percentile durations"), | "90th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::PCT99, "99p", | clEnumValN(GraphRenderer::StatType::PCT99, "99p", | ||||
"99th percentile durations"), | "99th percentile durations"), | ||||
clEnumValN(GraphRenderer::StatType::MAX, "max", | clEnumValN(GraphRenderer::StatType::MAX, "max", | ||||
"maximum function durations"), | "maximum function durations"), | ||||
clEnumValN(GraphRenderer::StatType::SUM, "sum", | clEnumValN(GraphRenderer::StatType::SUM, "sum", | ||||
"sum of call durations"))); | "sum of call durations"))); | ||||
static cl::alias GraphVertexColorType2("b", cl::aliasopt(GraphVertexColorType), | static cl::alias GraphVertexColorType2("b", cl::aliasopt(GraphVertexColorType), | ||||
cl::desc("Alias for -edge-label"), | cl::desc("Alias for -edge-label"), | ||||
cl::sub(Graph)); | cl::sub(GraphC)); | ||||
template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); } | template <class T> T diff(T L, T R) { return std::max(L, R) - std::min(L, R); } | ||||
// Updates the statistics for a GraphRenderer::TimeStat | // Updates the statistics for a GraphRenderer::TimeStat | ||||
static void updateStat(GraphRenderer::TimeStat &S, int64_t L) { | static void updateStat(GraphRenderer::TimeStat &S, int64_t L) { | ||||
S.Count++; | S.Count++; | ||||
if (S.Min > L || S.Min == 0) | if (S.Min > L || S.Min == 0) | ||||
S.Min = L; | S.Min = L; | ||||
Show All 28 Lines | Error GraphRenderer::accountRecord(const XRayRecord &Record) { | ||||
if (Record.TSC < CurrentMaxTSC) | if (Record.TSC < CurrentMaxTSC) | ||||
return make_error<StringError>("Records not in order", | return make_error<StringError>("Records not in order", | ||||
make_error_code(errc::invalid_argument)); | make_error_code(errc::invalid_argument)); | ||||
auto &ThreadStack = PerThreadFunctionStack[Record.TId]; | auto &ThreadStack = PerThreadFunctionStack[Record.TId]; | ||||
switch (Record.Type) { | switch (Record.Type) { | ||||
case RecordTypes::ENTER: { | case RecordTypes::ENTER: { | ||||
if (VertexAttrs.count(Record.FuncId) == 0) | if (G.count(Record.FuncId) == 0) | ||||
VertexAttrs[Record.FuncId].SymbolName = | G[Record.FuncId].SymbolName = FuncIdHelper.SymbolOrNumber(Record.FuncId); | ||||
FuncIdHelper.SymbolOrNumber(Record.FuncId); | |||||
ThreadStack.push_back({Record.FuncId, Record.TSC}); | ThreadStack.push_back({Record.FuncId, Record.TSC}); | ||||
break; | break; | ||||
} | } | ||||
case RecordTypes::EXIT: { | case RecordTypes::EXIT: { | ||||
// FIXME: Refactor this and the account subcommand to reducr code | // FIXME: Refactor this and the account subcommand to reduce code | ||||
// duplication | // duplication | ||||
if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) { | if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) { | ||||
if (!DeduceSiblingCalls) | if (!DeduceSiblingCalls) | ||||
return make_error<StringError>("No matching ENTRY record", | return make_error<StringError>("No matching ENTRY record", | ||||
make_error_code(errc::invalid_argument)); | make_error_code(errc::invalid_argument)); | ||||
auto Parent = std::find_if( | auto Parent = std::find_if( | ||||
ThreadStack.rbegin(), ThreadStack.rend(), | ThreadStack.rbegin(), ThreadStack.rend(), | ||||
[&](const FunctionAttr &A) { return A.FuncId == Record.FuncId; }); | [&](const FunctionAttr &A) { return A.FuncId == Record.FuncId; }); | ||||
if (Parent == ThreadStack.rend()) | if (Parent == ThreadStack.rend()) | ||||
return make_error<StringError>( | return make_error<StringError>( | ||||
"No matching Entry record in stack", | "No matching Entry record in stack", | ||||
make_error_code(errc::invalid_argument)); // There is no matching | make_error_code(errc::invalid_argument)); // There is no matching | ||||
// Function for this exit. | // Function for this exit. | ||||
while (ThreadStack.back().FuncId != Record.FuncId) { | while (ThreadStack.back().FuncId != Record.FuncId) { | ||||
uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); | TimestampT D = diff(ThreadStack.back().TSC, Record.TSC); | ||||
int32_t TopFuncId = ThreadStack.back().FuncId; | VertexIdentifier TopFuncId = ThreadStack.back().FuncId; | ||||
ThreadStack.pop_back(); | ThreadStack.pop_back(); | ||||
assert(ThreadStack.size() != 0); | assert(ThreadStack.size() != 0); | ||||
auto &EA = Graph[ThreadStack.back().FuncId][TopFuncId]; | EdgeIdentifier EI(ThreadStack.back().FuncId, TopFuncId); | ||||
EdgeAttribute &EA = G[EI]; | |||||
EA.Timings.push_back(D); | EA.Timings.push_back(D); | ||||
updateStat(EA.S, D); | updateStat(EA.S, D); | ||||
updateStat(VertexAttrs[TopFuncId].S, D); | updateStat(G[TopFuncId].S, D); | ||||
} | } | ||||
} | } | ||||
uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); | uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); | ||||
ThreadStack.pop_back(); | ThreadStack.pop_back(); | ||||
auto &V = Graph[ThreadStack.empty() ? 0 : ThreadStack.back().FuncId]; | VertexIdentifier VI = ThreadStack.empty() ? 0 : ThreadStack.back().FuncId; | ||||
auto &EA = V[Record.FuncId]; | EdgeIdentifier EI(VI, Record.FuncId); | ||||
auto &EA = G[EI]; | |||||
EA.Timings.push_back(D); | EA.Timings.push_back(D); | ||||
updateStat(EA.S, D); | updateStat(EA.S, D); | ||||
updateStat(VertexAttrs[Record.FuncId].S, D); | updateStat(G[Record.FuncId].S, D); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
return Error::success(); | return Error::success(); | ||||
} | } | ||||
template <typename U> | template <typename U> | ||||
Show All 17 Lines | void GraphRenderer::updateMaxStats(const GraphRenderer::TimeStat &S, | ||||
M.Median = std::max(M.Median, S.Median); | M.Median = std::max(M.Median, S.Median); | ||||
M.Pct90 = std::max(M.Pct90, S.Pct90); | M.Pct90 = std::max(M.Pct90, S.Pct90); | ||||
M.Pct99 = std::max(M.Pct99, S.Pct99); | M.Pct99 = std::max(M.Pct99, S.Pct99); | ||||
M.Max = std::max(M.Max, S.Max); | M.Max = std::max(M.Max, S.Max); | ||||
M.Sum = std::max(M.Sum, S.Sum); | M.Sum = std::max(M.Sum, S.Sum); | ||||
} | } | ||||
void GraphRenderer::calculateEdgeStatistics() { | void GraphRenderer::calculateEdgeStatistics() { | ||||
for (auto &V : Graph) { | assert(!G.edges().empty()); | ||||
for (auto &E : V.second) { | for (auto &E : G.edges()) { | ||||
auto &A = E.second; | auto &A = E.second; | ||||
assert(!A.Timings.empty()); | |||||
assert((A.Timings[0] > 0)); | |||||
getStats(A.Timings.begin(), A.Timings.end(), A.S); | getStats(A.Timings.begin(), A.Timings.end(), A.S); | ||||
updateMaxStats(A.S, GraphEdgeMax); | assert(A.S.Sum > 0); | ||||
} | updateMaxStats(A.S, G.GraphEdgeMax); | ||||
} | } | ||||
} | } | ||||
void GraphRenderer::calculateVertexStatistics() { | void GraphRenderer::calculateVertexStatistics() { | ||||
DenseMap<int32_t, std::pair<uint64_t, SmallVector<EdgeAttribute *, 4>>> | |||||
IncommingEdges; | |||||
uint64_t MaxCount = 0; | |||||
for (auto &V : Graph) { | |||||
for (auto &E : V.second) { | |||||
auto &IEV = IncommingEdges[E.first]; | |||||
IEV.second.push_back(&E.second); | |||||
IEV.first += E.second.S.Count; | |||||
if (IEV.first > MaxCount) | |||||
MaxCount = IEV.first; | |||||
} | |||||
} | |||||
std::vector<uint64_t> TempTimings; | std::vector<uint64_t> TempTimings; | ||||
TempTimings.reserve(MaxCount); | for (auto &V : G.vertices()) { | ||||
for (auto &V : IncommingEdges) { | assert(V.first == 0 || G[V.first].S.Sum != 0); | ||||
dberris: Pro-tip: when asserting, provide some human-readable explanation for what the expectation is. | |||||
for (auto &P : V.second.second) { | if (V.first != 0) { | ||||
TempTimings.insert(TempTimings.end(), P->Timings.begin(), | for (auto &E : G.inEdges(V.first)) { | ||||
P->Timings.end()); | auto &A = E.second; | ||||
TempTimings.insert(TempTimings.end(), A.Timings.begin(), | |||||
A.Timings.end()); | |||||
} | } | ||||
getStats(TempTimings.begin(), TempTimings.end(), VertexAttrs[V.first].S); | assert(!TempTimings.empty() && TempTimings[0] > 0); | ||||
updateMaxStats(VertexAttrs[V.first].S, GraphVertexMax); | getStats(TempTimings.begin(), TempTimings.end(), G[V.first].S); | ||||
updateMaxStats(G[V.first].S, G.GraphVertexMax); | |||||
TempTimings.clear(); | TempTimings.clear(); | ||||
} | } | ||||
} | } | ||||
} | |||||
// A Helper function for normalizeStatistics which normalises a single | // A Helper function for normalizeStatistics which normalises a single | ||||
// TimeStat element. | // TimeStat element. | ||||
static void normalizeTimeStat(GraphRenderer::TimeStat &S, | static void normalizeTimeStat(GraphRenderer::TimeStat &S, | ||||
double CycleFrequency) { | double CycleFrequency) { | ||||
S.Min /= CycleFrequency; | S.Min /= CycleFrequency; | ||||
S.Median /= CycleFrequency; | S.Median /= CycleFrequency; | ||||
S.Max /= CycleFrequency; | S.Max /= CycleFrequency; | ||||
S.Sum /= CycleFrequency; | S.Sum /= CycleFrequency; | ||||
S.Pct90 /= CycleFrequency; | S.Pct90 /= CycleFrequency; | ||||
S.Pct99 /= CycleFrequency; | S.Pct99 /= CycleFrequency; | ||||
} | } | ||||
// Normalises the statistics in the graph for a given TSC frequency. | // Normalises the statistics in the graph for a given TSC frequency. | ||||
void GraphRenderer::normalizeStatistics(double CycleFrequency) { | void GraphRenderer::normalizeStatistics(double CycleFrequency) { | ||||
for (auto &V : Graph) { | for (auto &E : G.edges()) { | ||||
for (auto &E : V.second) { | |||||
auto &S = E.second.S; | auto &S = E.second.S; | ||||
normalizeTimeStat(S, CycleFrequency); | normalizeTimeStat(S, CycleFrequency); | ||||
} | } | ||||
} | for (auto &V : G.vertices()) { | ||||
for (auto &V : VertexAttrs) { | |||||
auto &S = V.second.S; | auto &S = V.second.S; | ||||
normalizeTimeStat(S, CycleFrequency); | normalizeTimeStat(S, CycleFrequency); | ||||
} | } | ||||
normalizeTimeStat(GraphEdgeMax, CycleFrequency); | normalizeTimeStat(G.GraphEdgeMax, CycleFrequency); | ||||
normalizeTimeStat(GraphVertexMax, CycleFrequency); | normalizeTimeStat(G.GraphVertexMax, CycleFrequency); | ||||
} | } | ||||
// Returns a string containing the value of statistic field T | // Returns a string containing the value of statistic field T | ||||
std::string | std::string | ||||
GraphRenderer::TimeStat::getAsString(GraphRenderer::StatType T) const { | GraphRenderer::TimeStat::getAsString(GraphRenderer::StatType T) const { | ||||
std::string St; | std::string St; | ||||
raw_string_ostream S{St}; | raw_string_ostream S{St}; | ||||
switch (T) { | switch (T) { | ||||
▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | |||||
// object on OS. It does this in the expected way by itterating | // object on OS. It does this in the expected way by itterating | ||||
// through all edges then vertices and then outputting them and their | // through all edges then vertices and then outputting them and their | ||||
// annotations. | // annotations. | ||||
// | // | ||||
// FIXME: output more information, better presented. | // FIXME: output more information, better presented. | ||||
void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, | void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, | ||||
StatType ET, StatType EC, StatType VT, | StatType ET, StatType EC, StatType VT, | ||||
StatType VC) { | StatType VC) { | ||||
G.GraphEdgeMax = {}; | |||||
G.GraphVertexMax = {}; | |||||
calculateEdgeStatistics(); | calculateEdgeStatistics(); | ||||
Why the empty line? dberris: Why the empty line? | |||||
calculateVertexStatistics(); | calculateVertexStatistics(); | ||||
if (H.CycleFrequency) | if (H.CycleFrequency) | ||||
normalizeStatistics(H.CycleFrequency); | normalizeStatistics(H.CycleFrequency); | ||||
OS << "digraph xray {\n"; | OS << "digraph xray {\n"; | ||||
if (VT != StatType::NONE) | if (VT != StatType::NONE) | ||||
OS << "node [shape=record];\n"; | OS << "node [shape=record];\n"; | ||||
for (const auto &V : Graph) | for (const auto &E : G.edges()) { | ||||
for (const auto &E : V.second) { | |||||
const auto &S = E.second.S; | const auto &S = E.second.S; | ||||
OS << "F" << V.first << " -> " | OS << "F" << E.first.first << " -> " | ||||
<< "F" << E.first << " [label=\"" << S.getAsString(ET) << "\""; | << "F" << E.first.second << " [label=\"" << S.getAsString(ET) << "\""; | ||||
if (EC != StatType::NONE) | if (EC != StatType::NONE) | ||||
OS << " color=\"" << getColor(S.compare(EC, GraphEdgeMax)) << "\""; | OS << " color=\"" << getColor(S.compare(EC, G.GraphEdgeMax)) << "\""; | ||||
OS << "];\n"; | OS << "];\n"; | ||||
} | } | ||||
for (const auto &V : VertexAttrs) { | for (const auto &V : G.vertices()) { | ||||
const auto &VA = V.second; | const auto &VA = V.second; | ||||
if (V.first == 0) { | |||||
continue; | |||||
} | |||||
LLVM code usually skips {} on single line statements. dblaikie: LLVM code usually skips {} on single line statements. | |||||
OS << "F" << V.first << " [label=\"" << (VT != StatType::NONE ? "{" : "") | OS << "F" << V.first << " [label=\"" << (VT != StatType::NONE ? "{" : "") | ||||
<< (VA.SymbolName.size() > 40 ? VA.SymbolName.substr(0, 40) + "..." | << (VA.SymbolName.size() > 40 ? VA.SymbolName.substr(0, 40) + "..." | ||||
: VA.SymbolName); | : VA.SymbolName); | ||||
if (VT != StatType::NONE) | if (VT != StatType::NONE) | ||||
OS << "|" << VA.S.getAsString(VT) << "}\""; | OS << "|" << VA.S.getAsString(VT) << "}\""; | ||||
else | else | ||||
OS << "\""; | OS << "\""; | ||||
if (VC != StatType::NONE) | if (VC != StatType::NONE) | ||||
OS << " color=\"" << getColor(VA.S.compare(VC, GraphVertexMax)) << "\""; | OS << " color=\"" << getColor(VA.S.compare(VC, G.GraphVertexMax)) << "\""; | ||||
OS << "];\n"; | OS << "];\n"; | ||||
} | } | ||||
OS << "}\n"; | OS << "}\n"; | ||||
} | } | ||||
// Here we register and implement the llvm-xray graph subcommand. | // Here we register and implement the llvm-xray graph subcommand. | ||||
// The bulk of this code reads in the options, opens the required files, uses | // The bulk of this code reads in the options, opens the required files, uses | ||||
// those files to create a context for analysing the xray trace, then there is a | // those files to create a context for analysing the xray trace, then there is a | ||||
// short loop which actually analyses the trace, generates the graph and then | // short loop which actually analyses the trace, generates the graph and then | ||||
// outputs it as a DOT. | // outputs it as a DOT. | ||||
// | // | ||||
// FIXME: include additional filtering and annalysis passes to provide more | // FIXME: include additional filtering and annalysis passes to provide more | ||||
// specific useful information. | // specific useful information. | ||||
static CommandRegistration Unused(&Graph, []() -> Error { | static CommandRegistration Unused(&GraphC, []() -> Error { | ||||
InstrumentationMap Map; | InstrumentationMap Map; | ||||
if (!GraphInstrMap.empty()) { | if (!GraphInstrMap.empty()) { | ||||
auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); | auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); | ||||
if (!InstrumentationMapOrError) | if (!InstrumentationMapOrError) | ||||
return joinErrors( | return joinErrors( | ||||
make_error<StringError>( | make_error<StringError>( | ||||
Twine("Cannot open instrumentation map '") + GraphInstrMap + "'", | Twine("Cannot open instrumentation map '") + GraphInstrMap + "'", | ||||
std::make_error_code(std::errc::invalid_argument)), | std::make_error_code(std::errc::invalid_argument)), | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | if (!GraphKeepGoing) | ||||
return joinErrors(make_error<StringError>( | return joinErrors(make_error<StringError>( | ||||
"Error encountered generating the call graph.", | "Error encountered generating the call graph.", | ||||
std::make_error_code(std::errc::invalid_argument)), | std::make_error_code(std::errc::invalid_argument)), | ||||
std::move(E)); | std::move(E)); | ||||
handleAllErrors(std::move(E), | handleAllErrors(std::move(E), | ||||
[&](const ErrorInfoBase &E) { E.log(errs()); }); | [&](const ErrorInfoBase &E) { E.log(errs()); }); | ||||
} | } | ||||
GR.exportGraphAsDOT(OS, Header, GraphEdgeLabel, GraphEdgeColorType, | GR.exportGraphAsDOT(OS, Header, GraphEdgeLabel, GraphEdgeColorType, | ||||
GraphVertexLabel, GraphVertexColorType); | GraphVertexLabel, GraphVertexColorType); | ||||
return Error::success(); | return Error::success(); | ||||
}); | }); |
Pro-tip: when asserting, provide some human-readable explanation for what the expectation is. As in: