BaseProfileJSONWriter.h (20877B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef BASEPROFILEJSONWRITER_H 7 #define BASEPROFILEJSONWRITER_H 8 9 #include "mozilla/FailureLatch.h" 10 #include "mozilla/HashFunctions.h" 11 #include "mozilla/HashTable.h" 12 #include "mozilla/JSONWriter.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/NotNull.h" 15 #include "mozilla/ProgressLogger.h" 16 #include "mozilla/TimeStamp.h" 17 #include "mozilla/UniquePtrExtensions.h" 18 #include "mozilla/Flow.h" 19 20 #include <functional> 21 #include <ostream> 22 #include <string_view> 23 #include <stdint.h> 24 25 namespace mozilla { 26 namespace baseprofiler { 27 28 class SpliceableJSONWriter; 29 30 // On average, profile JSONs are large enough such that we want to avoid 31 // reallocating its buffer when expanding. Additionally, the contents of the 32 // profile are not accessed until the profile is entirely written. For these 33 // reasons we use a chunked writer that keeps an array of chunks, which is 34 // concatenated together after writing is finished. 35 class ChunkedJSONWriteFunc final : public JSONWriteFunc, public FailureLatch { 36 public: 37 friend class SpliceableJSONWriter; 38 39 explicit ChunkedJSONWriteFunc(FailureLatch& aFailureLatch) 40 : mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) { 41 (void)AllocChunk(kChunkSize); 42 } 43 44 [[nodiscard]] bool IsEmpty() const { 45 MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && mChunkList.length() == 0 && 46 mChunkLengths.length() == 0); 47 return !mChunkPtr; 48 } 49 50 // Length of data written so far, excluding null terminator. 51 [[nodiscard]] size_t Length() const { 52 MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); 53 size_t totalLen = 0; 54 for (size_t i = 0; i < mChunkLengths.length(); i++) { 55 MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]); 56 totalLen += mChunkLengths[i]; 57 } 58 return totalLen; 59 } 60 61 void Write(const Span<const char>& aStr) final { 62 if (Failed()) { 63 return; 64 } 65 66 MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd); 67 MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back()); 68 MOZ_ASSERT(*mChunkPtr == '\0'); 69 70 // Most strings to be written are small, but subprocess profiles (e.g., 71 // from the content process in e10s) may be huge. If the string is larger 72 // than a chunk, allocate its own chunk. 73 char* newPtr; 74 if (aStr.size() >= kChunkSize) { 75 if (!AllocChunk(aStr.size() + 1)) { 76 return; 77 } 78 newPtr = mChunkPtr + aStr.size(); 79 } else { 80 newPtr = mChunkPtr + aStr.size(); 81 if (newPtr >= mChunkEnd) { 82 if (!AllocChunk(kChunkSize)) { 83 return; 84 } 85 newPtr = mChunkPtr + aStr.size(); 86 } 87 } 88 89 memcpy(mChunkPtr, aStr.data(), aStr.size()); 90 *newPtr = '\0'; 91 mChunkPtr = newPtr; 92 mChunkLengths.back() += aStr.size(); 93 } 94 95 [[nodiscard]] bool CopyDataIntoLazilyAllocatedBuffer( 96 const std::function<char*(size_t)>& aAllocator) const { 97 // Request a buffer for the full content plus a null terminator. 98 if (Failed()) { 99 return false; 100 } 101 102 char* ptr = aAllocator(Length() + 1); 103 104 if (!ptr) { 105 // Failed to allocate memory. 106 return false; 107 } 108 109 for (size_t i = 0; i < mChunkList.length(); i++) { 110 size_t len = mChunkLengths[i]; 111 memcpy(ptr, mChunkList[i].get(), len); 112 ptr += len; 113 } 114 *ptr = '\0'; 115 return true; 116 } 117 118 [[nodiscard]] UniquePtr<char[]> CopyData() const { 119 UniquePtr<char[]> c; 120 if (!CopyDataIntoLazilyAllocatedBuffer([&](size_t allocationSize) { 121 c = MakeUnique<char[]>(allocationSize); 122 return c.get(); 123 })) { 124 // Something went wrong, make sure the returned pointer is null even if 125 // the allocation happened. 126 c = nullptr; 127 } 128 return c; 129 } 130 131 void Take(ChunkedJSONWriteFunc&& aOther) { 132 SetFailureFrom(aOther); 133 if (Failed()) { 134 return; 135 } 136 137 for (size_t i = 0; i < aOther.mChunkList.length(); i++) { 138 MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i])); 139 MOZ_ALWAYS_TRUE(mChunkList.append(std::move(aOther.mChunkList[i]))); 140 } 141 mChunkPtr = mChunkList.back().get() + mChunkLengths.back(); 142 mChunkEnd = mChunkPtr; 143 aOther.Clear(); 144 } 145 146 FAILURELATCH_IMPL_PROXY(*mFailureLatch) 147 148 // Change the failure latch to be used here, and if the previous latch was 149 // already in failure state, set that failure in the new latch. 150 // This allows using this WriteFunc in isolation, before attempting to bring 151 // it into another operation group with its own FailureLatch. 152 void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { 153 aFailureLatch.SetFailureFrom(*this); 154 mFailureLatch = WrapNotNullUnchecked(&aFailureLatch); 155 } 156 157 private: 158 void Clear() { 159 mChunkPtr = nullptr; 160 mChunkEnd = nullptr; 161 mChunkList.clear(); 162 mChunkLengths.clear(); 163 } 164 165 void ClearAndSetFailure(std::string aFailure) { 166 Clear(); 167 SetFailure(std::move(aFailure)); 168 } 169 170 [[nodiscard]] bool ClearAndSetFailureAndFalse(std::string aFailure) { 171 ClearAndSetFailure(std::move(aFailure)); 172 return false; 173 } 174 175 [[nodiscard]] bool AllocChunk(size_t aChunkSize) { 176 if (Failed()) { 177 if (mChunkPtr) { 178 // FailureLatch is in failed state, but chunks have not been cleared yet 179 // (error must have happened elsewhere). 180 Clear(); 181 } 182 return false; 183 } 184 185 MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); 186 UniquePtr<char[]> newChunk = MakeUniqueFallible<char[]>(aChunkSize); 187 if (!newChunk) { 188 return ClearAndSetFailureAndFalse( 189 "OOM in ChunkedJSONWriteFunc::AllocChunk allocating new chunk"); 190 } 191 mChunkPtr = newChunk.get(); 192 mChunkEnd = mChunkPtr + aChunkSize; 193 *mChunkPtr = '\0'; 194 if (!mChunkLengths.append(0)) { 195 return ClearAndSetFailureAndFalse( 196 "OOM in ChunkedJSONWriteFunc::AllocChunk appending length"); 197 } 198 if (!mChunkList.append(std::move(newChunk))) { 199 return ClearAndSetFailureAndFalse( 200 "OOM in ChunkedJSONWriteFunc::AllocChunk appending new chunk"); 201 } 202 return true; 203 } 204 205 static const size_t kChunkSize = 4096 * 512; 206 207 // Pointer for writing inside the current chunk. 208 // 209 // The current chunk is always at the back of mChunkList, i.e., 210 // mChunkList.back() <= mChunkPtr <= mChunkEnd. 211 char* mChunkPtr = nullptr; 212 213 // Pointer to the end of the current chunk. 214 // 215 // The current chunk is always at the back of mChunkList, i.e., 216 // mChunkEnd >= mChunkList.back() + mChunkLengths.back(). 217 char* mChunkEnd = nullptr; 218 219 // List of chunks and their lengths. 220 // 221 // For all i, the length of the string in mChunkList[i] is 222 // mChunkLengths[i]. 223 Vector<UniquePtr<char[]>> mChunkList; 224 Vector<size_t> mChunkLengths; 225 226 NotNull<FailureLatch*> mFailureLatch; 227 }; 228 229 struct OStreamJSONWriteFunc final : public JSONWriteFunc { 230 explicit OStreamJSONWriteFunc(std::ostream& aStream) : mStream(aStream) {} 231 232 void Write(const Span<const char>& aStr) final { 233 std::string_view sv(aStr.data(), aStr.size()); 234 mStream << sv; 235 } 236 237 std::ostream& mStream; 238 }; 239 240 class UniqueJSONStrings; 241 242 class SpliceableJSONWriter : public JSONWriter, public FailureLatch { 243 public: 244 SpliceableJSONWriter(JSONWriteFunc& aWriter, FailureLatch& aFailureLatch) 245 : JSONWriter(aWriter, JSONWriter::SingleLineStyle), 246 mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {} 247 248 SpliceableJSONWriter(UniquePtr<JSONWriteFunc> aWriter, 249 FailureLatch& aFailureLatch) 250 : JSONWriter(std::move(aWriter), JSONWriter::SingleLineStyle), 251 mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {} 252 253 void StartBareList() { StartCollection(scEmptyString, scEmptyString); } 254 255 void EndBareList() { EndCollection(scEmptyString); } 256 257 // Output a time (int64_t given in nanoseconds) in milliseconds. trim zeroes. 258 // E.g.: 1'234'567'890 -> "1234.56789" 259 void TimeI64NsProperty(const Span<const char>& aMaybePropertyName, 260 int64_t aTime_ns) { 261 if (aTime_ns == 0) { 262 Scalar(aMaybePropertyName, MakeStringSpan("0")); 263 return; 264 } 265 266 static constexpr int64_t million = 1'000'000; 267 const int64_t absNanos = std::abs(aTime_ns); 268 const int64_t integerMilliseconds = absNanos / million; 269 auto remainderNanoseconds = static_cast<uint32_t>(absNanos % million); 270 271 // Plenty enough to fit INT64_MIN (-9223372036854775808). 272 static constexpr size_t DIGITS_MAX = 23; 273 char buf[DIGITS_MAX + 1]; 274 int len = 275 snprintf(buf, DIGITS_MAX, (aTime_ns >= 0) ? "%" PRIu64 : "-%" PRIu64, 276 integerMilliseconds); 277 if (remainderNanoseconds != 0) { 278 buf[len++] = '.'; 279 // Output up to 6 fractional digits. Exit early if the rest would 280 // be trailing zeros. 281 uint32_t powerOfTen = static_cast<uint32_t>(million / 10); 282 for (;;) { 283 auto digit = remainderNanoseconds / powerOfTen; 284 buf[len++] = '0' + static_cast<char>(digit); 285 remainderNanoseconds %= powerOfTen; 286 if (remainderNanoseconds == 0) { 287 break; 288 } 289 powerOfTen /= 10; 290 if (powerOfTen == 0) { 291 break; 292 } 293 } 294 } 295 296 Scalar(aMaybePropertyName, Span<const char>(buf, len)); 297 } 298 299 // Output a (double) time in milliseconds, with at best nanosecond precision. 300 void TimeDoubleMsProperty(const Span<const char>& aMaybePropertyName, 301 double aTime_ms) { 302 const double dTime_ns = aTime_ms * 1'000'000.0; 303 // Make sure it's well within int64_t range. 304 // 2^63 nanoseconds is almost 300 years; these times are relative to 305 // firefox startup, this should be enough for most uses. 306 if (dTime_ns >= 0.0) { 307 MOZ_RELEASE_ASSERT(dTime_ns < double(INT64_MAX - 1)); 308 } else { 309 MOZ_RELEASE_ASSERT(dTime_ns > double(INT64_MIN + 2)); 310 } 311 // Round to nearest integer nanosecond. The conversion to integer truncates 312 // the fractional part, so first we need to push it 0.5 away from zero. 313 const int64_t iTime_ns = 314 (dTime_ns >= 0.0) ? int64_t(dTime_ns + 0.5) : int64_t(dTime_ns - 0.5); 315 TimeI64NsProperty(aMaybePropertyName, iTime_ns); 316 } 317 318 // Output a (double) time in milliseconds, with at best nanosecond precision. 319 void TimeDoubleMsElement(double aTime_ms) { 320 TimeDoubleMsProperty(nullptr, aTime_ms); 321 } 322 323 // This function must be used to correctly stream timestamps in profiles. 324 // Null timestamps don't output anything. 325 void TimeProperty(const Span<const char>& aMaybePropertyName, 326 const TimeStamp& aTime) { 327 if (!aTime.IsNull()) { 328 TimeDoubleMsProperty( 329 aMaybePropertyName, 330 (aTime - TimeStamp::ProcessCreation()).ToMilliseconds()); 331 } 332 } 333 334 // JSON doesn't support 64bit integers so we encode them as hex strings 335 static std::array<char, 17> HexString(uint64_t aId) { 336 std::array<char, 17> buf = {}; 337 static const char* hex_digits = "0123456789abcdef"; 338 for (int i = 0; i < 16; i++) { 339 buf[i] = hex_digits[(aId >> (60 - i * 4)) & 0xf]; 340 } 341 buf[16] = '0'; // null terminate the string 342 return buf; 343 } 344 345 // We store flows as strings because JS can't handle 64 bit numbers in JSON 346 void FlowProperty(const Span<const char>& aName, Flow aFlow) { 347 UniqueStringProperty(aName, HexString(aFlow.Id())); 348 } 349 350 void NullElements(uint32_t aCount) { 351 for (uint32_t i = 0; i < aCount; i++) { 352 NullElement(); 353 } 354 } 355 356 void Splice(const Span<const char>& aStr) { 357 Separator(); 358 WriteFunc().Write(aStr); 359 mNeedComma[mDepth] = true; 360 } 361 362 void Splice(const char* aStr, size_t aLen) { 363 Separator(); 364 WriteFunc().Write(Span<const char>(aStr, aLen)); 365 mNeedComma[mDepth] = true; 366 } 367 368 // Splice the given JSON directly in, without quoting. 369 void SplicedJSONProperty(const Span<const char>& aMaybePropertyName, 370 const Span<const char>& aJsonValue) { 371 Scalar(aMaybePropertyName, aJsonValue); 372 } 373 374 void CopyAndSplice(const ChunkedJSONWriteFunc& aFunc) { 375 Separator(); 376 for (size_t i = 0; i < aFunc.mChunkList.length(); i++) { 377 WriteFunc().Write( 378 Span<const char>(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i])); 379 } 380 mNeedComma[mDepth] = true; 381 } 382 383 // Takes the chunks from aFunc and write them. If move is not possible 384 // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its 385 // storage cleared. 386 virtual void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) { 387 Separator(); 388 for (size_t i = 0; i < aFunc.mChunkList.length(); i++) { 389 WriteFunc().Write( 390 Span<const char>(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i])); 391 } 392 aFunc.mChunkPtr = nullptr; 393 aFunc.mChunkEnd = nullptr; 394 aFunc.mChunkList.clear(); 395 aFunc.mChunkLengths.clear(); 396 mNeedComma[mDepth] = true; 397 } 398 399 // Set (or reset) the pointer to a UniqueJSONStrings. 400 void SetUniqueStrings(UniqueJSONStrings& aUniqueStrings) { 401 MOZ_RELEASE_ASSERT(!mUniqueStrings); 402 mUniqueStrings = &aUniqueStrings; 403 } 404 405 // Set (or reset) the pointer to a UniqueJSONStrings. 406 void ResetUniqueStrings() { 407 MOZ_RELEASE_ASSERT(mUniqueStrings); 408 mUniqueStrings = nullptr; 409 } 410 411 // Add `aStr` to the unique-strings list (if not already there), and write its 412 // index as a named object property. 413 inline void UniqueStringProperty(const Span<const char>& aName, 414 const Span<const char>& aStr); 415 416 // Add `aStr` to the unique-strings list (if not already there), and write its 417 // index as an array element. 418 inline void UniqueStringElement(const Span<const char>& aStr); 419 420 // THe following functions override JSONWriter functions non-virtually. The 421 // goal is to try and prevent calls that specify a style, which would be 422 // ignored anyway because the whole thing is single-lined. It's fine if some 423 // calls still make it through a `JSONWriter&`, no big deal. 424 void Start() { JSONWriter::Start(); } 425 void StartArrayProperty(const Span<const char>& aName) { 426 JSONWriter::StartArrayProperty(aName); 427 } 428 template <size_t N> 429 void StartArrayProperty(const char (&aName)[N]) { 430 JSONWriter::StartArrayProperty(Span<const char>(aName, N)); 431 } 432 void StartArrayElement() { JSONWriter::StartArrayElement(); } 433 void StartObjectProperty(const Span<const char>& aName) { 434 JSONWriter::StartObjectProperty(aName); 435 } 436 template <size_t N> 437 void StartObjectProperty(const char (&aName)[N]) { 438 JSONWriter::StartObjectProperty(Span<const char>(aName, N)); 439 } 440 void StartObjectElement() { JSONWriter::StartObjectElement(); } 441 442 FAILURELATCH_IMPL_PROXY(*mFailureLatch) 443 444 protected: 445 NotNull<FailureLatch*> mFailureLatch; 446 447 private: 448 UniqueJSONStrings* mUniqueStrings = nullptr; 449 }; 450 451 class SpliceableChunkedJSONWriter final : public SpliceableJSONWriter { 452 public: 453 explicit SpliceableChunkedJSONWriter(FailureLatch& aFailureLatch) 454 : SpliceableJSONWriter(MakeUnique<ChunkedJSONWriteFunc>(aFailureLatch), 455 aFailureLatch) {} 456 457 // Access the ChunkedJSONWriteFunc as reference-to-const, usually to copy data 458 // out. 459 const ChunkedJSONWriteFunc& ChunkedWriteFunc() const { 460 return ChunkedWriteFuncRef(); 461 } 462 463 // Access the ChunkedJSONWriteFunc as rvalue-reference, usually to take its 464 // data out. This writer shouldn't be used anymore after this. 465 ChunkedJSONWriteFunc&& TakeChunkedWriteFunc() { 466 ChunkedJSONWriteFunc& ref = ChunkedWriteFuncRef(); 467 #ifdef DEBUG 468 mTaken = true; 469 #endif // 470 return std::move(ref); 471 } 472 473 // Adopts the chunks from aFunc without copying. 474 void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) override { 475 MOZ_ASSERT(!mTaken); 476 Separator(); 477 ChunkedWriteFuncRef().Take(std::move(aFunc)); 478 mNeedComma[mDepth] = true; 479 } 480 481 void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { 482 mFailureLatch = WrapNotNullUnchecked(&aFailureLatch); 483 return ChunkedWriteFuncRef().ChangeFailureLatchAndForwardState( 484 aFailureLatch); 485 } 486 487 private: 488 const ChunkedJSONWriteFunc& ChunkedWriteFuncRef() const { 489 MOZ_ASSERT(!mTaken); 490 // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the 491 // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&. 492 return static_cast<const ChunkedJSONWriteFunc&>(WriteFunc()); 493 } 494 495 ChunkedJSONWriteFunc& ChunkedWriteFuncRef() { 496 MOZ_ASSERT(!mTaken); 497 // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the 498 // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&. 499 return static_cast<ChunkedJSONWriteFunc&>(WriteFunc()); 500 } 501 502 #ifdef DEBUG 503 bool mTaken = false; 504 #endif 505 }; 506 507 class JSONSchemaWriter { 508 JSONWriter& mWriter; 509 uint32_t mIndex; 510 511 public: 512 explicit JSONSchemaWriter(JSONWriter& aWriter) : mWriter(aWriter), mIndex(0) { 513 aWriter.StartObjectProperty("schema", 514 SpliceableJSONWriter::SingleLineStyle); 515 } 516 517 void WriteField(const Span<const char>& aName) { 518 mWriter.IntProperty(aName, mIndex++); 519 } 520 521 template <size_t Np1> 522 void WriteField(const char (&aName)[Np1]) { 523 WriteField(Span<const char>(aName, Np1 - 1)); 524 } 525 526 ~JSONSchemaWriter() { mWriter.EndObject(); } 527 }; 528 529 // This class helps create an indexed list of unique strings, and inserts the 530 // index as a JSON value. The collected list of unique strings can later be 531 // inserted as a JSON array. 532 // This can be useful for elements/properties with many repeated strings. 533 // 534 // With only JSONWriter w, 535 // `w.WriteElement("a"); w.WriteElement("b"); w.WriteElement("a");` 536 // when done inside a JSON array, will generate: 537 // `["a", "b", "c"]` 538 // 539 // With UniqueStrings u, 540 // `u.WriteElement(w, "a"); u.WriteElement(w, "b"); u.WriteElement(w, "a");` 541 // when done inside a JSON array, will generate: 542 // `[0, 1, 0]` 543 // and later, `u.SpliceStringTableElements(w)` (inside a JSON array), will 544 // output the corresponding indexed list of unique strings: 545 // `["a", "b"]` 546 class UniqueJSONStrings final : public FailureLatch { 547 public: 548 // Start an empty list of unique strings. 549 MFBT_API explicit UniqueJSONStrings(FailureLatch& aFailureLatch); 550 551 // Start with a copy of the strings from another list. 552 MFBT_API UniqueJSONStrings(FailureLatch& aFailureLatch, 553 const UniqueJSONStrings& aOther, 554 ProgressLogger aProgressLogger); 555 556 MFBT_API ~UniqueJSONStrings(); 557 558 // Add `aStr` to the list (if not already there), and write its index as a 559 // named object property. 560 void WriteProperty(SpliceableJSONWriter& aWriter, 561 const Span<const char>& aName, 562 const Span<const char>& aStr) { 563 if (const Maybe<uint32_t> maybeIndex = GetOrAddIndex(aStr); maybeIndex) { 564 aWriter.IntProperty(aName, *maybeIndex); 565 } else { 566 aWriter.SetFailureFrom(*this); 567 } 568 } 569 570 // Add `aStr` to the list (if not already there), and write its index as an 571 // array element. 572 void WriteElement(SpliceableJSONWriter& aWriter, 573 const Span<const char>& aStr) { 574 if (const Maybe<uint32_t> maybeIndex = GetOrAddIndex(aStr); maybeIndex) { 575 aWriter.IntElement(*maybeIndex); 576 } else if (!aWriter.Failed()) { 577 aWriter.SetFailureFrom(*this); 578 } 579 } 580 581 // Splice all collected unique strings into an array. This should only be done 582 // once, and then this UniqueStrings shouldn't be used anymore. 583 MFBT_API void SpliceStringTableElements(SpliceableJSONWriter& aWriter); 584 585 FAILURELATCH_IMPL_PROXY(mStringTableWriter) 586 587 void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) { 588 mStringTableWriter.ChangeFailureLatchAndForwardState(aFailureLatch); 589 } 590 591 private: 592 MFBT_API void ClearAndSetFailure(std::string aFailure); 593 594 // If `aStr` is already listed, return its index. 595 // Otherwise add it to the list and return the new index. 596 MFBT_API Maybe<uint32_t> GetOrAddIndex(const Span<const char>& aStr); 597 598 SpliceableChunkedJSONWriter mStringTableWriter; 599 HashMap<HashNumber, uint32_t> mStringHashToIndexMap; 600 }; 601 602 void SpliceableJSONWriter::UniqueStringProperty(const Span<const char>& aName, 603 const Span<const char>& aStr) { 604 MOZ_RELEASE_ASSERT(mUniqueStrings); 605 mUniqueStrings->WriteProperty(*this, aName, aStr); 606 } 607 608 // Add `aStr` to the list (if not already there), and write its index as an 609 // array element. 610 void SpliceableJSONWriter::UniqueStringElement(const Span<const char>& aStr) { 611 MOZ_RELEASE_ASSERT(mUniqueStrings); 612 mUniqueStrings->WriteElement(*this, aStr); 613 } 614 615 } // namespace baseprofiler 616 } // namespace mozilla 617 618 #endif // BASEPROFILEJSONWRITER_H