1 | //===-- Application to analyze benchmark JSON files -----------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "automemcpy/ResultAnalyzer.h" |
10 | #include "llvm/ADT/StringMap.h" |
11 | #include "llvm/ADT/StringSet.h" |
12 | #include "llvm/Support/CommandLine.h" |
13 | #include "llvm/Support/Error.h" |
14 | #include "llvm/Support/JSON.h" |
15 | #include "llvm/Support/MemoryBuffer.h" |
16 | |
17 | namespace llvm { |
18 | |
19 | // User can specify one or more json filenames to process on the command line. |
20 | static cl::list<std::string> InputFilenames(cl::Positional, cl::OneOrMore, |
21 | cl::desc("<input json files>" )); |
22 | |
23 | // User can filter the distributions to be taken into account. |
24 | static cl::list<std::string> |
25 | KeepOnlyDistributions("keep-only-distributions" , |
26 | cl::desc("<comma separated list of distribution " |
27 | "names, keeps all if unspecified>" )); |
28 | |
29 | namespace automemcpy { |
30 | |
31 | // This is defined in the autogenerated 'Implementations.cpp' file. |
32 | extern ArrayRef<NamedFunctionDescriptor> getFunctionDescriptors(); |
33 | |
34 | // Iterates over all functions and fills a map of function name to function |
35 | // descriptor pointers. |
36 | static StringMap<const FunctionDescriptor *> createFunctionDescriptorMap() { |
37 | StringMap<const FunctionDescriptor *> Descriptors; |
38 | for (const NamedFunctionDescriptor &FD : getFunctionDescriptors()) |
39 | Descriptors.insert_or_assign(FD.Name, &FD.Desc); |
40 | return Descriptors; |
41 | } |
42 | |
43 | // Retrieves the function descriptor for a particular function name. |
44 | static const FunctionDescriptor &getFunctionDescriptor(StringRef FunctionName) { |
45 | static StringMap<const FunctionDescriptor *> Descriptors = |
46 | createFunctionDescriptorMap(); |
47 | const auto *FD = Descriptors.lookup(FunctionName); |
48 | if (!FD) |
49 | report_fatal_error( |
50 | reason: Twine("No FunctionDescriptor for " ).concat(Suffix: FunctionName)); |
51 | return *FD; |
52 | } |
53 | |
54 | // Functions and distributions names are stored quite a few times so it's more |
55 | // efficient to internalize these strings and refer to them through 'StringRef'. |
56 | static StringRef getInternalizedString(StringRef VolatileStr) { |
57 | static llvm::StringSet StringCache; |
58 | return StringCache.insert(key: VolatileStr).first->getKey(); |
59 | } |
60 | |
61 | // Helper function for the LLVM JSON API. |
62 | bool fromJSON(const json::Value &V, Sample &Out, json::Path P) { |
63 | std::string Label; |
64 | std::string RunType; |
65 | json::ObjectMapper O(V, P); |
66 | if (O && O.map("bytes_per_second" , Out.BytesPerSecond) && |
67 | O.map(Prop: "run_type" , Out&: RunType) && O.map(Prop: "label" , Out&: Label)) { |
68 | const auto LabelPair = StringRef(Label).split(Separator: ','); |
69 | Out.Id.Function.Name = getInternalizedString(VolatileStr: LabelPair.first); |
70 | Out.Id.Function.Type = getFunctionDescriptor(LabelPair.first).Type; |
71 | Out.Id.Distribution.Name = getInternalizedString(VolatileStr: LabelPair.second); |
72 | Out.Type = StringSwitch<SampleType>(RunType) |
73 | .Case("aggregate" , SampleType::AGGREGATE) |
74 | .Case("iteration" , SampleType::ITERATION); |
75 | return true; |
76 | } |
77 | return false; |
78 | } |
79 | |
80 | // An object to represent the content of the JSON file. |
81 | // This is easier to parse/serialize JSON when the structures of the json file |
82 | // maps the structure of the object. |
83 | struct JsonFile { |
84 | std::vector<Sample> Samples; |
85 | }; |
86 | |
87 | // Helper function for the LLVM JSON API. |
88 | bool fromJSON(const json::Value &V, JsonFile &JF, json::Path P) { |
89 | json::ObjectMapper O(V, P); |
90 | return O && O.map("benchmarks" , JF.Samples); |
91 | } |
92 | |
93 | // Global object to ease error reporting, it consumes errors and crash the |
94 | // application with a meaningful message. |
95 | static ExitOnError ExitOnErr; |
96 | |
97 | // Main JSON parsing method. Reads the content of the file pointed to by |
98 | // 'Filename' and returns a JsonFile object. |
99 | JsonFile parseJsonResultFile(StringRef Filename) { |
100 | auto Buf = ExitOnErr(errorOrToExpected( |
101 | EO: MemoryBuffer::getFile(Filename, /*bool IsText=*/IsText: true, |
102 | /*RequiresNullTerminator=*/false))); |
103 | auto JsonValue = ExitOnErr(json::parse(JSON: Buf->getBuffer())); |
104 | json::Path::Root Root; |
105 | JsonFile JF; |
106 | if (!fromJSON(V: JsonValue, JF, P: Root)) |
107 | ExitOnErr(Root.getError()); |
108 | return JF; |
109 | } |
110 | |
111 | // Serializes the 'GradeHisto' to the provided 'Stream'. |
112 | static void Serialize(raw_ostream &Stream, const GradeHistogram &GH) { |
113 | static constexpr std::array<StringRef, 9> kCharacters = { |
114 | " " , "▁" , "▂" , "▃" , "▄" , "▅" , "▆" , "▇" , "█" }; |
115 | |
116 | const size_t Max = *std::max_element(GH.begin(), GH.end()); |
117 | for (size_t I = 0; I < GH.size(); ++I) { |
118 | size_t Index = (float(GH[I]) / Max) * (kCharacters.size() - 1); |
119 | Stream << kCharacters.at(n: Index); |
120 | } |
121 | } |
122 | |
123 | int Main(int argc, char **argv) { |
124 | ExitOnErr.setBanner("Automemcpy Json Results Analyzer stopped with error: " ); |
125 | cl::ParseCommandLineOptions(argc, argv, Overview: "Automemcpy Json Results Analyzer\n" ); |
126 | |
127 | // Reads all samples stored in the input JSON files. |
128 | std::vector<Sample> Samples; |
129 | for (const auto &Filename : InputFilenames) { |
130 | auto Result = parseJsonResultFile(Filename); |
131 | llvm::append_range(Samples, Result.Samples); |
132 | } |
133 | |
134 | if (!KeepOnlyDistributions.empty()) { |
135 | llvm::StringSet ValidDistributions; |
136 | ValidDistributions.insert(begin: KeepOnlyDistributions.begin(), |
137 | end: KeepOnlyDistributions.end()); |
138 | llvm::erase_if(Samples, [&ValidDistributions](const Sample &S) { |
139 | return !ValidDistributions.contains(S.Id.Distribution.Name); |
140 | }); |
141 | } |
142 | |
143 | // Extracts median of throughputs. |
144 | std::vector<FunctionData> Functions = getThroughputs(Samples); |
145 | fillScores(Functions); |
146 | castVotes(Functions); |
147 | |
148 | // Present data by function type, Grade and Geomean of scores. |
149 | std::sort(Functions.begin(), Functions.end(), |
150 | [](const FunctionData &A, const FunctionData &B) { |
151 | const auto Less = [](const FunctionData &FD) { |
152 | return std::make_tuple(FD.Id.Type, FD.FinalGrade, |
153 | -FD.ScoresGeoMean); |
154 | }; |
155 | return Less(A) < Less(B); |
156 | }); |
157 | |
158 | // Print result. |
159 | for (const FunctionData &Function : Functions) { |
160 | outs() << formatv("{0,-10}" , Grade::getString(Function.FinalGrade)); |
161 | outs() << " |" ; |
162 | Serialize(outs(), Function.GradeHisto); |
163 | outs() << "| " ; |
164 | outs().resetColor(); |
165 | outs() << formatv("{0,+25}" , Function.Id.Name); |
166 | outs() << "\n" ; |
167 | } |
168 | |
169 | return EXIT_SUCCESS; |
170 | } |
171 | |
172 | } // namespace automemcpy |
173 | } // namespace llvm |
174 | |
175 | int main(int argc, char **argv) { return llvm::automemcpy::Main(argc, argv); } |
176 | |