diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h --- a/llvm/include/llvm/ProfileData/SampleProfReader.h +++ b/llvm/include/llvm/ProfileData/SampleProfReader.h @@ -391,6 +391,9 @@ /// Print all the profiles on stream \p OS. void dump(raw_ostream &OS = dbgs()); + /// Print all the profiles on stream \p OS in the JSON format. + void dumpJson(raw_ostream &OS = dbgs()); + /// Return the samples collected for function \p F. FunctionSamples *getSamplesFor(const Function &F) { // The function name may have been updated by adding suffix. Call diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp --- a/llvm/lib/ProfileData/SampleProfReader.cpp +++ b/llvm/lib/ProfileData/SampleProfReader.cpp @@ -30,6 +30,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compression.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/LEB128.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MD5.h" @@ -72,6 +73,79 @@ dumpFunctionProfile(I.first, OS); } +static void dumpFunctionProfileJson(const FunctionSamples &S, + json::OStream &JOS, bool TopLevel = false) { + auto DumpBody = [&](const BodySampleMap &BodySamples) { + for (const auto &I : BodySamples) { + const LineLocation &Loc = I.first; + const SampleRecord &Sample = I.second; + JOS.object([&] { + JOS.attribute("line", Loc.LineOffset); + if (Loc.Discriminator) + JOS.attribute("discriminator", Loc.Discriminator); + JOS.attribute("samples", Sample.getSamples()); + + auto CallTargets = Sample.getSortedCallTargets(); + if (!CallTargets.empty()) { + JOS.attributeArray("calls", [&] { + for (const auto &J : CallTargets) { + JOS.object([&] { + JOS.attribute("function", J.first); + JOS.attribute("samples", J.second); + }); + } + }); + } + }); + } + }; + + auto DumpCallsiteSamples = [&](const CallsiteSampleMap &CallsiteSamples) { + for (const auto &I : CallsiteSamples) + for (const auto &FS : I.second) { + const LineLocation &Loc = I.first; + const FunctionSamples &CalleeSamples = FS.second; + JOS.object([&] { + JOS.attribute("line", Loc.LineOffset); + if (Loc.Discriminator) + JOS.attribute("discriminator", Loc.Discriminator); + JOS.attributeArray( + "samples", [&] { dumpFunctionProfileJson(CalleeSamples, JOS); }); + }); + } + }; + + JOS.object([&] { + JOS.attribute("name", S.getName()); + JOS.attribute("total", S.getTotalSamples()); + if (TopLevel) + JOS.attribute("head", S.getHeadSamples()); + + const auto &BodySamples = S.getBodySamples(); + if (!BodySamples.empty()) + JOS.attributeArray("body", [&] { DumpBody(BodySamples); }); + + const auto &CallsiteSamples = S.getCallsiteSamples(); + if (!CallsiteSamples.empty()) + JOS.attributeArray("callsites", + [&] { DumpCallsiteSamples(CallsiteSamples); }); + }); +} + +/// Dump all the function profiles found on stream \p OS in the JSON format. +void SampleProfileReader::dumpJson(raw_ostream &OS) { + std::vector V; + sortFuncProfiles(Profiles, V); + json::OStream JOS(OS, 2); + JOS.arrayBegin(); + for (const auto &[FC, FS] : V) + dumpFunctionProfileJson(*FS, JOS, true); + JOS.arrayEnd(); + + // Emit a newline character at the end as json::OStream doesn't emit one. + OS << "\n"; +} + /// Parse \p Input as function head. /// /// Parse one line of \p Input, and update function name in \p FName, diff --git a/llvm/test/tools/llvm-profdata/sample-profile-json.test b/llvm/test/tools/llvm-profdata/sample-profile-json.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-profdata/sample-profile-json.test @@ -0,0 +1,104 @@ +RUN: llvm-profdata show --sample --json %p/Inputs/sample-profile.proftext | FileCheck %s --check-prefix=JSON +JSON: [ +JSON-NEXT: { +JSON-NEXT: "name": "main", +JSON-NEXT: "total": 184019, +JSON-NEXT: "head": 0, +JSON-NEXT: "body": [ +JSON-NEXT: { +JSON-NEXT: "line": 4, +JSON-NEXT: "samples": 534 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 4, +JSON-NEXT: "discriminator": 2, +JSON-NEXT: "samples": 534 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 5, +JSON-NEXT: "samples": 1075 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 5, +JSON-NEXT: "discriminator": 1, +JSON-NEXT: "samples": 1075 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 6, +JSON-NEXT: "samples": 2080 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 7, +JSON-NEXT: "samples": 534 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 9, +JSON-NEXT: "samples": 2064, +JSON-NEXT: "calls": [ +JSON-NEXT: { +JSON-NEXT: "function": "_Z3bari", +JSON-NEXT: "samples": 1471 +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "function": "_Z3fooi", +JSON-NEXT: "samples": 631 +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: } +JSON-NEXT: ], +JSON-NEXT: "callsites": [ +JSON-NEXT: { +JSON-NEXT: "line": 10, +JSON-NEXT: "samples": [ +JSON-NEXT: { +JSON-NEXT: "name": "inline1", +JSON-NEXT: "total": 1000, +JSON-NEXT: "body": [ +JSON-NEXT: { +JSON-NEXT: "line": 1, +JSON-NEXT: "samples": 1000 +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "line": 10, +JSON-NEXT: "samples": [ +JSON-NEXT: { +JSON-NEXT: "name": "inline2", +JSON-NEXT: "total": 2000, +JSON-NEXT: "body": [ +JSON-NEXT: { +JSON-NEXT: "line": 1, +JSON-NEXT: "samples": 2000 +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "name": "_Z3bari", +JSON-NEXT: "total": 20301, +JSON-NEXT: "head": 1437, +JSON-NEXT: "body": [ +JSON-NEXT: { +JSON-NEXT: "line": 1, +JSON-NEXT: "samples": 1437 +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: }, +JSON-NEXT: { +JSON-NEXT: "name": "_Z3fooi", +JSON-NEXT: "total": 7711, +JSON-NEXT: "head": 610, +JSON-NEXT: "body": [ +JSON-NEXT: { +JSON-NEXT: "line": 1, +JSON-NEXT: "samples": 610 +JSON-NEXT: } +JSON-NEXT: ] +JSON-NEXT: } +JSON-NEXT: ] diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp --- a/llvm/tools/llvm-profdata/llvm-profdata.cpp +++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp @@ -2488,7 +2488,7 @@ const std::string &ShowFunction, bool ShowProfileSymbolList, bool ShowSectionInfoOnly, bool ShowHotFuncList, - raw_fd_ostream &OS) { + bool JsonFormat, raw_fd_ostream &OS) { using namespace sampleprof; LLVMContext Context; auto ReaderOrErr = @@ -2505,11 +2505,20 @@ if (std::error_code EC = Reader->read()) exitWithErrorCode(EC, Filename); - if (ShowAllFunctions || ShowFunction.empty()) - Reader->dump(OS); - else + if (ShowAllFunctions || ShowFunction.empty()) { + if (JsonFormat) + Reader->dumpJson(OS); + else + Reader->dump(OS); + } else { + if (JsonFormat) + exitWithError( + "the JSON format is supported only when all functions are to " + "be printed"); + // TODO: parse context string to support filtering by contexts. Reader->dumpFunctionProfile(StringRef(ShowFunction), OS); + } if (ShowProfileSymbolList) { std::unique_ptr ReaderList = @@ -2582,6 +2591,9 @@ cl::opt TextFormat( "text", cl::init(false), cl::desc("Show instr profile data in text dump format")); + cl::opt JsonFormat( + "json", cl::init(false), + cl::desc("Show sample profile data in the JSON format")); cl::opt ShowIndirectCallTargets( "ic-targets", cl::init(false), cl::desc("Show indirect call site target values for shown functions")); @@ -2679,10 +2691,10 @@ ShowAllFunctions, ShowCS, ValueCutoff, OnlyListBelow, ShowFunction, TextFormat, ShowBinaryIds, ShowCovered, OS); if (ProfileKind == sample) - return showSampleProfile(Filename, ShowCounts, TopNFunctions, - ShowAllFunctions, ShowDetailedSummary, - ShowFunction, ShowProfileSymbolList, - ShowSectionInfoOnly, ShowHotFuncList, OS); + return showSampleProfile( + Filename, ShowCounts, TopNFunctions, ShowAllFunctions, + ShowDetailedSummary, ShowFunction, ShowProfileSymbolList, + ShowSectionInfoOnly, ShowHotFuncList, JsonFormat, OS); return showMemProfProfile(Filename, ProfiledBinary, OS); }