| //===-- Application to analyze benchmark JSON files -----------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "automemcpy/ResultAnalyzer.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/JSON.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| |
| namespace llvm { |
| |
| // User can specify one or more json filenames to process on the command line. |
| static cl::list<std::string> InputFilenames(cl::Positional, cl::OneOrMore, |
| cl::desc("<input json files>")); |
| |
| namespace automemcpy { |
| |
| // This is defined in the autogenerated 'Implementations.cpp' file. |
| extern ArrayRef<NamedFunctionDescriptor> getFunctionDescriptors(); |
| |
| // Iterates over all functions and fills a map of function name to function |
| // descriptor pointers. |
| static StringMap<const FunctionDescriptor *> createFunctionDescriptorMap() { |
| StringMap<const FunctionDescriptor *> Descriptors; |
| for (const NamedFunctionDescriptor &FD : getFunctionDescriptors()) |
| Descriptors.insert_or_assign(FD.Name, &FD.Desc); |
| return Descriptors; |
| } |
| |
| // Retrieves the function descriptor for a particular function name. |
| static const FunctionDescriptor &getFunctionDescriptor(StringRef FunctionName) { |
| static StringMap<const FunctionDescriptor *> Descriptors = |
| createFunctionDescriptorMap(); |
| const auto *FD = Descriptors.lookup(FunctionName); |
| if (!FD) |
| report_fatal_error( |
| Twine("No FunctionDescriptor for ").concat(FunctionName)); |
| return *FD; |
| } |
| |
| // Functions and distributions names are stored quite a few times so it's more |
| // efficient to internalize these strings and refer to them through 'StringRef'. |
| static StringRef getInternalizedString(StringRef VolatileStr) { |
| static llvm::StringSet<> StringCache; |
| return StringCache.insert(VolatileStr).first->getKey(); |
| } |
| |
| // Helper function for the LLVM JSON API. |
| bool fromJSON(const json::Value &V, Sample &Out, json::Path P) { |
| std::string Label; |
| json::ObjectMapper O(V, P); |
| if (O && O.map("bytes_per_second", Out.BytesPerSecond) && |
| O.map("label", Label)) { |
| const auto LabelPair = StringRef(Label).split(','); |
| Out.Id.Function.Name = getInternalizedString(LabelPair.first); |
| Out.Id.Function.Type = getFunctionDescriptor(LabelPair.first).Type; |
| Out.Id.Distribution.Name = getInternalizedString(LabelPair.second); |
| return true; |
| } |
| return false; |
| } |
| |
| // An object to represent the content of the JSON file. |
| // This is easier to parse/serialize JSON when the structures of the json file |
| // maps the structure of the object. |
| struct JsonFile { |
| std::vector<Sample> Samples; |
| }; |
| |
| // Helper function for the LLVM JSON API. |
| bool fromJSON(const json::Value &V, JsonFile &JF, json::Path P) { |
| json::ObjectMapper O(V, P); |
| return O && O.map("benchmarks", JF.Samples); |
| } |
| |
| // Global object to ease error reporting, it consumes errors and crash the |
| // application with a meaningful message. |
| static ExitOnError ExitOnErr; |
| |
| // Main JSON parsing method. Reads the content of the file pointed to by |
| // 'Filename' and returns a JsonFile object. |
| JsonFile parseJsonResultFile(StringRef Filename) { |
| auto Buf = ExitOnErr(errorOrToExpected( |
| MemoryBuffer::getFile(Filename, /*bool IsText=*/true, |
| /*RequiresNullTerminator=*/false))); |
| auto JsonValue = ExitOnErr(json::parse(Buf->getBuffer())); |
| json::Path::Root Root; |
| JsonFile JF; |
| if (!fromJSON(JsonValue, JF, Root)) |
| ExitOnErr(Root.getError()); |
| return JF; |
| } |
| |
| // Serializes the 'GradeHisto' to the provided 'Stream'. |
| static void Serialize(raw_ostream &Stream, const GradeHistogram &GH) { |
| static constexpr std::array<StringRef, 9> kCharacters = { |
| " ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; |
| |
| const size_t Max = *std::max_element(GH.begin(), GH.end()); |
| for (size_t I = 0; I < GH.size(); ++I) { |
| size_t Index = (float(GH[I]) / Max) * (kCharacters.size() - 1); |
| Stream << kCharacters.at(Index); |
| } |
| } |
| |
| int Main(int argc, char **argv) { |
| ExitOnErr.setBanner("Automemcpy Json Results Analyzer stopped with error: "); |
| cl::ParseCommandLineOptions(argc, argv, "Automemcpy Json Results Analyzer\n"); |
| |
| // Reads all samples stored in the input JSON files. |
| std::vector<Sample> Samples; |
| for (const auto &Filename : InputFilenames) { |
| auto Result = parseJsonResultFile(Filename); |
| llvm::append_range(Samples, Result.Samples); |
| } |
| |
| // Extracts median of throughputs. |
| std::vector<FunctionData> Functions = getThroughputs(Samples); |
| fillScores(Functions); |
| castVotes(Functions); |
| |
| // TODO: Implement tie breaking algorithm. |
| std::sort(Functions.begin(), Functions.end(), |
| [](const FunctionData &A, const FunctionData &B) { |
| return A.FinalGrade < B.FinalGrade; |
| }); |
| |
| // Present data by function type. |
| std::stable_sort(Functions.begin(), Functions.end(), |
| [](const FunctionData &A, const FunctionData &B) { |
| return A.Id.Type < B.Id.Type; |
| }); |
| |
| // Print result. |
| for (const FunctionData &Function : Functions) { |
| outs() << formatv("{0,-10}", Grade::getString(Function.FinalGrade)); |
| outs() << " |"; |
| Serialize(outs(), Function.GradeHisto); |
| outs() << "| "; |
| outs().resetColor(); |
| outs() << formatv("{0,+25}", Function.Id.Name); |
| outs() << "\n"; |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| } // namespace automemcpy |
| } // namespace llvm |
| |
| int main(int argc, char **argv) { return llvm::automemcpy::Main(argc, argv); } |