| 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, S: "Out of bound Integer" ); |
| 38 | Out = Value; |
| 39 | return Error::success(); |
| 40 | } |
| 41 | return createStringError(EC: errc::io_error, S: "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, S: "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, S: "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, S: "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, S: "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 | S: "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 | S: "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 | S: "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, S: "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, S: "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 | |