diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h --- a/llvm/include/llvm/ProfileData/SampleProf.h +++ b/llvm/include/llvm/ProfileData/SampleProf.h @@ -92,6 +92,7 @@ SPF_Compact_Binary = 0x2, SPF_GCC = 0x3, SPF_Ext_Binary = 0x4, + SPF_JSON = 0x5, // Output only SPF_Binary = 0xff }; diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h --- a/llvm/include/llvm/ProfileData/SampleProfWriter.h +++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h @@ -17,6 +17,7 @@ #include "llvm/IR/ProfileSummary.h" #include "llvm/ProfileData/SampleProf.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -76,6 +77,11 @@ /// Write a file header for the profile file. virtual std::error_code writeHeader(const SampleProfileMap &ProfileMap) = 0; + /// Write a file footer for the profile file. + virtual std::error_code writeFooter(const SampleProfileMap &ProfileMap) { + return sampleprof_error::success; + } + // Write function profiles to the profile file. virtual std::error_code writeFuncProfiles(const SampleProfileMap &ProfileMap); @@ -116,6 +122,36 @@ SampleProfileFormat Format); }; +/// Sample-based profile writer (JSON format). +class SampleProfileWriterJSON : public SampleProfileWriter { +public: + std::error_code writeSample(const FunctionSamples &S) override; + +protected: + SampleProfileWriterJSON(std::unique_ptr &OS) + : SampleProfileWriter(OS), + JOS(std::make_unique(*OutputStream, 2)) {} + + std::error_code writeHeader(const SampleProfileMap &ProfileMap) override { + JOS->arrayBegin(); + return sampleprof_error::success; + } + + std::error_code writeFooter(const SampleProfileMap &ProfileMap) override { + JOS->arrayEnd(); + return sampleprof_error::success; + } + +private: + std::unique_ptr JOS; + + void writeSampleImpl(const FunctionSamples &S, bool TopLevel = false); + + friend ErrorOr> + SampleProfileWriter::create(std::unique_ptr &OS, + SampleProfileFormat Format); +}; + /// Sample-based profile writer (binary format). class SampleProfileWriterBinary : public SampleProfileWriter { public: diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp --- a/llvm/lib/ProfileData/SampleProfWriter.cpp +++ b/llvm/lib/ProfileData/SampleProfWriter.cpp @@ -58,6 +58,9 @@ if (std::error_code EC = writeFuncProfiles(ProfileMap)) return EC; + if (std::error_code EC = writeFooter(ProfileMap)) + return EC; + return sampleprof_error::success; } @@ -525,6 +528,70 @@ return sampleprof_error::success; } +void SampleProfileWriterJSON::writeSampleImpl(const FunctionSamples &S, + bool TopLevel) { + JOS->object([&] { + JOS->attribute("name", S.getName()); + JOS->attribute("total", S.getTotalSamples()); + if (TopLevel) + JOS->attribute("head", S.getHeadSamples()); + + SampleSorter SortedSamples( + S.getBodySamples()); + if (!SortedSamples.get().empty()) { + JOS->attributeArray("body", [&] { + for (const auto &I : SortedSamples.get()) { + 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); + }); + } + }); + } + }); + } + }); + } + + SampleSorter SortedCallsiteSamples( + S.getCallsiteSamples()); + auto CallsiteSamples = SortedCallsiteSamples.get(); + if (!CallsiteSamples.empty()) { + JOS->attributeArray("callsites", [&] { + for (const auto &I : CallsiteSamples) + for (const auto &FS : I->second) { + 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", + [&] { writeSampleImpl(CalleeSamples); }); + }); + } + }); + } + }); +} + +std::error_code SampleProfileWriterJSON::writeSample(const FunctionSamples &S) { + writeSampleImpl(S, true); + return sampleprof_error::success; +} + std::error_code SampleProfileWriterBinary::writeContextIdx(const SampleContext &Context) { assert(!Context.hasContext() && "cs profile is not supported"); @@ -881,6 +948,8 @@ Writer.reset(new SampleProfileWriterCompactBinary(OS)); else if (Format == SPF_Text) Writer.reset(new SampleProfileWriterText(OS)); + else if (Format == SPF_JSON) + Writer.reset(new SampleProfileWriterJSON(OS)); else if (Format == SPF_GCC) EC = sampleprof_error::unsupported_writing_format; else 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 merge --sample --json %p/Inputs/sample-profile.proftext | FileCheck %s --check-prefix=JSON +JSON: [ +JSON: { +JSON: "name": "main", +JSON: "total": 184019, +JSON: "head": 0, +JSON: "body": [ +JSON: { +JSON: "line": 4, +JSON: "samples": 534 +JSON: }, +JSON: { +JSON: "line": 4, +JSON: "discriminator": 2, +JSON: "samples": 534 +JSON: }, +JSON: { +JSON: "line": 5, +JSON: "samples": 1075 +JSON: }, +JSON: { +JSON: "line": 5, +JSON: "discriminator": 1, +JSON: "samples": 1075 +JSON: }, +JSON: { +JSON: "line": 6, +JSON: "samples": 2080 +JSON: }, +JSON: { +JSON: "line": 7, +JSON: "samples": 534 +JSON: }, +JSON: { +JSON: "line": 9, +JSON: "samples": 2064, +JSON: "calls": [ +JSON: { +JSON: "function": "_Z3bari", +JSON: "samples": 1471 +JSON: }, +JSON: { +JSON: "function": "_Z3fooi", +JSON: "samples": 631 +JSON: } +JSON: ] +JSON: } +JSON: ], +JSON: "callsites": [ +JSON: { +JSON: "line": 10, +JSON: "samples": [ +JSON: { +JSON: "name": "inline1", +JSON: "total": 1000, +JSON: "body": [ +JSON: { +JSON: "line": 1, +JSON: "samples": 1000 +JSON: } +JSON: ] +JSON: } +JSON: ] +JSON: }, +JSON: { +JSON: "line": 10, +JSON: "samples": [ +JSON: { +JSON: "name": "inline2", +JSON: "total": 2000, +JSON: "body": [ +JSON: { +JSON: "line": 1, +JSON: "samples": 2000 +JSON: } +JSON: ] +JSON: } +JSON: ] +JSON: } +JSON: ] +JSON: }, +JSON: { +JSON: "name": "_Z3bari", +JSON: "total": 20301, +JSON: "head": 1437, +JSON: "body": [ +JSON: { +JSON: "line": 1, +JSON: "samples": 1437 +JSON: } +JSON: ] +JSON: }, +JSON: { +JSON: "name": "_Z3fooi", +JSON: "total": 7711, +JSON: "head": 610, +JSON: "body": [ +JSON: { +JSON: "line": 1, +JSON: "samples": 610 +JSON: } +JSON: ] +JSON: } +JSON: ] 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 @@ -45,6 +45,7 @@ enum ProfileFormat { PF_None = 0, PF_Text, + PF_JSON, PF_Compact_Binary, PF_Ext_Binary, PF_GCC, @@ -679,6 +680,7 @@ static sampleprof::SampleProfileFormat FormatMap[] = { sampleprof::SPF_None, sampleprof::SPF_Text, + sampleprof::SPF_JSON, sampleprof::SPF_Compact_Binary, sampleprof::SPF_Ext_Binary, sampleprof::SPF_GCC, @@ -943,6 +945,7 @@ "Compact binary encoding"), clEnumValN(PF_Ext_Binary, "extbinary", "Extensible binary encoding"), clEnumValN(PF_Text, "text", "Text encoding"), + clEnumValN(PF_JSON, "json", "JSON"), clEnumValN(PF_GCC, "gcc", "GCC encoding (only meaningful for -sample)"))); cl::opt FailureMode(