diff options
Diffstat (limited to 'contrib/llvm/tools/llvm-xray')
-rw-r--r-- | contrib/llvm/tools/llvm-xray/llvm-xray.cc | 10 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-account.cc | 96 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-color-helper.cc | 224 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-color-helper.h | 89 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-converter.cc | 69 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-extract.cc | 263 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-extract.h | 58 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-graph-diff.cc | 484 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-graph-diff.h | 74 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-graph.cc | 521 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-graph.h | 233 | ||||
-rw-r--r-- | contrib/llvm/tools/llvm-xray/xray-sleds.h | 32 |
12 files changed, 1743 insertions, 410 deletions
diff --git a/contrib/llvm/tools/llvm-xray/llvm-xray.cc b/contrib/llvm/tools/llvm-xray/llvm-xray.cc index ac5faaa..98303e7 100644 --- a/contrib/llvm/tools/llvm-xray/llvm-xray.cc +++ b/contrib/llvm/tools/llvm-xray/llvm-xray.cc @@ -30,12 +30,20 @@ int main(int argc, char *argv[]) { " This program consolidates multiple XRay trace " "processing tools for convenient access.\n"); for (auto *SC : cl::getRegisteredSubcommands()) { - if (*SC) + if (*SC) { + // If no subcommand was provided, we need to explicitly check if this is + // the top-level subcommand. + if (SC == &*cl::TopLevelSubCommand) { + cl::PrintHelpMessage(false, true); + return 0; + } if (auto C = dispatch(SC)) { ExitOnError("llvm-xray: ")(C()); return 0; } + } } + // If all else fails, we still print the usage message. cl::PrintHelpMessage(false, true); } diff --git a/contrib/llvm/tools/llvm-xray/xray-account.cc b/contrib/llvm/tools/llvm-xray/xray-account.cc index 671a5a0..13654c3 100644 --- a/contrib/llvm/tools/llvm-xray/xray-account.cc +++ b/contrib/llvm/tools/llvm-xray/xray-account.cc @@ -18,10 +18,10 @@ #include <utility> #include "xray-account.h" -#include "xray-extract.h" #include "xray-registry.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/XRay/InstrumentationMap.h" #include "llvm/XRay/Trace.h" using namespace llvm; @@ -120,16 +120,6 @@ static cl::opt<std::string> static cl::alias AccountInstrMap2("m", cl::aliasopt(AccountInstrMap), cl::desc("Alias for -instr_map"), cl::sub(Account)); -static cl::opt<InstrumentationMapExtractor::InputFormats> InstrMapFormat( - "instr-map-format", cl::desc("format of instrumentation map"), - cl::values(clEnumValN(InstrumentationMapExtractor::InputFormats::ELF, "elf", - "instrumentation map in an ELF header"), - clEnumValN(InstrumentationMapExtractor::InputFormats::YAML, - "yaml", "instrumentation map in YAML")), - cl::sub(Account), cl::init(InstrumentationMapExtractor::InputFormats::ELF)); -static cl::alias InstrMapFormat2("t", cl::aliasopt(InstrMapFormat), - cl::desc("Alias for -instr-map-format"), - cl::sub(Account)); namespace { @@ -418,67 +408,63 @@ void LatencyAccountant::exportStatsAsCSV(raw_ostream &OS, using namespace llvm::xray; static CommandRegistration Unused(&Account, []() -> Error { - int Fd; - auto EC = sys::fs::openFileForRead(AccountInput, Fd); - if (EC) - return make_error<StringError>( - Twine("Cannot open file '") + AccountInput + "'", EC); - - Error Err = Error::success(); - xray::InstrumentationMapExtractor Extractor(AccountInstrMap, InstrMapFormat, - Err); - if (auto E = handleErrors( - std::move(Err), [&](std::unique_ptr<StringError> SE) -> Error { - if (SE->convertToErrorCode() == std::errc::no_such_file_or_directory) - return Error::success(); - return Error(std::move(SE)); - })) - return E; + InstrumentationMap Map; + if (!AccountInstrMap.empty()) { + auto InstrumentationMapOrError = loadInstrumentationMap(AccountInstrMap); + if (!InstrumentationMapOrError) + return joinErrors(make_error<StringError>( + Twine("Cannot open instrumentation map '") + + AccountInstrMap + "'", + std::make_error_code(std::errc::invalid_argument)), + InstrumentationMapOrError.takeError()); + Map = std::move(*InstrumentationMapOrError); + } + std::error_code EC; raw_fd_ostream OS(AccountOutput, EC, sys::fs::OpenFlags::F_Text); if (EC) return make_error<StringError>( Twine("Cannot open file '") + AccountOutput + "' for writing.", EC); - const auto &FunctionAddresses = Extractor.getFunctionAddresses(); + const auto &FunctionAddresses = Map.getFunctionAddresses(); symbolize::LLVMSymbolizer::Options Opts( symbolize::FunctionNameKind::LinkageName, true, true, false, ""); symbolize::LLVMSymbolizer Symbolizer(Opts); llvm::xray::FuncIdConversionHelper FuncIdHelper(AccountInstrMap, Symbolizer, FunctionAddresses); xray::LatencyAccountant FCA(FuncIdHelper, AccountDeduceSiblingCalls); - if (auto TraceOrErr = loadTraceFile(AccountInput)) { - auto &T = *TraceOrErr; - for (const auto &Record : T) { - if (FCA.accountRecord(Record)) - continue; - for (const auto &ThreadStack : FCA.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.first) << '\n'; - } - if (!AccountKeepGoing) - return make_error<StringError>( - Twine("Failed accounting function calls in file '") + AccountInput + - "'.", - std::make_error_code(std::errc::executable_format_error)); - } - switch (AccountOutputFormat) { - case AccountOutputFormats::TEXT: - FCA.exportStatsAsText(OS, T.getFileHeader()); - break; - case AccountOutputFormats::CSV: - FCA.exportStatsAsCSV(OS, T.getFileHeader()); - break; - } - } else { + auto TraceOrErr = loadTraceFile(AccountInput); + if (!TraceOrErr) return joinErrors( make_error<StringError>( Twine("Failed loading input file '") + AccountInput + "'", std::make_error_code(std::errc::executable_format_error)), TraceOrErr.takeError()); + + auto &T = *TraceOrErr; + for (const auto &Record : T) { + if (FCA.accountRecord(Record)) + continue; + for (const auto &ThreadStack : FCA.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.first) << '\n'; + } + if (!AccountKeepGoing) + return make_error<StringError>( + Twine("Failed accounting function calls in file '") + AccountInput + + "'.", + std::make_error_code(std::errc::executable_format_error)); + } + switch (AccountOutputFormat) { + case AccountOutputFormats::TEXT: + FCA.exportStatsAsText(OS, T.getFileHeader()); + break; + case AccountOutputFormats::CSV: + FCA.exportStatsAsCSV(OS, T.getFileHeader()); + break; } return Error::success(); diff --git a/contrib/llvm/tools/llvm-xray/xray-color-helper.cc b/contrib/llvm/tools/llvm-xray/xray-color-helper.cc new file mode 100644 index 0000000..7b6a73a --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-color-helper.cc @@ -0,0 +1,224 @@ +//===-- xray-graph.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. +// +//===----------------------------------------------------------------------===// +// +// A class to get a color from a specified gradient. +// +//===----------------------------------------------------------------------===// +#include <algorithm> +#include <iostream> + +#include "xray-color-helper.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace xray; + +// Sequential ColorMaps, which are used to represent information +// from some minimum to some maximum. + +static const std::tuple<uint8_t, uint8_t, uint8_t> SequentialMaps[][9] = { + {// The greys color scheme from http://colorbrewer2.org/ + std::make_tuple(255, 255, 255), std::make_tuple(240, 240, 240), + std::make_tuple(217, 217, 217), std::make_tuple(189, 189, 189), + std::make_tuple(150, 150, 150), std::make_tuple(115, 115, 115), + std::make_tuple(82, 82, 82), std::make_tuple(37, 37, 37), + std::make_tuple(0, 0, 0)}, + {// The OrRd color scheme from http://colorbrewer2.org/ + std::make_tuple(255, 247, 236), std::make_tuple(254, 232, 200), + std::make_tuple(253, 212, 158), std::make_tuple(253, 187, 132), + std::make_tuple(252, 141, 89), std::make_tuple(239, 101, 72), + std::make_tuple(215, 48, 31), std::make_tuple(179, 0, 0), + std::make_tuple(127, 0, 0)}, + {// The PuBu color scheme from http://colorbrewer2.org/ + std::make_tuple(255, 247, 251), std::make_tuple(236, 231, 242), + std::make_tuple(208, 209, 230), std::make_tuple(166, 189, 219), + std::make_tuple(116, 169, 207), std::make_tuple(54, 144, 192), + std::make_tuple(5, 112, 176), std::make_tuple(4, 90, 141), + std::make_tuple(2, 56, 88)}}; + +// Sequential Maps extend the last colors given out of range inputs. +static const std::tuple<uint8_t, uint8_t, uint8_t> SequentialBounds[][2] = { + {// The Bounds for the greys color scheme + std::make_tuple(255, 255, 255), std::make_tuple(0, 0, 0)}, + {// The Bounds for the OrRd color Scheme + std::make_tuple(255, 247, 236), std::make_tuple(127, 0, 0)}, + {// The Bounds for the PuBu color Scheme + std::make_tuple(255, 247, 251), std::make_tuple(2, 56, 88)}}; + +ColorHelper::ColorHelper(ColorHelper::SequentialScheme S) + : MinIn(0.0), MaxIn(1.0), ColorMap(SequentialMaps[static_cast<int>(S)]), + BoundMap(SequentialBounds[static_cast<int>(S)]) {} + +// Diverging ColorMaps, which are used to represent information +// representing differenes, or a range that goes from negative to positive. +// These take an input in the range [-1,1]. + +static const std::tuple<uint8_t, uint8_t, uint8_t> DivergingCoeffs[][11] = { + {// The PiYG color scheme from http://colorbrewer2.org/ + std::make_tuple(142, 1, 82), std::make_tuple(197, 27, 125), + std::make_tuple(222, 119, 174), std::make_tuple(241, 182, 218), + std::make_tuple(253, 224, 239), std::make_tuple(247, 247, 247), + std::make_tuple(230, 245, 208), std::make_tuple(184, 225, 134), + std::make_tuple(127, 188, 65), std::make_tuple(77, 146, 33), + std::make_tuple(39, 100, 25)}}; + +// Diverging maps use out of bounds ranges to show missing data. Missing Right +// Being below min, and missing left being above max. +static const std::tuple<uint8_t, uint8_t, uint8_t> DivergingBounds[][2] = { + {// The PiYG color scheme has green and red for missing right and left + // respectively. + std::make_tuple(255, 0, 0), std::make_tuple(0, 255, 0)}}; + +ColorHelper::ColorHelper(ColorHelper::DivergingScheme S) + : MinIn(-1.0), MaxIn(1.0), ColorMap(DivergingCoeffs[static_cast<int>(S)]), + BoundMap(DivergingBounds[static_cast<int>(S)]) {} + +// Takes a tuple of uint8_ts representing a color in RGB and converts them to +// HSV represented by a tuple of doubles +static std::tuple<double, double, double> +convertToHSV(const std::tuple<uint8_t, uint8_t, uint8_t> &Color) { + double Scaled[3] = {std::get<0>(Color) / 255.0, std::get<1>(Color) / 255.0, + std::get<2>(Color) / 255.0}; + int Min = 0; + int Max = 0; + for (int i = 1; i < 3; ++i) { + if (Scaled[i] < Scaled[Min]) + Min = i; + if (Scaled[i] > Scaled[Max]) + Max = i; + } + + double C = Scaled[Max] - Scaled[Min]; + + double HPrime = + (C == 0) ? 0 : (Scaled[(Max + 1) % 3] - Scaled[(Max + 2) % 3]) / C; + HPrime = HPrime + 2.0 * Max; + + double H = (HPrime < 0) ? (HPrime + 6.0) * 60 + : HPrime * 60; // Scale to between 0 and 360 + double V = Scaled[Max]; + + double S = (V == 0.0) ? 0.0 : C / V; + + return std::make_tuple(H, S, V); +} + +// Takes a double precision number, clips it between 0 and 1 and then converts +// that to an integer between 0x00 and 0xFF with proxpper rounding. +static uint8_t unitIntervalTo8BitChar(double B) { + double n = std::max(std::min(B, 1.0), 0.0); + return static_cast<uint8_t>(255 * n + 0.5); +} + +// Takes a typle of doubles representing a color in HSV and converts them to +// RGB represented as a tuple of uint8_ts +static std::tuple<uint8_t, uint8_t, uint8_t> +convertToRGB(const std::tuple<double, double, double> &Color) { + const double &H = std::get<0>(Color); + const double &S = std::get<1>(Color); + const double &V = std::get<2>(Color); + + double C = V * S; + + double HPrime = H / 60; + double X = C * (1 - std::abs(std::fmod(HPrime, 2.0) - 1)); + + double RGB1[3]; + int HPrimeInt = static_cast<int>(HPrime); + if (HPrimeInt % 2 == 0) { + RGB1[(HPrimeInt / 2) % 3] = C; + RGB1[(HPrimeInt / 2 + 1) % 3] = X; + RGB1[(HPrimeInt / 2 + 2) % 3] = 0.0; + } else { + RGB1[(HPrimeInt / 2) % 3] = X; + RGB1[(HPrimeInt / 2 + 1) % 3] = C; + RGB1[(HPrimeInt / 2 + 2) % 3] = 0.0; + } + + double Min = V - C; + double RGB2[3] = {RGB1[0] + Min, RGB1[1] + Min, RGB1[2] + Min}; + + return std::make_tuple(unitIntervalTo8BitChar(RGB2[0]), + unitIntervalTo8BitChar(RGB2[1]), + unitIntervalTo8BitChar(RGB2[2])); +} + +// The Hue component of the HSV interpolation Routine +static double interpolateHue(double H0, double H1, double T) { + double D = H1 - H0; + if (H0 > H1) { + std::swap(H0, H1); + + D = -D; + T = 1 - T; + } + + if (D <= 180) { + return H0 + T * (H1 - H0); + } else { + H0 = H0 + 360; + return std::fmod(H0 + T * (H1 - H0) + 720, 360); + } +} + +// Interpolates between two HSV Colors both represented as a tuple of doubles +// Returns an HSV Color represented as a tuple of doubles +static std::tuple<double, double, double> +interpolateHSV(const std::tuple<double, double, double> &C0, + const std::tuple<double, double, double> &C1, double T) { + double H = interpolateHue(std::get<0>(C0), std::get<0>(C1), T); + double S = std::get<1>(C0) + T * (std::get<1>(C1) - std::get<1>(C0)); + double V = std::get<2>(C0) + T * (std::get<2>(C1) - std::get<2>(C0)); + return std::make_tuple(H, S, V); +} + +// Get the Color as a tuple of uint8_ts +std::tuple<uint8_t, uint8_t, uint8_t> +ColorHelper::getColorTuple(double Point) const { + assert(!ColorMap.empty() && "ColorMap must not be empty!"); + assert(!BoundMap.empty() && "BoundMap must not be empty!"); + + if (Point < MinIn) + return BoundMap[0]; + if (Point > MaxIn) + return BoundMap[1]; + + size_t MaxIndex = ColorMap.size() - 1; + double IntervalWidth = MaxIn - MinIn; + double OffsetP = Point - MinIn; + double SectionWidth = IntervalWidth / static_cast<double>(MaxIndex); + size_t SectionNo = std::floor(OffsetP / SectionWidth); + double T = (OffsetP - SectionNo * SectionWidth) / SectionWidth; + + auto &RGBColor0 = ColorMap[SectionNo]; + auto &RGBColor1 = ColorMap[std::min(SectionNo + 1, MaxIndex)]; + + auto HSVColor0 = convertToHSV(RGBColor0); + auto HSVColor1 = convertToHSV(RGBColor1); + + auto InterpolatedHSVColor = interpolateHSV(HSVColor0, HSVColor1, T); + return convertToRGB(InterpolatedHSVColor); +} + +// A helper method to convert a color represented as tuple of uint8s to a hex +// string. +std::string +ColorHelper::getColorString(std::tuple<uint8_t, uint8_t, uint8_t> t) { + return llvm::formatv("#{0:X-2}{1:X-2}{2:X-2}", std::get<0>(t), std::get<1>(t), + std::get<2>(t)); +} + +// Gets a color in a gradient given a number in the interval [0,1], it does this +// by evaluating a polynomial which maps [0, 1] -> [0, 1] for each of the R G +// and B values in the color. It then converts this [0,1] colors to a 24 bit +// color as a hex string. +std::string ColorHelper::getColorString(double Point) const { + return getColorString(getColorTuple(Point)); +} diff --git a/contrib/llvm/tools/llvm-xray/xray-color-helper.h b/contrib/llvm/tools/llvm-xray/xray-color-helper.h new file mode 100644 index 0000000..b2dcf62 --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-color-helper.h @@ -0,0 +1,89 @@ +//===-- xray-graph.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. +// +//===----------------------------------------------------------------------===// +// +// A class to get a color from a specified gradient. +// +//===----------------------------------------------------------------------===// + +#ifndef XRAY_COLOR_HELPER_H +#define XRAY_COLOR_HELPER_H + +#include <tuple> + +#include "llvm/ADT/ArrayRef.h" + +namespace llvm { +namespace xray { + +/// The color helper class it a healper class which allows you to easily get a +/// color in a gradient. This is used to color-code edges in XRay-Graph tools. +/// +/// There are two types of color schemes in this class: +/// - Sequential schemes, which are used to represent information from some +/// minimum to some maximum. These take an input in the range [0,1] +/// - Diverging schemes, which are used to represent information representing +/// differenes, or a range that goes from negative to positive. These take +/// an input in the range [-1,1]. +/// Usage; +/// ColorHelper S(ColorHelper::SequentialScheme::OrRd); //Chose a color scheme. +/// for (double p = 0.0; p <= 1; p += 0.1){ +/// cout() << S.getColor(p) << " \n"; // Sample the gradient at 0.1 intervals +/// } +/// +/// ColorHelper D(ColorHelper::DivergingScheme::Spectral); // Choose a color +/// // scheme. +/// for (double p= -1; p <= 1 ; p += 0.1){ +/// cout() << D.getColor(p) << " \n"; // sample the gradient at 0.1 intervals +/// } +class ColorHelper { + double MinIn; + double MaxIn; + + ArrayRef<std::tuple<uint8_t, uint8_t, uint8_t>> ColorMap; + ArrayRef<std::tuple<uint8_t, uint8_t, uint8_t>> BoundMap; + +public: + /// Enum of the availible Sequential Color Schemes + enum class SequentialScheme { + // Schemes based on the ColorBrewer Color schemes of the same name from + // http://www.colorbrewer.org/ by Cynthis A Brewer Penn State University. + Greys, + OrRd, + PuBu + }; + + ColorHelper(SequentialScheme S); + + /// Enum of the availible Diverging Color Schemes + enum class DivergingScheme { + // Schemes based on the ColorBrewer Color schemes of the same name from + // http://www.colorbrewer.org/ by Cynthis A Brewer Penn State University. + PiYG + }; + + ColorHelper(DivergingScheme S); + + // Sample the gradient at the input point. + std::tuple<uint8_t, uint8_t, uint8_t> getColorTuple(double Point) const; + + std::string getColorString(double Point) const; + + // Get the Default color, at the moment allways black. + std::tuple<uint8_t, uint8_t, uint8_t> getDefaultColorTuple() const { + return std::make_tuple(0, 0, 0); + } + + std::string getDefaultColorString() const { return "black"; } + + // Convert a tuple to a string + static std::string getColorString(std::tuple<uint8_t, uint8_t, uint8_t> t); +}; +} // namespace xray +} // namespace llvm +#endif diff --git a/contrib/llvm/tools/llvm-xray/xray-converter.cc b/contrib/llvm/tools/llvm-xray/xray-converter.cc index 1bc9b15..2583ec9 100644 --- a/contrib/llvm/tools/llvm-xray/xray-converter.cc +++ b/contrib/llvm/tools/llvm-xray/xray-converter.cc @@ -12,13 +12,14 @@ //===----------------------------------------------------------------------===// #include "xray-converter.h" -#include "xray-extract.h" #include "xray-registry.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Support/EndianStream.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/XRay/InstrumentationMap.h" #include "llvm/XRay/Trace.h" #include "llvm/XRay/YAMLXRayRecord.h" @@ -72,18 +73,7 @@ static cl::opt<bool> ConvertSortInput( static cl::alias ConvertSortInput2("s", cl::aliasopt(ConvertSortInput), cl::desc("Alias for -sort"), cl::sub(Convert)); -static cl::opt<InstrumentationMapExtractor::InputFormats> InstrMapFormat( - "instr-map-format", cl::desc("format of instrumentation map"), - cl::values(clEnumValN(InstrumentationMapExtractor::InputFormats::ELF, "elf", - "instrumentation map in an ELF header"), - clEnumValN(InstrumentationMapExtractor::InputFormats::YAML, - "yaml", "instrumentation map in YAML")), - cl::sub(Convert), cl::init(InstrumentationMapExtractor::InputFormats::ELF)); -static cl::alias InstrMapFormat2("t", cl::aliasopt(InstrMapFormat), - cl::desc("Alias for -instr-map-format"), - cl::sub(Convert)); - -using llvm::yaml::IO; + using llvm::yaml::Output; void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) { @@ -95,7 +85,7 @@ void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) { for (const auto &R : Records) { Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId, Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId) - : std::to_string(R.FuncId), + : llvm::to_string(R.FuncId), R.TSC, R.TId}); } Output Out(OS, nullptr, 0); @@ -128,7 +118,9 @@ void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) { // format. for (const auto &R : Records) { Writer.write(R.RecordType); - Writer.write(R.CPU); + // The on disk naive raw format uses 8 bit CPUs, but the record has 16. + // There's no choice but truncation. + Writer.write(static_cast<uint8_t>(R.CPU)); switch (R.Type) { case RecordTypes::ENTER: Writer.write(uint8_t{0}); @@ -151,25 +143,26 @@ namespace xray { static CommandRegistration Unused(&Convert, []() -> Error { // FIXME: Support conversion to BINARY when upgrading XRay trace versions. - int Fd; - auto EC = sys::fs::openFileForRead(ConvertInput, Fd); - if (EC) - return make_error<StringError>( - Twine("Cannot open file '") + ConvertInput + "'", EC); - - Error Err = Error::success(); - xray::InstrumentationMapExtractor Extractor(ConvertInstrMap, InstrMapFormat, - Err); - handleAllErrors(std::move(Err), - [&](const ErrorInfoBase &E) { E.log(errs()); }); + InstrumentationMap Map; + if (!ConvertInstrMap.empty()) { + auto InstrumentationMapOrError = loadInstrumentationMap(ConvertInstrMap); + if (!InstrumentationMapOrError) + return joinErrors(make_error<StringError>( + Twine("Cannot open instrumentation map '") + + ConvertInstrMap + "'", + std::make_error_code(std::errc::invalid_argument)), + InstrumentationMapOrError.takeError()); + Map = std::move(*InstrumentationMapOrError); + } - const auto &FunctionAddresses = Extractor.getFunctionAddresses(); + const auto &FunctionAddresses = Map.getFunctionAddresses(); symbolize::LLVMSymbolizer::Options Opts( symbolize::FunctionNameKind::LinkageName, true, true, false, ""); symbolize::LLVMSymbolizer Symbolizer(Opts); llvm::xray::FuncIdConversionHelper FuncIdHelper(ConvertInstrMap, Symbolizer, FunctionAddresses); llvm::xray::TraceConverter TC(FuncIdHelper, ConvertSymbolize); + std::error_code EC; raw_fd_ostream OS(ConvertOutput, EC, ConvertOutputFormat == ConvertFormats::BINARY ? sys::fs::OpenFlags::F_None @@ -178,22 +171,22 @@ static CommandRegistration Unused(&Convert, []() -> Error { return make_error<StringError>( Twine("Cannot open file '") + ConvertOutput + "' for writing.", EC); - if (auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput)) { - auto &T = *TraceOrErr; - switch (ConvertOutputFormat) { - case ConvertFormats::YAML: - TC.exportAsYAML(T, OS); - break; - case ConvertFormats::BINARY: - TC.exportAsRAWv1(T, OS); - break; - } - } else { + auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput); + if (!TraceOrErr) return joinErrors( make_error<StringError>( Twine("Failed loading input file '") + ConvertInput + "'.", std::make_error_code(std::errc::executable_format_error)), TraceOrErr.takeError()); + + auto &T = *TraceOrErr; + switch (ConvertOutputFormat) { + case ConvertFormats::YAML: + TC.exportAsYAML(T, OS); + break; + case ConvertFormats::BINARY: + TC.exportAsRAWv1(T, OS); + break; } return Error::success(); }); diff --git a/contrib/llvm/tools/llvm-xray/xray-extract.cc b/contrib/llvm/tools/llvm-xray/xray-extract.cc index ecd5351..6b72b81 100644 --- a/contrib/llvm/tools/llvm-xray/xray-extract.cc +++ b/contrib/llvm/tools/llvm-xray/xray-extract.cc @@ -16,20 +16,18 @@ #include <type_traits> #include <utility> -#include "xray-extract.h" - +#include "func-id-helper.h" #include "xray-registry.h" -#include "xray-sleds.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/Object/ELF.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/DataExtractor.h" -#include "llvm/Support/ELF.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" -#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/XRay/InstrumentationMap.h" using namespace llvm; using namespace llvm::xray; @@ -48,244 +46,57 @@ static cl::opt<std::string> static cl::alias ExtractOutput2("o", cl::aliasopt(ExtractOutput), cl::desc("Alias for -output"), cl::sub(Extract)); - -struct YAMLXRaySledEntry { - int32_t FuncId; - Hex64 Address; - Hex64 Function; - SledEntry::FunctionKinds Kind; - bool AlwaysInstrument; -}; - -namespace llvm { -namespace yaml { - -template <> struct ScalarEnumerationTraits<SledEntry::FunctionKinds> { - static void enumeration(IO &IO, SledEntry::FunctionKinds &Kind) { - IO.enumCase(Kind, "function-enter", SledEntry::FunctionKinds::ENTRY); - IO.enumCase(Kind, "function-exit", SledEntry::FunctionKinds::EXIT); - IO.enumCase(Kind, "tail-exit", SledEntry::FunctionKinds::TAIL); - } -}; - -template <> struct MappingTraits<YAMLXRaySledEntry> { - static void mapping(IO &IO, YAMLXRaySledEntry &Entry) { - IO.mapRequired("id", Entry.FuncId); - IO.mapRequired("address", Entry.Address); - IO.mapRequired("function", Entry.Function); - IO.mapRequired("kind", Entry.Kind); - IO.mapRequired("always-instrument", Entry.AlwaysInstrument); - } - - static constexpr bool flow = true; -}; -} -} - -LLVM_YAML_IS_SEQUENCE_VECTOR(YAMLXRaySledEntry) +static cl::opt<bool> ExtractSymbolize("symbolize", cl::value_desc("symbolize"), + cl::init(false), + cl::desc("symbolize functions"), + cl::sub(Extract)); +static cl::alias ExtractSymbolize2("s", cl::aliasopt(ExtractSymbolize), + cl::desc("alias for -symbolize"), + cl::sub(Extract)); namespace { -llvm::Error LoadBinaryInstrELF( - StringRef Filename, std::deque<SledEntry> &OutputSleds, - InstrumentationMapExtractor::FunctionAddressMap &InstrMap, - InstrumentationMapExtractor::FunctionAddressReverseMap &FunctionIds) { - auto ObjectFile = object::ObjectFile::createObjectFile(Filename); - - if (!ObjectFile) - return ObjectFile.takeError(); - - // FIXME: Maybe support other ELF formats. For now, 64-bit Little Endian only. - if (!ObjectFile->getBinary()->isELF()) - return make_error<StringError>( - "File format not supported (only does ELF).", - std::make_error_code(std::errc::not_supported)); - if (ObjectFile->getBinary()->getArch() != Triple::x86_64) - return make_error<StringError>( - "File format not supported (only does ELF little endian 64-bit).", - std::make_error_code(std::errc::not_supported)); - - // Find the section named "xray_instr_map". - StringRef Contents = ""; - const auto &Sections = ObjectFile->getBinary()->sections(); - auto I = find_if(Sections, [&](object::SectionRef Section) { - StringRef Name = ""; - if (Section.getName(Name)) - return false; - return Name == "xray_instr_map"; - }); - if (I == Sections.end()) - return make_error<StringError>( - "Failed to find XRay instrumentation map.", - std::make_error_code(std::errc::not_supported)); - if (I->getContents(Contents)) - return make_error<StringError>( - "Failed to get contents of 'xray_instr_map' section.", - std::make_error_code(std::errc::executable_format_error)); - - // Copy the instrumentation map data into the Sleds data structure. - auto C = Contents.bytes_begin(); - static constexpr size_t ELF64SledEntrySize = 32; - - if ((C - Contents.bytes_end()) % ELF64SledEntrySize != 0) - return make_error<StringError>( - "Instrumentation map entries not evenly divisible by size of an XRay " - "sled entry in ELF64.", - std::make_error_code(std::errc::executable_format_error)); - - int32_t FuncId = 1; - uint64_t CurFn = 0; - std::deque<SledEntry> Sleds; - for (; C != Contents.bytes_end(); C += ELF64SledEntrySize) { - DataExtractor Extractor( - StringRef(reinterpret_cast<const char *>(C), ELF64SledEntrySize), true, - 8); - Sleds.push_back({}); - auto &Entry = Sleds.back(); - uint32_t OffsetPtr = 0; - Entry.Address = Extractor.getU64(&OffsetPtr); - Entry.Function = Extractor.getU64(&OffsetPtr); - auto Kind = Extractor.getU8(&OffsetPtr); - switch (Kind) { - case 0: // ENTRY - Entry.Kind = SledEntry::FunctionKinds::ENTRY; - break; - case 1: // EXIT - Entry.Kind = SledEntry::FunctionKinds::EXIT; - break; - case 2: // TAIL - Entry.Kind = SledEntry::FunctionKinds::TAIL; - break; - default: - return make_error<StringError>( - Twine("Encountered unknown sled type ") + "'" + Twine(int32_t{Kind}) + - "'.", - std::make_error_code(std::errc::executable_format_error)); - } - Entry.AlwaysInstrument = Extractor.getU8(&OffsetPtr) != 0; - - // We replicate the function id generation scheme implemented in the runtime - // here. Ideally we should be able to break it out, or output this map from - // the runtime, but that's a design point we can discuss later on. For now, - // we replicate the logic and move on. - if (CurFn == 0) { - CurFn = Entry.Function; - InstrMap[FuncId] = Entry.Function; - FunctionIds[Entry.Function] = FuncId; - } - if (Entry.Function != CurFn) { - ++FuncId; - CurFn = Entry.Function; - InstrMap[FuncId] = Entry.Function; - FunctionIds[Entry.Function] = FuncId; - } - } - OutputSleds = std::move(Sleds); - return llvm::Error::success(); -} - -Error LoadYAMLInstrMap( - StringRef Filename, std::deque<SledEntry> &Sleds, - InstrumentationMapExtractor::FunctionAddressMap &InstrMap, - InstrumentationMapExtractor::FunctionAddressReverseMap &FunctionIds) { - int Fd; - if (auto EC = sys::fs::openFileForRead(Filename, Fd)) - return make_error<StringError>( - Twine("Failed opening file '") + Filename + "' for reading.", EC); - - uint64_t FileSize; - if (auto EC = sys::fs::file_size(Filename, FileSize)) - return make_error<StringError>( - Twine("Failed getting size of file '") + Filename + "'.", EC); - - std::error_code EC; - sys::fs::mapped_file_region MappedFile( - Fd, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0, EC); - if (EC) - return make_error<StringError>( - Twine("Failed memory-mapping file '") + Filename + "'.", EC); - - std::vector<YAMLXRaySledEntry> YAMLSleds; - Input In(StringRef(MappedFile.data(), MappedFile.size())); - In >> YAMLSleds; - if (In.error()) - return make_error<StringError>( - Twine("Failed loading YAML document from '") + Filename + "'.", - In.error()); - - for (const auto &Y : YAMLSleds) { - InstrMap[Y.FuncId] = Y.Function; - FunctionIds[Y.Function] = Y.FuncId; - Sleds.push_back( - SledEntry{Y.Address, Y.Function, Y.Kind, Y.AlwaysInstrument}); - } - return Error::success(); -} - -} // namespace - -InstrumentationMapExtractor::InstrumentationMapExtractor(std::string Filename, - InputFormats Format, - Error &EC) { - ErrorAsOutParameter ErrAsOutputParam(&EC); - if (Filename.empty()) { - EC = Error::success(); - return; - } - switch (Format) { - case InputFormats::ELF: { - EC = handleErrors( - LoadBinaryInstrELF(Filename, Sleds, FunctionAddresses, FunctionIds), - [&](std::unique_ptr<ErrorInfoBase> E) { - return joinErrors( - make_error<StringError>( - Twine("Cannot extract instrumentation map from '") + - Filename + "'.", - std::make_error_code(std::errc::executable_format_error)), - std::move(E)); - }); - break; - } - case InputFormats::YAML: { - EC = handleErrors( - LoadYAMLInstrMap(Filename, Sleds, FunctionAddresses, FunctionIds), - [&](std::unique_ptr<ErrorInfoBase> E) { - return joinErrors( - make_error<StringError>( - Twine("Cannot load YAML instrumentation map from '") + - Filename + "'.", - std::make_error_code(std::errc::executable_format_error)), - std::move(E)); - }); - break; - } - } -} - -void InstrumentationMapExtractor::exportAsYAML(raw_ostream &OS) { +void exportAsYAML(const InstrumentationMap &Map, raw_ostream &OS, + FuncIdConversionHelper &FH) { // First we translate the sleds into the YAMLXRaySledEntry objects in a deque. std::vector<YAMLXRaySledEntry> YAMLSleds; - YAMLSleds.reserve(Sleds.size()); + auto Sleds = Map.sleds(); + YAMLSleds.reserve(std::distance(Sleds.begin(), Sleds.end())); for (const auto &Sled : Sleds) { - YAMLSleds.push_back({FunctionIds[Sled.Function], Sled.Address, - Sled.Function, Sled.Kind, Sled.AlwaysInstrument}); + auto FuncId = Map.getFunctionId(Sled.Function); + if (!FuncId) + return; + YAMLSleds.push_back({*FuncId, Sled.Address, Sled.Function, Sled.Kind, + Sled.AlwaysInstrument, + ExtractSymbolize ? FH.SymbolOrNumber(*FuncId) : ""}); } Output Out(OS, nullptr, 0); Out << YAMLSleds; } +} // namespace + static CommandRegistration Unused(&Extract, []() -> Error { - Error Err = Error::success(); - xray::InstrumentationMapExtractor Extractor( - ExtractInput, InstrumentationMapExtractor::InputFormats::ELF, Err); - if (Err) - return Err; + auto InstrumentationMapOrError = loadInstrumentationMap(ExtractInput); + if (!InstrumentationMapOrError) + return joinErrors(make_error<StringError>( + Twine("Cannot extract instrumentation map from '") + + ExtractInput + "'.", + std::make_error_code(std::errc::invalid_argument)), + InstrumentationMapOrError.takeError()); std::error_code EC; raw_fd_ostream OS(ExtractOutput, EC, sys::fs::OpenFlags::F_Text); if (EC) return make_error<StringError>( Twine("Cannot open file '") + ExtractOutput + "' for writing.", EC); - Extractor.exportAsYAML(OS); + const auto &FunctionAddresses = + InstrumentationMapOrError->getFunctionAddresses(); + symbolize::LLVMSymbolizer::Options Opts( + symbolize::FunctionNameKind::LinkageName, true, true, false, ""); + symbolize::LLVMSymbolizer Symbolizer(Opts); + llvm::xray::FuncIdConversionHelper FuncIdHelper(ExtractInput, Symbolizer, + FunctionAddresses); + exportAsYAML(*InstrumentationMapOrError, OS, FuncIdHelper); return Error::success(); }); diff --git a/contrib/llvm/tools/llvm-xray/xray-extract.h b/contrib/llvm/tools/llvm-xray/xray-extract.h deleted file mode 100644 index 91e4db3..0000000 --- a/contrib/llvm/tools/llvm-xray/xray-extract.h +++ /dev/null @@ -1,58 +0,0 @@ -//===- xray-extract.h - XRay Instrumentation Map Extraction ---------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Defines the interface for extracting the instrumentation map from an -// XRay-instrumented binary. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_XRAY_EXTRACT_H -#define LLVM_TOOLS_XRAY_EXTRACT_H - -#include <deque> -#include <map> -#include <string> -#include <unordered_map> - -#include "xray-sleds.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/raw_ostream.h" - -namespace llvm { -namespace xray { - -class InstrumentationMapExtractor { -public: - typedef std::unordered_map<int32_t, uint64_t> FunctionAddressMap; - typedef std::unordered_map<uint64_t, int32_t> FunctionAddressReverseMap; - - enum class InputFormats { ELF, YAML }; - -private: - std::deque<SledEntry> Sleds; - FunctionAddressMap FunctionAddresses; - FunctionAddressReverseMap FunctionIds; - -public: - /// Loads the instrumentation map from |Filename|. Updates |EC| in case there - /// were errors encountered opening the file. |Format| defines what the input - /// instrumentation map is in. - InstrumentationMapExtractor(std::string Filename, InputFormats Format, - Error &EC); - - const FunctionAddressMap &getFunctionAddresses() { return FunctionAddresses; } - - /// Exports the loaded function address map as YAML through |OS|. - void exportAsYAML(raw_ostream &OS); -}; - -} // namespace xray -} // namespace llvm - -#endif // LLVM_TOOLS_XRAY_EXTRACT_H diff --git a/contrib/llvm/tools/llvm-xray/xray-graph-diff.cc b/contrib/llvm/tools/llvm-xray/xray-graph-diff.cc new file mode 100644 index 0000000..3c69b3f --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-graph-diff.cc @@ -0,0 +1,484 @@ +//===-- 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 <cassert> +#include <cmath> +#include <limits> +#include <string> + +#include "xray-graph-diff.h" +#include "xray-graph.h" +#include "xray-registry.h" + +#include "xray-color-helper.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/FormatVariadic.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<std::string> GraphDiffInput1(cl::Positional, + cl::desc("<xray log file 1>"), + cl::Required, cl::sub(GraphDiff)); +static cl::opt<std::string> GraphDiffInput2(cl::Positional, + cl::desc("<xray log file 2>"), + cl::Required, cl::sub(GraphDiff)); + +static cl::opt<bool> + 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<bool> + 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<bool> + 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<std::string> + 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<std::string> + 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<std::string> + 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<bool> 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<bool> 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<bool> 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<GraphRenderer::StatType> 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<GraphRenderer::StatType> GraphDiffEdgeColor( + "edge-color", cl::desc("Output graphs with edges colored 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 GraphDiffEdgeColorA("c", cl::aliasopt(GraphDiffEdgeColor), + cl::desc("Alias for -edge-color"), + cl::sub(GraphDiff)); + +static cl::opt<GraphRenderer::StatType> 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<GraphRenderer::StatType> GraphDiffVertexColor( + "vertex-color", + cl::desc("Output graphs with vertices colored 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 GraphDiffVertexColorA("b", cl::aliasopt(GraphDiffVertexColor), + cl::desc("Alias for -vertex-color"), + cl::sub(GraphDiff)); + +static cl::opt<int> GraphDiffVertexLabelTrunc( + "vertex-label-trun", cl::desc("What length to truncate vertex labels to "), + cl::sub(GraphDiff), cl::init(40)); +static cl::alias + GraphDiffVertexLabelTrunc1("t", cl::aliasopt(GraphDiffVertexLabelTrunc), + cl::desc("Alias for -vertex-label-trun"), + cl::sub(GraphDiff)); + +static cl::opt<std::string> + 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> GraphDiffRenderer::Factory::getGraphDiffRenderer() { + GraphDiffRenderer R; + + for (int i = 0; i < N; ++i) { + const auto &G = this->G[i].get(); + for (const auto &V : G.vertices()) { + const auto &VAttr = V.second; + R.G[VAttr.SymbolName].CorrVertexPtr[i] = &V; + } + for (const auto &E : G.edges()) { + auto &EdgeTailID = E.first.first; + auto &EdgeHeadID = E.first.second; + auto EdgeTailAttrOrErr = G.at(EdgeTailID); + auto EdgeHeadAttrOrErr = G.at(EdgeHeadID); + if (!EdgeTailAttrOrErr) + return EdgeTailAttrOrErr.takeError(); + if (!EdgeHeadAttrOrErr) + return EdgeHeadAttrOrErr.takeError(); + GraphT::EdgeIdentifier ID{EdgeTailAttrOrErr->SymbolName, + EdgeHeadAttrOrErr->SymbolName}; + R.G[ID].CorrEdgePtr[i] = &E; + } + } + + return R; +} +// Returns the Relative change With respect to LeftStat between LeftStat +// and RightStat. +static double statRelDiff(const GraphDiffRenderer::TimeStat &LeftStat, + const GraphDiffRenderer::TimeStat &RightStat, + GraphDiffRenderer::StatType T) { + double LeftAttr = LeftStat.getDouble(T); + double RightAttr = RightStat.getDouble(T); + + return RightAttr / LeftAttr - 1.0; +} + +static std::string getColor(const GraphDiffRenderer::GraphT::EdgeValueType &E, + const GraphDiffRenderer::GraphT &G, ColorHelper H, + GraphDiffRenderer::StatType T) { + auto &EdgeAttr = E.second; + if (EdgeAttr.CorrEdgePtr[0] == nullptr) + return H.getColorString(2.0); // A number greater than 1.0 + if (EdgeAttr.CorrEdgePtr[1] == nullptr) + return H.getColorString(-2.0); // A number less than -1.0 + + if (T == GraphDiffRenderer::StatType::NONE) + return H.getDefaultColorString(); + + const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S; + const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S; + + double RelDiff = statRelDiff(LeftStat, RightStat, T); + double CappedRelDiff = std::min(1.0, std::max(-1.0, RelDiff)); + + return H.getColorString(CappedRelDiff); +} + +static std::string getColor(const GraphDiffRenderer::GraphT::VertexValueType &V, + const GraphDiffRenderer::GraphT &G, ColorHelper H, + GraphDiffRenderer::StatType T) { + auto &VertexAttr = V.second; + if (VertexAttr.CorrVertexPtr[0] == nullptr) + return H.getColorString(2.0); // A number greater than 1.0 + if (VertexAttr.CorrVertexPtr[1] == nullptr) + return H.getColorString(-2.0); // A number less than -1.0 + + if (T == GraphDiffRenderer::StatType::NONE) + return H.getDefaultColorString(); + + const auto &LeftStat = VertexAttr.CorrVertexPtr[0]->second.S; + const auto &RightStat = VertexAttr.CorrVertexPtr[1]->second.S; + + double RelDiff = statRelDiff(LeftStat, RightStat, T); + double CappedRelDiff = std::min(1.0, std::max(-1.0, RelDiff)); + + return H.getColorString(CappedRelDiff); +} + +static Twine truncateString(const StringRef &S, size_t n) { + return (S.size() > n) ? Twine(S.substr(0, n)) + "..." : Twine(S); +} + +template <typename T> static bool containsNullptr(const T &Collection) { + for (const auto &E : Collection) + if (E == nullptr) + return true; + return false; +} + +static std::string getLabel(const GraphDiffRenderer::GraphT::EdgeValueType &E, + GraphDiffRenderer::StatType EL) { + auto &EdgeAttr = E.second; + switch (EL) { + case GraphDiffRenderer::StatType::NONE: + return ""; + default: + if (containsNullptr(EdgeAttr.CorrEdgePtr)) + return ""; + + const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S; + const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S; + + double RelDiff = statRelDiff(LeftStat, RightStat, EL); + return formatv(R"({0:P})", RelDiff); + } +} + +static std::string getLabel(const GraphDiffRenderer::GraphT::VertexValueType &V, + GraphDiffRenderer::StatType VL, int TrunLen) { + const auto &VertexId = V.first; + const auto &VertexAttr = V.second; + switch (VL) { + case GraphDiffRenderer::StatType::NONE: + return formatv(R"({0})", truncateString(VertexId, TrunLen).str()); + default: + if (containsNullptr(VertexAttr.CorrVertexPtr)) + return formatv(R"({0})", truncateString(VertexId, TrunLen).str()); + + const auto &LeftStat = VertexAttr.CorrVertexPtr[0]->second.S; + const auto &RightStat = VertexAttr.CorrVertexPtr[1]->second.S; + + double RelDiff = statRelDiff(LeftStat, RightStat, VL); + return formatv(R"({{{0}|{1:P}})", truncateString(VertexId, TrunLen).str(), + RelDiff); + } +} + +static double getLineWidth(const GraphDiffRenderer::GraphT::EdgeValueType &E, + GraphDiffRenderer::StatType EL) { + auto &EdgeAttr = E.second; + switch (EL) { + case GraphDiffRenderer::StatType::NONE: + return 1.0; + default: + if (containsNullptr(EdgeAttr.CorrEdgePtr)) + return 1.0; + + const auto &LeftStat = EdgeAttr.CorrEdgePtr[0]->second.S; + const auto &RightStat = EdgeAttr.CorrEdgePtr[1]->second.S; + + double RelDiff = statRelDiff(LeftStat, RightStat, EL); + return (RelDiff > 1.0) ? RelDiff : 1.0; + } +} + +void GraphDiffRenderer::exportGraphAsDOT(raw_ostream &OS, StatType EdgeLabel, + StatType EdgeColor, + StatType VertexLabel, + StatType VertexColor, int TruncLen) { + // Get numbering of vertices for dot output. + StringMap<int32_t> VertexNo; + + int i = 0; + for (const auto &V : G.vertices()) { + VertexNo[V.first] = i++; + } + + ColorHelper H(ColorHelper::DivergingScheme::PiYG); + + OS << "digraph xrayDiff {\n"; + + if (VertexLabel != StatType::NONE) + OS << "node [shape=record]\n"; + + for (const auto &E : G.edges()) { + const auto &HeadId = E.first.first; + const auto &TailId = E.first.second; + OS << formatv(R"(F{0} -> F{1} [tooltip="{2} -> {3}" label="{4}" )" + R"(color="{5}" labelfontcolor="{5}" penwidth={6}])" + "\n", + VertexNo[HeadId], VertexNo[TailId], + (HeadId.equals("")) ? static_cast<StringRef>("F0") : HeadId, + TailId, getLabel(E, EdgeLabel), getColor(E, G, H, EdgeColor), + getLineWidth(E, EdgeColor)); + } + + for (const auto &V : G.vertices()) { + const auto &VertexId = V.first; + if (VertexId.equals("")) { + OS << formatv(R"(F{0} [label="F0"])" + "\n", + VertexNo[VertexId]); + continue; + } + OS << formatv(R"(F{0} [label="{1}" color="{2}"])" + "\n", + VertexNo[VertexId], getLabel(V, VertexLabel, TruncLen), + getColor(V, G, H, VertexColor)); + } + + OS << "}\n"; +} + +template <typename T> 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 { + std::array<GraphRenderer::Factory, 2> Factories{ + {{ifSpecified(GraphDiffKeepGoing1, GraphDiffKeepGoing1A, + GraphDiffKeepGoing), + ifSpecified(GraphDiffDeduceSiblingCalls1, GraphDiffDeduceSiblingCalls1A, + GraphDiffDeduceSiblingCalls), + ifSpecified(GraphDiffInstrMap1, GraphDiffInstrMap1A, GraphDiffInstrMap), + Trace()}, + {ifSpecified(GraphDiffKeepGoing2, GraphDiffKeepGoing2A, + GraphDiffKeepGoing), + ifSpecified(GraphDiffDeduceSiblingCalls2, GraphDiffDeduceSiblingCalls2A, + GraphDiffDeduceSiblingCalls), + ifSpecified(GraphDiffInstrMap2, GraphDiffInstrMap2A, GraphDiffInstrMap), + Trace()}}}; + + std::array<std::string, 2> Inputs{{GraphDiffInput1, GraphDiffInput2}}; + + std::array<GraphRenderer::GraphT, 2> Graphs; + + for (int i = 0; i < 2; i++) { + auto TraceOrErr = loadTraceFile(Inputs[i], true); + if (!TraceOrErr) + return make_error<StringError>( + Twine("Failed Loading Input File '") + Inputs[i] + "'", + make_error_code(llvm::errc::invalid_argument)); + Factories[i].Trace = std::move(*TraceOrErr); + + auto GraphRendererOrErr = Factories[i].getGraphRenderer(); + + if (!GraphRendererOrErr) + return GraphRendererOrErr.takeError(); + + auto GraphRenderer = *GraphRendererOrErr; + + Graphs[i] = GraphRenderer.getGraph(); + } + + GraphDiffRenderer::Factory DGF(Graphs[0], Graphs[1]); + + 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<StringError>( + Twine("Cannot open file '") + GraphDiffOutput + "' for writing.", EC); + + GDR.exportGraphAsDOT(OS, GraphDiffEdgeLabel, GraphDiffEdgeColor, + GraphDiffVertexLabel, GraphDiffVertexColor, + GraphDiffVertexLabelTrunc); + + return Error::success(); +}); diff --git a/contrib/llvm/tools/llvm-xray/xray-graph-diff.h b/contrib/llvm/tools/llvm-xray/xray-graph-diff.h new file mode 100644 index 0000000..5abec91 --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-graph-diff.h @@ -0,0 +1,74 @@ +//===-- xray-graph-diff.h - XRay Graph Diff 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 difference between the function call +// graph of two differnent traces. +// +//===----------------------------------------------------------------------===// + +#ifndef XRAY_GRAPH_DIFF_H +#define XRAY_GRAPH_DIFF_H + +#include "xray-graph.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/XRay/Graph.h" + +namespace llvm { +namespace xray { + +// This class creates a graph representing the difference between two +// xray-graphs And allows you to print it to a dot file, with optional color +// coding. +class GraphDiffRenderer { + static const int N = 2; + +public: + using StatType = GraphRenderer::StatType; + using TimeStat = GraphRenderer::TimeStat; + + using GREdgeValueType = GraphRenderer::GraphT::EdgeValueType; + using GRVertexValueType = GraphRenderer::GraphT::VertexValueType; + + struct EdgeAttribute { + std::array<const GREdgeValueType *, N> CorrEdgePtr = {}; + }; + + struct VertexAttribute { + std::array<const GRVertexValueType *, N> CorrVertexPtr = {}; + }; + + using GraphT = Graph<VertexAttribute, EdgeAttribute, StringRef>; + + class Factory { + std::array<std::reference_wrapper<const GraphRenderer::GraphT>, N> G; + + public: + template <typename... Ts> Factory(Ts &... Args) : G{{Args...}} {} + + Expected<GraphDiffRenderer> getGraphDiffRenderer(); + }; + +private: + GraphT G; + + GraphDiffRenderer() = default; + +public: + void exportGraphAsDOT(raw_ostream &OS, StatType EdgeLabel = StatType::NONE, + StatType EdgeColor = StatType::NONE, + StatType VertexLabel = StatType::NONE, + StatType VertexColor = StatType::NONE, + int TruncLen = 40); + + const GraphT &getGraph() { return G; } +}; +} // namespace xray +} // namespace llvm + +#endif diff --git a/contrib/llvm/tools/llvm-xray/xray-graph.cc b/contrib/llvm/tools/llvm-xray/xray-graph.cc new file mode 100644 index 0000000..685c24c --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-graph.cc @@ -0,0 +1,521 @@ +//===-- xray-graph.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 <algorithm> +#include <cassert> +#include <cmath> +#include <system_error> +#include <utility> + +#include "xray-graph.h" +#include "xray-registry.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/XRay/InstrumentationMap.h" +#include "llvm/XRay/Trace.h" +#include "llvm/XRay/YAMLXRayRecord.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: { + 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: { + // 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(); +}); diff --git a/contrib/llvm/tools/llvm-xray/xray-graph.h b/contrib/llvm/tools/llvm-xray/xray-graph.h new file mode 100644 index 0000000..a43df26 --- /dev/null +++ b/contrib/llvm/tools/llvm-xray/xray-graph.h @@ -0,0 +1,233 @@ +//===-- xray-graph.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_H +#define XRAY_GRAPH_H + +#include <string> +#include <vector> + +#include "func-id-helper.h" +#include "xray-color-helper.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#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" + +namespace llvm { +namespace xray { + +/// A class encapsulating the logic related to analyzing XRay traces, producting +/// Graphs from them and then exporting those graphs for review. +class GraphRenderer { +public: + /// An enum for enumerating the various statistics gathered on latencies + enum class StatType { NONE, COUNT, MIN, MED, PCT90, PCT99, MAX, SUM }; + + /// An inner struct for common timing statistics information + struct TimeStat { + int64_t Count; + double Min; + double Median; + double Pct90; + double Pct99; + double Max; + double Sum; + + std::string getString(StatType T) const; + double getDouble(StatType T) const; + }; + using TimestampT = uint64_t; + + /// 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 CallStats { + TimeStat S; + std::vector<TimestampT> 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 FunctionStats { + std::string SymbolName; + TimeStat S = {}; + }; + + struct FunctionAttr { + int32_t FuncId; + uint64_t TSC; + }; + + using FunctionStack = SmallVector<FunctionAttr, 4>; + + using PerThreadFunctionStackMap = + DenseMap<llvm::sys::ProcessInfo::ProcessId, FunctionStack>; + + class GraphT : public Graph<FunctionStats, CallStats, int32_t> { + public: + TimeStat GraphEdgeMax = {}; + TimeStat GraphVertexMax = {}; + }; + + GraphT G; + 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. + /// + /// FIXME: Perhaps we can Build this into LatencyAccountant? or vise versa? + PerThreadFunctionStackMap PerThreadFunctionStack; + + /// Usefull object for getting human readable Symbol Names. + FuncIdConversionHelper FuncIdHelper; + bool DeduceSiblingCalls = false; + TimestampT CurrentMaxTSC = 0; + + /// A private function to help implement the statistic generation functions; + template <typename U> + void getStats(U begin, U end, GraphRenderer::TimeStat &S); + void updateMaxStats(const TimeStat &S, TimeStat &M); + + /// Calculates latency statistics for each edge and stores the data in the + /// Graph + void calculateEdgeStatistics(); + + /// Calculates latency statistics for each vertex and stores the data in the + /// Graph + void calculateVertexStatistics(); + + /// Normalises latency statistics for each edge and vertex by CycleFrequency; + void normalizeStatistics(double CycleFrequency); + + /// An object to color gradients + ColorHelper CHelper; + +public: + /// Takes in a reference to a FuncIdHelper in order to have ready access to + /// Symbol names. + explicit GraphRenderer(const FuncIdConversionHelper &FuncIdHelper, bool DSC) + : FuncIdHelper(FuncIdHelper), DeduceSiblingCalls(DSC), + CHelper(ColorHelper::SequentialScheme::OrRd) { + G[0] = {}; + } + + /// Process an Xray record and expand the graph. + /// + /// This Function will return true on success, or false if records are not + /// presented in per-thread call-tree DFS order. (That is for each thread the + /// Records should be in order runtime on an ideal system.) + /// + /// FIXME: Make this more robust against small irregularities. + Error accountRecord(const XRayRecord &Record); + + const PerThreadFunctionStackMap &getPerThreadFunctionStack() const { + return PerThreadFunctionStack; + } + + class Factory { + public: + bool KeepGoing; + bool DeduceSiblingCalls; + std::string InstrMap; + ::llvm::xray::Trace Trace; + Expected<GraphRenderer> getGraphRenderer(); + }; + + /// Output the Embedded graph in DOT format on \p OS, labeling the edges by + /// \p T + 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() { 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<int64_t>(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<int64_t>(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}; +} +} // namespace xray +} // namespace llvm + +#endif // XRAY_GRAPH_H diff --git a/contrib/llvm/tools/llvm-xray/xray-sleds.h b/contrib/llvm/tools/llvm-xray/xray-sleds.h deleted file mode 100644 index 9927957..0000000 --- a/contrib/llvm/tools/llvm-xray/xray-sleds.h +++ /dev/null @@ -1,32 +0,0 @@ -//===- xray-sleds.h - XRay Sleds Data Structure ---------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Defines the structure used to represent XRay instrumentation map entries. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_TOOLS_LLVM_XRAY_XRAY_SLEDS_H -#define LLVM_TOOLS_LLVM_XRAY_XRAY_SLEDS_H - -namespace llvm { -namespace xray { - -struct SledEntry { - enum class FunctionKinds { ENTRY, EXIT, TAIL }; - - uint64_t Address; - uint64_t Function; - FunctionKinds Kind; - bool AlwaysInstrument; -}; - -} // namespace xray -} // namespace llvm - -#endif // LLVM_TOOLS_LLVM_XRAY_XRAY_SLEDS_H |