Paula Toth | 66d00fe | 2020-04-08 10:16:30 -0700 | [diff] [blame] | 1 | //===-- JSON serialization routines ---------------------------------------===// |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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" |
Siva Chandra Reddy | 1852af1 | 2020-04-28 14:00:12 -0700 | [diff] [blame] | 20 | |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 21 | #include <chrono> |
| 22 | #include <limits> |
| 23 | #include <memory> |
Siva Chandra Reddy | 8dcb7f6 | 2022-12-11 00:20:33 +0000 | [diff] [blame] | 24 | #include <optional> |
Siva Chandra Reddy | 1852af1 | 2020-04-28 14:00:12 -0700 | [diff] [blame] | 25 | #include <string> |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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(errc::io_error, "Out of bound Integer"); |
| 38 | Out = Value; |
| 39 | return Error::success(); |
| 40 | } |
| 41 | return createStringError(errc::io_error, "Can't parse Integer"); |
| 42 | } |
| 43 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 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(errc::io_error, "Can't parse Boolean"); |
| 50 | } |
| 51 | |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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(errc::io_error, "Can't parse Double"); |
| 58 | } |
| 59 | |
| 60 | static Error fromJson(const json::Value &V, std::string &Out) { |
| 61 | if (auto S = V.getAsString()) { |
Siva Chandra Reddy | 1852af1 | 2020-04-28 14:00:12 -0700 | [diff] [blame] | 62 | Out = std::string(*S); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 63 | return Error::success(); |
| 64 | } |
| 65 | return createStringError(errc::io_error, "Can't parse String"); |
| 66 | } |
| 67 | |
| 68 | static Error fromJson(const json::Value &V, uint32_t &Out) { |
| 69 | return intFromJsonTemplate(V, Out); |
| 70 | } |
| 71 | |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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(errc::io_error, "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(errc::io_error, |
| 87 | "Can't parse Align, not an Integer"); |
| 88 | const int64_t Value = *MaybeInt; |
| 89 | if (!Value) { |
Siva Chandra Reddy | 8dcb7f6 | 2022-12-11 00:20:33 +0000 | [diff] [blame] | 90 | Out = std::nullopt; |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 91 | return Error::success(); |
| 92 | } |
| 93 | if (isPowerOf2_64(Value)) { |
| 94 | Out = Align(Value); |
| 95 | return Error::success(); |
| 96 | } |
| 97 | return createStringError(errc::io_error, |
| 98 | "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(errc::io_error, |
| 105 | "Can't parse BenchmarkLog, not a String"); |
| 106 | const auto String = *V.getAsString(); |
| 107 | auto Parsed = |
Kazu Hirata | 660c33e | 2023-01-14 21:10:14 -0800 | [diff] [blame] | 108 | llvm::StringSwitch<std::optional<libc_benchmarks::BenchmarkLog>>(String) |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 109 | .Case("None", libc_benchmarks::BenchmarkLog::None) |
| 110 | .Case("Last", libc_benchmarks::BenchmarkLog::Last) |
| 111 | .Case("Full", libc_benchmarks::BenchmarkLog::Full) |
Siva Chandra Reddy | 8dcb7f6 | 2022-12-11 00:20:33 +0000 | [diff] [blame] | 112 | .Default(std::nullopt); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 113 | if (!Parsed) |
| 114 | return createStringError(errc::io_error, |
| 115 | Twine("Can't parse BenchmarkLog, invalid value '") |
| 116 | .concat(String) |
| 117 | .concat("'")); |
| 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(errc::io_error, "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 | |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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(errc::io_error, "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(Key)) |
| 158 | E = createStringError(errc::io_error, |
| 159 | Twine("Unknown field: ").concat(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(Key)) { |
| 168 | SeenFields.insert(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("MinDuration", Out.MinDuration); |
| 178 | O.map("MaxDuration", Out.MaxDuration); |
| 179 | O.map("InitialIterations", Out.InitialIterations); |
| 180 | O.map("MaxIterations", Out.MaxIterations); |
| 181 | O.map("MinSamples", Out.MinSamples); |
| 182 | O.map("MaxSamples", Out.MaxSamples); |
| 183 | O.map("Epsilon", Out.Epsilon); |
| 184 | O.map("ScalingFactor", Out.ScalingFactor); |
| 185 | O.map("Log", Out.Log); |
| 186 | return O.takeError(); |
| 187 | } |
| 188 | |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 189 | static Error fromJson(const json::Value &V, |
| 190 | libc_benchmarks::StudyConfiguration &Out) { |
| 191 | JsonObjectMapper O(V); |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 192 | O.map("Function", Out.Function); |
| 193 | O.map("NumTrials", Out.NumTrials); |
| 194 | O.map("IsSweepMode", Out.IsSweepMode); |
| 195 | O.map("SweepModeMaxSize", Out.SweepModeMaxSize); |
| 196 | O.map("SizeDistributionName", Out.SizeDistributionName); |
| 197 | O.map("AccessAlignment", Out.AccessAlignment); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 198 | O.map("MemcmpMismatchAt", 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("Type", Out.Type); |
| 205 | O.map("Level", Out.Level); |
| 206 | O.map("Size", Out.Size); |
| 207 | O.map("NumSharing", 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("CpuName", Out.CpuName); |
| 214 | O.map("CpuFrequency", Out.CpuFrequency); |
| 215 | O.map("Caches", Out.Caches); |
| 216 | return O.takeError(); |
| 217 | } |
| 218 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 219 | static Error fromJson(const json::Value &V, libc_benchmarks::Runtime &Out) { |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 220 | JsonObjectMapper O(V); |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 221 | O.map("Host", Out.Host); |
| 222 | O.map("BufferSize", Out.BufferSize); |
| 223 | O.map("BatchParameterCount", Out.BatchParameterCount); |
| 224 | O.map("BenchmarkOptions", Out.BenchmarkOptions); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 225 | return O.takeError(); |
| 226 | } |
| 227 | |
| 228 | static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) { |
| 229 | JsonObjectMapper O(V); |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 230 | O.map("StudyName", Out.StudyName); |
| 231 | O.map("Runtime", Out.Runtime); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 232 | O.map("Configuration", Out.Configuration); |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 233 | O.map("Measurements", Out.Measurements); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 234 | return O.takeError(); |
| 235 | } |
| 236 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 237 | static double seconds(const Duration &D) { |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 238 | return std::chrono::duration<double>(D).count(); |
| 239 | } |
| 240 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 241 | Expected<Study> parseJsonStudy(StringRef Content) { |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 242 | Expected<json::Value> EV = json::parse(Content); |
| 243 | if (!EV) |
| 244 | return EV.takeError(); |
| 245 | Study S; |
| 246 | if (Error E = fromJson(*EV, S)) |
| 247 | return std::move(E); |
| 248 | return S; |
| 249 | } |
| 250 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 251 | static StringRef serialize(const BenchmarkLog &L) { |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 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 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 263 | static void serialize(const BenchmarkOptions &BO, json::OStream &JOS) { |
| 264 | JOS.attribute("MinDuration", seconds(BO.MinDuration)); |
| 265 | JOS.attribute("MaxDuration", seconds(BO.MaxDuration)); |
| 266 | JOS.attribute("InitialIterations", BO.InitialIterations); |
| 267 | JOS.attribute("MaxIterations", BO.MaxIterations); |
| 268 | JOS.attribute("MinSamples", BO.MinSamples); |
| 269 | JOS.attribute("MaxSamples", BO.MaxSamples); |
| 270 | JOS.attribute("Epsilon", BO.Epsilon); |
| 271 | JOS.attribute("ScalingFactor", BO.ScalingFactor); |
| 272 | JOS.attribute("Log", serialize(BO.Log)); |
| 273 | } |
| 274 | |
| 275 | static void serialize(const CacheInfo &CI, json::OStream &JOS) { |
| 276 | JOS.attribute("Type", CI.Type); |
| 277 | JOS.attribute("Level", CI.Level); |
| 278 | JOS.attribute("Size", CI.Size); |
| 279 | JOS.attribute("NumSharing", CI.NumSharing); |
| 280 | } |
| 281 | |
| 282 | static void serialize(const StudyConfiguration &SC, json::OStream &JOS) { |
| 283 | JOS.attribute("Function", SC.Function); |
| 284 | JOS.attribute("NumTrials", SC.NumTrials); |
| 285 | JOS.attribute("IsSweepMode", SC.IsSweepMode); |
| 286 | JOS.attribute("SweepModeMaxSize", SC.SweepModeMaxSize); |
| 287 | JOS.attribute("SizeDistributionName", SC.SizeDistributionName); |
| 288 | JOS.attribute("AccessAlignment", |
| 289 | static_cast<int64_t>(SC.AccessAlignment->value())); |
| 290 | JOS.attribute("MemcmpMismatchAt", SC.MemcmpMismatchAt); |
| 291 | } |
| 292 | |
| 293 | static void serialize(const HostState &HS, json::OStream &JOS) { |
| 294 | JOS.attribute("CpuName", HS.CpuName); |
| 295 | JOS.attribute("CpuFrequency", HS.CpuFrequency); |
| 296 | JOS.attributeArray("Caches", [&]() { |
| 297 | for (const auto &CI : HS.Caches) |
| 298 | JOS.object([&]() { serialize(CI, JOS); }); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 299 | }); |
| 300 | } |
| 301 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 302 | static void serialize(const Runtime &RI, json::OStream &JOS) { |
| 303 | JOS.attributeObject("Host", [&]() { serialize(RI.Host, JOS); }); |
| 304 | JOS.attribute("BufferSize", RI.BufferSize); |
| 305 | JOS.attribute("BatchParameterCount", RI.BatchParameterCount); |
| 306 | JOS.attributeObject("BenchmarkOptions", |
| 307 | [&]() { serialize(RI.BenchmarkOptions, JOS); }); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 308 | } |
| 309 | |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 310 | void serializeToJson(const Study &S, json::OStream &JOS) { |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 311 | JOS.object([&]() { |
Guillaume Chatelet | deae7e9 | 2020-12-17 13:16:14 +0000 | [diff] [blame] | 312 | JOS.attribute("StudyName", S.StudyName); |
| 313 | JOS.attributeObject("Runtime", [&]() { serialize(S.Runtime, JOS); }); |
| 314 | JOS.attributeObject("Configuration", |
| 315 | [&]() { serialize(S.Configuration, JOS); }); |
| 316 | if (!S.Measurements.empty()) { |
| 317 | JOS.attributeArray("Measurements", [&]() { |
| 318 | for (const auto &M : S.Measurements) |
| 319 | JOS.value(seconds(M)); |
Guillaume Chatelet | aba80d0 | 2020-01-06 13:17:04 +0100 | [diff] [blame] | 320 | }); |
| 321 | } |
| 322 | }); |
| 323 | } |
| 324 | |
| 325 | } // namespace libc_benchmarks |
| 326 | } // namespace llvm |