Path: blob/main/contrib/llvm-project/llvm/tools/llvm-xray/xray-graph-diff.cpp
35231 views
//===-- xray-graph-diff.cpp: XRay Function Call Graph Renderer ------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Generate a DOT file to represent the function call graph encountered in9// the trace.10//11//===----------------------------------------------------------------------===//12#include <cassert>13#include <cmath>14#include <limits>15#include <string>1617#include "xray-graph-diff.h"18#include "xray-graph.h"19#include "xray-registry.h"2021#include "xray-color-helper.h"22#include "llvm/Support/FormatVariadic.h"23#include "llvm/Support/MemoryBuffer.h"24#include "llvm/XRay/Trace.h"2526using namespace llvm;27using namespace xray;2829static cl::SubCommand GraphDiff("graph-diff",30"Generate diff of function-call graphs");31static cl::opt<std::string> GraphDiffInput1(cl::Positional,32cl::desc("<xray log file 1>"),33cl::Required, cl::sub(GraphDiff));34static cl::opt<std::string> GraphDiffInput2(cl::Positional,35cl::desc("<xray log file 2>"),36cl::Required, cl::sub(GraphDiff));3738static cl::opt<bool>39GraphDiffKeepGoing("keep-going",40cl::desc("Keep going on errors encountered"),41cl::sub(GraphDiff), cl::init(false));42static cl::alias GraphDiffKeepGoingA("k", cl::aliasopt(GraphDiffKeepGoing),43cl::desc("Alias for -keep-going"));44static cl::opt<bool>45GraphDiffKeepGoing1("keep-going-1",46cl::desc("Keep going on errors encountered in trace 1"),47cl::sub(GraphDiff), cl::init(false));48static cl::alias GraphDiffKeepGoing1A("k1", cl::aliasopt(GraphDiffKeepGoing1),49cl::desc("Alias for -keep-going-1"));50static cl::opt<bool>51GraphDiffKeepGoing2("keep-going-2",52cl::desc("Keep going on errors encountered in trace 2"),53cl::sub(GraphDiff), cl::init(false));54static cl::alias GraphDiffKeepGoing2A("k2", cl::aliasopt(GraphDiffKeepGoing2),55cl::desc("Alias for -keep-going-2"));5657static cl::opt<std::string>58GraphDiffInstrMap("instr-map",59cl::desc("binary with the instrumentation map, or "60"a separate instrumentation map for graph"),61cl::value_desc("binary with xray_instr_map or yaml"),62cl::sub(GraphDiff), cl::init(""));63static cl::alias GraphDiffInstrMapA("m", cl::aliasopt(GraphDiffInstrMap),64cl::desc("Alias for -instr-map"));65static cl::opt<std::string>66GraphDiffInstrMap1("instr-map-1",67cl::desc("binary with the instrumentation map, or "68"a separate instrumentation map for graph 1"),69cl::value_desc("binary with xray_instr_map or yaml"),70cl::sub(GraphDiff), cl::init(""));71static cl::alias GraphDiffInstrMap1A("m1", cl::aliasopt(GraphDiffInstrMap1),72cl::desc("Alias for -instr-map-1"));73static cl::opt<std::string>74GraphDiffInstrMap2("instr-map-2",75cl::desc("binary with the instrumentation map, or "76"a separate instrumentation map for graph 2"),77cl::value_desc("binary with xray_instr_map or yaml"),78cl::sub(GraphDiff), cl::init(""));79static cl::alias GraphDiffInstrMap2A("m2", cl::aliasopt(GraphDiffInstrMap2),80cl::desc("Alias for -instr-map-2"));8182static cl::opt<bool> GraphDiffDeduceSiblingCalls(83"deduce-sibling-calls",84cl::desc("Deduce sibling calls when unrolling function call stacks"),85cl::sub(GraphDiff), cl::init(false));86static cl::alias87GraphDiffDeduceSiblingCallsA("d", cl::aliasopt(GraphDiffDeduceSiblingCalls),88cl::desc("Alias for -deduce-sibling-calls"));89static cl::opt<bool> GraphDiffDeduceSiblingCalls1(90"deduce-sibling-calls-1",91cl::desc("Deduce sibling calls when unrolling function call stacks"),92cl::sub(GraphDiff), cl::init(false));93static cl::alias GraphDiffDeduceSiblingCalls1A(94"d1", cl::aliasopt(GraphDiffDeduceSiblingCalls1),95cl::desc("Alias for -deduce-sibling-calls-1"));96static cl::opt<bool> GraphDiffDeduceSiblingCalls2(97"deduce-sibling-calls-2",98cl::desc("Deduce sibling calls when unrolling function call stacks"),99cl::sub(GraphDiff), cl::init(false));100static cl::alias GraphDiffDeduceSiblingCalls2A(101"d2", cl::aliasopt(GraphDiffDeduceSiblingCalls2),102cl::desc("Alias for -deduce-sibling-calls-2"));103104static cl::opt<GraphRenderer::StatType> GraphDiffEdgeLabel(105"edge-label", cl::desc("Output graphs with edges labeled with this field"),106cl::value_desc("field"), cl::sub(GraphDiff),107cl::init(GraphRenderer::StatType::NONE),108cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",109"Do not label Edges"),110clEnumValN(GraphRenderer::StatType::COUNT, "count",111"function call counts"),112clEnumValN(GraphRenderer::StatType::MIN, "min",113"minimum function durations"),114clEnumValN(GraphRenderer::StatType::MED, "med",115"median function durations"),116clEnumValN(GraphRenderer::StatType::PCT90, "90p",117"90th percentile durations"),118clEnumValN(GraphRenderer::StatType::PCT99, "99p",119"99th percentile durations"),120clEnumValN(GraphRenderer::StatType::MAX, "max",121"maximum function durations"),122clEnumValN(GraphRenderer::StatType::SUM, "sum",123"sum of call durations")));124static cl::alias GraphDiffEdgeLabelA("e", cl::aliasopt(GraphDiffEdgeLabel),125cl::desc("Alias for -edge-label"));126127static cl::opt<GraphRenderer::StatType> GraphDiffEdgeColor(128"edge-color", cl::desc("Output graphs with edges colored by this field"),129cl::value_desc("field"), cl::sub(GraphDiff),130cl::init(GraphRenderer::StatType::NONE),131cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",132"Do not color Edges"),133clEnumValN(GraphRenderer::StatType::COUNT, "count",134"function call counts"),135clEnumValN(GraphRenderer::StatType::MIN, "min",136"minimum function durations"),137clEnumValN(GraphRenderer::StatType::MED, "med",138"median function durations"),139clEnumValN(GraphRenderer::StatType::PCT90, "90p",140"90th percentile durations"),141clEnumValN(GraphRenderer::StatType::PCT99, "99p",142"99th percentile durations"),143clEnumValN(GraphRenderer::StatType::MAX, "max",144"maximum function durations"),145clEnumValN(GraphRenderer::StatType::SUM, "sum",146"sum of call durations")));147static cl::alias GraphDiffEdgeColorA("c", cl::aliasopt(GraphDiffEdgeColor),148cl::desc("Alias for -edge-color"));149150static cl::opt<GraphRenderer::StatType> GraphDiffVertexLabel(151"vertex-label",152cl::desc("Output graphs with vertices labeled with this field"),153cl::value_desc("field"), cl::sub(GraphDiff),154cl::init(GraphRenderer::StatType::NONE),155cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",156"Do not label Vertices"),157clEnumValN(GraphRenderer::StatType::COUNT, "count",158"function call counts"),159clEnumValN(GraphRenderer::StatType::MIN, "min",160"minimum function durations"),161clEnumValN(GraphRenderer::StatType::MED, "med",162"median function durations"),163clEnumValN(GraphRenderer::StatType::PCT90, "90p",164"90th percentile durations"),165clEnumValN(GraphRenderer::StatType::PCT99, "99p",166"99th percentile durations"),167clEnumValN(GraphRenderer::StatType::MAX, "max",168"maximum function durations"),169clEnumValN(GraphRenderer::StatType::SUM, "sum",170"sum of call durations")));171static cl::alias GraphDiffVertexLabelA("v", cl::aliasopt(GraphDiffVertexLabel),172cl::desc("Alias for -vertex-label"));173174static cl::opt<GraphRenderer::StatType> GraphDiffVertexColor(175"vertex-color",176cl::desc("Output graphs with vertices colored by this field"),177cl::value_desc("field"), cl::sub(GraphDiff),178cl::init(GraphRenderer::StatType::NONE),179cl::values(clEnumValN(GraphRenderer::StatType::NONE, "none",180"Do not color Vertices"),181clEnumValN(GraphRenderer::StatType::COUNT, "count",182"function call counts"),183clEnumValN(GraphRenderer::StatType::MIN, "min",184"minimum function durations"),185clEnumValN(GraphRenderer::StatType::MED, "med",186"median function durations"),187clEnumValN(GraphRenderer::StatType::PCT90, "90p",188"90th percentile durations"),189clEnumValN(GraphRenderer::StatType::PCT99, "99p",190"99th percentile durations"),191clEnumValN(GraphRenderer::StatType::MAX, "max",192"maximum function durations"),193clEnumValN(GraphRenderer::StatType::SUM, "sum",194"sum of call durations")));195static cl::alias GraphDiffVertexColorA("b", cl::aliasopt(GraphDiffVertexColor),196cl::desc("Alias for -vertex-color"));197198static cl::opt<int> GraphDiffVertexLabelTrunc(199"vertex-label-trun", cl::desc("What length to truncate vertex labels to "),200cl::sub(GraphDiff), cl::init(40));201static cl::alias202GraphDiffVertexLabelTrunc1("t", cl::aliasopt(GraphDiffVertexLabelTrunc),203cl::desc("Alias for -vertex-label-trun"));204205static cl::opt<std::string>206GraphDiffOutput("output", cl::value_desc("Output file"), cl::init("-"),207cl::desc("output file; use '-' for stdout"),208cl::sub(GraphDiff));209static cl::alias GraphDiffOutputA("o", cl::aliasopt(GraphDiffOutput),210cl::desc("Alias for -output"));211212Expected<GraphDiffRenderer> GraphDiffRenderer::Factory::getGraphDiffRenderer() {213GraphDiffRenderer R;214215for (int i = 0; i < N; ++i) {216const auto &G = this->G[i].get();217for (const auto &V : G.vertices()) {218const auto &VAttr = V.second;219R.G[VAttr.SymbolName].CorrVertexPtr[i] = &V;220}221for (const auto &E : G.edges()) {222auto &EdgeTailID = E.first.first;223auto &EdgeHeadID = E.first.second;224auto EdgeTailAttrOrErr = G.at(EdgeTailID);225auto EdgeHeadAttrOrErr = G.at(EdgeHeadID);226if (!EdgeTailAttrOrErr)227return EdgeTailAttrOrErr.takeError();228if (!EdgeHeadAttrOrErr)229return EdgeHeadAttrOrErr.takeError();230GraphT::EdgeIdentifier ID{EdgeTailAttrOrErr->SymbolName,231EdgeHeadAttrOrErr->SymbolName};232R.G[ID].CorrEdgePtr[i] = &E;233}234}235236return R;237}238// Returns the Relative change With respect to LeftStat between LeftStat239// and RightStat.240static double statRelDiff(const GraphDiffRenderer::TimeStat &LeftStat,241const GraphDiffRenderer::TimeStat &RightStat,242GraphDiffRenderer::StatType T) {243double LeftAttr = LeftStat.getDouble(T);244double RightAttr = RightStat.getDouble(T);245246return RightAttr / LeftAttr - 1.0;247}248249static std::string getColor(const GraphDiffRenderer::GraphT::EdgeValueType &E,250const GraphDiffRenderer::GraphT &G, ColorHelper H,251GraphDiffRenderer::StatType T) {252auto &EdgeAttr = E.second;253if (EdgeAttr.CorrEdgePtr[0] == nullptr)254return H.getColorString(2.0); // A number greater than 1.0255if (EdgeAttr.CorrEdgePtr[1] == nullptr)256return H.getColorString(-2.0); // A number less than -1.0257258if (T == GraphDiffRenderer::StatType::NONE)259return H.getDefaultColorString();260261const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S;262const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S;263264double RelDiff = statRelDiff(LeftStat, RightStat, T);265double CappedRelDiff = std::clamp(RelDiff, -1.0, 1.0);266267return H.getColorString(CappedRelDiff);268}269270static std::string getColor(const GraphDiffRenderer::GraphT::VertexValueType &V,271const GraphDiffRenderer::GraphT &G, ColorHelper H,272GraphDiffRenderer::StatType T) {273auto &VertexAttr = V.second;274if (VertexAttr.CorrVertexPtr[0] == nullptr)275return H.getColorString(2.0); // A number greater than 1.0276if (VertexAttr.CorrVertexPtr[1] == nullptr)277return H.getColorString(-2.0); // A number less than -1.0278279if (T == GraphDiffRenderer::StatType::NONE)280return H.getDefaultColorString();281282const auto &LeftStat = VertexAttr.CorrVertexPtr[0]->second.S;283const auto &RightStat = VertexAttr.CorrVertexPtr[1]->second.S;284285double RelDiff = statRelDiff(LeftStat, RightStat, T);286double CappedRelDiff = std::clamp(RelDiff, -1.0, 1.0);287288return H.getColorString(CappedRelDiff);289}290291static Twine truncateString(const StringRef &S, size_t n) {292return (S.size() > n) ? Twine(S.substr(0, n)) + "..." : Twine(S);293}294295template <typename T> static bool containsNullptr(const T &Collection) {296return llvm::is_contained(Collection, nullptr);297}298299static std::string getLabel(const GraphDiffRenderer::GraphT::EdgeValueType &E,300GraphDiffRenderer::StatType EL) {301auto &EdgeAttr = E.second;302switch (EL) {303case GraphDiffRenderer::StatType::NONE:304return "";305default:306if (containsNullptr(EdgeAttr.CorrEdgePtr))307return "";308309const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S;310const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S;311312double RelDiff = statRelDiff(LeftStat, RightStat, EL);313return std::string(formatv(R"({0:P})", RelDiff));314}315}316317static std::string getLabel(const GraphDiffRenderer::GraphT::VertexValueType &V,318GraphDiffRenderer::StatType VL, int TrunLen) {319const auto &VertexId = V.first;320const auto &VertexAttr = V.second;321switch (VL) {322case GraphDiffRenderer::StatType::NONE:323return std::string(324formatv(R"({0})", truncateString(VertexId, TrunLen).str()));325default:326if (containsNullptr(VertexAttr.CorrVertexPtr))327return std::string(328formatv(R"({0})", truncateString(VertexId, TrunLen).str()));329330const auto &LeftStat = VertexAttr.CorrVertexPtr[0]->second.S;331const auto &RightStat = VertexAttr.CorrVertexPtr[1]->second.S;332333double RelDiff = statRelDiff(LeftStat, RightStat, VL);334return std::string(formatv(335R"({{{0}|{1:P}})", truncateString(VertexId, TrunLen).str(), RelDiff));336}337}338339static double getLineWidth(const GraphDiffRenderer::GraphT::EdgeValueType &E,340GraphDiffRenderer::StatType EL) {341auto &EdgeAttr = E.second;342switch (EL) {343case GraphDiffRenderer::StatType::NONE:344return 1.0;345default:346if (containsNullptr(EdgeAttr.CorrEdgePtr))347return 1.0;348349const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S;350const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S;351352double RelDiff = statRelDiff(LeftStat, RightStat, EL);353return (RelDiff > 1.0) ? RelDiff : 1.0;354}355}356357void GraphDiffRenderer::exportGraphAsDOT(raw_ostream &OS, StatType EdgeLabel,358StatType EdgeColor,359StatType VertexLabel,360StatType VertexColor, int TruncLen) {361// Get numbering of vertices for dot output.362StringMap<int32_t> VertexNo;363364int i = 0;365for (const auto &V : G.vertices()) {366VertexNo[V.first] = i++;367}368369ColorHelper H(ColorHelper::DivergingScheme::PiYG);370371OS << "digraph xrayDiff {\n";372373if (VertexLabel != StatType::NONE)374OS << "node [shape=record]\n";375376for (const auto &E : G.edges()) {377const auto &HeadId = E.first.first;378const auto &TailId = E.first.second;379OS << formatv(R"(F{0} -> F{1} [tooltip="{2} -> {3}" label="{4}" )"380R"(color="{5}" labelfontcolor="{5}" penwidth={6}])"381"\n",382VertexNo[HeadId], VertexNo[TailId],383HeadId.empty() ? static_cast<StringRef>("F0") : HeadId,384TailId, getLabel(E, EdgeLabel), getColor(E, G, H, EdgeColor),385getLineWidth(E, EdgeColor));386}387388for (const auto &V : G.vertices()) {389const auto &VertexId = V.first;390if (VertexId.empty()) {391OS << formatv(R"(F{0} [label="F0"])"392"\n",393VertexNo[VertexId]);394continue;395}396OS << formatv(R"(F{0} [label="{1}" color="{2}"])"397"\n",398VertexNo[VertexId], getLabel(V, VertexLabel, TruncLen),399getColor(V, G, H, VertexColor));400}401402OS << "}\n";403}404405template <typename T> static T &ifSpecified(T &A, cl::alias &AA, T &B) {406if (A.getPosition() == 0 && AA.getPosition() == 0)407return B;408409return A;410}411412static CommandRegistration Unused(&GraphDiff, []() -> Error {413std::array<GraphRenderer::Factory, 2> Factories{414{{ifSpecified(GraphDiffKeepGoing1, GraphDiffKeepGoing1A,415GraphDiffKeepGoing),416ifSpecified(GraphDiffDeduceSiblingCalls1, GraphDiffDeduceSiblingCalls1A,417GraphDiffDeduceSiblingCalls),418ifSpecified(GraphDiffInstrMap1, GraphDiffInstrMap1A, GraphDiffInstrMap),419Trace()},420{ifSpecified(GraphDiffKeepGoing2, GraphDiffKeepGoing2A,421GraphDiffKeepGoing),422ifSpecified(GraphDiffDeduceSiblingCalls2, GraphDiffDeduceSiblingCalls2A,423GraphDiffDeduceSiblingCalls),424ifSpecified(GraphDiffInstrMap2, GraphDiffInstrMap2A, GraphDiffInstrMap),425Trace()}}};426427std::array<std::string, 2> Inputs{{GraphDiffInput1, GraphDiffInput2}};428429std::array<GraphRenderer::GraphT, 2> Graphs;430431for (int i = 0; i < 2; i++) {432auto TraceOrErr = loadTraceFile(Inputs[i], true);433if (!TraceOrErr)434return make_error<StringError>(435Twine("Failed Loading Input File '") + Inputs[i] + "'",436make_error_code(llvm::errc::invalid_argument));437Factories[i].Trace = std::move(*TraceOrErr);438439auto GraphRendererOrErr = Factories[i].getGraphRenderer();440441if (!GraphRendererOrErr)442return GraphRendererOrErr.takeError();443444auto GraphRenderer = *GraphRendererOrErr;445446Graphs[i] = GraphRenderer.getGraph();447}448449GraphDiffRenderer::Factory DGF(Graphs[0], Graphs[1]);450451auto GDROrErr = DGF.getGraphDiffRenderer();452if (!GDROrErr)453return GDROrErr.takeError();454455auto &GDR = *GDROrErr;456457std::error_code EC;458raw_fd_ostream OS(GraphDiffOutput, EC, sys::fs::OpenFlags::OF_TextWithCRLF);459if (EC)460return make_error<StringError>(461Twine("Cannot open file '") + GraphDiffOutput + "' for writing.", EC);462463GDR.exportGraphAsDOT(OS, GraphDiffEdgeLabel, GraphDiffEdgeColor,464GraphDiffVertexLabel, GraphDiffVertexColor,465GraphDiffVertexLabelTrunc);466467return Error::success();468});469470471