Index: tools/llvm-xray/CMakeLists.txt =================================================================== --- tools/llvm-xray/CMakeLists.txt +++ tools/llvm-xray/CMakeLists.txt @@ -14,6 +14,7 @@ xray-extract.cc xray-extract.cc xray-graph.cc + xray-graph-diff.cc xray-registry.cc) add_llvm_tool(llvm-xray llvm-xray.cc ${LLVM_XRAY_TOOLS}) Index: tools/llvm-xray/xray-graph-diff.h =================================================================== --- /dev/null +++ tools/llvm-xray/xray-graph-diff.h @@ -0,0 +1,82 @@ +//===-- xray-graph-diff.h - XRay Function Call Graph Renderer ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Generate a DOT file to represent the function call graph encountered in +// the trace. +// +//===----------------------------------------------------------------------===// + +#ifndef XRAY_GRAPH_DIFF_H +#define XRAY_GRAPH_DIFF_H + +#include "xray-graph.h" +#include "llvm/XRay/Graph.h" +#include "llvm/ADT/StringMap.h" + +namespace llvm { +namespace xray { + +class GraphDiffRenderer{ + public: + using StatType = GraphRenderer::StatType; + using TimeStat = GraphRenderer::TimeStat; + + struct EdgeAttribute { + const GraphRenderer::GraphT::EdgeValueType *A = nullptr; + const GraphRenderer::GraphT::EdgeValueType *B = nullptr; + }; + + struct VertexAttribute { + const GraphRenderer::GraphT::VertexValueType *A = nullptr; + const GraphRenderer::GraphT::VertexValueType *B = nullptr; + }; + + class GraphT : public Graph{ + public: + std::pair EdgeMaxDiff = {{},{}}; + std::pair VertexMaxDiff = {{},{}}; + }; + + class Factory{ + const GraphRenderer::GraphT &G1; + const GraphRenderer::GraphT &G2; + public: + Factory(const GraphRenderer::GraphT &_G1, + const GraphRenderer::GraphT &_G2): G1(_G1), G2(_G2){}; + + Expected getGraphDiffRenderer(); + }; + + + private: + GraphT G; + StringMap IM; + + GraphDiffRenderer() = default; + + void updateEdgeDiff(const TimeStat & A,const TimeStat& B); + void updateVertexDiff(const TimeStat &A,const TimeStat& B); + + public: + void exportGraphAsDOT(raw_ostream &OS, + StatType EdgeLabel = StatType::NONE, + StatType EdgeColor = StatType::NONE, + StatType VertexLabel = StatType::NONE, + StatType VertexColor = StatType::NONE); + + const GraphT &getGraph(){ + return G; + }; +}; + +} +} + + +#endif Index: tools/llvm-xray/xray-graph-diff.cc =================================================================== --- /dev/null +++ tools/llvm-xray/xray-graph-diff.cc @@ -0,0 +1,495 @@ +//===-- xray-graph-diff.cc - XRay Function Call Graph Renderer ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Generate a DOT file to represent the function call graph encountered in +// the trace. +// +//===----------------------------------------------------------------------===// +#include +#include + +#include "xray-graph.h" +#include "xray-graph-diff.h" +#include "xray-registry.h" + +#include "xray-color-helper.h" +#include "llvm/XRay/Trace.h" + +using namespace llvm; +using namespace xray; + +static cl::SubCommand GraphDiff("graph-diff", + "Generate diff of function-call graphs"); +static cl::opt GraphDiffInput1(cl::Positional, + cl::desc(""), + cl::Required, cl::sub(GraphDiff)); +static cl::opt GraphDiffInput2(cl::Positional, + cl::desc(""), + cl::Required, cl::sub(GraphDiff)); + +static cl::opt + GraphDiffKeepGoing("keep-going", + cl::desc("Keep going on errors encountered"), + cl::sub(GraphDiff), + cl::init(false)); +static cl::alias + GraphDiffKeepGoingA("k", cl::aliasopt(GraphDiffKeepGoing), + cl::desc("Alias for -keep-going"), + cl::sub(GraphDiff)); +static cl::opt + GraphDiffKeepGoing1("keep-going-1", + cl::desc("Keep going on errors encountered in trace 1"), + cl::sub(GraphDiff), + cl::init(false)); +static cl::alias + GraphDiffKeepGoing1A("k1", cl::aliasopt(GraphDiffKeepGoing1), + cl::desc("Alias for -keep-going-1"), + cl::sub(GraphDiff)); +static cl::opt + GraphDiffKeepGoing2("keep-going-2", + cl::desc("Keep going on errors encountered in trace 2"), + cl::sub(GraphDiff), + cl::init(false)); +static cl::alias + GraphDiffKeepGoing2A("k2", cl::aliasopt(GraphDiffKeepGoing2), + cl::desc("Alias for -keep-going-2"), + cl::sub(GraphDiff)); + +static cl::opt + GraphDiffInstrMap("instr-map", + cl::desc("binary with the instrumentation map, or " + "a separate instrumentation map for graph"), + cl::value_desc("binary with xray_instr_map or yaml"), + cl::sub(GraphDiff), cl::init("")); +static cl::alias + GraphDiffInstrMapA("m", cl::aliasopt(GraphDiffInstrMap), + cl::desc("Alias for -instr-map"), + cl::sub(GraphDiff)); +static cl::opt + GraphDiffInstrMap1("instr-map-1", + cl::desc("binary with the instrumentation map, or " + "a separate instrumentation map for graph 1"), + cl::value_desc("binary with xray_instr_map or yaml"), + cl::sub(GraphDiff), cl::init("")); +static cl::alias + GraphDiffInstrMap1A("m1", cl::aliasopt(GraphDiffInstrMap1), + cl::desc("Alias for -instr-map-1"), + cl::sub(GraphDiff)); +static cl::opt + GraphDiffInstrMap2("instr-map-2", + cl::desc("binary with the instrumentation map, or " + "a separate instrumentation map for graph 2"), + cl::value_desc("binary with xray_instr_map or yaml"), + cl::sub(GraphDiff), cl::init("")); +static cl::alias + GraphDiffInstrMap2A("m2", cl::aliasopt(GraphDiffInstrMap2), + cl::desc("Alias for -instr-map-2"), + cl::sub(GraphDiff)); + +static cl::opt GraphDiffDeduceSiblingCalls( + "deduce-sibling-calls", + cl::desc("Deduce sibling calls when unrolling function call stacks"), + cl::sub(GraphDiff), cl::init(false)); +static cl::alias + GraphDiffDeduceSiblingCallsA("d", cl::aliasopt(GraphDiffDeduceSiblingCalls), + cl::desc("Alias for -deduce-sibling-calls"), + cl::sub(GraphDiff)); +static cl::opt GraphDiffDeduceSiblingCalls1( + "deduce-sibling-calls-1", + cl::desc("Deduce sibling calls when unrolling function call stacks"), + cl::sub(GraphDiff), cl::init(false)); +static cl::alias + GraphDiffDeduceSiblingCalls1A("d1", cl::aliasopt(GraphDiffDeduceSiblingCalls1), + cl::desc("Alias for -deduce-sibling-calls-1"), + cl::sub(GraphDiff)); +static cl::opt GraphDiffDeduceSiblingCalls2( + "deduce-sibling-calls-2", + cl::desc("Deduce sibling calls when unrolling function call stacks"), + cl::sub(GraphDiff), cl::init(false)); +static cl::alias + GraphDiffDeduceSiblingCalls2A("d2", cl::aliasopt(GraphDiffDeduceSiblingCalls2), + cl::desc("Alias for -deduce-sibling-calls-2"), + cl::sub(GraphDiff)); + +static cl::opt + GraphDiffEdgeLabel("edge-label", + cl::desc("Output graphs with edges labeled with this field"), + cl::value_desc("field"), cl::sub(GraphDiff), + cl::init(GraphRenderer::StatType::NONE), + cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", + "Do not label Edges"), + clEnumValN(GraphRenderer::StatType::COUNT, + "count", "function call counts"), + clEnumValN(GraphRenderer::StatType::MIN, "min", + "minimum function durations"), + clEnumValN(GraphRenderer::StatType::MED, "med", + "median function durations"), + clEnumValN(GraphRenderer::StatType::PCT90, "90p", + "90th percentile durations"), + clEnumValN(GraphRenderer::StatType::PCT99, "99p", + "99th percentile durations"), + clEnumValN(GraphRenderer::StatType::MAX, "max", + "maximum function durations"), + clEnumValN(GraphRenderer::StatType::SUM, "sum", + "sum of call durations"))); +static cl::alias + GraphDiffEdgeLabelA("e", cl::aliasopt(GraphDiffEdgeLabel), + cl::desc("Alias for -edge-label"), + cl::sub(GraphDiff)); + +static cl::opt GraphDiffVertexLabel( + "vertex-label", + cl::desc("Output graphs with vertices labeled with this field"), + cl::value_desc("field"), cl::sub(GraphDiff), + cl::init(GraphRenderer::StatType::NONE), + cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", + "Do not label Vertices"), + clEnumValN(GraphRenderer::StatType::COUNT, "count", + "function call counts"), + clEnumValN(GraphRenderer::StatType::MIN, "min", + "minimum function durations"), + clEnumValN(GraphRenderer::StatType::MED, "med", + "median function durations"), + clEnumValN(GraphRenderer::StatType::PCT90, "90p", + "90th percentile durations"), + clEnumValN(GraphRenderer::StatType::PCT99, "99p", + "99th percentile durations"), + clEnumValN(GraphRenderer::StatType::MAX, "max", + "maximum function durations"), + clEnumValN(GraphRenderer::StatType::SUM, "sum", + "sum of call durations"))); +static cl::alias + GraphDiffVertexLabelA("v", cl::aliasopt(GraphDiffVertexLabel), + cl::desc("Alias for -vertex-label"), + cl::sub(GraphDiff)); + +static cl::opt GraphDiffEdgeColorType( + "color-edges", + cl::desc("Output graphs with edge colors determined by this field"), + cl::value_desc("field"), cl::sub(GraphDiff), + cl::init(GraphRenderer::StatType::NONE), + cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", + "Do not color Edges"), + clEnumValN(GraphRenderer::StatType::COUNT, "count", + "function call counts"), + clEnumValN(GraphRenderer::StatType::MIN, "min", + "minimum function durations"), + clEnumValN(GraphRenderer::StatType::MED, "med", + "median function durations"), + clEnumValN(GraphRenderer::StatType::PCT90, "90p", + "90th percentile durations"), + clEnumValN(GraphRenderer::StatType::PCT99, "99p", + "99th percentile durations"), + clEnumValN(GraphRenderer::StatType::MAX, "max", + "maximum function durations"), + clEnumValN(GraphRenderer::StatType::SUM, "sum", + "sum of call durations"))); +static cl::alias + GraphDiffEdgeColorTypeA("c", cl::aliasopt(GraphDiffEdgeColorType), + cl::desc("Alias for -color-edges"), + cl::sub(GraphDiff)); + + +static cl::opt GraphDiffVertexColorType( + "color-vertices", + cl::desc("Output graphs with vertex colors determined by this field"), + cl::value_desc("field"), cl::sub(GraphDiff), + cl::init(GraphRenderer::StatType::NONE), + cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", + "Do not color vertices"), + clEnumValN(GraphRenderer::StatType::COUNT, "count", + "function call counts"), + clEnumValN(GraphRenderer::StatType::MIN, "min", + "minimum function durations"), + clEnumValN(GraphRenderer::StatType::MED, "med", + "median function durations"), + clEnumValN(GraphRenderer::StatType::PCT90, "90p", + "90th percentile durations"), + clEnumValN(GraphRenderer::StatType::PCT99, "99p", + "99th percentile durations"), + clEnumValN(GraphRenderer::StatType::MAX, "max", + "maximum function durations"), + clEnumValN(GraphRenderer::StatType::SUM, "sum", + "sum of call durations"))); +static cl::alias + GraphDiffVertexColorTypeA("b", cl::aliasopt(GraphDiffVertexColorType), + cl::desc("Alias for -color-vertices"), + cl::sub(GraphDiff)); + +static cl::opt + GraphDiffOutput("output", cl::value_desc("Output file"), cl::init("-"), + cl::desc("output file; use '-' for stdout"), cl::sub(GraphDiff)); +static cl::alias + GraphDiffOutputA("o", cl::aliasopt(GraphDiffOutput), + cl::desc("Alias for -output"), + cl::sub(GraphDiff)); + +Expected GraphDiffRenderer::Factory::getGraphDiffRenderer(){ + GraphDiffRenderer R; + + for (const auto &V : G1.vertices()) + R.G[V.second.SymbolName].A = &V; + + for (const auto &V : G2.vertices()) + R.G[V.second.SymbolName].B = &V; + + for (const auto &E : G1.edges()){ + auto SOrErr = G1.at(E.first.first); + auto EOrErr = G1.at(E.first.second); + if(!SOrErr) + return SOrErr.takeError(); + if(!EOrErr) + return EOrErr.takeError(); + GraphDiffRenderer::GraphT::EdgeIdentifier I + {SOrErr->SymbolName, + EOrErr->SymbolName}; + R.G[I].A = &E; + } + + for (const auto &E : G2.edges()){ + auto SOrErr = G2.at(E.first.first); + auto EOrErr = G2.at(E.first.second); + if(!SOrErr) + return SOrErr.takeError(); + if(!EOrErr) + return EOrErr.takeError(); + GraphDiffRenderer::GraphT::EdgeIdentifier I + {SOrErr->SymbolName, + EOrErr->SymbolName}; + R.G[I].B = &E; + } + + int i = 0; + for (const auto &V : R.G.vertices()){ + R.IM[V.first] = i++; + + if (V.second.A == nullptr || V.second.B == nullptr) + continue; + + R.updateVertexDiff(V.second.A->second.S, V.second.B->second.S); + } + for (const auto &E : R.G.edges()){ + if(E.second.A == nullptr || E.second.B == nullptr) + continue; + R.updateEdgeDiff(E.second.A->second.S, E.second.B->second.S); + } + + return R; +} +static double divF(double A, double B){ + return A/B - B/A; +} +static std::pair maxDiff(double AA, double AB,double BA,double BB){ + if(AA == 0.0 || AB == 0.0 || std::abs(divF(AA, AB)) < std::abs(divF(BA, BB))) + return {BA,BB}; + return {AA, AB}; +} +static std::pair maxDiff(int64_t AA, int64_t AB, int64_t BA, int64_t BB){ + if(AA == 0 || AB == 0 || std::abs(divF(AA, AB)) < std::abs(divF(BA,BB))) + return {BA, BB}; + return {AA, AB}; +} + +using TimeStat = GraphDiffRenderer::TimeStat; + +static void updateMaxDiff(TimeStat &MA, TimeStat &MB, + const TimeStat &A, const TimeStat &B){ + auto IP = maxDiff(MA.Count, MB.Count, A.Count, B.Count); + MA.Count = IP.first; + MB.Count = IP.second; + auto DP = maxDiff(MA.Min, MB.Min, A.Min, B.Min); + MA.Min = DP.first; + MB.Min = DP.second; + DP = maxDiff(MA.Median, MB.Median, A.Median, B.Median); + MA.Median = DP.first; + MA.Median = DP.second; + DP = maxDiff(MA.Pct90, MB.Pct90, A.Pct90, B.Pct90); + MA.Pct90 = DP.first; + MB.Pct90 = DP.second; + DP = maxDiff(MA.Pct99, MB.Pct99, A.Pct99, B.Pct99); + MA.Pct99 = DP.first; + MB.Pct99 = DP.second; + DP = maxDiff(MA.Max, MB.Max, A.Max, B.Max); + MA.Max = DP.first; + MB.Max = DP.second; + DP = maxDiff(MA.Sum, MB.Sum, A.Sum, B.Sum); + MA.Sum = DP.first; + MB.Sum = DP.first; +} + +void GraphDiffRenderer::updateEdgeDiff(const TimeStat &A, + const TimeStat &B){ + updateMaxDiff(G.EdgeMaxDiff.first, G.EdgeMaxDiff.second, A, B); +} + +void GraphDiffRenderer::updateVertexDiff(const TimeStat &A, + const TimeStat &B){ + updateMaxDiff(G.VertexMaxDiff.first, G.VertexMaxDiff.second, A, B); +} + +static std::string getColor(const GraphDiffRenderer::GraphT::EdgeValueType &E, + const GraphDiffRenderer::GraphT &G, + ColorHelper H, + GraphDiffRenderer::StatType T){ + if (E.second.A == nullptr) + return "green"; + if (E.second.B == nullptr) + return "red"; + + if (T == GraphDiffRenderer::StatType::NONE) + return "black"; + double R = divF(E.second.A->second.S.getDouble(T), + E.second.B->second.S.getDouble(T)); + double MR = std::abs(divF(G.EdgeMaxDiff.first.getDouble(T), + G.EdgeMaxDiff.second.getDouble(T))); + if (MR == 0.0 && R == 0.0){ + MR = 1.0; + } + return H.getColor(copysign(std::sqrt(std::abs(R/MR)), R)); +} + +static std::string getColor(const GraphDiffRenderer::GraphT::VertexValueType &E, + const GraphDiffRenderer::GraphT &G, + ColorHelper H, + GraphDiffRenderer::StatType T){ + if (E.second.A == nullptr) + return "green"; + if (E.second.B == nullptr) + return "red"; + + if (T == GraphDiffRenderer::StatType::NONE) + return "black"; + + double R = divF(E.second.A->second.S.getDouble(T), + E.second.B->second.S.getDouble(T)); + + + double MR = divF(G.VertexMaxDiff.first.getDouble(T), + G.VertexMaxDiff.second.getDouble(T)); + + return H.getColor(copysign(std::sqrt(std::abs(R/MR)), R)); +} + +static Twine trunS(const StringRef &S, size_t n){ + return (S.size() > n) ? Twine(S.substr(0, n)) + "..." + : Twine(S); +} + +void GraphDiffRenderer::exportGraphAsDOT(raw_ostream &OS, + StatType EdgeLabel, + StatType EdgeColor, + StatType VertexLabel, + StatType VertexColor){ + ColorHelper H(ColorHelper::DivergingScheme::PiYG); + + OS << "digraph xray {\n"; + + if (VertexLabel != StatType::NONE) + OS << "node [shape=record];\n"; + + for (const auto &E : G.edges()) { + OS << "F" << IM[E.first.first] << " -> " + << "F" << IM[E.first.second] + << " [tooltip=\"" << trunS(E.first.first, 20) << " -> " + << trunS(E.first.second,20) << "\"" + << " color=\"" << getColor(E, G, H, EdgeColor) << "\"]\n"; + } + + for (const auto &V : G.vertices()) { + if (V.first.equals("")) { + OS << "F" << IM[V.first] << " [label=\"F0\"]"; + continue; + } + OS << "F" << IM[V.first] << " [label=\"" << trunS(V.first, 40) << "\"" + << " color=\"" << getColor(V, G, H, VertexColor) << "\"];\n"; + } + OS << "}\n"; +}; + +template +static T &ifSpecified(T &A, cl::alias &AA, T &B){ + if(A.getPosition() == 0 && AA.getPosition() == 0){ + return B; + } + return A; +} + +static CommandRegistration Unused(&GraphDiff, []() -> Error { + GraphRenderer::Factory F1; + GraphRenderer::Factory F2; + + F1.KeepGoing = ifSpecified(GraphDiffKeepGoing1, + GraphDiffKeepGoing1A, + GraphDiffKeepGoing); + F2.KeepGoing = ifSpecified(GraphDiffKeepGoing2, + GraphDiffKeepGoing2A, + GraphDiffKeepGoing); + + F1.DeduceSiblingCalls = ifSpecified(GraphDiffDeduceSiblingCalls1, + GraphDiffDeduceSiblingCalls1A, + GraphDiffDeduceSiblingCalls); + F2.DeduceSiblingCalls = ifSpecified(GraphDiffDeduceSiblingCalls2, + GraphDiffDeduceSiblingCalls2A, + GraphDiffDeduceSiblingCalls); + + F1.InstrMap = ifSpecified(GraphDiffInstrMap1, + GraphDiffInstrMap1A, + GraphDiffInstrMap); + F2.InstrMap = ifSpecified(GraphDiffInstrMap2, + GraphDiffInstrMap2A, + GraphDiffInstrMap); + + auto Trace1OrErr = loadTraceFile(GraphDiffInput1,true); + auto Trace2OrErr = loadTraceFile(GraphDiffInput2,true); + + if(!Trace1OrErr) + return make_error(Twine("Failed Loading Input File '") + + GraphDiffInput1 + "'", + make_error_code(llvm::errc::invalid_argument)); + if(!Trace2OrErr) + return make_error(Twine("Failed Loading Input File '") + + GraphDiffInput2 + "'", + make_error_code(llvm::errc::invalid_argument)); + + F1.Trace = std::move(*Trace1OrErr); + F2.Trace = std::move(*Trace2OrErr); + + auto GR1OrErr = F1.getGraphRenderer(); + auto GR2OrErr = F2.getGraphRenderer(); + + if (!GR1OrErr) + return GR1OrErr.takeError(); + if (!GR2OrErr) + return GR2OrErr.takeError(); + + auto &GR1 = *GR1OrErr; + auto &GR2 = *GR2OrErr; + + auto &G1 = GR1.getGraph(); + auto &G2 = GR2.getGraph(); + + GraphDiffRenderer::Factory DGF(G1, G2); + + auto GDROrErr = DGF.getGraphDiffRenderer(); + if(!GDROrErr) + return GDROrErr.takeError(); + + auto &GDR = *GDROrErr; + + std::error_code EC; + raw_fd_ostream OS(GraphDiffOutput, EC, sys::fs::OpenFlags::F_Text); + if (EC) + return make_error( + Twine("Cannot open file '") + GraphDiffOutput + "' for writing.", EC); + + GDR.exportGraphAsDOT(OS, GraphDiffEdgeLabel, GraphDiffEdgeColorType, + GraphDiffVertexLabel, GraphDiffVertexColorType); + + return Error::success(); +}); Index: tools/llvm-xray/xray-graph.h =================================================================== --- tools/llvm-xray/xray-graph.h +++ tools/llvm-xray/xray-graph.h @@ -41,17 +41,29 @@ /// An inner struct for common timing statistics information struct TimeStat { - uint64_t Count = 0; - double Min = 0; - double Median = 0; - double Pct90 = 0; - double Pct99 = 0; - double Max = 0; - double Sum = 0; - std::string getAsString(StatType T) const; - double compare(StatType T, const TimeStat &Other) const; + int64_t Count; + double Min; + double Median; + double Pct90; + double Pct99; + double Max; + double Sum; + + TimeStat(): TimeStat(0,0,0,0,0,0,0){}; + TimeStat(int64_t _Count, + double _Min, + double _Median, + double _Pct90, + double _Pct99, + double _Max, + double _Sum): + Count(_Count), Min(_Min), Median(_Median), Pct90(_Pct90), Pct99(_Pct99), + Max(_Max), Sum(_Sum){} + + std::string getString(StatType T) const; + double getDouble(StatType T) const; }; - typedef uint64_t TimestampT; + using TimestampT = uint64_t; /// An inner struct for storing edge attributes for our graph. Here the /// attributes are mainly function call statistics. @@ -76,10 +88,10 @@ uint64_t TSC; }; - typedef SmallVector FunctionStack; + using FunctionStack = SmallVector; - typedef DenseMap - PerThreadFunctionStackMap; + using PerThreadFunctionStackMap = + DenseMap; class GraphT : public Graph { public: @@ -88,8 +100,8 @@ }; GraphT G; - typedef typename decltype(G)::VertexIdentifier VertexIdentifier; - typedef typename decltype(G)::EdgeIdentifier EdgeIdentifier; + using VertexIdentifier = typename decltype(G)::VertexIdentifier; + using EdgeIdentifier = decltype(G)::EdgeIdentifier; /// Use a Map to store the Function stack for each thread whilst building the /// graph. @@ -98,7 +110,7 @@ PerThreadFunctionStackMap PerThreadFunctionStack; /// Usefull object for getting human readable Symbol Names. - const FuncIdConversionHelper &FuncIdHelper; + FuncIdConversionHelper FuncIdHelper; bool DeduceSiblingCalls = false; TimestampT CurrentMaxTSC = 0; @@ -144,20 +156,109 @@ return PerThreadFunctionStack; } + class Factory{ + public: + bool KeepGoing; + bool DeduceSiblingCalls; + std::string InstrMap; + Trace Trace; + Expected getGraphRenderer(); + }; + /// Output the Embedded graph in DOT format on \p OS, labeling the edges by /// \p T - void exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, + void exportGraphAsDOT(raw_ostream &OS, StatType EdgeLabel = StatType::NONE, StatType EdgeColor = StatType::NONE, StatType VertexLabel = StatType::NONE, StatType VertexColor = StatType::NONE); /// Get a reference to the internal graph. const GraphT &getGraph() { - calculateEdgeStatistics(); - calculateVertexStatistics(); return G; } }; + +/// Vector Sum of TimeStats +inline GraphRenderer::TimeStat operator+(const GraphRenderer::TimeStat &A, + const GraphRenderer::TimeStat &B){ + return {A.Count + B.Count, + A.Min + B.Min, + A.Median + B.Median, + A.Pct90 + B.Pct90, + A.Pct99 + B.Pct99, + A.Max + B.Max, + A.Sum + B.Sum}; +} + +/// Vector Difference of Timestats +inline GraphRenderer::TimeStat operator-(const GraphRenderer::TimeStat &A, + const GraphRenderer::TimeStat &B){ + + return {A.Count - B.Count, + A.Min - B.Min, + A.Median - B.Median, + A.Pct90 - B.Pct90, + A.Pct99 - B.Pct99, + A.Max - B.Max, + A.Sum - B.Sum}; +} + +/// Scalar Diference of TimeStat and double +inline GraphRenderer::TimeStat operator/(const GraphRenderer::TimeStat &A, + double B){ + + return {static_cast(A.Count / B), + A.Min / B, + A.Median / B, + A.Pct90 / B, + A.Pct99 / B, + A.Max / B, + A.Sum / B}; +} + +/// Scalar product of TimeStat and Double +inline GraphRenderer::TimeStat operator*(const GraphRenderer::TimeStat &A, + double B){ + return {static_cast(A.Count * B), + A.Min * B, + A.Median * B, + A.Pct90 * B, + A.Pct99 * B, + A.Max * B, + A.Sum * B}; +} + +/// Scalar product of double TimeStat +inline GraphRenderer::TimeStat operator*(double A, + const GraphRenderer::TimeStat &B){ + return B * A; +} + +///Hadamard Product of TimeStats +inline GraphRenderer::TimeStat operator*(const GraphRenderer::TimeStat &A, + const GraphRenderer::TimeStat &B){ + return {A.Count * B.Count, + A.Min * B.Min, + A.Median * B.Median, + A.Pct90 * B.Pct90, + A.Pct99 * B.Pct99, + A.Max * B.Max, + A.Sum * B.Sum}; +} + +/// Hadamard Division of TimeStats +inline GraphRenderer::TimeStat operator/(const GraphRenderer::TimeStat &A, + const GraphRenderer::TimeStat &B){ + return {A.Count * B.Count, + A.Min * B.Min, + A.Median * B.Median, + A.Pct90 * B.Pct90, + A.Pct99 * B.Pct99, + A.Max * B.Max, + A.Sum * B.Sum}; + +} + } } Index: tools/llvm-xray/xray-graph.cc =================================================================== --- tools/llvm-xray/xray-graph.cc +++ tools/llvm-xray/xray-graph.cc @@ -19,7 +19,6 @@ #include "xray-graph.h" #include "xray-registry.h" -#include "llvm/ADT/ArrayRef.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/XRay/InstrumentationMap.h" @@ -96,7 +95,7 @@ cl::value_desc("field"), cl::sub(GraphC), cl::init(GraphRenderer::StatType::NONE), cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", - "Do not label Edges"), + "Do not label Vertices"), clEnumValN(GraphRenderer::StatType::COUNT, "count", "function call counts"), clEnumValN(GraphRenderer::StatType::MIN, "min", @@ -121,7 +120,7 @@ cl::value_desc("field"), cl::sub(GraphC), cl::init(GraphRenderer::StatType::NONE), cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", - "Do not label Edges"), + "Do not color Edges"), clEnumValN(GraphRenderer::StatType::COUNT, "count", "function call counts"), clEnumValN(GraphRenderer::StatType::MIN, "min", @@ -146,7 +145,7 @@ cl::value_desc("field"), cl::sub(GraphC), cl::init(GraphRenderer::StatType::NONE), cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none", - "Do not label Edges"), + "Do not color vertices"), clEnumValN(GraphRenderer::StatType::COUNT, "count", "function call counts"), clEnumValN(GraphRenderer::StatType::MIN, "min", @@ -208,7 +207,7 @@ auto &ThreadStack = PerThreadFunctionStack[Record.TId]; switch (Record.Type) { case RecordTypes::ENTER: { - if (G.count(Record.FuncId) == 0) + if (Record.FuncId != 0 && G.count(Record.FuncId) == 0) G[Record.FuncId].SymbolName = FuncIdHelper.SymbolOrNumber(Record.FuncId); ThreadStack.push_back({Record.FuncId, Record.TSC}); break; @@ -314,12 +313,9 @@ // TimeStat element. static void normalizeTimeStat(GraphRenderer::TimeStat &S, double CycleFrequency) { - S.Min /= CycleFrequency; - S.Median /= CycleFrequency; - S.Max /= CycleFrequency; - S.Sum /= CycleFrequency; - S.Pct90 /= CycleFrequency; - S.Pct99 /= CycleFrequency; + int64_t OldCount = S.Count; + S = S / CycleFrequency; + S.Count = OldCount; } // Normalises the statistics in the graph for a given TSC frequency. @@ -339,7 +335,7 @@ // Returns a string containing the value of statistic field T std::string -GraphRenderer::TimeStat::getAsString(GraphRenderer::StatType T) const { +GraphRenderer::TimeStat::getString(GraphRenderer::StatType T) const { std::string St; raw_string_ostream S{St}; switch (T) { @@ -372,38 +368,35 @@ // Returns the quotient between the property T of this and another TimeStat as // a double -double GraphRenderer::TimeStat::compare(StatType T, const TimeStat &O) const { +double GraphRenderer::TimeStat::getDouble(StatType T) const { double retval = 0; switch (T) { case GraphRenderer::StatType::COUNT: - retval = static_cast(Count) / static_cast(O.Count); + retval = static_cast(Count); break; case GraphRenderer::StatType::MIN: - retval = Min / O.Min; + retval = Min; break; case GraphRenderer::StatType::MED: - retval = Median / O.Median; + retval = Median; break; case GraphRenderer::StatType::PCT90: - retval = Pct90 / O.Pct90; + retval = Pct90; break; case GraphRenderer::StatType::PCT99: - retval = Pct99 / O.Pct99; + retval = Pct99; break; case GraphRenderer::StatType::MAX: - retval = Max / O.Max; + retval = Max; break; case GraphRenderer::StatType::SUM: - retval = Sum / O.Sum; + retval = Sum; break; case GraphRenderer::StatType::NONE: retval = 0.0; break; } - return std::sqrt( - retval); // the square root here provides more dynamic contrast for - // low runtime edges, giving better separation and - // coloring lower down the call stack. + return retval; } // Outputs a DOT format version of the Graph embedded in the GraphRenderer @@ -412,17 +405,9 @@ // annotations. // // FIXME: output more information, better presented. -void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, +void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, StatType ET, StatType EC, StatType VT, StatType VC) { - G.GraphEdgeMax = {}; - G.GraphVertexMax = {}; - calculateEdgeStatistics(); - - calculateVertexStatistics(); - if (H.CycleFrequency) - normalizeStatistics(H.CycleFrequency); - OS << "digraph xray {\n"; if (VT != StatType::NONE) @@ -431,9 +416,9 @@ for (const auto &E : G.edges()) { const auto &S = E.second.S; OS << "F" << E.first.first << " -> " - << "F" << E.first.second << " [label=\"" << S.getAsString(ET) << "\""; + << "F" << E.first.second << " [label=\"" << S.getString(ET) << "\""; if (EC != StatType::NONE) - OS << " color=\"" << CHelper.getColor(S.compare(EC, G.GraphEdgeMax)) << "\""; + OS << " color=\"" << CHelper.getColor(std::sqrt(S.getDouble(EC)/G.GraphEdgeMax.getDouble(EC))) << "\""; OS << "];\n"; } @@ -446,25 +431,17 @@ << (VA.SymbolName.size() > 40 ? VA.SymbolName.substr(0, 40) + "..." : VA.SymbolName); if (VT != StatType::NONE) - OS << "|" << VA.S.getAsString(VT) << "}\""; + OS << "|" << VA.S.getString(VT) << "}\""; else OS << "\""; if (VC != StatType::NONE) - OS << " color=\"" << CHelper.getColor(VA.S.compare(VC, G.GraphVertexMax)) << "\""; + OS << " color=\"" << CHelper.getColor(std::sqrt(VA.S.getDouble(VC)/G.GraphVertexMax.getDouble(VC))) << "\""; OS << "];\n"; } OS << "}\n"; } -// Here we register and implement the llvm-xray graph subcommand. -// 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 -// short loop which actually analyses the trace, generates the graph and then -// outputs it as a DOT. -// -// FIXME: include additional filtering and annalysis passes to provide more -// specific useful information. -static CommandRegistration Unused(&GraphC, []() -> Error { +Expected GraphRenderer::Factory::getGraphRenderer(){ InstrumentationMap Map; if (!GraphInstrMap.empty()) { auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); @@ -478,30 +455,16 @@ } const auto &FunctionAddresses = Map.getFunctionAddresses(); + symbolize::LLVMSymbolizer::Options Opts( symbolize::FunctionNameKind::LinkageName, true, true, false, ""); symbolize::LLVMSymbolizer Symbolizer(Opts); - llvm::xray::FuncIdConversionHelper FuncIdHelper(GraphInstrMap, Symbolizer, - FunctionAddresses); - xray::GraphRenderer GR(FuncIdHelper, GraphDeduceSiblingCalls); - std::error_code EC; - raw_fd_ostream OS(GraphOutput, EC, sys::fs::OpenFlags::F_Text); - if (EC) - return make_error( - Twine("Cannot open file '") + GraphOutput + "' for writing.", EC); - - auto TraceOrErr = loadTraceFile(GraphInput, true); - if (!TraceOrErr) - return joinErrors( - make_error(Twine("Failed loading input file '") + - GraphInput + "'", - make_error_code(llvm::errc::invalid_argument)), - TraceOrErr.takeError()); - - auto &Trace = *TraceOrErr; const auto &Header = Trace.getFileHeader(); - // Here we generate the call graph from entries we find in the trace. + llvm::xray::FuncIdConversionHelper FuncIdHelper(InstrMap, Symbolizer, + FunctionAddresses); + + xray::GraphRenderer GR(FuncIdHelper, DeduceSiblingCalls); for (const auto &Record : Trace) { auto E = GR.accountRecord(Record); if (!E) @@ -524,7 +487,55 @@ handleAllErrors(std::move(E), [&](const ErrorInfoBase &E) { E.log(errs()); }); } - GR.exportGraphAsDOT(OS, Header, GraphEdgeLabel, GraphEdgeColorType, + + GR.G.GraphEdgeMax = {}; + GR.G.GraphVertexMax = {}; + GR.calculateEdgeStatistics(); + + GR.calculateVertexStatistics(); + if (Header.CycleFrequency) + GR.normalizeStatistics(Header.CycleFrequency); + + return GR; +} + +// Here we register and implement the llvm-xray graph subcommand. +// 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 +// short loop which actually analyses the trace, generates the graph and then +// outputs it as a DOT. +// +// FIXME: include additional filtering and annalysis passes to provide more +// specific useful information. +static CommandRegistration Unused(&GraphC, []() -> Error { + GraphRenderer::Factory F; + + F.KeepGoing = GraphKeepGoing; + F.DeduceSiblingCalls = GraphDeduceSiblingCalls; + F.InstrMap = GraphInstrMap; + + + auto TraceOrErr = loadTraceFile(GraphInput, true); + + if (!TraceOrErr) + return make_error(Twine("Failed loading input file '") + + GraphInput + "'", + make_error_code(llvm::errc::invalid_argument)); + + F.Trace = std::move(*TraceOrErr); + auto GROrError = F.getGraphRenderer(); + if (!GROrError) + return GROrError.takeError(); + auto &GR = *GROrError; + + std::error_code EC; + raw_fd_ostream OS(GraphOutput, EC, sys::fs::OpenFlags::F_Text); + if (EC) + return make_error( + Twine("Cannot open file '") + GraphOutput + "' for writing.", EC); + + + GR.exportGraphAsDOT(OS, GraphEdgeLabel, GraphEdgeColorType, GraphVertexLabel, GraphVertexColorType); return Error::success(); });