1 | //===-- JSON serialization routines ---------------------------------------===// |
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 "JSON.h" |
10 | #include "LibcBenchmark.h" |
11 | #include "llvm/ADT/DenseSet.h" |
12 | #include "llvm/ADT/SmallVector.h" |
13 | #include "llvm/ADT/StringRef.h" |
14 | #include "llvm/ADT/StringSwitch.h" |
15 | #include "llvm/Support/Errc.h" |
16 | #include "llvm/Support/Error.h" |
17 | #include "llvm/Support/ErrorHandling.h" |
18 | #include "llvm/Support/JSON.h" |
19 | #include "llvm/Support/MathExtras.h" |
20 | |
21 | #include <chrono> |
22 | #include <limits> |
23 | #include <memory> |
24 | #include <optional> |
25 | #include <string> |
26 | #include <vector> |
27 | |
28 | namespace llvm { |
29 | namespace libc_benchmarks { |
30 | |
31 | template <typename T> |
32 | static Error intFromJsonTemplate(const json::Value &V, T &Out) { |
33 | if (const auto &MaybeInt64 = V.getAsInteger()) { |
34 | int64_t Value = *MaybeInt64; |
35 | if (Value < std::numeric_limits<T>::min() || |
36 | Value > std::numeric_limits<T>::max()) |
37 | return createStringError(EC: errc::io_error, Msg: "Out of bound Integer" ); |
38 | Out = Value; |
39 | return Error::success(); |
40 | } |
41 | return createStringError(EC: errc::io_error, Msg: "Can't parse Integer" ); |
42 | } |
43 | |
44 | static Error fromJson(const json::Value &V, bool &Out) { |
45 | if (auto B = V.getAsBoolean()) { |
46 | Out = *B; |
47 | return Error::success(); |
48 | } |
49 | return createStringError(EC: errc::io_error, Msg: "Can't parse Boolean" ); |
50 | } |
51 | |
52 | static Error fromJson(const json::Value &V, double &Out) { |
53 | if (auto S = V.getAsNumber()) { |
54 | Out = *S; |
55 | return Error::success(); |
56 | } |
57 | return createStringError(EC: errc::io_error, Msg: "Can't parse Double" ); |
58 | } |
59 | |
60 | static Error fromJson(const json::Value &V, std::string &Out) { |
61 | if (auto S = V.getAsString()) { |
62 | Out = std::string(*S); |
63 | return Error::success(); |
64 | } |
65 | return createStringError(EC: errc::io_error, Msg: "Can't parse String" ); |
66 | } |
67 | |
68 | static Error fromJson(const json::Value &V, uint32_t &Out) { |
69 | return intFromJsonTemplate(V, Out); |
70 | } |
71 | |
72 | static Error fromJson(const json::Value &V, int &Out) { |
73 | return intFromJsonTemplate(V, Out); |
74 | } |
75 | |
76 | static Error fromJson(const json::Value &V, libc_benchmarks::Duration &D) { |
77 | if (V.kind() != json::Value::Kind::Number) |
78 | return createStringError(EC: errc::io_error, Msg: "Can't parse Duration" ); |
79 | D = libc_benchmarks::Duration(*V.getAsNumber()); |
80 | return Error::success(); |
81 | } |
82 | |
83 | static Error fromJson(const json::Value &V, MaybeAlign &Out) { |
84 | const auto MaybeInt = V.getAsInteger(); |
85 | if (!MaybeInt) |
86 | return createStringError(EC: errc::io_error, |
87 | Msg: "Can't parse Align, not an Integer" ); |
88 | const int64_t Value = *MaybeInt; |
89 | if (!Value) { |
90 | Out = std::nullopt; |
91 | return Error::success(); |
92 | } |
93 | if (isPowerOf2_64(Value)) { |
94 | Out = Align(Value); |
95 | return Error::success(); |
96 | } |
97 | return createStringError(EC: errc::io_error, |
98 | Msg: "Can't parse Align, not a power of two" ); |
99 | } |
100 | |
101 | static Error fromJson(const json::Value &V, |
102 | libc_benchmarks::BenchmarkLog &Out) { |
103 | if (V.kind() != json::Value::Kind::String) |
104 | return createStringError(EC: errc::io_error, |
105 | Msg: "Can't parse BenchmarkLog, not a String" ); |
106 | const auto String = *V.getAsString(); |
107 | auto Parsed = |
108 | llvm::StringSwitch<std::optional<libc_benchmarks::BenchmarkLog>>(String) |
109 | .Case(S: "None" , Value: libc_benchmarks::BenchmarkLog::None) |
110 | .Case(S: "Last" , Value: libc_benchmarks::BenchmarkLog::Last) |
111 | .Case(S: "Full" , Value: libc_benchmarks::BenchmarkLog::Full) |
112 | .Default(Value: std::nullopt); |
113 | if (!Parsed) |
114 | return createStringError(EC: errc::io_error, |
115 | S: Twine("Can't parse BenchmarkLog, invalid value '" ) |
116 | .concat(Suffix: String) |
117 | .concat(Suffix: "'" )); |
118 | Out = *Parsed; |
119 | return Error::success(); |
120 | } |
121 | |
122 | template <typename C> |
123 | Error vectorFromJsonTemplate(const json::Value &V, C &Out) { |
124 | auto *A = V.getAsArray(); |
125 | if (!A) |
126 | return createStringError(EC: errc::io_error, Msg: "Can't parse Array" ); |
127 | Out.clear(); |
128 | Out.resize(A->size()); |
129 | for (auto InOutPair : llvm::zip(*A, Out)) |
130 | if (auto E = fromJson(std::get<0>(InOutPair), std::get<1>(InOutPair))) |
131 | return std::move(E); |
132 | return Error::success(); |
133 | } |
134 | |
135 | template <typename T> |
136 | static Error fromJson(const json::Value &V, std::vector<T> &Out) { |
137 | return vectorFromJsonTemplate(V, Out); |
138 | } |
139 | |
140 | // Same as llvm::json::ObjectMapper but adds a finer error reporting mechanism. |
141 | class JsonObjectMapper { |
142 | const json::Object *O; |
143 | Error E; |
144 | SmallDenseSet<StringRef> SeenFields; |
145 | |
146 | public: |
147 | explicit JsonObjectMapper(const json::Value &V) |
148 | : O(V.getAsObject()), |
149 | E(O ? Error::success() |
150 | : createStringError(EC: errc::io_error, Msg: "Expected JSON Object" )) {} |
151 | |
152 | Error takeError() { |
153 | if (E) |
154 | return std::move(E); |
155 | for (const auto &Itr : *O) { |
156 | const StringRef Key = Itr.getFirst(); |
157 | if (!SeenFields.count(V: Key)) |
158 | E = createStringError(EC: errc::io_error, |
159 | S: Twine("Unknown field: " ).concat(Suffix: Key)); |
160 | } |
161 | return std::move(E); |
162 | } |
163 | |
164 | template <typename T> void map(StringRef Key, T &Out) { |
165 | if (E) |
166 | return; |
167 | if (const json::Value *Value = O->get(K: Key)) { |
168 | SeenFields.insert(V: Key); |
169 | E = fromJson(*Value, Out); |
170 | } |
171 | } |
172 | }; |
173 | |
174 | static Error fromJson(const json::Value &V, |
175 | libc_benchmarks::BenchmarkOptions &Out) { |
176 | JsonObjectMapper O(V); |
177 | O.map(Key: "MinDuration" , Out&: Out.MinDuration); |
178 | O.map(Key: "MaxDuration" , Out&: Out.MaxDuration); |
179 | O.map(Key: "InitialIterations" , Out&: Out.InitialIterations); |
180 | O.map(Key: "MaxIterations" , Out&: Out.MaxIterations); |
181 | O.map(Key: "MinSamples" , Out&: Out.MinSamples); |
182 | O.map(Key: "MaxSamples" , Out&: Out.MaxSamples); |
183 | O.map(Key: "Epsilon" , Out&: Out.Epsilon); |
184 | O.map(Key: "ScalingFactor" , Out&: Out.ScalingFactor); |
185 | O.map(Key: "Log" , Out&: Out.Log); |
186 | return O.takeError(); |
187 | } |
188 | |
189 | static Error fromJson(const json::Value &V, |
190 | libc_benchmarks::StudyConfiguration &Out) { |
191 | JsonObjectMapper O(V); |
192 | O.map(Key: "Function" , Out&: Out.Function); |
193 | O.map(Key: "NumTrials" , Out&: Out.NumTrials); |
194 | O.map(Key: "IsSweepMode" , Out&: Out.IsSweepMode); |
195 | O.map(Key: "SweepModeMaxSize" , Out&: Out.SweepModeMaxSize); |
196 | O.map(Key: "SizeDistributionName" , Out&: Out.SizeDistributionName); |
197 | O.map(Key: "AccessAlignment" , Out&: Out.AccessAlignment); |
198 | O.map(Key: "MemcmpMismatchAt" , Out&: Out.MemcmpMismatchAt); |
199 | return O.takeError(); |
200 | } |
201 | |
202 | static Error fromJson(const json::Value &V, libc_benchmarks::CacheInfo &Out) { |
203 | JsonObjectMapper O(V); |
204 | O.map(Key: "Type" , Out&: Out.Type); |
205 | O.map(Key: "Level" , Out&: Out.Level); |
206 | O.map(Key: "Size" , Out&: Out.Size); |
207 | O.map(Key: "NumSharing" , Out&: Out.NumSharing); |
208 | return O.takeError(); |
209 | } |
210 | |
211 | static Error fromJson(const json::Value &V, libc_benchmarks::HostState &Out) { |
212 | JsonObjectMapper O(V); |
213 | O.map(Key: "CpuName" , Out&: Out.CpuName); |
214 | O.map(Key: "CpuFrequency" , Out&: Out.CpuFrequency); |
215 | O.map(Key: "Caches" , Out&: Out.Caches); |
216 | return O.takeError(); |
217 | } |
218 | |
219 | static Error fromJson(const json::Value &V, libc_benchmarks::Runtime &Out) { |
220 | JsonObjectMapper O(V); |
221 | O.map(Key: "Host" , Out&: Out.Host); |
222 | O.map(Key: "BufferSize" , Out&: Out.BufferSize); |
223 | O.map(Key: "BatchParameterCount" , Out&: Out.BatchParameterCount); |
224 | O.map(Key: "BenchmarkOptions" , Out&: Out.BenchmarkOptions); |
225 | return O.takeError(); |
226 | } |
227 | |
228 | static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) { |
229 | JsonObjectMapper O(V); |
230 | O.map(Key: "StudyName" , Out&: Out.StudyName); |
231 | O.map(Key: "Runtime" , Out&: Out.Runtime); |
232 | O.map(Key: "Configuration" , Out&: Out.Configuration); |
233 | O.map(Key: "Measurements" , Out&: Out.Measurements); |
234 | return O.takeError(); |
235 | } |
236 | |
237 | static double seconds(const Duration &D) { |
238 | return std::chrono::duration<double>(D).count(); |
239 | } |
240 | |
241 | Expected<Study> parseJsonStudy(StringRef Content) { |
242 | Expected<json::Value> EV = json::parse(JSON: Content); |
243 | if (!EV) |
244 | return EV.takeError(); |
245 | Study S; |
246 | if (Error E = fromJson(V: *EV, Out&: S)) |
247 | return std::move(E); |
248 | return S; |
249 | } |
250 | |
251 | static StringRef serialize(const BenchmarkLog &L) { |
252 | switch (L) { |
253 | case BenchmarkLog::None: |
254 | return "None" ; |
255 | case BenchmarkLog::Last: |
256 | return "Last" ; |
257 | case BenchmarkLog::Full: |
258 | return "Full" ; |
259 | } |
260 | llvm_unreachable("Unhandled BenchmarkLog value" ); |
261 | } |
262 | |
263 | static void serialize(const BenchmarkOptions &BO, json::OStream &JOS) { |
264 | JOS.attribute(Key: "MinDuration" , Contents: seconds(D: BO.MinDuration)); |
265 | JOS.attribute(Key: "MaxDuration" , Contents: seconds(D: BO.MaxDuration)); |
266 | JOS.attribute(Key: "InitialIterations" , Contents: BO.InitialIterations); |
267 | JOS.attribute(Key: "MaxIterations" , Contents: BO.MaxIterations); |
268 | JOS.attribute(Key: "MinSamples" , Contents: BO.MinSamples); |
269 | JOS.attribute(Key: "MaxSamples" , Contents: BO.MaxSamples); |
270 | JOS.attribute(Key: "Epsilon" , Contents: BO.Epsilon); |
271 | JOS.attribute(Key: "ScalingFactor" , Contents: BO.ScalingFactor); |
272 | JOS.attribute(Key: "Log" , Contents: serialize(L: BO.Log)); |
273 | } |
274 | |
275 | static void serialize(const CacheInfo &CI, json::OStream &JOS) { |
276 | JOS.attribute(Key: "Type" , Contents: CI.Type); |
277 | JOS.attribute(Key: "Level" , Contents: CI.Level); |
278 | JOS.attribute(Key: "Size" , Contents: CI.Size); |
279 | JOS.attribute(Key: "NumSharing" , Contents: CI.NumSharing); |
280 | } |
281 | |
282 | static void serialize(const StudyConfiguration &SC, json::OStream &JOS) { |
283 | JOS.attribute(Key: "Function" , Contents: SC.Function); |
284 | JOS.attribute(Key: "NumTrials" , Contents: SC.NumTrials); |
285 | JOS.attribute(Key: "IsSweepMode" , Contents: SC.IsSweepMode); |
286 | JOS.attribute(Key: "SweepModeMaxSize" , Contents: SC.SweepModeMaxSize); |
287 | JOS.attribute(Key: "SizeDistributionName" , Contents: SC.SizeDistributionName); |
288 | JOS.attribute(Key: "AccessAlignment" , |
289 | Contents: static_cast<int64_t>(SC.AccessAlignment->value())); |
290 | JOS.attribute(Key: "MemcmpMismatchAt" , Contents: SC.MemcmpMismatchAt); |
291 | } |
292 | |
293 | static void serialize(const HostState &HS, json::OStream &JOS) { |
294 | JOS.attribute(Key: "CpuName" , Contents: HS.CpuName); |
295 | JOS.attribute(Key: "CpuFrequency" , Contents: HS.CpuFrequency); |
296 | JOS.attributeArray(Key: "Caches" , Contents: [&]() { |
297 | for (const auto &CI : HS.Caches) |
298 | JOS.object(Contents: [&]() { serialize(CI, JOS); }); |
299 | }); |
300 | } |
301 | |
302 | static void serialize(const Runtime &RI, json::OStream &JOS) { |
303 | JOS.attributeObject(Key: "Host" , Contents: [&]() { serialize(HS: RI.Host, JOS); }); |
304 | JOS.attribute(Key: "BufferSize" , Contents: RI.BufferSize); |
305 | JOS.attribute(Key: "BatchParameterCount" , Contents: RI.BatchParameterCount); |
306 | JOS.attributeObject(Key: "BenchmarkOptions" , |
307 | Contents: [&]() { serialize(BO: RI.BenchmarkOptions, JOS); }); |
308 | } |
309 | |
310 | void serializeToJson(const Study &S, json::OStream &JOS) { |
311 | JOS.object(Contents: [&]() { |
312 | JOS.attribute(Key: "StudyName" , Contents: S.StudyName); |
313 | JOS.attributeObject(Key: "Runtime" , Contents: [&]() { serialize(RI: S.Runtime, JOS); }); |
314 | JOS.attributeObject(Key: "Configuration" , |
315 | Contents: [&]() { serialize(SC: S.Configuration, JOS); }); |
316 | if (!S.Measurements.empty()) { |
317 | JOS.attributeArray(Key: "Measurements" , Contents: [&]() { |
318 | for (const auto &M : S.Measurements) |
319 | JOS.value(V: seconds(D: M)); |
320 | }); |
321 | } |
322 | }); |
323 | } |
324 | |
325 | } // namespace libc_benchmarks |
326 | } // namespace llvm |
327 | |