| //===-- xray-graph.cpp: 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 "xray-graph.h" | 
 | #include "xray-registry.h" | 
 | #include "llvm/Support/ErrorHandling.h" | 
 | #include "llvm/XRay/InstrumentationMap.h" | 
 | #include "llvm/XRay/Trace.h" | 
 |  | 
 | using namespace llvm; | 
 | using namespace llvm::xray; | 
 |  | 
 | // Setup llvm-xray graph subcommand and its options. | 
 | static cl::SubCommand GraphC("graph", "Generate function-call graph"); | 
 | static cl::opt<std::string> GraphInput(cl::Positional, | 
 |                                        cl::desc("<xray log file>"), | 
 |                                        cl::Required, cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<bool> | 
 |     GraphKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), | 
 |                    cl::sub(GraphC), cl::init(false)); | 
 | static cl::alias GraphKeepGoing2("k", cl::aliasopt(GraphKeepGoing), | 
 |                                  cl::desc("Alias for -keep-going"), | 
 |                                  cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<std::string> | 
 |     GraphOutput("output", cl::value_desc("Output file"), cl::init("-"), | 
 |                 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(GraphC)); | 
 |  | 
 | static cl::opt<std::string> | 
 |     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(GraphC)); | 
 |  | 
 | static cl::opt<bool> GraphDeduceSiblingCalls( | 
 |     "deduce-sibling-calls", | 
 |     cl::desc("Deduce sibling calls when unrolling function call stacks"), | 
 |     cl::sub(GraphC), cl::init(false)); | 
 | static cl::alias | 
 |     GraphDeduceSiblingCalls2("d", cl::aliasopt(GraphDeduceSiblingCalls), | 
 |                              cl::desc("Alias for -deduce-sibling-calls"), | 
 |                              cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<GraphRenderer::StatType> | 
 |     GraphEdgeLabel("edge-label", | 
 |                    cl::desc("Output graphs with edges labeled with this field"), | 
 |                    cl::value_desc("field"), cl::sub(GraphC), | 
 |                    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 GraphEdgeLabel2("e", cl::aliasopt(GraphEdgeLabel), | 
 |                                  cl::desc("Alias for -edge-label"), | 
 |                                  cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<GraphRenderer::StatType> GraphVertexLabel( | 
 |     "vertex-label", | 
 |     cl::desc("Output graphs with vertices labeled with this field"), | 
 |     cl::value_desc("field"), cl::sub(GraphC), | 
 |     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 GraphVertexLabel2("v", cl::aliasopt(GraphVertexLabel), | 
 |                                    cl::desc("Alias for -edge-label"), | 
 |                                    cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<GraphRenderer::StatType> GraphEdgeColorType( | 
 |     "color-edges", | 
 |     cl::desc("Output graphs with edge colors determined by this field"), | 
 |     cl::value_desc("field"), cl::sub(GraphC), | 
 |     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 GraphEdgeColorType2("c", cl::aliasopt(GraphEdgeColorType), | 
 |                                      cl::desc("Alias for -color-edges"), | 
 |                                      cl::sub(GraphC)); | 
 |  | 
 | static cl::opt<GraphRenderer::StatType> GraphVertexColorType( | 
 |     "color-vertices", | 
 |     cl::desc("Output graphs with vertex colors determined by this field"), | 
 |     cl::value_desc("field"), cl::sub(GraphC), | 
 |     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 GraphVertexColorType2("b", cl::aliasopt(GraphVertexColorType), | 
 |                                        cl::desc("Alias for -edge-label"), | 
 |                                        cl::sub(GraphC)); | 
 |  | 
 | 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 | 
 | static void updateStat(GraphRenderer::TimeStat &S, int64_t L) { | 
 |   S.Count++; | 
 |   if (S.Min > L || S.Min == 0) | 
 |     S.Min = L; | 
 |   if (S.Max < L) | 
 |     S.Max = L; | 
 |   S.Sum += L; | 
 | } | 
 |  | 
 | // Evaluates an XRay record and performs accounting on it. | 
 | // | 
 | // If the record is an ENTER record it pushes the FuncID and TSC onto a | 
 | // structure representing the call stack for that function. | 
 | // If the record is an EXIT record it checks computes computes the ammount of | 
 | // time the function took to complete and then stores that information in an | 
 | // edge of the graph. If there is no matching ENTER record the function tries | 
 | // to recover by assuming that there were EXIT records which were missed, for | 
 | // example caused by tail call elimination and if the option is enabled then | 
 | // then tries to recover from this. | 
 | // | 
 | // This funciton will also error if the records are out of order, as the trace | 
 | // is expected to be sorted. | 
 | // | 
 | // The graph generated has an immaginary root for functions called by no-one at | 
 | // FuncId 0. | 
 | // | 
 | // FIXME: Refactor this and account subcommand to reduce code duplication. | 
 | Error GraphRenderer::accountRecord(const XRayRecord &Record) { | 
 |   using std::make_error_code; | 
 |   using std::errc; | 
 |   if (CurrentMaxTSC == 0) | 
 |     CurrentMaxTSC = Record.TSC; | 
 |  | 
 |   if (Record.TSC < CurrentMaxTSC) | 
 |     return make_error<StringError>("Records not in order", | 
 |                                    make_error_code(errc::invalid_argument)); | 
 |  | 
 |   auto &ThreadStack = PerThreadFunctionStack[Record.TId]; | 
 |   switch (Record.Type) { | 
 |   case RecordTypes::ENTER: | 
 |   case RecordTypes::ENTER_ARG: { | 
 |     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; | 
 |   } | 
 |   case RecordTypes::EXIT: | 
 |   case RecordTypes::TAIL_EXIT: { | 
 |     // FIXME: Refactor this and the account subcommand to reduce code | 
 |     // duplication | 
 |     if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) { | 
 |       if (!DeduceSiblingCalls) | 
 |         return make_error<StringError>("No matching ENTRY record", | 
 |                                        make_error_code(errc::invalid_argument)); | 
 |       auto Parent = std::find_if( | 
 |           ThreadStack.rbegin(), ThreadStack.rend(), | 
 |           [&](const FunctionAttr &A) { return A.FuncId == Record.FuncId; }); | 
 |       if (Parent == ThreadStack.rend()) | 
 |         return make_error<StringError>( | 
 |             "No matching Entry record in stack", | 
 |             make_error_code(errc::invalid_argument)); // There is no matching | 
 |                                                       // Function for this exit. | 
 |       while (ThreadStack.back().FuncId != Record.FuncId) { | 
 |         TimestampT D = diff(ThreadStack.back().TSC, Record.TSC); | 
 |         VertexIdentifier TopFuncId = ThreadStack.back().FuncId; | 
 |         ThreadStack.pop_back(); | 
 |         assert(ThreadStack.size() != 0); | 
 |         EdgeIdentifier EI(ThreadStack.back().FuncId, TopFuncId); | 
 |         auto &EA = G[EI]; | 
 |         EA.Timings.push_back(D); | 
 |         updateStat(EA.S, D); | 
 |         updateStat(G[TopFuncId].S, D); | 
 |       } | 
 |     } | 
 |     uint64_t D = diff(ThreadStack.back().TSC, Record.TSC); | 
 |     ThreadStack.pop_back(); | 
 |     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(G[Record.FuncId].S, D); | 
 |     break; | 
 |   } | 
 |   } | 
 |  | 
 |   return Error::success(); | 
 | } | 
 |  | 
 | template <typename U> | 
 | void GraphRenderer::getStats(U begin, U end, GraphRenderer::TimeStat &S) { | 
 |   if (begin == end) return; | 
 |   std::ptrdiff_t MedianOff = S.Count / 2; | 
 |   std::nth_element(begin, begin + MedianOff, end); | 
 |   S.Median = *(begin + MedianOff); | 
 |   std::ptrdiff_t Pct90Off = (S.Count * 9) / 10; | 
 |   std::nth_element(begin, begin + Pct90Off, end); | 
 |   S.Pct90 = *(begin + Pct90Off); | 
 |   std::ptrdiff_t Pct99Off = (S.Count * 99) / 100; | 
 |   std::nth_element(begin, begin + Pct99Off, end); | 
 |   S.Pct99 = *(begin + Pct99Off); | 
 | } | 
 |  | 
 | void GraphRenderer::updateMaxStats(const GraphRenderer::TimeStat &S, | 
 |                                    GraphRenderer::TimeStat &M) { | 
 |   M.Count = std::max(M.Count, S.Count); | 
 |   M.Min = std::max(M.Min, S.Min); | 
 |   M.Median = std::max(M.Median, S.Median); | 
 |   M.Pct90 = std::max(M.Pct90, S.Pct90); | 
 |   M.Pct99 = std::max(M.Pct99, S.Pct99); | 
 |   M.Max = std::max(M.Max, S.Max); | 
 |   M.Sum = std::max(M.Sum, S.Sum); | 
 | } | 
 |  | 
 | void GraphRenderer::calculateEdgeStatistics() { | 
 |   assert(!G.edges().empty()); | 
 |   for (auto &E : G.edges()) { | 
 |     auto &A = E.second; | 
 |     assert(!A.Timings.empty()); | 
 |     getStats(A.Timings.begin(), A.Timings.end(), A.S); | 
 |     updateMaxStats(A.S, G.GraphEdgeMax); | 
 |   } | 
 | } | 
 |  | 
 | void GraphRenderer::calculateVertexStatistics() { | 
 |   std::vector<uint64_t> TempTimings; | 
 |   for (auto &V : G.vertices()) { | 
 |     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()); | 
 |       } | 
 |       getStats(TempTimings.begin(), TempTimings.end(), G[V.first].S); | 
 |       updateMaxStats(G[V.first].S, G.GraphVertexMax); | 
 |       TempTimings.clear(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // A Helper function for normalizeStatistics which normalises a single | 
 | // TimeStat element. | 
 | static void normalizeTimeStat(GraphRenderer::TimeStat &S, | 
 |                               double CycleFrequency) { | 
 |   int64_t OldCount = S.Count; | 
 |   S = S / CycleFrequency; | 
 |   S.Count = OldCount; | 
 | } | 
 |  | 
 | // Normalises the statistics in the graph for a given TSC frequency. | 
 | void GraphRenderer::normalizeStatistics(double CycleFrequency) { | 
 |   for (auto &E : G.edges()) { | 
 |     auto &S = E.second.S; | 
 |     normalizeTimeStat(S, CycleFrequency); | 
 |   } | 
 |   for (auto &V : G.vertices()) { | 
 |     auto &S = V.second.S; | 
 |     normalizeTimeStat(S, CycleFrequency); | 
 |   } | 
 |  | 
 |   normalizeTimeStat(G.GraphEdgeMax, CycleFrequency); | 
 |   normalizeTimeStat(G.GraphVertexMax, CycleFrequency); | 
 | } | 
 |  | 
 | // Returns a string containing the value of statistic field T | 
 | std::string | 
 | GraphRenderer::TimeStat::getString(GraphRenderer::StatType T) const { | 
 |   std::string St; | 
 |   raw_string_ostream S{St}; | 
 |   double TimeStat::*DoubleStatPtrs[] = {&TimeStat::Min,   &TimeStat::Median, | 
 |                                         &TimeStat::Pct90, &TimeStat::Pct99, | 
 |                                         &TimeStat::Max,   &TimeStat::Sum}; | 
 |   switch (T) { | 
 |   case GraphRenderer::StatType::NONE: | 
 |     break; | 
 |   case GraphRenderer::StatType::COUNT: | 
 |     S << Count; | 
 |     break; | 
 |   default: | 
 |     S << (*this).* | 
 |              DoubleStatPtrs[static_cast<int>(T) - | 
 |                             static_cast<int>(GraphRenderer::StatType::MIN)]; | 
 |     break; | 
 |   } | 
 |   return S.str(); | 
 | } | 
 |  | 
 | // Returns the quotient between the property T of this and another TimeStat as | 
 | // a double | 
 | double GraphRenderer::TimeStat::getDouble(StatType T) const { | 
 |   double retval = 0; | 
 |   double TimeStat::*DoubleStatPtrs[] = {&TimeStat::Min,   &TimeStat::Median, | 
 |                                         &TimeStat::Pct90, &TimeStat::Pct99, | 
 |                                         &TimeStat::Max,   &TimeStat::Sum}; | 
 |   switch (T) { | 
 |   case GraphRenderer::StatType::NONE: | 
 |     retval = 0.0; | 
 |     break; | 
 |   case GraphRenderer::StatType::COUNT: | 
 |     retval = static_cast<double>(Count); | 
 |     break; | 
 |   default: | 
 |     retval = | 
 |         (*this).*DoubleStatPtrs[static_cast<int>(T) - | 
 |                                 static_cast<int>(GraphRenderer::StatType::MIN)]; | 
 |     break; | 
 |   } | 
 |   return retval; | 
 | } | 
 |  | 
 | // Outputs a DOT format version of the Graph embedded in the GraphRenderer | 
 | // object on OS. It does this in the expected way by itterating | 
 | // through all edges then vertices and then outputting them and their | 
 | // annotations. | 
 | // | 
 | // FIXME: output more information, better presented. | 
 | void GraphRenderer::exportGraphAsDOT(raw_ostream &OS, StatType ET, StatType EC, | 
 |                                      StatType VT, StatType VC) { | 
 |   OS << "digraph xray {\n"; | 
 |  | 
 |   if (VT != StatType::NONE) | 
 |     OS << "node [shape=record];\n"; | 
 |  | 
 |   for (const auto &E : G.edges()) { | 
 |     const auto &S = E.second.S; | 
 |     OS << "F" << E.first.first << " -> " | 
 |        << "F" << E.first.second << " [label=\"" << S.getString(ET) << "\""; | 
 |     if (EC != StatType::NONE) | 
 |       OS << " color=\"" | 
 |          << CHelper.getColorString( | 
 |                 std::sqrt(S.getDouble(EC) / G.GraphEdgeMax.getDouble(EC))) | 
 |          << "\""; | 
 |     OS << "];\n"; | 
 |   } | 
 |  | 
 |   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); | 
 |     if (VT != StatType::NONE) | 
 |       OS << "|" << VA.S.getString(VT) << "}\""; | 
 |     else | 
 |       OS << "\""; | 
 |     if (VC != StatType::NONE) | 
 |       OS << " color=\"" | 
 |          << CHelper.getColorString( | 
 |                 std::sqrt(VA.S.getDouble(VC) / G.GraphVertexMax.getDouble(VC))) | 
 |          << "\""; | 
 |     OS << "];\n"; | 
 |   } | 
 |   OS << "}\n"; | 
 | } | 
 |  | 
 | Expected<GraphRenderer> GraphRenderer::Factory::getGraphRenderer() { | 
 |   InstrumentationMap Map; | 
 |   if (!GraphInstrMap.empty()) { | 
 |     auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); | 
 |     if (!InstrumentationMapOrError) | 
 |       return joinErrors( | 
 |           make_error<StringError>( | 
 |               Twine("Cannot open instrumentation map '") + GraphInstrMap + "'", | 
 |               std::make_error_code(std::errc::invalid_argument)), | 
 |           InstrumentationMapOrError.takeError()); | 
 |     Map = std::move(*InstrumentationMapOrError); | 
 |   } | 
 |  | 
 |   const auto &FunctionAddresses = Map.getFunctionAddresses(); | 
 |  | 
 |   symbolize::LLVMSymbolizer::Options Opts( | 
 |       symbolize::FunctionNameKind::LinkageName, true, true, false, ""); | 
 |   symbolize::LLVMSymbolizer Symbolizer(Opts); | 
 |   const auto &Header = Trace.getFileHeader(); | 
 |  | 
 |   llvm::xray::FuncIdConversionHelper FuncIdHelper(InstrMap, Symbolizer, | 
 |                                                   FunctionAddresses); | 
 |  | 
 |   xray::GraphRenderer GR(FuncIdHelper, DeduceSiblingCalls); | 
 |   for (const auto &Record : Trace) { | 
 |     auto E = GR.accountRecord(Record); | 
 |     if (!E) | 
 |       continue; | 
 |  | 
 |     for (const auto &ThreadStack : GR.getPerThreadFunctionStack()) { | 
 |       errs() << "Thread ID: " << ThreadStack.first << "\n"; | 
 |       auto Level = ThreadStack.second.size(); | 
 |       for (const auto &Entry : llvm::reverse(ThreadStack.second)) | 
 |         errs() << "#" << Level-- << "\t" | 
 |                << FuncIdHelper.SymbolOrNumber(Entry.FuncId) << '\n'; | 
 |     } | 
 |  | 
 |     if (!GraphKeepGoing) | 
 |       return joinErrors(make_error<StringError>( | 
 |                             "Error encountered generating the call graph.", | 
 |                             std::make_error_code(std::errc::invalid_argument)), | 
 |                         std::move(E)); | 
 |  | 
 |     handleAllErrors(std::move(E), | 
 |                     [&](const ErrorInfoBase &E) { E.log(errs()); }); | 
 |   } | 
 |  | 
 |   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<StringError>( | 
 |         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<StringError>( | 
 |         Twine("Cannot open file '") + GraphOutput + "' for writing.", EC); | 
 |  | 
 |   GR.exportGraphAsDOT(OS, GraphEdgeLabel, GraphEdgeColorType, GraphVertexLabel, | 
 |                       GraphVertexColorType); | 
 |   return Error::success(); | 
 | }); |