Index: llvm/trunk/tools/llvm-xray/xray-account.cc =================================================================== --- llvm/trunk/tools/llvm-xray/xray-account.cc (revision 314268) +++ llvm/trunk/tools/llvm-xray/xray-account.cc (revision 314269) @@ -1,503 +1,504 @@ //===- xray-account.h - XRay Function Call Accounting ---------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file implements basic function call accounting from an XRay trace. // //===----------------------------------------------------------------------===// #include #include #include #include #include #include "xray-account.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; using namespace llvm::xray; static cl::SubCommand Account("account", "Function call accounting"); static cl::opt AccountInput(cl::Positional, cl::desc(""), cl::Required, cl::sub(Account)); static cl::opt AccountKeepGoing("keep-going", cl::desc("Keep going on errors encountered"), cl::sub(Account), cl::init(false)); static cl::alias AccountKeepGoing2("k", cl::aliasopt(AccountKeepGoing), cl::desc("Alias for -keep_going"), cl::sub(Account)); static cl::opt AccountDeduceSiblingCalls( "deduce-sibling-calls", cl::desc("Deduce sibling calls when unrolling function call stacks"), cl::sub(Account), cl::init(false)); static cl::alias AccountDeduceSiblingCalls2("d", cl::aliasopt(AccountDeduceSiblingCalls), cl::desc("Alias for -deduce_sibling_calls"), cl::sub(Account)); static cl::opt AccountOutput("output", cl::value_desc("output file"), cl::init("-"), cl::desc("output file; use '-' for stdout"), cl::sub(Account)); static cl::alias AccountOutput2("o", cl::aliasopt(AccountOutput), cl::desc("Alias for -output"), cl::sub(Account)); enum class AccountOutputFormats { TEXT, CSV }; static cl::opt AccountOutputFormat("format", cl::desc("output format"), cl::values(clEnumValN(AccountOutputFormats::TEXT, "text", "report stats in text"), clEnumValN(AccountOutputFormats::CSV, "csv", "report stats in csv")), cl::sub(Account)); static cl::alias AccountOutputFormat2("f", cl::desc("Alias of -format"), cl::aliasopt(AccountOutputFormat), cl::sub(Account)); enum class SortField { FUNCID, COUNT, MIN, MED, PCT90, PCT99, MAX, SUM, FUNC, }; static cl::opt AccountSortOutput( "sort", cl::desc("sort output by this field"), cl::value_desc("field"), cl::sub(Account), cl::init(SortField::FUNCID), cl::values(clEnumValN(SortField::FUNCID, "funcid", "function id"), clEnumValN(SortField::COUNT, "count", "funciton call counts"), clEnumValN(SortField::MIN, "min", "minimum function durations"), clEnumValN(SortField::MED, "med", "median function durations"), clEnumValN(SortField::PCT90, "90p", "90th percentile durations"), clEnumValN(SortField::PCT99, "99p", "99th percentile durations"), clEnumValN(SortField::MAX, "max", "maximum function durations"), clEnumValN(SortField::SUM, "sum", "sum of call durations"), clEnumValN(SortField::FUNC, "func", "function names"))); static cl::alias AccountSortOutput2("s", cl::aliasopt(AccountSortOutput), cl::desc("Alias for -sort"), cl::sub(Account)); enum class SortDirection { ASCENDING, DESCENDING, }; static cl::opt AccountSortOrder( "sortorder", cl::desc("sort ordering"), cl::init(SortDirection::ASCENDING), cl::values(clEnumValN(SortDirection::ASCENDING, "asc", "ascending"), clEnumValN(SortDirection::DESCENDING, "dsc", "descending")), cl::sub(Account)); static cl::alias AccountSortOrder2("r", cl::aliasopt(AccountSortOrder), cl::desc("Alias for -sortorder"), cl::sub(Account)); static cl::opt AccountTop("top", cl::desc("only show the top N results"), cl::value_desc("N"), cl::sub(Account), cl::init(-1)); static cl::alias AccountTop2("p", cl::desc("Alias for -top"), cl::aliasopt(AccountTop), cl::sub(Account)); static cl::opt AccountInstrMap("instr_map", cl::desc("binary with the instrumentation map, or " "a separate instrumentation map"), cl::value_desc("binary with xray_instr_map"), cl::sub(Account), cl::init("")); static cl::alias AccountInstrMap2("m", cl::aliasopt(AccountInstrMap), cl::desc("Alias for -instr_map"), cl::sub(Account)); namespace { template void setMinMax(std::pair &MM, U &&V) { if (MM.first == 0 || MM.second == 0) MM = std::make_pair(std::forward(V), std::forward(V)); else MM = std::make_pair(std::min(MM.first, V), std::max(MM.second, V)); } template T diff(T L, T R) { return std::max(L, R) - std::min(L, R); } } // namespace bool LatencyAccountant::accountRecord(const XRayRecord &Record) { setMinMax(PerThreadMinMaxTSC[Record.TId], Record.TSC); setMinMax(PerCPUMinMaxTSC[Record.CPU], Record.TSC); if (CurrentMaxTSC == 0) CurrentMaxTSC = Record.TSC; if (Record.TSC < CurrentMaxTSC) return false; auto &ThreadStack = PerThreadFunctionStack[Record.TId]; switch (Record.Type) { - case RecordTypes::ENTER: { + case RecordTypes::ENTER: + case RecordTypes::ENTER_ARG: { ThreadStack.emplace_back(Record.FuncId, Record.TSC); break; } case RecordTypes::EXIT: case RecordTypes::TAIL_EXIT: { if (ThreadStack.empty()) return false; if (ThreadStack.back().first == Record.FuncId) { const auto &Top = ThreadStack.back(); recordLatency(Top.first, diff(Top.second, Record.TSC)); ThreadStack.pop_back(); break; } if (!DeduceSiblingCalls) return false; // Look for the parent up the stack. auto Parent = std::find_if(ThreadStack.rbegin(), ThreadStack.rend(), [&](const std::pair &E) { return E.first == Record.FuncId; }); if (Parent == ThreadStack.rend()) return false; // Account time for this apparently sibling call exit up the stack. // Considering the following case: // // f() // g() // h() // // We might only ever see the following entries: // // -> f() // -> g() // -> h() // <- h() // <- f() // // Now we don't see the exit to g() because some older version of the XRay // runtime wasn't instrumenting tail exits. If we don't deduce tail calls, // we may potentially never account time for g() -- and this code would have // already bailed out, because `<- f()` doesn't match the current "top" of // stack where we're waiting for the exit to `g()` instead. This is not // ideal and brittle -- so instead we provide a potentially inaccurate // accounting of g() instead, computing it from the exit of f(). // // While it might be better that we account the time between `-> g()` and // `-> h()` as the proper accounting of time for g() here, this introduces // complexity to do correctly (need to backtrack, etc.). // // FIXME: Potentially implement the more complex deduction algorithm? auto I = std::next(Parent).base(); for (auto &E : make_range(I, ThreadStack.end())) { recordLatency(E.first, diff(E.second, Record.TSC)); } ThreadStack.erase(I, ThreadStack.end()); break; } } return true; } namespace { // We consolidate the data into a struct which we can output in various forms. struct ResultRow { uint64_t Count; double Min; double Median; double Pct90; double Pct99; double Max; double Sum; std::string DebugInfo; std::string Function; }; ResultRow getStats(std::vector &Timings) { assert(!Timings.empty()); ResultRow R; R.Sum = std::accumulate(Timings.begin(), Timings.end(), 0.0); auto MinMax = std::minmax_element(Timings.begin(), Timings.end()); R.Min = *MinMax.first; R.Max = *MinMax.second; auto MedianOff = Timings.size() / 2; std::nth_element(Timings.begin(), Timings.begin() + MedianOff, Timings.end()); R.Median = Timings[MedianOff]; auto Pct90Off = std::floor(Timings.size() * 0.9); std::nth_element(Timings.begin(), Timings.begin() + Pct90Off, Timings.end()); R.Pct90 = Timings[Pct90Off]; auto Pct99Off = std::floor(Timings.size() * 0.99); std::nth_element(Timings.begin(), Timings.begin() + Pct90Off, Timings.end()); R.Pct99 = Timings[Pct99Off]; R.Count = Timings.size(); return R; } } // namespace template void LatencyAccountant::exportStats(const XRayFileHeader &Header, F Fn) const { using TupleType = std::tuple; std::vector Results; Results.reserve(FunctionLatencies.size()); for (auto FT : FunctionLatencies) { const auto &FuncId = FT.first; auto &Timings = FT.second; Results.emplace_back(FuncId, Timings.size(), getStats(Timings)); auto &Row = std::get<2>(Results.back()); if (Header.CycleFrequency) { double CycleFrequency = Header.CycleFrequency; Row.Min /= CycleFrequency; Row.Median /= CycleFrequency; Row.Pct90 /= CycleFrequency; Row.Pct99 /= CycleFrequency; Row.Max /= CycleFrequency; Row.Sum /= CycleFrequency; } Row.Function = FuncIdHelper.SymbolOrNumber(FuncId); Row.DebugInfo = FuncIdHelper.FileLineAndColumn(FuncId); } // Sort the data according to user-provided flags. switch (AccountSortOutput) { case SortField::FUNCID: std::sort(Results.begin(), Results.end(), [](const TupleType &L, const TupleType &R) { if (AccountSortOrder == SortDirection::ASCENDING) return std::get<0>(L) < std::get<0>(R); if (AccountSortOrder == SortDirection::DESCENDING) return std::get<0>(L) > std::get<0>(R); llvm_unreachable("Unknown sort direction"); }); break; case SortField::COUNT: std::sort(Results.begin(), Results.end(), [](const TupleType &L, const TupleType &R) { if (AccountSortOrder == SortDirection::ASCENDING) return std::get<1>(L) < std::get<1>(R); if (AccountSortOrder == SortDirection::DESCENDING) return std::get<1>(L) > std::get<1>(R); llvm_unreachable("Unknown sort direction"); }); break; default: // Here we need to look into the ResultRow for the rest of the data that // we want to sort by. std::sort(Results.begin(), Results.end(), [&](const TupleType &L, const TupleType &R) { auto &LR = std::get<2>(L); auto &RR = std::get<2>(R); switch (AccountSortOutput) { case SortField::COUNT: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Count < RR.Count; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Count > RR.Count; llvm_unreachable("Unknown sort direction"); case SortField::MIN: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Min < RR.Min; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Min > RR.Min; llvm_unreachable("Unknown sort direction"); case SortField::MED: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Median < RR.Median; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Median > RR.Median; llvm_unreachable("Unknown sort direction"); case SortField::PCT90: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Pct90 < RR.Pct90; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Pct90 > RR.Pct90; llvm_unreachable("Unknown sort direction"); case SortField::PCT99: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Pct99 < RR.Pct99; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Pct99 > RR.Pct99; llvm_unreachable("Unknown sort direction"); case SortField::MAX: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Max < RR.Max; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Max > RR.Max; llvm_unreachable("Unknown sort direction"); case SortField::SUM: if (AccountSortOrder == SortDirection::ASCENDING) return LR.Sum < RR.Sum; if (AccountSortOrder == SortDirection::DESCENDING) return LR.Sum > RR.Sum; llvm_unreachable("Unknown sort direction"); default: llvm_unreachable("Unsupported sort order"); } }); break; } if (AccountTop > 0) Results.erase(Results.begin() + AccountTop.getValue(), Results.end()); for (const auto &R : Results) Fn(std::get<0>(R), std::get<1>(R), std::get<2>(R)); } void LatencyAccountant::exportStatsAsText(raw_ostream &OS, const XRayFileHeader &Header) const { OS << "Functions with latencies: " << FunctionLatencies.size() << "\n"; // We spend some effort to make the text output more readable, so we do the // following formatting decisions for each of the fields: // // - funcid: 32-bit, but we can determine the largest number and be // between // a minimum of 5 characters, up to 9 characters, right aligned. // - count: 64-bit, but we can determine the largest number and be // between // a minimum of 5 characters, up to 9 characters, right aligned. // - min, median, 90pct, 99pct, max: double precision, but we want to keep // the values in seconds, with microsecond precision (0.000'001), so we // have at most 6 significant digits, with the whole number part to be // at // least 1 character. For readability we'll right-align, with full 9 // characters each. // - debug info, function name: we format this as a concatenation of the // debug info and the function name. // static constexpr char StatsHeaderFormat[] = "{0,+9} {1,+10} [{2,+9}, {3,+9}, {4,+9}, {5,+9}, {6,+9}] {7,+9}"; static constexpr char StatsFormat[] = R"({0,+9} {1,+10} [{2,+9:f6}, {3,+9:f6}, {4,+9:f6}, {5,+9:f6}, {6,+9:f6}] {7,+9:f6})"; OS << llvm::formatv(StatsHeaderFormat, "funcid", "count", "min", "med", "90p", "99p", "max", "sum") << llvm::formatv(" {0,-12}\n", "function"); exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) { OS << llvm::formatv(StatsFormat, FuncId, Count, Row.Min, Row.Median, Row.Pct90, Row.Pct99, Row.Max, Row.Sum) << " " << Row.DebugInfo << ": " << Row.Function << "\n"; }); } void LatencyAccountant::exportStatsAsCSV(raw_ostream &OS, const XRayFileHeader &Header) const { OS << "funcid,count,min,median,90%ile,99%ile,max,sum,debug,function\n"; exportStats(Header, [&](int32_t FuncId, size_t Count, const ResultRow &Row) { OS << FuncId << ',' << Count << ',' << Row.Min << ',' << Row.Median << ',' << Row.Pct90 << ',' << Row.Pct99 << ',' << Row.Max << "," << Row.Sum << ",\"" << Row.DebugInfo << "\",\"" << Row.Function << "\"\n"; }); } using namespace llvm::xray; namespace llvm { template <> struct format_provider { static void format(const llvm::xray::RecordTypes &T, raw_ostream &Stream, StringRef Style) { switch(T) { case RecordTypes::ENTER: Stream << "enter"; break; case RecordTypes::EXIT: Stream << "exit"; break; case RecordTypes::TAIL_EXIT: Stream << "tail-exit"; break; } } }; } // namespace llvm static CommandRegistration Unused(&Account, []() -> Error { InstrumentationMap Map; if (!AccountInstrMap.empty()) { auto InstrumentationMapOrError = loadInstrumentationMap(AccountInstrMap); if (!InstrumentationMapOrError) return joinErrors(make_error( 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( Twine("Cannot open file '") + AccountOutput + "' for writing.", EC); 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); auto TraceOrErr = loadTraceFile(AccountInput); if (!TraceOrErr) return joinErrors( make_error( 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; errs() << "Error processing record: " << llvm::formatv( R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}}})", Record.RecordType, Record.CPU, Record.Type, Record.FuncId, Record.TId) << '\n'; for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) { errs() << "Thread ID: " << ThreadStack.first << "\n"; if (ThreadStack.second.empty()) { errs() << " (empty stack)\n"; continue; } 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( 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(); }); Index: llvm/trunk/tools/llvm-xray/xray-graph.cc =================================================================== --- llvm/trunk/tools/llvm-xray/xray-graph.cc (revision 314268) +++ llvm/trunk/tools/llvm-xray/xray-graph.cc (revision 314269) @@ -1,522 +1,523 @@ //===-- 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 #include #include #include #include #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 GraphInput(cl::Positional, cl::desc(""), cl::Required, cl::sub(GraphC)); static cl::opt 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 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 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 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 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 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 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 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 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("Records not in order", make_error_code(errc::invalid_argument)); auto &ThreadStack = PerThreadFunctionStack[Record.TId]; switch (Record.Type) { - case RecordTypes::ENTER: { + case RecordTypes::ENTER: + case RecordTypes::ENTER_ARG: { if (Record.FuncId != 0 && G.count(Record.FuncId) == 0) G[Record.FuncId].SymbolName = FuncIdHelper.SymbolOrNumber(Record.FuncId); ThreadStack.push_back({Record.FuncId, Record.TSC}); break; } case RecordTypes::EXIT: case RecordTypes::TAIL_EXIT: { // FIXME: Refactor this and the account subcommand to reduce code // duplication if (ThreadStack.size() == 0 || ThreadStack.back().FuncId != Record.FuncId) { if (!DeduceSiblingCalls) return make_error("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( "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 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 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(T) - static_cast(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(Count); break; default: retval = (*this).*DoubleStatPtrs[static_cast(T) - static_cast(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::Factory::getGraphRenderer() { InstrumentationMap Map; if (!GraphInstrMap.empty()) { auto InstrumentationMapOrError = loadInstrumentationMap(GraphInstrMap); if (!InstrumentationMapOrError) return joinErrors( make_error( 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( "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( Twine("Failed loading input file '") + GraphInput + "'", make_error_code(llvm::errc::invalid_argument)); F.Trace = std::move(*TraceOrErr); auto GROrError = F.getGraphRenderer(); if (!GROrError) return GROrError.takeError(); auto &GR = *GROrError; std::error_code EC; raw_fd_ostream OS(GraphOutput, EC, sys::fs::OpenFlags::F_Text); if (EC) return make_error( Twine("Cannot open file '") + GraphOutput + "' for writing.", EC); GR.exportGraphAsDOT(OS, GraphEdgeLabel, GraphEdgeColorType, GraphVertexLabel, GraphVertexColorType); return Error::success(); }); Index: llvm/trunk/tools/llvm-xray/xray-converter.cc =================================================================== --- llvm/trunk/tools/llvm-xray/xray-converter.cc (revision 314268) +++ llvm/trunk/tools/llvm-xray/xray-converter.cc (revision 314269) @@ -1,198 +1,199 @@ //===- xray-converter.cc - XRay Trace Conversion --------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // Implements the trace conversion functions. // //===----------------------------------------------------------------------===// #include "xray-converter.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" using namespace llvm; using namespace xray; // llvm-xray convert // ---------------------------------------------------------------------------- static cl::SubCommand Convert("convert", "Trace Format Conversion"); static cl::opt ConvertInput(cl::Positional, cl::desc(""), cl::Required, cl::sub(Convert)); enum class ConvertFormats { BINARY, YAML }; static cl::opt ConvertOutputFormat( "output-format", cl::desc("output format"), cl::values(clEnumValN(ConvertFormats::BINARY, "raw", "output in binary"), clEnumValN(ConvertFormats::YAML, "yaml", "output in yaml")), cl::sub(Convert)); static cl::alias ConvertOutputFormat2("f", cl::aliasopt(ConvertOutputFormat), cl::desc("Alias for -output-format"), cl::sub(Convert)); static cl::opt ConvertOutput("output", cl::value_desc("output file"), cl::init("-"), cl::desc("output file; use '-' for stdout"), cl::sub(Convert)); static cl::alias ConvertOutput2("o", cl::aliasopt(ConvertOutput), cl::desc("Alias for -output"), cl::sub(Convert)); static cl::opt ConvertSymbolize("symbolize", cl::desc("symbolize function ids from the input log"), cl::init(false), cl::sub(Convert)); static cl::alias ConvertSymbolize2("y", cl::aliasopt(ConvertSymbolize), cl::desc("Alias for -symbolize"), cl::sub(Convert)); static cl::opt ConvertInstrMap("instr_map", cl::desc("binary with the instrumentation map, or " "a separate instrumentation map"), cl::value_desc("binary with xray_instr_map"), cl::sub(Convert), cl::init("")); static cl::alias ConvertInstrMap2("m", cl::aliasopt(ConvertInstrMap), cl::desc("Alias for -instr_map"), cl::sub(Convert)); static cl::opt ConvertSortInput( "sort", cl::desc("determines whether to sort input log records by timestamp"), cl::sub(Convert), cl::init(true)); static cl::alias ConvertSortInput2("s", cl::aliasopt(ConvertSortInput), cl::desc("Alias for -sort"), cl::sub(Convert)); using llvm::yaml::Output; void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) { YAMLXRayTrace Trace; const auto &FH = Records.getFileHeader(); Trace.Header = {FH.Version, FH.Type, FH.ConstantTSC, FH.NonstopTSC, FH.CycleFrequency}; Trace.Records.reserve(Records.size()); for (const auto &R : Records) { Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId, Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId) : llvm::to_string(R.FuncId), - R.TSC, R.TId}); + R.TSC, R.TId, R.CallArgs}); } Output Out(OS, nullptr, 0); Out << Trace; } void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) { // First write out the file header, in the correct endian-appropriate format // (XRay assumes currently little endian). support::endian::Writer Writer(OS); const auto &FH = Records.getFileHeader(); Writer.write(FH.Version); Writer.write(FH.Type); uint32_t Bitfield{0}; if (FH.ConstantTSC) Bitfield |= 1uL; if (FH.NonstopTSC) Bitfield |= 1uL << 1; Writer.write(Bitfield); Writer.write(FH.CycleFrequency); // There's 16 bytes of padding at the end of the file header. static constexpr uint32_t Padding4B = 0; Writer.write(Padding4B); Writer.write(Padding4B); Writer.write(Padding4B); Writer.write(Padding4B); // Then write out the rest of the records, still in an endian-appropriate // format. for (const auto &R : Records) { Writer.write(R.RecordType); // 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(R.CPU)); switch (R.Type) { case RecordTypes::ENTER: + case RecordTypes::ENTER_ARG: Writer.write(uint8_t{0}); break; case RecordTypes::EXIT: Writer.write(uint8_t{1}); break; case RecordTypes::TAIL_EXIT: Writer.write(uint8_t{2}); break; } Writer.write(R.FuncId); Writer.write(R.TSC); Writer.write(R.TId); Writer.write(Padding4B); Writer.write(Padding4B); Writer.write(Padding4B); } } namespace llvm { namespace xray { static CommandRegistration Unused(&Convert, []() -> Error { // FIXME: Support conversion to BINARY when upgrading XRay trace versions. InstrumentationMap Map; if (!ConvertInstrMap.empty()) { auto InstrumentationMapOrError = loadInstrumentationMap(ConvertInstrMap); if (!InstrumentationMapOrError) return joinErrors(make_error( Twine("Cannot open instrumentation map '") + ConvertInstrMap + "'", 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); 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 : sys::fs::OpenFlags::F_Text); if (EC) return make_error( Twine("Cannot open file '") + ConvertOutput + "' for writing.", EC); auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput); if (!TraceOrErr) return joinErrors( make_error( 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(); }); } // namespace xray } // namespace llvm Index: llvm/trunk/lib/XRay/Trace.cpp =================================================================== --- llvm/trunk/lib/XRay/Trace.cpp (revision 314268) +++ llvm/trunk/lib/XRay/Trace.cpp (revision 314269) @@ -1,560 +1,585 @@ //===- Trace.cpp - XRay Trace Loading implementation. ---------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // XRay log reader implementation. // //===----------------------------------------------------------------------===// #include "llvm/XRay/Trace.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/DataExtractor.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/XRay/YAMLXRayRecord.h" using namespace llvm; using namespace llvm::xray; using llvm::yaml::Input; namespace { using XRayRecordStorage = std::aligned_storage::type; // Populates the FileHeader reference by reading the first 32 bytes of the file. Error readBinaryFormatHeader(StringRef Data, XRayFileHeader &FileHeader) { // FIXME: Maybe deduce whether the data is little or big-endian using some // magic bytes in the beginning of the file? // First 32 bytes of the file will always be the header. We assume a certain // format here: // // (2) uint16 : version // (2) uint16 : type // (4) uint32 : bitfield // (8) uint64 : cycle frequency // (16) - : padding DataExtractor HeaderExtractor(Data, true, 8); uint32_t OffsetPtr = 0; FileHeader.Version = HeaderExtractor.getU16(&OffsetPtr); FileHeader.Type = HeaderExtractor.getU16(&OffsetPtr); uint32_t Bitfield = HeaderExtractor.getU32(&OffsetPtr); FileHeader.ConstantTSC = Bitfield & 1uL; FileHeader.NonstopTSC = Bitfield & 1uL << 1; FileHeader.CycleFrequency = HeaderExtractor.getU64(&OffsetPtr); std::memcpy(&FileHeader.FreeFormData, Data.bytes_begin() + OffsetPtr, 16); if (FileHeader.Version != 1 && FileHeader.Version != 2) return make_error( Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version), std::make_error_code(std::errc::invalid_argument)); return Error::success(); } Error loadNaiveFormatLog(StringRef Data, XRayFileHeader &FileHeader, std::vector &Records) { if (Data.size() < 32) return make_error( "Not enough bytes for an XRay log.", std::make_error_code(std::errc::invalid_argument)); if (Data.size() - 32 == 0 || Data.size() % 32 != 0) return make_error( "Invalid-sized XRay data.", std::make_error_code(std::errc::invalid_argument)); if (auto E = readBinaryFormatHeader(Data, FileHeader)) return E; // Each record after the header will be 32 bytes, in the following format: // // (2) uint16 : record type // (1) uint8 : cpu id // (1) uint8 : type // (4) sint32 : function id // (8) uint64 : tsc // (4) uint32 : thread id // (12) - : padding for (auto S = Data.drop_front(32); !S.empty(); S = S.drop_front(32)) { DataExtractor RecordExtractor(S, true, 8); uint32_t OffsetPtr = 0; Records.emplace_back(); auto &Record = Records.back(); Record.RecordType = RecordExtractor.getU16(&OffsetPtr); Record.CPU = RecordExtractor.getU8(&OffsetPtr); auto Type = RecordExtractor.getU8(&OffsetPtr); switch (Type) { case 0: Record.Type = RecordTypes::ENTER; break; case 1: Record.Type = RecordTypes::EXIT; break; case 2: Record.Type = RecordTypes::TAIL_EXIT; break; default: return make_error( Twine("Unknown record type '") + Twine(int{Type}) + "'", std::make_error_code(std::errc::executable_format_error)); } Record.FuncId = RecordExtractor.getSigned(&OffsetPtr, sizeof(int32_t)); Record.TSC = RecordExtractor.getU64(&OffsetPtr); Record.TId = RecordExtractor.getU32(&OffsetPtr); } return Error::success(); } /// When reading from a Flight Data Recorder mode log, metadata records are /// sparse compared to packed function records, so we must maintain state as we /// read through the sequence of entries. This allows the reader to denormalize /// the CPUId and Thread Id onto each Function Record and transform delta /// encoded TSC values into absolute encodings on each record. struct FDRState { uint16_t CPUId; uint16_t ThreadId; uint64_t BaseTSC; /// Encode some of the state transitions for the FDR log reader as explicit /// checks. These are expectations for the next Record in the stream. enum class Token { NEW_BUFFER_RECORD_OR_EOF, WALLCLOCK_RECORD, NEW_CPU_ID_RECORD, FUNCTION_SEQUENCE, SCAN_TO_END_OF_THREAD_BUF, CUSTOM_EVENT_DATA, + CALL_ARGUMENT, }; Token Expects; // Each threads buffer may have trailing garbage to scan over, so we track our // progress. uint64_t CurrentBufferSize; uint64_t CurrentBufferConsumed; }; const char *fdrStateToTwine(const FDRState::Token &state) { switch (state) { case FDRState::Token::NEW_BUFFER_RECORD_OR_EOF: return "NEW_BUFFER_RECORD_OR_EOF"; case FDRState::Token::WALLCLOCK_RECORD: return "WALLCLOCK_RECORD"; case FDRState::Token::NEW_CPU_ID_RECORD: return "NEW_CPU_ID_RECORD"; case FDRState::Token::FUNCTION_SEQUENCE: return "FUNCTION_SEQUENCE"; case FDRState::Token::SCAN_TO_END_OF_THREAD_BUF: return "SCAN_TO_END_OF_THREAD_BUF"; case FDRState::Token::CUSTOM_EVENT_DATA: return "CUSTOM_EVENT_DATA"; + case FDRState::Token::CALL_ARGUMENT: + return "CALL_ARGUMENT"; } return "UNKNOWN"; } /// State transition when a NewBufferRecord is encountered. Error processFDRNewBufferRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor) { if (State.Expects != FDRState::Token::NEW_BUFFER_RECORD_OR_EOF) return make_error( "Malformed log. Read New Buffer record kind out of sequence", std::make_error_code(std::errc::executable_format_error)); uint32_t OffsetPtr = 1; // 1 byte into record. State.ThreadId = RecordExtractor.getU16(&OffsetPtr); State.Expects = FDRState::Token::WALLCLOCK_RECORD; return Error::success(); } /// State transition when an EndOfBufferRecord is encountered. Error processFDREndOfBufferRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor) { if (State.Expects == FDRState::Token::NEW_BUFFER_RECORD_OR_EOF) return make_error( "Malformed log. Received EOB message without current buffer.", std::make_error_code(std::errc::executable_format_error)); State.Expects = FDRState::Token::SCAN_TO_END_OF_THREAD_BUF; return Error::success(); } /// State transition when a NewCPUIdRecord is encountered. Error processFDRNewCPUIdRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor) { if (State.Expects != FDRState::Token::FUNCTION_SEQUENCE && State.Expects != FDRState::Token::NEW_CPU_ID_RECORD) return make_error( "Malformed log. Read NewCPUId record kind out of sequence", std::make_error_code(std::errc::executable_format_error)); uint32_t OffsetPtr = 1; // Read starting after the first byte. State.CPUId = RecordExtractor.getU16(&OffsetPtr); State.BaseTSC = RecordExtractor.getU64(&OffsetPtr); State.Expects = FDRState::Token::FUNCTION_SEQUENCE; return Error::success(); } /// State transition when a TSCWrapRecord (overflow detection) is encountered. Error processFDRTSCWrapRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor) { if (State.Expects != FDRState::Token::FUNCTION_SEQUENCE) return make_error( "Malformed log. Read TSCWrap record kind out of sequence", std::make_error_code(std::errc::executable_format_error)); uint32_t OffsetPtr = 1; // Read starting after the first byte. State.BaseTSC = RecordExtractor.getU64(&OffsetPtr); return Error::success(); } /// State transition when a WallTimeMarkerRecord is encountered. Error processFDRWallTimeRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor) { if (State.Expects != FDRState::Token::WALLCLOCK_RECORD) return make_error( "Malformed log. Read Wallclock record kind out of sequence", std::make_error_code(std::errc::executable_format_error)); // We don't encode the wall time into any of the records. // XRayRecords are concerned with the TSC instead. State.Expects = FDRState::Token::NEW_CPU_ID_RECORD; return Error::success(); } /// State transition when a CustomEventMarker is encountered. Error processCustomEventMarker(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor, size_t &RecordSize) { // We can encounter a CustomEventMarker anywhere in the log, so we can handle // it regardless of the expectation. However, we do set the expectation to // read a set number of fixed bytes, as described in the metadata. uint32_t OffsetPtr = 1; // Read after the first byte. uint32_t DataSize = RecordExtractor.getU32(&OffsetPtr); uint64_t TSC = RecordExtractor.getU64(&OffsetPtr); // FIXME: Actually represent the record through the API. For now we only skip // through the data. (void)TSC; RecordSize = 16 + DataSize; return Error::success(); } +/// State transition when a CallArgumentRecord is encountered. +Error processFDRCallArgumentRecord(FDRState &State, uint8_t RecordFirstByte, + DataExtractor &RecordExtractor, + std::vector &Records) { + uint32_t OffsetPtr = 1; // Read starting after the first byte. + auto &Enter = Records.back(); + + if (Enter.Type != RecordTypes::ENTER) + return make_error( + "CallArgument needs to be right after a function entry", + std::make_error_code(std::errc::executable_format_error)); + Enter.Type = RecordTypes::ENTER_ARG; + Enter.CallArgs.emplace_back(RecordExtractor.getU64(&OffsetPtr)); + return Error::success(); +} + /// Advances the state machine for reading the FDR record type by reading one /// Metadata Record and updating the State appropriately based on the kind of /// record encountered. The RecordKind is encoded in the first byte of the /// Record, which the caller should pass in because they have already read it /// to determine that this is a metadata record as opposed to a function record. Error processFDRMetadataRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor, - size_t &RecordSize) { + size_t &RecordSize, + std::vector &Records) { // The remaining 7 bits are the RecordKind enum. uint8_t RecordKind = RecordFirstByte >> 1; switch (RecordKind) { case 0: // NewBuffer if (auto E = processFDRNewBufferRecord(State, RecordFirstByte, RecordExtractor)) return E; break; case 1: // EndOfBuffer if (auto E = processFDREndOfBufferRecord(State, RecordFirstByte, RecordExtractor)) return E; break; case 2: // NewCPUId if (auto E = processFDRNewCPUIdRecord(State, RecordFirstByte, RecordExtractor)) return E; break; case 3: // TSCWrap if (auto E = processFDRTSCWrapRecord(State, RecordFirstByte, RecordExtractor)) return E; break; case 4: // WallTimeMarker if (auto E = processFDRWallTimeRecord(State, RecordFirstByte, RecordExtractor)) return E; break; case 5: // CustomEventMarker if (auto E = processCustomEventMarker(State, RecordFirstByte, RecordExtractor, RecordSize)) return E; break; + case 6: // CallArgument + if (auto E = processFDRCallArgumentRecord(State, RecordFirstByte, + RecordExtractor, Records)) + return E; + break; default: // Widen the record type to uint16_t to prevent conversion to char. return make_error( Twine("Illegal metadata record type: ") .concat(Twine(static_cast(RecordKind))), std::make_error_code(std::errc::executable_format_error)); } return Error::success(); } /// Reads a function record from an FDR format log, appending a new XRayRecord /// to the vector being populated and updating the State with a new value /// reference value to interpret TSC deltas. /// /// The XRayRecord constructed includes information from the function record /// processed here as well as Thread ID and CPU ID formerly extracted into /// State. Error processFDRFunctionRecord(FDRState &State, uint8_t RecordFirstByte, DataExtractor &RecordExtractor, std::vector &Records) { switch (State.Expects) { case FDRState::Token::NEW_BUFFER_RECORD_OR_EOF: return make_error( "Malformed log. Received Function Record before new buffer setup.", std::make_error_code(std::errc::executable_format_error)); case FDRState::Token::WALLCLOCK_RECORD: return make_error( "Malformed log. Received Function Record when expecting wallclock.", std::make_error_code(std::errc::executable_format_error)); case FDRState::Token::NEW_CPU_ID_RECORD: return make_error( "Malformed log. Received Function Record before first CPU record.", std::make_error_code(std::errc::executable_format_error)); default: Records.emplace_back(); auto &Record = Records.back(); Record.RecordType = 0; // Record is type NORMAL. // Strip off record type bit and use the next three bits. uint8_t RecordType = (RecordFirstByte >> 1) & 0x07; switch (RecordType) { case static_cast(RecordTypes::ENTER): Record.Type = RecordTypes::ENTER; break; case static_cast(RecordTypes::EXIT): Record.Type = RecordTypes::EXIT; break; case static_cast(RecordTypes::TAIL_EXIT): Record.Type = RecordTypes::TAIL_EXIT; break; default: // Cast to an unsigned integer to not interpret the record type as a char. return make_error( Twine("Illegal function record type: ") .concat(Twine(static_cast(RecordType))), std::make_error_code(std::errc::executable_format_error)); } Record.CPU = State.CPUId; Record.TId = State.ThreadId; // Back up to read first 32 bits, including the 4 we pulled RecordType // and RecordKind out of. The remaining 28 are FunctionId. uint32_t OffsetPtr = 0; // Despite function Id being a signed int on XRayRecord, // when it is written to an FDR format, the top bits are truncated, // so it is effectively an unsigned value. When we shift off the // top four bits, we want the shift to be logical, so we read as // uint32_t. uint32_t FuncIdBitField = RecordExtractor.getU32(&OffsetPtr); Record.FuncId = FuncIdBitField >> 4; // FunctionRecords have a 32 bit delta from the previous absolute TSC // or TSC delta. If this would overflow, we should read a TSCWrap record // with an absolute TSC reading. uint64_t NewTSC = State.BaseTSC + RecordExtractor.getU32(&OffsetPtr); State.BaseTSC = NewTSC; Record.TSC = NewTSC; } return Error::success(); } /// Reads a log in FDR mode for version 1 of this binary format. FDR mode is /// defined as part of the compiler-rt project in xray_fdr_logging.h, and such /// a log consists of the familiar 32 bit XRayHeader, followed by sequences of /// of interspersed 16 byte Metadata Records and 8 byte Function Records. /// /// The following is an attempt to document the grammar of the format, which is /// parsed by this function for little-endian machines. Since the format makes /// use of BitFields, when we support big-endian architectures, we will need to /// adjust not only the endianness parameter to llvm's RecordExtractor, but also /// the bit twiddling logic, which is consistent with the little-endian /// convention that BitFields within a struct will first be packed into the /// least significant bits the address they belong to. /// /// We expect a format complying with the grammar in the following pseudo-EBNF. /// /// FDRLog: XRayFileHeader ThreadBuffer* /// XRayFileHeader: 32 bytes to identify the log as FDR with machine metadata. /// Includes BufferSize /// ThreadBuffer: NewBuffer WallClockTime NewCPUId FunctionSequence EOB /// BufSize: 8 byte unsigned integer indicating how large the buffer is. /// NewBuffer: 16 byte metadata record with Thread Id. /// WallClockTime: 16 byte metadata record with human readable time. /// NewCPUId: 16 byte metadata record with CPUId and a 64 bit TSC reading. /// EOB: 16 byte record in a thread buffer plus mem garbage to fill BufSize. /// FunctionSequence: NewCPUId | TSCWrap | FunctionRecord /// TSCWrap: 16 byte metadata record with a full 64 bit TSC reading. /// FunctionRecord: 8 byte record with FunctionId, entry/exit, and TSC delta. Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader, std::vector &Records) { if (Data.size() < 32) return make_error( "Not enough bytes for an XRay log.", std::make_error_code(std::errc::invalid_argument)); // For an FDR log, there are records sized 16 and 8 bytes. // There actually may be no records if no non-trivial functions are // instrumented. if (Data.size() % 8 != 0) return make_error( "Invalid-sized XRay data.", std::make_error_code(std::errc::invalid_argument)); if (auto E = readBinaryFormatHeader(Data, FileHeader)) return E; uint64_t BufferSize = 0; { StringRef ExtraDataRef(FileHeader.FreeFormData, 16); DataExtractor ExtraDataExtractor(ExtraDataRef, true, 8); uint32_t ExtraDataOffset = 0; BufferSize = ExtraDataExtractor.getU64(&ExtraDataOffset); } FDRState State{0, 0, 0, FDRState::Token::NEW_BUFFER_RECORD_OR_EOF, BufferSize, 0}; // RecordSize will tell the loop how far to seek ahead based on the record // type that we have just read. size_t RecordSize = 0; for (auto S = Data.drop_front(32); !S.empty(); S = S.drop_front(RecordSize)) { DataExtractor RecordExtractor(S, true, 8); uint32_t OffsetPtr = 0; if (State.Expects == FDRState::Token::SCAN_TO_END_OF_THREAD_BUF) { RecordSize = State.CurrentBufferSize - State.CurrentBufferConsumed; if (S.size() < RecordSize) { return make_error( Twine("Incomplete thread buffer. Expected at least ") + Twine(RecordSize) + " bytes but found " + Twine(S.size()), make_error_code(std::errc::invalid_argument)); } State.CurrentBufferConsumed = 0; State.Expects = FDRState::Token::NEW_BUFFER_RECORD_OR_EOF; continue; } uint8_t BitField = RecordExtractor.getU8(&OffsetPtr); bool isMetadataRecord = BitField & 0x01uL; if (isMetadataRecord) { RecordSize = 16; if (auto E = processFDRMetadataRecord(State, BitField, RecordExtractor, - RecordSize)) + RecordSize, Records)) return E; } else { // Process Function Record RecordSize = 8; if (auto E = processFDRFunctionRecord(State, BitField, RecordExtractor, Records)) return E; } State.CurrentBufferConsumed += RecordSize; } // Having iterated over everything we've been given, we've either consumed // everything and ended up in the end state, or were told to skip the rest. bool Finished = State.Expects == FDRState::Token::SCAN_TO_END_OF_THREAD_BUF && State.CurrentBufferSize == State.CurrentBufferConsumed; if (State.Expects != FDRState::Token::NEW_BUFFER_RECORD_OR_EOF && !Finished) return make_error( Twine("Encountered EOF with unexpected state expectation ") + fdrStateToTwine(State.Expects) + ". Remaining expected bytes in thread buffer total " + Twine(State.CurrentBufferSize - State.CurrentBufferConsumed), std::make_error_code(std::errc::executable_format_error)); return Error::success(); } Error loadYAMLLog(StringRef Data, XRayFileHeader &FileHeader, std::vector &Records) { YAMLXRayTrace Trace; Input In(Data); In >> Trace; if (In.error()) return make_error("Failed loading YAML Data.", In.error()); FileHeader.Version = Trace.Header.Version; FileHeader.Type = Trace.Header.Type; FileHeader.ConstantTSC = Trace.Header.ConstantTSC; FileHeader.NonstopTSC = Trace.Header.NonstopTSC; FileHeader.CycleFrequency = Trace.Header.CycleFrequency; if (FileHeader.Version != 1) return make_error( Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version), std::make_error_code(std::errc::invalid_argument)); Records.clear(); std::transform(Trace.Records.begin(), Trace.Records.end(), std::back_inserter(Records), [&](const YAMLXRayRecord &R) { return XRayRecord{R.RecordType, R.CPU, R.Type, R.FuncId, R.TSC, R.TId}; }); return Error::success(); } } // namespace Expected llvm::xray::loadTraceFile(StringRef Filename, bool Sort) { int Fd; if (auto EC = sys::fs::openFileForRead(Filename, Fd)) { return make_error( Twine("Cannot read log from '") + Filename + "'", EC); } uint64_t FileSize; if (auto EC = sys::fs::file_size(Filename, FileSize)) { return make_error( Twine("Cannot read log from '") + Filename + "'", EC); } if (FileSize < 4) { return make_error( Twine("File '") + Filename + "' too small for XRay.", std::make_error_code(std::errc::executable_format_error)); } // Map the opened file into memory and use a StringRef to access it later. 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( Twine("Cannot read log from '") + Filename + "'", EC); } auto Data = StringRef(MappedFile.data(), MappedFile.size()); // Attempt to detect the file type using file magic. We have a slight bias // towards the binary format, and we do this by making sure that the first 4 // bytes of the binary file is some combination of the following byte // patterns: (observe the code loading them assumes they're little endian) // // 0x01 0x00 0x00 0x00 - version 1, "naive" format // 0x01 0x00 0x01 0x00 - version 1, "flight data recorder" format // // YAML files don't typically have those first four bytes as valid text so we // try loading assuming YAML if we don't find these bytes. // // Only if we can't load either the binary or the YAML format will we yield an // error. StringRef Magic(MappedFile.data(), 4); DataExtractor HeaderExtractor(Magic, true, 8); uint32_t OffsetPtr = 0; uint16_t Version = HeaderExtractor.getU16(&OffsetPtr); uint16_t Type = HeaderExtractor.getU16(&OffsetPtr); enum BinaryFormatType { NAIVE_FORMAT = 0, FLIGHT_DATA_RECORDER_FORMAT = 1 }; Trace T; if (Type == NAIVE_FORMAT && (Version == 1 || Version == 2)) { if (auto E = loadNaiveFormatLog(Data, T.FileHeader, T.Records)) return std::move(E); } else if (Version == 1 && Type == FLIGHT_DATA_RECORDER_FORMAT) { if (auto E = loadFDRLog(Data, T.FileHeader, T.Records)) return std::move(E); } else { if (auto E = loadYAMLLog(Data, T.FileHeader, T.Records)) return std::move(E); } if (Sort) std::sort(T.Records.begin(), T.Records.end(), [&](const XRayRecord &L, const XRayRecord &R) { return L.TSC < R.TSC; }); return std::move(T); } Index: llvm/trunk/test/tools/llvm-xray/X86/Inputs/fdr-log-arg1.xray =================================================================== Binary files llvm/trunk/test/tools/llvm-xray/X86/Inputs/fdr-log-arg1.xray (revision 0) and llvm/trunk/test/tools/llvm-xray/X86/Inputs/fdr-log-arg1.xray (revision 314269) differ Index: llvm/trunk/test/tools/llvm-xray/X86/convert-fdr-arg1-to-yaml.txt =================================================================== --- llvm/trunk/test/tools/llvm-xray/X86/convert-fdr-arg1-to-yaml.txt (revision 0) +++ llvm/trunk/test/tools/llvm-xray/X86/convert-fdr-arg1-to-yaml.txt (revision 314269) @@ -0,0 +1,13 @@ +; RUN: llvm-xray convert %S/Inputs/fdr-log-arg1.xray -f=yaml -o - | FileCheck %s + +; CHECK: --- +; CHECK-NEXT: header: +; CHECK-NEXT: version: 1 +; CHECK-NEXT: type: 1 +; CHECK-NEXT: constant-tsc: true +; CHECK-NEXT: nonstop-tsc: true +; CHECK-NEXT: cycle-frequency: 3500000000 +; CHECK-NEXT: records: +; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', args: [ 1 ], cpu: 49, thread: 14648, kind: function-enter-arg, tsc: 18828908666543318 } +; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 49, thread: 14648, kind: function-exit, tsc: 18828908666595604 } +; CHECK-NEXT: ... Index: llvm/trunk/include/llvm/XRay/XRayRecord.h =================================================================== --- llvm/trunk/include/llvm/XRay/XRayRecord.h (revision 314268) +++ llvm/trunk/include/llvm/XRay/XRayRecord.h (revision 314269) @@ -1,81 +1,84 @@ //===- XRayRecord.h - XRay Trace Record -----------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file replicates the record definition for XRay log entries. This should // follow the evolution of the log record versions supported in the compiler-rt // xray project. // //===----------------------------------------------------------------------===// #ifndef LLVM_XRAY_XRAY_RECORD_H #define LLVM_XRAY_XRAY_RECORD_H #include namespace llvm { namespace xray { /// XRay traces all have a header providing some top-matter information useful /// to help tools determine how to interpret the information available in the /// trace. struct XRayFileHeader { /// Version of the XRay implementation that produced this file. uint16_t Version = 0; /// A numeric identifier for the type of file this is. Best used in /// combination with Version. uint16_t Type = 0; /// Whether the CPU that produced the timestamp counters (TSC) move at a /// constant rate. bool ConstantTSC; /// Whether the CPU that produced the timestamp counters (TSC) do not stop. bool NonstopTSC; /// The number of cycles per second for the CPU that produced the timestamp /// counter (TSC) values. Useful for estimating the amount of time that /// elapsed between two TSCs on some platforms. uint64_t CycleFrequency = 0; // This is different depending on the type of xray record. The naive format // stores a Wallclock timespec. FDR logging stores the size of a thread // buffer. char FreeFormData[16]; }; /// Determines the supported types of records that could be seen in XRay traces. /// This may or may not correspond to actual record types in the raw trace (as /// the loader implementation may synthesize this information in the process of /// of loading). -enum class RecordTypes { ENTER, EXIT, TAIL_EXIT }; +enum class RecordTypes { ENTER, EXIT, TAIL_EXIT, ENTER_ARG }; struct XRayRecord { /// The type of record. uint16_t RecordType; /// The CPU where the thread is running. We assume number of CPUs <= 65536. uint16_t CPU; /// Identifies the type of record. RecordTypes Type; /// The function ID for the record. int32_t FuncId; /// Get the full 8 bytes of the TSC when we get the log record. uint64_t TSC; /// The thread ID for the currently running thread. uint32_t TId; + + /// The function call arguments. + std::vector CallArgs; }; } // namespace xray } // namespace llvm #endif // LLVM_XRAY_XRAY_RECORD_H Index: llvm/trunk/include/llvm/XRay/YAMLXRayRecord.h =================================================================== --- llvm/trunk/include/llvm/XRay/YAMLXRayRecord.h (revision 314268) +++ llvm/trunk/include/llvm/XRay/YAMLXRayRecord.h (revision 314269) @@ -1,100 +1,113 @@ //===- YAMLXRayRecord.h - XRay Record YAML Support Definitions ------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // Types and traits specialisations for YAML I/O of XRay log entries. // //===----------------------------------------------------------------------===// #ifndef LLVM_XRAY_YAML_XRAY_RECORD_H #define LLVM_XRAY_YAML_XRAY_RECORD_H #include #include "llvm/Support/YAMLTraits.h" #include "llvm/XRay/XRayRecord.h" namespace llvm { namespace xray { struct YAMLXRayFileHeader { uint16_t Version; uint16_t Type; bool ConstantTSC; bool NonstopTSC; uint64_t CycleFrequency; }; struct YAMLXRayRecord { uint16_t RecordType; uint16_t CPU; RecordTypes Type; int32_t FuncId; std::string Function; uint64_t TSC; uint32_t TId; + std::vector CallArgs; }; struct YAMLXRayTrace { YAMLXRayFileHeader Header; std::vector Records; }; } // namespace xray namespace yaml { // YAML Traits // ----------- template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, xray::RecordTypes &Type) { IO.enumCase(Type, "function-enter", xray::RecordTypes::ENTER); IO.enumCase(Type, "function-exit", xray::RecordTypes::EXIT); IO.enumCase(Type, "function-tail-exit", xray::RecordTypes::TAIL_EXIT); + IO.enumCase(Type, "function-enter-arg", xray::RecordTypes::ENTER_ARG); } }; template <> struct MappingTraits { static void mapping(IO &IO, xray::YAMLXRayFileHeader &Header) { IO.mapRequired("version", Header.Version); IO.mapRequired("type", Header.Type); IO.mapRequired("constant-tsc", Header.ConstantTSC); IO.mapRequired("nonstop-tsc", Header.NonstopTSC); IO.mapRequired("cycle-frequency", Header.CycleFrequency); } }; template <> struct MappingTraits { static void mapping(IO &IO, xray::YAMLXRayRecord &Record) { // FIXME: Make this type actually be descriptive IO.mapRequired("type", Record.RecordType); IO.mapRequired("func-id", Record.FuncId); IO.mapOptional("function", Record.Function); + IO.mapOptional("args", Record.CallArgs); IO.mapRequired("cpu", Record.CPU); IO.mapRequired("thread", Record.TId); IO.mapRequired("kind", Record.Type); IO.mapRequired("tsc", Record.TSC); } static constexpr bool flow = true; }; +template <> struct SequenceTraits> { + static constexpr bool flow = true; + static size_t size(IO &IO, std::vector &V) { return V.size(); } + static uint64_t &element(IO &IO, std::vector &V, size_t Index) { + if (Index >= V.size()) + V.resize(Index + 1); + return V[Index]; + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, xray::YAMLXRayTrace &Trace) { // A trace file contains two parts, the header and the list of all the // trace records. IO.mapRequired("header", Trace.Header); IO.mapRequired("records", Trace.Records); } }; } // namespace yaml } // namespace llvm LLVM_YAML_IS_SEQUENCE_VECTOR(xray::YAMLXRayRecord) #endif // LLVM_XRAY_YAML_XRAY_RECORD_H