From cadcaaab9b22c4f36fbc79a3473d79952c291910 Mon Sep 17 00:00:00 2001 From: Dean Michael Berris Date: Fri, 10 Feb 2017 05:40:37 +0000 Subject: [PATCH] [XRay] A graph Class for the llvm-xray graph Summary: In preparation for graph comparison and filtering, this is a library for representing graphs in LLVM. This will enable easier encapsulation and reuse of graphs in llvm-xray. Depends on D28999, D28225 Reviewers: dblaikie, dberris Reviewed By: dberris Subscribers: mgorny, llvm-commits Differential Revision: https://reviews.llvm.org/D29005 llvm-svn: 294713 --- include/llvm/XRay/Graph.h | 494 ++++++++++++++++++++++++++++++++++ tools/llvm-xray/xray-graph.cc | 160 +++++------ tools/llvm-xray/xray-graph.h | 41 +-- unittests/CMakeLists.txt | 1 + unittests/XRay/CMakeLists.txt | 13 + unittests/XRay/GraphTest.cpp | 256 ++++++++++++++++++ 6 files changed, 869 insertions(+), 96 deletions(-) create mode 100644 include/llvm/XRay/Graph.h create mode 100644 unittests/XRay/CMakeLists.txt create mode 100644 unittests/XRay/GraphTest.cpp diff --git a/include/llvm/XRay/Graph.h b/include/llvm/XRay/Graph.h new file mode 100644 index 00000000000..a4d34a8a4be --- /dev/null +++ b/include/llvm/XRay/Graph.h @@ -0,0 +1,494 @@ +//===-- Graph.h - XRay Graph Class ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A Graph Datatype for XRay. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_XRAY_GRAPH_T_H +#define LLVM_XRAY_GRAPH_T_H + +#include +#include +#include +#include + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/iterator.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace xray { + +/// A Graph object represents a Directed Graph and is used in XRay to compute +/// and store function call graphs and associated statistical information. +/// +/// The graph takes in four template parameters, these are: +/// - VertexAttribute, this is a structure which is stored for each vertex. +/// Must be DefaultConstructible, CopyConstructible, CopyAssignable and +/// Destructible. +/// - EdgeAttribute, this is a structure which is stored for each edge +/// Must be DefaultConstructible, CopyConstructible, CopyAssignable and +/// Destructible. +/// - EdgeAttribute, this is a structure which is stored for each variable +/// - VI, this is a type over which DenseMapInfo is defined and is the type +/// used look up strings, available as VertexIdentifier. +/// - If the built in DenseMapInfo is not defined, provide a specialization +/// class type here. +/// +/// Graph is CopyConstructible, CopyAssignable, MoveConstructible and +/// MoveAssignable but is not EqualityComparible or LessThanComparible. +/// +/// Usage Example Graph with weighted edges and vertices: +/// Graph G; +/// +/// G[1] = 0; +/// G[2] = 2; +/// G[{1,2}] = 1; +/// G[{2,1}] = -1; +/// for(const auto &v : G.vertices()){ +/// // Do something with the vertices in the graph; +/// } +/// for(const auto &e : G.edges()){ +/// // Do something with the edges in the graph; +/// } +/// +/// Usage Example with StrRef keys. +/// Graph StrG; +/// char va[] = "Vertex A"; +/// char vaa[] = "Vertex A"; +/// char vb[] = "Vertex B"; // Vertices are referenced by String Refs. +/// G[va] = 0; +/// G[vb] = 1; +/// G[{va, vb}] = 1.0; +/// cout() << G[vaa] << " " << G[{vaa, vb}]; //prints "0 1.0". +/// +template +class Graph { +public: + /// These objects are used to name edges and vertices in the graph. + typedef VI VertexIdentifier; + typedef std::pair EdgeIdentifier; + + /// This type is the value_type of all iterators which range over vertices, + /// Determined by the Vertices DenseMap + using VertexValueType = + detail::DenseMapPair; + + /// This type is the value_type of all iterators which range over edges, + /// Determined by the Edges DenseMap. + using EdgeValueType = detail::DenseMapPair; + + using size_type = std::size_t; + +private: + /// The type used for storing the EdgeAttribute for each edge in the graph + using EdgeMapT = DenseMap; + + /// The type used for storing the VertexAttribute for each vertex in + /// the graph. + using VertexMapT = DenseMap; + + /// The type used for storing the edges entering a vertex. Indexed by + /// the VertexIdentifier of the start of the edge. Only used to determine + /// where the incoming edges are, the EdgeIdentifiers are stored in an + /// InnerEdgeMapT. + using NeighborSetT = DenseSet; + + /// The type storing the InnerInvGraphT corresponding to each vertex in + /// the graph (When a vertex has an incoming edge incident to it) + using NeighborLookupT = DenseMap; + +private: + /// Stores the map from the start and end vertex of an edge to it's + /// EdgeAttribute + EdgeMapT Edges; + + /// Stores the map from VertexIdentifier to VertexAttribute + VertexMapT Vertices; + + /// Allows fast lookup for the incoming edge set of any given vertex. + NeighborLookupT InNeighbors; + + /// Allows fast lookup for the outgoing edge set of any given vertex. + NeighborLookupT OutNeighbors; + + /// An Iterator adapter using an InnerInvGraphT::iterator as a base iterator, + /// and storing the VertexIdentifier the iterator range comes from. The + /// dereference operator is then performed using a pointer to the graph's edge + /// set. + template ::type> + class NeighborEdgeIteratorT + : public iterator_adaptor_base< + NeighborEdgeIteratorT, BaseIt, + typename std::iterator_traits::iterator_category, T> { + using InternalEdgeMapT = + typename std::conditional::type; + + friend class NeighborEdgeIteratorT; + friend class NeighborEdgeIteratorT; + + InternalEdgeMapT *MP; + VertexIdentifier SI; + + public: + template ::type> + operator NeighborEdgeIteratorT() const { + return NeighborEdgeIteratorT(this->I, MP, SI); + } + + NeighborEdgeIteratorT() = default; + NeighborEdgeIteratorT(BaseIt _I, InternalEdgeMapT *_MP, + VertexIdentifier _SI) + : iterator_adaptor_base< + NeighborEdgeIteratorT, BaseIt, + typename std::iterator_traits::iterator_category, T>(_I), + MP(_MP), SI(_SI) {} + + T &operator*() const { + if (!IsOut) + return *(MP->find({*(this->I), SI})); + else + return *(MP->find({SI, *(this->I)})); + } + }; + +public: + /// A const iterator type for iterating through the set of edges entering a + /// vertex. + /// + /// Has a const EdgeValueType as its value_type + using ConstInEdgeIterator = NeighborEdgeIteratorT; + + /// An iterator type for iterating through the set of edges leaving a vertex. + /// + /// Has an EdgeValueType as its value_type + using InEdgeIterator = NeighborEdgeIteratorT; + + /// A const iterator type for iterating through the set of edges entering a + /// vertex. + /// + /// Has a const EdgeValueType as its value_type + using ConstOutEdgeIterator = NeighborEdgeIteratorT; + + /// An iterator type for iterating through the set of edges leaving a vertex. + /// + /// Has an EdgeValueType as its value_type + using OutEdgeIterator = NeighborEdgeIteratorT; + + /// A class for ranging over the incoming edges incident to a vertex. + /// + /// Like all views in this class it provides methods to get the beginning and + /// past the range iterators for the range, as well as methods to determine + /// the number of elements in the range and whether the range is empty. + template class InOutEdgeView { + public: + using iterator = NeighborEdgeIteratorT; + using const_iterator = NeighborEdgeIteratorT; + using GraphT = typename std::conditional::type; + using InternalEdgeMapT = + typename std::conditional::type; + + private: + InternalEdgeMapT &M; + const VertexIdentifier A; + const NeighborLookupT &NL; + + public: + iterator begin() { + auto It = NL.find(A); + if (It == NL.end()) + return iterator(); + return iterator(It->second.begin(), &M, A); + } + + const_iterator cbegin() const { + auto It = NL.find(A); + if (It == NL.end()) + return const_iterator(); + return const_iterator(It->second.begin(), &M, A); + } + + const_iterator begin() const { return cbegin(); } + + iterator end() { + auto It = NL.find(A); + if (It == NL.end()) + return iterator(); + return iterator(It->second.end(), &M, A); + } + const_iterator cend() const { + auto It = NL.find(A); + if (It == NL.end()) + return const_iterator(); + return const_iterator(It->second.end(), &M, A); + } + + const_iterator end() const { return cend(); } + + size_type size() const { + auto I = NL.find(A); + if (I == NL.end()) + return 0; + else + return I->second.size(); + } + + bool empty() const { return NL.count(A) == 0; }; + + InOutEdgeView(GraphT &G, VertexIdentifier A) + : M(G.Edges), A(A), NL(isOut ? G.OutNeighbors : G.InNeighbors) {} + }; + + /// A const iterator type for iterating through the whole vertex set of the + /// graph. + /// + /// Has a const VertexValueType as its value_type + using ConstVertexIterator = typename VertexMapT::const_iterator; + + /// An iterator type for iterating through the whole vertex set of the graph. + /// + /// Has a VertexValueType as its value_type + using VertexIterator = typename VertexMapT::iterator; + + /// A class for ranging over the vertices in the graph. + /// + /// Like all views in this class it provides methods to get the beginning and + /// past the range iterators for the range, as well as methods to determine + /// the number of elements in the range and whether the range is empty. + template class VertexView { + public: + using iterator = typename std::conditional::type; + using const_iterator = ConstVertexIterator; + using GraphT = typename std::conditional::type; + + private: + GraphT &G; + + public: + iterator begin() { return G.Vertices.begin(); } + iterator end() { return G.Vertices.end(); } + const_iterator cbegin() const { return G.Vertices.cbegin(); } + const_iterator cend() const { return G.Vertices.cend(); } + const_iterator begin() const { return G.Vertices.begin(); } + const_iterator end() const { return G.Vertices.end(); } + size_type size() const { return G.Vertices.size(); } + bool empty() const { return G.Vertices.empty(); } + VertexView(GraphT &_G) : G(_G) {} + }; + + /// A const iterator for iterating through the entire edge set of the graph. + /// + /// Has a const EdgeValueType as its value_type + using ConstEdgeIterator = typename EdgeMapT::const_iterator; + + /// An iterator for iterating through the entire edge set of the graph. + /// + /// Has an EdgeValueType as its value_type + using EdgeIterator = typename EdgeMapT::iterator; + + /// A class for ranging over all the edges in the graph. + /// + /// Like all views in this class it provides methods to get the beginning and + /// past the range iterators for the range, as well as methods to determine + /// the number of elements in the range and whether the range is empty. + template class EdgeView { + public: + using iterator = typename std::conditional::type; + using const_iterator = ConstEdgeIterator; + using GraphT = typename std::conditional::type; + + private: + GraphT &G; + + public: + iterator begin() { return G.Edges.begin(); } + iterator end() { return G.Edges.end(); } + const_iterator cbegin() const { return G.Edges.cbegin(); } + const_iterator cend() const { return G.Edges.cend(); } + const_iterator begin() const { return G.Edges.begin(); } + const_iterator end() const { return G.Edges.end(); } + size_type size() const { return G.Edges.size(); } + bool empty() const { return G.Edges.empty(); } + EdgeView(GraphT &_G) : G(_G) {} + }; + +public: + // TODO: implement constructor to enable Graph Initialisation.\ + // Something like: + // Graph G( + // {1, 2, 3, 4, 5}, + // {{1, 2}, {2, 3}, {3, 4}}); + + /// Empty the Graph + void clear() { + Edges.clear(); + Vertices.clear(); + InNeighbors.clear(); + OutNeighbors.clear(); + } + + /// Returns a view object allowing iteration over the vertices of the graph. + /// also allows access to the size of the vertex set. + VertexView vertices() { return VertexView(*this); } + + VertexView vertices() const { return VertexView(*this); } + + /// Returns a view object allowing iteration over the edges of the graph. + /// also allows access to the size of the edge set. + EdgeView edges() { return EdgeView(*this); } + + EdgeView edges() const { return EdgeView(*this); } + + /// Returns a view object allowing iteration over the edges which start at + /// a vertex I. + InOutEdgeView outEdges(const VertexIdentifier I) { + return InOutEdgeView(*this, I); + } + + InOutEdgeView outEdges(const VertexIdentifier I) const { + return InOutEdgeView(*this, I); + } + + /// Returns a view object allowing iteration over the edges which point to + /// a vertex I. + InOutEdgeView inEdges(const VertexIdentifier I) { + return InOutEdgeView(*this, I); + } + + InOutEdgeView inEdges(const VertexIdentifier I) const { + return InOutEdgeView(*this, I); + } + + /// Looks up the vertex with identifier I, if it does not exist it default + /// constructs it. + VertexAttribute &operator[](const VertexIdentifier &I) { + return Vertices.FindAndConstruct(I).second; + } + + /// Looks up the edge with identifier I, if it does not exist it default + /// constructs it, if it's endpoints do not exist it also default constructs + /// them. + EdgeAttribute &operator[](const EdgeIdentifier &I) { + auto &P = Edges.FindAndConstruct(I); + Vertices.FindAndConstruct(I.first); + Vertices.FindAndConstruct(I.second); + InNeighbors[I.second].insert(I.first); + OutNeighbors[I.first].insert(I.second); + return P.second; + } + + /// Looks up a vertex with Identifier I, or an error if it does not exist. + Expected at(const VertexIdentifier &I) { + auto It = Vertices.find(I); + if (It == Vertices.end()) + return make_error( + "Vertex Identifier Does Not Exist", + std::make_error_code(std::errc::invalid_argument)); + return It->second; + } + + Expected at(const VertexIdentifier &I) const { + auto It = Vertices.find(I); + if (It == Vertices.end()) + return make_error( + "Vertex Identifier Does Not Exist", + std::make_error_code(std::errc::invalid_argument)); + return It->second; + } + + /// Looks up an edge with Identifier I, or an error if it does not exist. + Expected at(const EdgeIdentifier &I) { + auto It = Edges.find(I); + if (It == Edges.end()) + return make_error( + "Edge Identifier Does Not Exist", + std::make_error_code(std::errc::invalid_argument)); + return It->second; + } + + Expected at(const EdgeIdentifier &I) const { + auto It = Edges.find(I); + if (It == Edges.end()) + return make_error( + "Edge Identifier Does Not Exist", + std::make_error_code(std::errc::invalid_argument)); + return It->second; + } + + /// Looks for a vertex with identifier I, returns 1 if one exists, and + /// 0 otherwise + size_type count(const VertexIdentifier &I) const { + return Vertices.count(I); + } + + /// Looks for an edge with Identifier I, returns 1 if one exists and 0 + /// otherwise + size_type count(const EdgeIdentifier &I) const { return Edges.count(I); } + + /// Inserts a vertex into the graph with Identifier Val.first, and + /// Attribute Val.second. + std::pair + insert(const std::pair &Val) { + return Vertices.insert(Val); + } + + std::pair + insert(std::pair &&Val) { + return Vertices.insert(std::move(Val)); + } + + /// Inserts an edge into the graph with Identifier Val.first, and + /// Attribute Val.second. If the key is already in the map, it returns false + /// and doesn't update the value. + std::pair + insert(const std::pair &Val) { + const auto &p = Edges.insert(Val); + if (p.second) { + const auto &EI = Val.first; + Vertices.FindAndConstruct(EI.first); + Vertices.FindAndConstruct(EI.second); + InNeighbors[EI.second].insert(EI.first); + OutNeighbors[EI.first].insert(EI.second); + }; + + return p; + } + + /// Inserts an edge into the graph with Identifier Val.first, and + /// Attribute Val.second. If the key is already in the map, it returns false + /// and doesn't update the value. + std::pair + insert(std::pair &&Val) { + auto EI = Val.first; + const auto &p = Edges.insert(std::move(Val)); + if (p.second) { + Vertices.FindAndConstruct(EI.first); + Vertices.FindAndConstruct(EI.second); + InNeighbors[EI.second].insert(EI.first); + OutNeighbors[EI.first].insert(EI.second); + }; + + return p; + } +}; +} +} +#endif diff --git a/tools/llvm-xray/xray-graph.cc b/tools/llvm-xray/xray-graph.cc index 3780ce8672e..e16837e722d 100644 --- a/tools/llvm-xray/xray-graph.cc +++ b/tools/llvm-xray/xray-graph.cc @@ -1,4 +1,4 @@ -//===-- xray-graph.c - XRay Function Call Graph Renderer ------------------===// +//===-- xray-graph.cc - XRay Function Call Graph Renderer -----------------===// // // The LLVM Compiler Infrastructure // @@ -30,45 +30,47 @@ using namespace llvm; using namespace llvm::xray; // 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 GraphInput(cl::Positional, cl::desc(""), - cl::Required, cl::sub(Graph)); + cl::Required, cl::sub(GraphC)); static cl::opt 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), cl::desc("Alias for -keep-going"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt 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), - cl::desc("Alias for -output"), cl::sub(Graph)); + cl::desc("Alias for -output"), cl::sub(GraphC)); -static cl::opt GraphInstrMap( - "instr_map", cl::desc("binary with the instrumrntation map, or " - "a separate instrumentation map"), - cl::value_desc("binary with xray_instr_map"), cl::sub(Graph), cl::init("")); +static cl::opt + GraphInstrMap("instr_map", + cl::desc("binary with the instrumrntation map, or " + "a separate instrumentation map"), + cl::value_desc("binary with xray_instr_map"), cl::sub(GraphC), + cl::init("")); static cl::alias GraphInstrMap2("m", cl::aliasopt(GraphInstrMap), cl::desc("alias for -instr_map"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt GraphDeduceSiblingCalls( "deduce-sibling-calls", 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 GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls), cl::desc("Alias for -deduce-sibling-calls"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt GraphEdgeLabel("edge-label", 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::values(clEnumValN(GraphRenderer::StatType::NONE, "none", "Do not label Edges"), @@ -88,12 +90,12 @@ static cl::opt "sum of call durations"))); static cl::alias GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel), cl::desc("Alias for -edge-label"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt GraphVertexLabel( "vertex-label", 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::values(clEnumValN(GraphRenderer::StatType::NONE, "none", "Do not label Edges"), @@ -113,12 +115,12 @@ static cl::opt GraphVertexLabel( "sum of call durations"))); static cl::alias GraphVertexLabel2("v", cl::aliasopt(GraphVertexLabel), cl::desc("Alias for -edge-label"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt GraphEdgeColorType( "color-edges", 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::values(clEnumValN(GraphRenderer::StatType::NONE, "none", "Do not label Edges"), @@ -138,12 +140,12 @@ static cl::opt GraphEdgeColorType( "sum of call durations"))); static cl::alias GraphEdgeColorType2("c", cl::aliasopt(GraphEdgeColorType), cl::desc("Alias for -color-edges"), - cl::sub(Graph)); + cl::sub(GraphC)); static cl::opt GraphVertexColorType( "color-vertices", 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::values(clEnumValN(GraphRenderer::StatType::NONE, "none", "Do not label Edges"), @@ -163,7 +165,7 @@ static cl::opt GraphVertexColorType( "sum of call durations"))); static cl::alias GraphVertexColorType2("b", cl::aliasopt(GraphVertexColorType), cl::desc("Alias for -edge-label"), - cl::sub(Graph)); + cl::sub(GraphC)); template T diff(T L, T R) { return std::max(L, R) - std::min(L, R); } @@ -208,14 +210,13 @@ Error GraphRenderer::accountRecord(const XRayRecord &Record) { auto &ThreadStack = PerThreadFunctionStack[Record.TId]; switch (Record.Type) { case RecordTypes::ENTER: { - if (VertexAttrs.count(Record.FuncId) == 0) - VertexAttrs[Record.FuncId].SymbolName = - FuncIdHelper.SymbolOrNumber(Record.FuncId); + if (G.count(Record.FuncId) == 0) + G[Record.FuncId].SymbolName = FuncIdHelper.SymbolOrNumber(Record.FuncId); ThreadStack.push_back({Record.FuncId, Record.TSC}); break; } case RecordTypes::EXIT: { - // FIXME: Refactor this and the account subcommand to reducr code + // FIXME: Refactor this and the account subcommand to reduce code // duplication if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) { if (!DeduceSiblingCalls) @@ -230,23 +231,25 @@ Error GraphRenderer::accountRecord(const XRayRecord &Record) { make_error_code(errc::invalid_argument)); // There is no matching // Function for this exit. while (ThreadStack.back().FuncId != Record.FuncId) { - uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); - int32_t TopFuncId = ThreadStack.back().FuncId; + TimestampT D = diff(ThreadStack.back().TSC, Record.TSC); + VertexIdentifier TopFuncId = ThreadStack.back().FuncId; ThreadStack.pop_back(); assert(ThreadStack.size() != 0); - auto &EA = Graph[ThreadStack.back().FuncId][TopFuncId]; + EdgeIdentifier EI(ThreadStack.back().FuncId, TopFuncId); + auto &EA = G[EI]; EA.Timings.push_back(D); updateStat(EA.S, D); - updateStat(VertexAttrs[TopFuncId].S, D); + updateStat(G[TopFuncId].S, D); } } uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); ThreadStack.pop_back(); - auto &V = Graph[ThreadStack.empty() ? 0 : ThreadStack.back().FuncId]; - auto &EA = V[Record.FuncId]; + VertexIdentifier VI = ThreadStack.empty() ? 0 : ThreadStack.back().FuncId; + EdgeIdentifier EI(VI, Record.FuncId); + auto &EA = G[EI]; EA.Timings.push_back(D); updateStat(EA.S, D); - updateStat(VertexAttrs[Record.FuncId].S, D); + updateStat(G[Record.FuncId].S, D); break; } } @@ -280,38 +283,34 @@ void GraphRenderer::updateMaxStats(const GraphRenderer::TimeStat &S, } void GraphRenderer::calculateEdgeStatistics() { - for (auto &V : Graph) { - for (auto &E : V.second) { - auto &A = E.second; - getStats(A.Timings.begin(), A.Timings.end(), A.S); - updateMaxStats(A.S, GraphEdgeMax); - } + assert(!G.edges().empty()); + for (auto &E : G.edges()) { + auto &A = E.second; + assert(!A.Timings.empty()); + assert((A.Timings[0] > 0)); + getStats(A.Timings.begin(), A.Timings.end(), A.S); + assert(A.S.Sum > 0); + updateMaxStats(A.S, G.GraphEdgeMax); } } void GraphRenderer::calculateVertexStatistics() { - DenseMap>> - 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 TempTimings; - TempTimings.reserve(MaxCount); - for (auto &V : IncommingEdges) { - for (auto &P : V.second.second) { - TempTimings.insert(TempTimings.end(), P->Timings.begin(), - P->Timings.end()); + for (auto &V : G.vertices()) { + assert(V.first == 0 || + G[V.first].S.Sum != 0 && + "Every non-root vertex should have at least one call"); + if (V.first != 0) { + for (auto &E : G.inEdges(V.first)) { + auto &A = E.second; + TempTimings.insert(TempTimings.end(), A.Timings.begin(), + A.Timings.end()); + } + assert(!TempTimings.empty() && TempTimings[0] > 0); + getStats(TempTimings.begin(), TempTimings.end(), G[V.first].S); + updateMaxStats(G[V.first].S, G.GraphVertexMax); + TempTimings.clear(); } - getStats(TempTimings.begin(), TempTimings.end(), VertexAttrs[V.first].S); - updateMaxStats(VertexAttrs[V.first].S, GraphVertexMax); - TempTimings.clear(); } } @@ -329,19 +328,17 @@ static void normalizeTimeStat(GraphRenderer::TimeStat &S, // Normalises the statistics in the graph for a given TSC frequency. void GraphRenderer::normalizeStatistics(double CycleFrequency) { - for (auto &V : Graph) { - for (auto &E : V.second) { - auto &S = E.second.S; - normalizeTimeStat(S, CycleFrequency); - } + for (auto &E : G.edges()) { + auto &S = E.second.S; + normalizeTimeStat(S, CycleFrequency); } - for (auto &V : VertexAttrs) { + for (auto &V : G.vertices()) { auto &S = V.second.S; normalizeTimeStat(S, CycleFrequency); } - normalizeTimeStat(GraphEdgeMax, CycleFrequency); - normalizeTimeStat(GraphVertexMax, CycleFrequency); + normalizeTimeStat(G.GraphEdgeMax, CycleFrequency); + normalizeTimeStat(G.GraphVertexMax, CycleFrequency); } // Returns a string containing the value of statistic field T @@ -477,8 +474,11 @@ double GraphRenderer::TimeStat::compare(StatType T, const TimeStat &O) const { void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, StatType ET, StatType EC, StatType VT, StatType VC) { + G.GraphEdgeMax = {}; + G.GraphVertexMax = {}; calculateEdgeStatistics(); calculateVertexStatistics(); + if (H.CycleFrequency) normalizeStatistics(H.CycleFrequency); @@ -487,18 +487,19 @@ void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, if (VT != StatType::NONE) OS << "node [shape=record];\n"; - for (const auto &V : Graph) - for (const auto &E : V.second) { - const auto &S = E.second.S; - OS << "F" << V.first << " -> " - << "F" << E.first << " [label=\"" << S.getAsString(ET) << "\""; - if (EC != StatType::NONE) - OS << " color=\"" << getColor(S.compare(EC, GraphEdgeMax)) << "\""; - OS << "];\n"; - } + 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) << "\""; + if (EC != StatType::NONE) + OS << " color=\"" << getColor(S.compare(EC, G.GraphEdgeMax)) << "\""; + OS << "];\n"; + } - for (const auto &V : VertexAttrs) { + for (const auto &V : G.vertices()) { const auto &VA = V.second; + if (V.first == 0) + continue; OS << "F" << V.first << " [label=\"" << (VT != StatType::NONE ? "{" : "") << (VA.SymbolName.size() > 40 ? VA.SymbolName.substr(0, 40) + "..." : VA.SymbolName); @@ -507,7 +508,7 @@ void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, else OS << "\""; 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"; @@ -521,7 +522,7 @@ void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, const XRayFileHeader &H, // // FIXME: include additional filtering and annalysis passes to provide more // specific useful information. -static CommandRegistration Unused(&Graph, []() -> Error { +static CommandRegistration Unused(&GraphC, []() -> Error { InstrumentationMap Map; if (!GraphInstrMap.empty()) { auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); @@ -581,7 +582,6 @@ static CommandRegistration Unused(&Graph, []() -> Error { handleAllErrors(std::move(E), [&](const ErrorInfoBase &E) { E.log(errs()); }); } - GR.exportGraphAsDOT(OS, Header, GraphEdgeLabel, GraphEdgeColorType, GraphVertexLabel, GraphVertexColorType); return Error::success(); diff --git a/tools/llvm-xray/xray-graph.h b/tools/llvm-xray/xray-graph.h index 8b0e2082520..fd8ac17f902 100644 --- a/tools/llvm-xray/xray-graph.h +++ b/tools/llvm-xray/xray-graph.h @@ -24,6 +24,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/XRay/Graph.h" #include "llvm/XRay/Trace.h" #include "llvm/XRay/XRayRecord.h" @@ -49,21 +50,22 @@ public: std::string getAsString(StatType T) const; double compare(StatType T, const TimeStat &Other) const; }; + typedef uint64_t TimestampT; /// An inner struct for storing edge attributes for our graph. Here the /// attributes are mainly function call statistics. /// /// FIXME: expand to contain more information eg call latencies. - struct EdgeAttribute { + struct CallStats { TimeStat S; - std::vector Timings; + std::vector Timings; }; /// An Inner Struct for storing vertex attributes, at the moment just /// SymbolNames, however in future we could store bulk function statistics. /// /// FIXME: Store more attributes based on instrumentation map. - struct VertexAttribute { + struct FunctionStats { std::string SymbolName; TimeStat S; }; @@ -78,17 +80,15 @@ public: typedef DenseMap PerThreadFunctionStackMap; -private: - /// The Graph stored in an edge-list like format, with the edges also having - /// An attached set of attributes. - DenseMap> Graph; + class GraphT : public Graph { + public: + TimeStat GraphEdgeMax = {}; + TimeStat GraphVertexMax = {}; + }; - /// Graph Vertex Attributes. These are presently stored seperate from the - /// main graph. - DenseMap VertexAttrs; - - TimeStat GraphEdgeMax; - TimeStat GraphVertexMax; + GraphT G; + typedef typename decltype(G)::VertexIdentifier VertexIdentifier; + typedef typename decltype(G)::EdgeIdentifier EdgeIdentifier; /// Use a Map to store the Function stack for each thread whilst building the /// graph. @@ -99,7 +99,7 @@ private: /// Usefull object for getting human readable Symbol Names. FuncIdConversionHelper &FuncIdHelper; bool DeduceSiblingCalls = false; - uint64_t CurrentMaxTSC = 0; + TimestampT CurrentMaxTSC = 0; /// A private function to help implement the statistic generation functions; template @@ -121,7 +121,9 @@ public: /// Takes in a reference to a FuncIdHelper in order to have ready access to /// Symbol names. explicit GraphRenderer(FuncIdConversionHelper &FuncIdHelper, bool DSC) - : FuncIdHelper(FuncIdHelper), DeduceSiblingCalls(DSC) {} + : FuncIdHelper(FuncIdHelper), DeduceSiblingCalls(DSC) { + G[0] = {}; + } /// Process an Xray record and expand the graph. /// @@ -132,7 +134,7 @@ public: /// FIXME: Make this more robust against small irregularities. Error accountRecord(const XRayRecord &Record); - const PerThreadFunctionStackMap getPerThreadFunctionStack() const { + const PerThreadFunctionStackMap &getPerThreadFunctionStack() const { return PerThreadFunctionStack; } @@ -143,6 +145,13 @@ public: 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; + } }; } } diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 8dbca211d02..8e40f141463 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -24,3 +24,4 @@ add_subdirectory(ProfileData) add_subdirectory(Support) add_subdirectory(Target) add_subdirectory(Transforms) +add_subdirectory(XRay) diff --git a/unittests/XRay/CMakeLists.txt b/unittests/XRay/CMakeLists.txt new file mode 100644 index 00000000000..30bccd1bbe6 --- /dev/null +++ b/unittests/XRay/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +set(XRAYSources + GraphTest.cpp + ) + +add_llvm_unittest(XRayTests + ${XRAYSources} + ) + +add_dependencies(XRayTests intrinsics_gen) diff --git a/unittests/XRay/GraphTest.cpp b/unittests/XRay/GraphTest.cpp new file mode 100644 index 00000000000..89d13b1ef58 --- /dev/null +++ b/unittests/XRay/GraphTest.cpp @@ -0,0 +1,256 @@ +//===- llvm/unittest/XRay/GraphTest.cpp - XRay Graph unit tests -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/XRay/Graph.h" +#include "gtest/gtest.h" +#include +#include +#include + +using namespace llvm; +using namespace xray; + +namespace { +struct VA { + unsigned VA; +}; +struct EA { + unsigned EA; +}; +typedef Graph GraphT; +typedef typename GraphT::VertexIdentifier VI; +typedef typename GraphT::EdgeIdentifier EI; + +// Test Fixture +template class GraphTest : public testing::Test { +protected: + T Graph = getTestGraph(); + +private: + static T getTestGraph() { + using std::make_pair; + typename std::remove_const::type G; + G.insert(make_pair(1u, VA({3u}))); + G.insert(make_pair(2u, VA({5u}))); + G.insert(make_pair(3u, VA({7u}))); + G.insert(make_pair(4u, VA({11u}))); + G.insert(make_pair(5u, VA({13u}))); + G.insert(make_pair(6u, VA({17u}))); + + G.insert(std::make_pair(EI(1u, 2u), EA({3u * 5u}))); + G.insert(std::make_pair(EI(2u, 3u), EA({5u * 7u}))); + G.insert(std::make_pair(EI(6u, 3u), EA({2u * 7u * 17u}))); + G.insert(std::make_pair(EI(4u, 6u), EA({11u * 17u}))); + G.insert(std::make_pair(EI(2u, 4u), EA({5u * 11u}))); + G.insert(std::make_pair(EI(2u, 5u), EA({5u * 13u}))); + G.insert(std::make_pair(EI(4u, 5u), EA({11u * 13u}))); + + return G; + } +}; + +typedef ::testing::Types GraphTestTypes; + +using VVT = typename GraphT::VertexValueType; +using EVT = typename GraphT::EdgeValueType; + +TYPED_TEST_CASE(GraphTest, GraphTestTypes); + +template void graphVertexTester(T &G) { + std::set V({1u, 2u, 3u, 4u, 5u, 6u}); + std::vector VA({0u, 3u, 5u, 7u, 11u, 13u, 17u}); + + EXPECT_EQ(V.size(), G.vertices().size()); + EXPECT_FALSE(G.vertices().empty()); + for (unsigned u : V) { + auto EVV = G.at(u); + ASSERT_TRUE(bool{EVV}); + EXPECT_EQ(1u, G.count(u)); + EXPECT_EQ(VA[u], EVV->VA); + EXPECT_NE(G.vertices().end(), + std::find_if(G.vertices().begin(), G.vertices().end(), + [&](const VVT &VV) { return VV.first == u; })); + consumeError(EVV.takeError()); + } + + for (auto &VVT : G.vertices()) { + EXPECT_EQ(1u, V.count(VVT.first)); + EXPECT_EQ(VA[VVT.first], VVT.second.VA); + } +} + +template void graphEdgeTester(T &G) { + std::set V({1u, 2u, 3u, 4u, 5u, 6u}); + + std::set> E( + {{1u, 2u}, {2u, 3u}, {6u, 3u}, {4u, 6u}, {2u, 4u}, {2u, 5u}, {4u, 5u}}); + std::vector VA({0u, 3u, 5u, 7u, 11u, 13u, 17u}); + + EXPECT_EQ(E.size(), G.edges().size()); + EXPECT_FALSE(G.edges().empty()); + for (std::pair u : E) { + auto EEV = G.at(u); + ASSERT_TRUE(bool{EEV}); + EXPECT_EQ(1u, G.count(u)); + EXPECT_EQ(VA[u.first] * VA[u.second] * ((u.first > u.second) ? 2 : 1), + EEV->EA); + EXPECT_NE(G.edges().end(), + std::find_if(G.edges().begin(), G.edges().end(), + [&](const EVT &EV) { return EV.first == u; })); + consumeError(EEV.takeError()); + } + + for (auto &EV : G.edges()) { + EXPECT_EQ(1u, E.count(EV.first)); + EXPECT_EQ(VA[EV.first.first] * VA[EV.first.second] * + ((EV.first.first > EV.first.second) ? 2 : 1), + EV.second.EA); + const auto &IE = G.inEdges(EV.first.second); + const auto &OE = G.outEdges(EV.first.first); + EXPECT_NE(IE.size(), 0u); + EXPECT_NE(OE.size(), 0u); + EXPECT_NE(IE.begin(), IE.end()); + EXPECT_NE(OE.begin(), OE.end()); + EXPECT_NE( + G.inEdges(EV.first.second).end(), + std::find_if(G.inEdges(EV.first.second).begin(), + G.inEdges(EV.first.second).end(), + [&](const EVT &EVI) { return EVI.first == EV.first; })); + EXPECT_EQ( + G.inEdges(EV.first.first).end(), + std::find_if(G.inEdges(EV.first.first).begin(), + G.inEdges(EV.first.first).end(), + [&](const EVT &EVI) { return EVI.first == EV.first; })); + EXPECT_EQ( + G.outEdges(EV.first.second).end(), + std::find_if(G.outEdges(EV.first.second).begin(), + G.outEdges(EV.first.second).end(), + [&](const EVT &EVI) { return EVI.first == EV.first; })); + EXPECT_NE( + G.outEdges(EV.first.first).end(), + std::find_if(G.outEdges(EV.first.first).begin(), + G.outEdges(EV.first.first).end(), + [&](const EVT &EVI) { return EVI.first == EV.first; })); + } +} + +TYPED_TEST(GraphTest, TestGraphEdge) { + auto &G = this->Graph; + + graphEdgeTester(G); +} + +TYPED_TEST(GraphTest, TestGraphVertex) { + auto &G = this->Graph; + + graphVertexTester(G); +} + +TYPED_TEST(GraphTest, TestCopyConstructor) { + TypeParam G(this->Graph); + + graphEdgeTester(G); + graphVertexTester(G); +} + +TYPED_TEST(GraphTest, TestCopyAssign) { + TypeParam G = this->Graph; + + graphEdgeTester(G); + graphVertexTester(G); +} + +TYPED_TEST(GraphTest, TestMoveConstructor) { + TypeParam G(std::move(this->Graph)); + + graphEdgeTester(G); + graphVertexTester(G); +} + +// Tests the incremental Construction of a graph +TEST(GraphTest, TestConstruction) { + GraphT MG; + const GraphT &G = MG; + EXPECT_EQ(0u, G.count(0u)); + EXPECT_EQ(0u, G.count({0u, 1u})); + auto VE = G.at(0); + auto EE = G.at({0, 0}); + EXPECT_FALSE(VE); // G.at[0] returns an error + EXPECT_FALSE(EE); // G.at[{0,0}] returns an error + consumeError(VE.takeError()); + consumeError(EE.takeError()); + EXPECT_TRUE(G.vertices().empty()); + EXPECT_TRUE(G.edges().empty()); + EXPECT_EQ(G.vertices().begin(), G.vertices().end()); + EXPECT_EQ(G.edges().begin(), G.edges().end()); +} + +TEST(GraphTest, TestiVertexAccessOperator) { + GraphT MG; + const GraphT &G = MG; + + MG[0u] = {1u}; + EXPECT_EQ(1u, MG[0u].VA); + EXPECT_EQ(1u, G.count(0u)); + EXPECT_EQ(0u, G.count(1u)); + EXPECT_EQ(1u, MG[0u].VA); + auto T = G.at(0u); + EXPECT_TRUE(bool{T}); + EXPECT_EQ(1u, T->VA); + + EXPECT_EQ(1u, G.vertices().size()); + EXPECT_EQ(0u, G.edges().size()); + EXPECT_FALSE(G.vertices().empty()); + EXPECT_TRUE(G.edges().empty()); + EXPECT_NE(G.vertices().begin(), G.vertices().end()); + EXPECT_EQ(G.edges().begin(), G.edges().end()); + EXPECT_EQ(1u, G.vertices().begin()->second.VA); + EXPECT_EQ(0u, G.vertices().begin()->first); + EXPECT_EQ(0u, G.outEdges(0u).size()); + EXPECT_TRUE(G.outEdges(0u).empty()); + EXPECT_EQ(G.outEdges(0u).begin(), G.outEdges(0u).end()); + EXPECT_EQ(0u, G.inEdges(0u).size()); + EXPECT_TRUE(G.inEdges(0u).empty()); + EXPECT_EQ(G.inEdges(0u).begin(), G.inEdges(0u).end()); +} + +TEST(GraphTest, TestEdgeAccessOperator) { + GraphT MG; + const GraphT &G = MG; + + MG[{0u, 0u}] = {2u}; + EI EdgeIdent({0u, 0u}); + EXPECT_EQ(2u, MG[EdgeIdent].EA); + EXPECT_EQ(1u, G.count({0u, 0u})); + EXPECT_EQ(0u, G.count({0u, 1u})); + EXPECT_EQ(1u, G.count(0u)); + EXPECT_NE(1u, G.count(1u)); + auto T = G.at({0u, 0u}); + EXPECT_TRUE(T && T->EA == 2u); + EXPECT_EQ(1u, G.edges().size()); + EXPECT_EQ(1u, G.vertices().size()); + EXPECT_FALSE(G.edges().empty()); + EXPECT_FALSE(G.vertices().empty()); + EXPECT_NE(G.edges().begin(), G.edges().end()); + EXPECT_EQ(EI(0u, 0u), G.edges().begin()->first); + EXPECT_EQ(2u, G.edges().begin()->second.EA); + EXPECT_EQ(1u, G.outEdges(0u).size()); + EXPECT_FALSE(G.outEdges(0u).empty()); + EXPECT_NE(G.outEdges(0u).begin(), G.outEdges(0u).end()); + EXPECT_EQ(EI(0u, 0u), G.outEdges(0u).begin()->first); + EXPECT_EQ(2u, G.outEdges(0u).begin()->second.EA); + EXPECT_EQ(++(G.outEdges(0u).begin()), G.outEdges(0u).end()); + EXPECT_EQ(1u, G.inEdges(0u).size()); + EXPECT_FALSE(G.inEdges(0u).empty()); + EXPECT_NE(G.inEdges(0u).begin(), G.inEdges(0u).end()); + EXPECT_EQ(EI(0u, 0u), G.inEdges(0u).begin()->first); + EXPECT_EQ(2u, G.inEdges(0u).begin()->second.EA); + EXPECT_EQ(++(G.inEdges(0u).begin()), G.inEdges(0u).end()); +} +}