/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BaseProfiler.h" #ifdef MOZ_BASE_PROFILER # include "BaseProfileJSONWriter.h" # include "BaseProfilerMarkerPayload.h" # include "mozilla/BlocksRingBuffer.h" # include "mozilla/leb128iterator.h" # include "mozilla/ModuloBuffer.h" # include "mozilla/PowerOfTwo.h" # include "mozilla/Attributes.h" # include "mozilla/Vector.h" # if defined(_MSC_VER) # include # include # include # else # include # include # endif # include # include # include # include using namespace mozilla; MOZ_MAYBE_UNUSED static void SleepMilli(unsigned aMilliseconds) { # if defined(_MSC_VER) Sleep(aMilliseconds); # else struct timespec ts; ts.tv_sec = aMilliseconds / 1000; ts.tv_nsec = long(aMilliseconds % 1000) * 1000000; struct timespec tr; while (nanosleep(&ts, &tr)) { if (errno == EINTR) { ts = tr; } else { printf("nanosleep() -> %s\n", strerror(errno)); exit(1); } } # endif } void TestPowerOfTwoMask() { printf("TestPowerOfTwoMask...\n"); static_assert(MakePowerOfTwoMask().MaskValue() == 0, ""); constexpr PowerOfTwoMask c0 = MakePowerOfTwoMask(); MOZ_RELEASE_ASSERT(c0.MaskValue() == 0); static_assert(MakePowerOfTwoMask().MaskValue() == 0xFFu, ""); constexpr PowerOfTwoMask cFF = MakePowerOfTwoMask(); MOZ_RELEASE_ASSERT(cFF.MaskValue() == 0xFFu); static_assert( MakePowerOfTwoMask().MaskValue() == 0xFFFFFFFFu, ""); constexpr PowerOfTwoMask cFFFFFFFF = MakePowerOfTwoMask(); MOZ_RELEASE_ASSERT(cFFFFFFFF.MaskValue() == 0xFFFFFFFFu); struct TestDataU32 { uint32_t mInput; uint32_t mMask; }; // clang-format off TestDataU32 tests[] = { { 0, 0 }, { 1, 1 }, { 2, 3 }, { 3, 3 }, { 4, 7 }, { 5, 7 }, { (1u << 31) - 1, (1u << 31) - 1 }, { (1u << 31), uint32_t(-1) }, { (1u << 31) + 1, uint32_t(-1) }, { uint32_t(-1), uint32_t(-1) } }; // clang-format on for (const TestDataU32& test : tests) { PowerOfTwoMask p2m(test.mInput); MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask); for (const TestDataU32& inner : tests) { if (p2m.MaskValue() != uint32_t(-1)) { MOZ_RELEASE_ASSERT((inner.mInput % p2m) == (inner.mInput % (p2m.MaskValue() + 1))); } MOZ_RELEASE_ASSERT((inner.mInput & p2m) == (inner.mInput % p2m)); MOZ_RELEASE_ASSERT((p2m & inner.mInput) == (inner.mInput & p2m)); } } printf("TestPowerOfTwoMask done\n"); } void TestPowerOfTwo() { printf("TestPowerOfTwo...\n"); static_assert(MakePowerOfTwo().Value() == 1, ""); constexpr PowerOfTwo c1 = MakePowerOfTwo(); MOZ_RELEASE_ASSERT(c1.Value() == 1); static_assert(MakePowerOfTwo().Mask().MaskValue() == 0, ""); static_assert(MakePowerOfTwo().Value() == 128, ""); constexpr PowerOfTwo c128 = MakePowerOfTwo(); MOZ_RELEASE_ASSERT(c128.Value() == 128); static_assert(MakePowerOfTwo().Mask().MaskValue() == 127, ""); static_assert(MakePowerOfTwo().Value() == 0x80000000u, ""); constexpr PowerOfTwo cMax = MakePowerOfTwo(); MOZ_RELEASE_ASSERT(cMax.Value() == 0x80000000u); static_assert( MakePowerOfTwo().Mask().MaskValue() == 0x7FFFFFFFu, ""); struct TestDataU32 { uint32_t mInput; uint32_t mValue; uint32_t mMask; }; // clang-format off TestDataU32 tests[] = { { 0, 1, 0 }, { 1, 1, 0 }, { 2, 2, 1 }, { 3, 4, 3 }, { 4, 4, 3 }, { 5, 8, 7 }, { (1u << 31) - 1, (1u << 31), (1u << 31) - 1 }, { (1u << 31), (1u << 31), (1u << 31) - 1 }, { (1u << 31) + 1, (1u << 31), (1u << 31) - 1 }, { uint32_t(-1), (1u << 31), (1u << 31) - 1 } }; // clang-format on for (const TestDataU32& test : tests) { PowerOfTwo p2(test.mInput); MOZ_RELEASE_ASSERT(p2.Value() == test.mValue); MOZ_RELEASE_ASSERT(p2.MaskValue() == test.mMask); PowerOfTwoMask p2m = p2.Mask(); MOZ_RELEASE_ASSERT(p2m.MaskValue() == test.mMask); for (const TestDataU32& inner : tests) { MOZ_RELEASE_ASSERT((inner.mInput % p2) == (inner.mInput % p2.Value())); } } printf("TestPowerOfTwo done\n"); } void TestLEB128() { printf("TestLEB128...\n"); MOZ_RELEASE_ASSERT(ULEB128MaxSize() == 2); MOZ_RELEASE_ASSERT(ULEB128MaxSize() == 3); MOZ_RELEASE_ASSERT(ULEB128MaxSize() == 5); MOZ_RELEASE_ASSERT(ULEB128MaxSize() == 10); struct TestDataU64 { uint64_t mValue; unsigned mSize; const char* mBytes; }; // clang-format off TestDataU64 tests[] = { // Small numbers should keep their normal byte representation. { 0u, 1, "\0" }, { 1u, 1, "\x01" }, // 0111 1111 (127, or 0x7F) is the highest number that fits into a single // LEB128 byte. It gets encoded as 0111 1111, note the most significant bit // is off. { 0x7Fu, 1, "\x7F" }, // Next number: 128, or 0x80. // Original data representation: 1000 0000 // Broken up into groups of 7: 1 0000000 // Padded with 0 (msB) or 1 (lsB): 00000001 10000000 // Byte representation: 0x01 0x80 // Little endian order: -> 0x80 0x01 { 0x80u, 2, "\x80\x01" }, // Next: 129, or 0x81 (showing that we don't lose low bits.) // Original data representation: 1000 0001 // Broken up into groups of 7: 1 0000001 // Padded with 0 (msB) or 1 (lsB): 00000001 10000001 // Byte representation: 0x01 0x81 // Little endian order: -> 0x81 0x01 { 0x81u, 2, "\x81\x01" }, // Highest 8-bit number: 255, or 0xFF. // Original data representation: 1111 1111 // Broken up into groups of 7: 1 1111111 // Padded with 0 (msB) or 1 (lsB): 00000001 11111111 // Byte representation: 0x01 0xFF // Little endian order: -> 0xFF 0x01 { 0xFFu, 2, "\xFF\x01" }, // Next: 256, or 0x100. // Original data representation: 1 0000 0000 // Broken up into groups of 7: 10 0000000 // Padded with 0 (msB) or 1 (lsB): 00000010 10000000 // Byte representation: 0x10 0x80 // Little endian order: -> 0x80 0x02 { 0x100u, 2, "\x80\x02" }, // Highest 32-bit number: 0xFFFFFFFF (8 bytes, all bits set). // Original: 1111 1111 1111 1111 1111 1111 1111 1111 // Groups: 1111 1111111 1111111 1111111 1111111 // Padded: 00001111 11111111 11111111 11111111 11111111 // Bytes: 0x0F 0xFF 0xFF 0xFF 0xFF // Little Endian: -> 0xFF 0xFF 0xFF 0xFF 0x0F { 0xFFFFFFFFu, 5, "\xFF\xFF\xFF\xFF\x0F" }, // Highest 64-bit number: 0xFFFFFFFFFFFFFFFF (16 bytes, all bits set). // 64 bits, that's 9 groups of 7 bits, plus 1 (most significant) bit. { 0xFFFFFFFFFFFFFFFFu, 10, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01" } }; // clang-format on for (const TestDataU64& test : tests) { MOZ_RELEASE_ASSERT(ULEB128Size(test.mValue) == test.mSize); // Prepare a buffer that can accomodate the largest-possible LEB128. uint8_t buffer[ULEB128MaxSize()]; // Use a pointer into the buffer as iterator. uint8_t* p = buffer; // And write the LEB128. WriteULEB128(test.mValue, p); // Pointer (iterator) should have advanced just past the expected LEB128 // size. MOZ_RELEASE_ASSERT(p == buffer + test.mSize); // Check expected bytes. for (unsigned i = 0; i < test.mSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t(test.mBytes[i])); } // Move pointer (iterator) back to start of buffer. p = buffer; // And read the LEB128 we wrote above. uint64_t read = ReadULEB128(p); // Pointer (iterator) should have also advanced just past the expected // LEB128 size. MOZ_RELEASE_ASSERT(p == buffer + test.mSize); // And check the read value. MOZ_RELEASE_ASSERT(read == test.mValue); } printf("TestLEB128 done\n"); } static void TestModuloBuffer(ModuloBuffer<>& mb, uint32_t MBSize) { using MB = ModuloBuffer<>; MOZ_RELEASE_ASSERT(mb.BufferLength().Value() == MBSize); // Iterator comparisons. MOZ_RELEASE_ASSERT(mb.ReaderAt(2) == mb.ReaderAt(2)); MOZ_RELEASE_ASSERT(mb.ReaderAt(2) != mb.ReaderAt(3)); MOZ_RELEASE_ASSERT(mb.ReaderAt(2) < mb.ReaderAt(3)); MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(2)); MOZ_RELEASE_ASSERT(mb.ReaderAt(2) <= mb.ReaderAt(3)); MOZ_RELEASE_ASSERT(mb.ReaderAt(3) > mb.ReaderAt(2)); MOZ_RELEASE_ASSERT(mb.ReaderAt(2) >= mb.ReaderAt(2)); MOZ_RELEASE_ASSERT(mb.ReaderAt(3) >= mb.ReaderAt(2)); // Iterators indices don't wrap around (even though they may be pointing at // the same location). MOZ_RELEASE_ASSERT(mb.ReaderAt(2) != mb.ReaderAt(MBSize + 2)); MOZ_RELEASE_ASSERT(mb.ReaderAt(MBSize + 2) != mb.ReaderAt(2)); // Dereference. static_assert(std::is_same::value, "Dereferencing from a reader should return const Byte*"); static_assert(std::is_same::value, "Dereferencing from a writer should return Byte*"); // Contiguous between 0 and MBSize-1. MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize - 1) == &*mb.ReaderAt(0) + (MBSize - 1)); // Wraps around. MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize) == &*mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize - 1) == &*mb.ReaderAt(MBSize - 1)); MOZ_RELEASE_ASSERT(&*mb.ReaderAt(MBSize + MBSize) == &*mb.ReaderAt(0)); // Power of 2 modulo wrapping. MOZ_RELEASE_ASSERT(&*mb.ReaderAt(uint32_t(-1)) == &*mb.ReaderAt(MBSize - 1)); MOZ_RELEASE_ASSERT(&*mb.ReaderAt(static_cast(-1)) == &*mb.ReaderAt(MBSize - 1)); // Arithmetic. MB::Reader arit = mb.ReaderAt(0); MOZ_RELEASE_ASSERT(++arit == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(--arit == mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(arit++ == mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(arit-- == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(arit + 3 == mb.ReaderAt(3)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); MOZ_RELEASE_ASSERT(4 + arit == mb.ReaderAt(4)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(0)); // (Can't have assignments inside asserts, hence the split.) const bool checkPlusEq = ((arit += 3) == mb.ReaderAt(3)); MOZ_RELEASE_ASSERT(checkPlusEq); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3)); MOZ_RELEASE_ASSERT((arit - 2) == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(3)); const bool checkMinusEq = ((arit -= 2) == mb.ReaderAt(1)); MOZ_RELEASE_ASSERT(checkMinusEq); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); // Random access. MOZ_RELEASE_ASSERT(&arit[3] == &*(arit + 3)); MOZ_RELEASE_ASSERT(arit == mb.ReaderAt(1)); // Iterator difference. MOZ_RELEASE_ASSERT(mb.ReaderAt(3) - mb.ReaderAt(1) == 2); MOZ_RELEASE_ASSERT(mb.ReaderAt(1) - mb.ReaderAt(3) == MB::Index(-2)); // Only testing Writer, as Reader is just a subset with no code differences. MB::Writer it = mb.WriterAt(0); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0); // Write two characters at the start. it.WriteObject('x'); it.WriteObject('y'); // Backtrack to read them. it -= 2; // PeekObject should read without moving. MOZ_RELEASE_ASSERT(it.PeekObject() == 'x'); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 0); // ReadObject should read and move past the character. MOZ_RELEASE_ASSERT(it.ReadObject() == 'x'); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1); MOZ_RELEASE_ASSERT(it.PeekObject() == 'y'); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 1); MOZ_RELEASE_ASSERT(it.ReadObject() == 'y'); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 2); // Checking that a reader can be created from a writer. MB::Reader it2(it); MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2); // Or assigned. it2 = it; MOZ_RELEASE_ASSERT(it2.CurrentIndex() == 2); // Iterator traits. static_assert(std::is_same::difference_type, MB::Index>::value, "ModuloBuffer::Reader::difference_type should be Index"); static_assert(std::is_same::value_type, MB::Byte>::value, "ModuloBuffer::Reader::value_type should be Byte"); static_assert(std::is_same::pointer, const MB::Byte*>::value, "ModuloBuffer::Reader::pointer should be const Byte*"); static_assert(std::is_same::reference, const MB::Byte&>::value, "ModuloBuffer::Reader::reference should be const Byte&"); static_assert(std::is_base_of< std::input_iterator_tag, std::iterator_traits::iterator_category>::value, "ModuloBuffer::Reader::iterator_category should be derived " "from input_iterator_tag"); static_assert(std::is_base_of< std::forward_iterator_tag, std::iterator_traits::iterator_category>::value, "ModuloBuffer::Reader::iterator_category should be derived " "from forward_iterator_tag"); static_assert(std::is_base_of< std::bidirectional_iterator_tag, std::iterator_traits::iterator_category>::value, "ModuloBuffer::Reader::iterator_category should be derived " "from bidirectional_iterator_tag"); static_assert( std::is_same::iterator_category, std::random_access_iterator_tag>::value, "ModuloBuffer::Reader::iterator_category should be " "random_access_iterator_tag"); // Use as input iterator by std::string constructor (which is only considered // with proper input iterators.) std::string s(mb.ReaderAt(0), mb.ReaderAt(2)); MOZ_RELEASE_ASSERT(s == "xy"); // Write 4-byte number at index 2. it.WriteObject(int32_t(123)); MOZ_RELEASE_ASSERT(it.CurrentIndex() == 6); // And another, which should now wrap around (but index continues on.) it.WriteObject(int32_t(456)); MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 2); // Even though index==MBSize+2, we can read the object we wrote at 2. MOZ_RELEASE_ASSERT(it.ReadObject() == 123); MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + 6); // And similarly, index MBSize+6 points at the same location as index 6. MOZ_RELEASE_ASSERT(it.ReadObject() == 456); MOZ_RELEASE_ASSERT(it.CurrentIndex() == MBSize + MBSize + 2); } void TestModuloBuffer() { printf("TestModuloBuffer...\n"); // Testing ModuloBuffer with default template arguments. using MB = ModuloBuffer<>; // Only 8-byte buffers, to easily test wrap-around. constexpr uint32_t MBSize = 8; // MB with self-allocated heap buffer. MB mbByLength(MakePowerOfTwo32()); TestModuloBuffer(mbByLength, MBSize); // MB taking ownership of a provided UniquePtr to a buffer. auto uniqueBuffer = MakeUnique(MBSize); MB mbByUniquePtr(MakeUnique(MBSize), MakePowerOfTwo32()); TestModuloBuffer(mbByUniquePtr, MBSize); // MB using part of a buffer on the stack. The buffer is three times the // required size: The middle third is where ModuloBuffer will work, the first // and last thirds are only used to later verify that ModuloBuffer didn't go // out of its bounds. uint8_t buffer[MBSize * 3]; // Pre-fill the buffer with a known pattern, so we can later see what changed. for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } MB mbByBuffer(&buffer[MBSize], MakePowerOfTwo32()); TestModuloBuffer(mbByBuffer, MBSize); // Check that only the provided stack-based sub-buffer was modified. uint32_t changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } // Check that move-construction is allowed. This verifies that we do not // crash from a double free, when `mbByBuffer` and `mbByStolenBuffer` are both // destroyed at the end of this function. MB mbByStolenBuffer = std::move(mbByBuffer); TestModuloBuffer(mbByStolenBuffer, MBSize); // Check that only the provided stack-based sub-buffer was modified. changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } // This test function does a `ReadInto` as directed, and checks that the // result is the same as if the copy had been done manually byte-by-byte. // `TestReadInto(3, 7, 2)` copies from index 3 to index 7, 2 bytes long. // Return the output string (from `ReadInto`) for external checks. auto TestReadInto = [](MB::Index aReadFrom, MB::Index aWriteTo, MB::Length aBytes) { constexpr uint32_t TRISize = 16; // Prepare an input buffer, all different elements. uint8_t input[TRISize + 1] = "ABCDEFGHIJKLMNOP"; const MB mbInput(input, MakePowerOfTwo32()); // Prepare an output buffer, different from input. uint8_t output[TRISize + 1] = "abcdefghijklmnop"; MB mbOutput(output, MakePowerOfTwo32()); // Run ReadInto. auto writer = mbOutput.WriterAt(aWriteTo); mbInput.ReaderAt(aReadFrom).ReadInto(writer, aBytes); // Do the same operation manually. uint8_t outputCheck[TRISize + 1] = "abcdefghijklmnop"; MB mbOutputCheck(outputCheck, MakePowerOfTwo32()); auto readerCheck = mbInput.ReaderAt(aReadFrom); auto writerCheck = mbOutputCheck.WriterAt(aWriteTo); for (MB::Length i = 0; i < aBytes; ++i) { *writerCheck++ = *readerCheck++; } // Compare the two outputs. for (uint32_t i = 0; i < TRISize; ++i) { # ifdef TEST_MODULOBUFFER_FAILURE_DEBUG // Only used when debugging failures. if (output[i] != outputCheck[i]) { printf( "*** from=%u to=%u bytes=%u i=%u\ninput: '%s'\noutput: " "'%s'\ncheck: '%s'\n", unsigned(aReadFrom), unsigned(aWriteTo), unsigned(aBytes), unsigned(i), input, output, outputCheck); } # endif MOZ_RELEASE_ASSERT(output[i] == outputCheck[i]); } # ifdef TEST_MODULOBUFFER_HELPER // Only used when adding more tests. printf("*** from=%u to=%u bytes=%u output: %s\n", unsigned(aReadFrom), unsigned(aWriteTo), unsigned(aBytes), output); # endif return std::string(reinterpret_cast(output)); }; // A few manual checks: constexpr uint32_t TRISize = 16; MOZ_RELEASE_ASSERT(TestReadInto(0, 0, 0) == "abcdefghijklmnop"); MOZ_RELEASE_ASSERT(TestReadInto(0, 0, TRISize) == "ABCDEFGHIJKLMNOP"); MOZ_RELEASE_ASSERT(TestReadInto(0, 5, TRISize) == "LMNOPABCDEFGHIJK"); MOZ_RELEASE_ASSERT(TestReadInto(5, 0, TRISize) == "FGHIJKLMNOPABCDE"); // Test everything! (16^3 = 4096, not too much.) for (MB::Index r = 0; r < TRISize; ++r) { for (MB::Index w = 0; w < TRISize; ++w) { for (MB::Length len = 0; len < TRISize; ++len) { TestReadInto(r, w, len); } } } printf("TestModuloBuffer done\n"); } // Backdoor into value of BlockIndex, only for unit-testing. static uint64_t ExtractBlockIndex(const BlocksRingBuffer::BlockIndex bi) { uint64_t index; static_assert(sizeof(bi) == sizeof(index), "BlockIndex expected to only contain a uint64_t"); memcpy(&index, &bi, sizeof(index)); return index; }; void TestBlocksRingBufferAPI() { printf("TestBlocksRingBufferAPI...\n"); // Create a 16-byte buffer, enough to store up to 3 entries (1 byte size + 4 // bytes uint64_t). constexpr uint32_t MBSize = 16; uint8_t buffer[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } // Start a temporary block to constrain buffer lifetime. { BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex, &buffer[MBSize], MakePowerOfTwo32()); # define VERIFY_START_END_PUSHED_CLEARED(aStart, aEnd, aPushed, aCleared) \ { \ BlocksRingBuffer::State state = rb.GetState(); \ MOZ_RELEASE_ASSERT(ExtractBlockIndex(state.mRangeStart) == (aStart)); \ MOZ_RELEASE_ASSERT(ExtractBlockIndex(state.mRangeEnd) == (aEnd)); \ MOZ_RELEASE_ASSERT(state.mPushedBlockCount == (aPushed)); \ MOZ_RELEASE_ASSERT(state.mClearedBlockCount == (aCleared)); \ } // All entries will contain one 32-bit number. The resulting blocks will // have the following structure: // - 1 byte for the LEB128 size of 4 // - 4 bytes for the number. // E.g., if we have entries with `123` and `456`: // .-- Index 0 reserved for empty BlockIndex, nothing there. // | .-- first readable block at index 1 // | |.-- first block at index 1 // | ||.-- 1 byte for the entry size, which is `4` (32 bits) // | ||| .-- entry starts at index 2, contains 32-bit int // | ||| | .-- entry and block finish *after* index 5 (so 6) // | ||| | | .-- second block starts at index 6 // | ||| | | | etc. // | ||| | | | .-- End readable blocks: 11 // v vvv v v V v // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // - S[4 | int(123) ] [4 | int(456) ]E // Empty buffer to start with. // Start&end indices still at 1 (0 is reserved for the default BlockIndex{} // that cannot point at a valid entry), nothing cleared. VERIFY_START_END_PUSHED_CLEARED(1, 1, 0, 0); // Default BlockIndex. BlocksRingBuffer::BlockIndex bi0; if (bi0) { MOZ_RELEASE_ASSERT(false, "if (BlockIndex{}) should fail test"); } if (!bi0) { } else { MOZ_RELEASE_ASSERT(false, "if (!BlockIndex{}) should succeed test"); } MOZ_RELEASE_ASSERT(!bi0); MOZ_RELEASE_ASSERT(bi0 == bi0); MOZ_RELEASE_ASSERT(bi0 <= bi0); MOZ_RELEASE_ASSERT(bi0 >= bi0); MOZ_RELEASE_ASSERT(!(bi0 != bi0)); MOZ_RELEASE_ASSERT(!(bi0 < bi0)); MOZ_RELEASE_ASSERT(!(bi0 > bi0)); // Default BlockIndex can be used, but returns no valid entry. rb.ReadAt(bi0, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isNothing()); }); // Push `1` directly. MOZ_RELEASE_ASSERT(ExtractBlockIndex(rb.PutObject(uint32_t(1))) == 1); // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // - S[4 | int(1) ]E VERIFY_START_END_PUSHED_CLEARED(1, 6, 1, 0); // Push `2` through ReserveAndPut, check output BlockIndex. auto bi2 = rb.ReserveAndPut([]() { return sizeof(uint32_t); }, [](BlocksRingBuffer::EntryWriter* aEW) { MOZ_RELEASE_ASSERT(!!aEW); aEW->WriteObject(uint32_t(2)); return aEW->CurrentBlockIndex(); }); static_assert( std::is_same::value, "All index-returning functions should return a " "BlocksRingBuffer::BlockIndex"); MOZ_RELEASE_ASSERT(ExtractBlockIndex(bi2) == 6); // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // - S[4 | int(1) ] [4 | int(2) ]E VERIFY_START_END_PUSHED_CLEARED(1, 11, 2, 0); // Check single entry at bi2, store next block index. auto bi2Next = rb.ReadAt(bi2, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isSome()); MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject() == 2); MOZ_RELEASE_ASSERT( aMaybeReader->GetEntryAt(aMaybeReader->NextBlockIndex()) .isNothing()); return aMaybeReader->NextBlockIndex(); }); // bi2Next is at the end, nothing to read. rb.ReadAt(bi2Next, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isNothing()); }); // BlockIndex tests. if (bi2) { } else { MOZ_RELEASE_ASSERT(false, "if (non-default-BlockIndex) should succeed test"); } if (!bi2) { MOZ_RELEASE_ASSERT(false, "if (!non-default-BlockIndex) should fail test"); } MOZ_RELEASE_ASSERT(!!bi2); MOZ_RELEASE_ASSERT(bi2 == bi2); MOZ_RELEASE_ASSERT(bi2 <= bi2); MOZ_RELEASE_ASSERT(bi2 >= bi2); MOZ_RELEASE_ASSERT(!(bi2 != bi2)); MOZ_RELEASE_ASSERT(!(bi2 < bi2)); MOZ_RELEASE_ASSERT(!(bi2 > bi2)); MOZ_RELEASE_ASSERT(bi0 != bi2); MOZ_RELEASE_ASSERT(bi0 < bi2); MOZ_RELEASE_ASSERT(bi0 <= bi2); MOZ_RELEASE_ASSERT(!(bi0 == bi2)); MOZ_RELEASE_ASSERT(!(bi0 > bi2)); MOZ_RELEASE_ASSERT(!(bi0 >= bi2)); MOZ_RELEASE_ASSERT(bi2 != bi0); MOZ_RELEASE_ASSERT(bi2 > bi0); MOZ_RELEASE_ASSERT(bi2 >= bi0); MOZ_RELEASE_ASSERT(!(bi2 == bi0)); MOZ_RELEASE_ASSERT(!(bi2 < bi0)); MOZ_RELEASE_ASSERT(!(bi2 <= bi0)); MOZ_RELEASE_ASSERT(bi2 != bi2Next); MOZ_RELEASE_ASSERT(bi2 < bi2Next); MOZ_RELEASE_ASSERT(bi2 <= bi2Next); MOZ_RELEASE_ASSERT(!(bi2 == bi2Next)); MOZ_RELEASE_ASSERT(!(bi2 > bi2Next)); MOZ_RELEASE_ASSERT(!(bi2 >= bi2Next)); MOZ_RELEASE_ASSERT(bi2Next != bi2); MOZ_RELEASE_ASSERT(bi2Next > bi2); MOZ_RELEASE_ASSERT(bi2Next >= bi2); MOZ_RELEASE_ASSERT(!(bi2Next == bi2)); MOZ_RELEASE_ASSERT(!(bi2Next < bi2)); MOZ_RELEASE_ASSERT(!(bi2Next <= bi2)); // Push `3` through Put, check writer output // is returned to the initial caller. auto put3 = rb.Put(sizeof(uint32_t), [&](BlocksRingBuffer::EntryWriter* aEW) { MOZ_RELEASE_ASSERT(!!aEW); aEW->WriteObject(uint32_t(3)); return float(ExtractBlockIndex(aEW->CurrentBlockIndex())); }); static_assert(std::is_same::value, "Expect float as returned by callback."); MOZ_RELEASE_ASSERT(put3 == 11.0); // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16) // - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E VERIFY_START_END_PUSHED_CLEARED(1, 16, 3, 0); // Re-Read single entry at bi2, should now have a next entry. rb.ReadAt(bi2, [&](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isSome()); MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject() == 2); MOZ_RELEASE_ASSERT(aMaybeReader->NextBlockIndex() == bi2Next); MOZ_RELEASE_ASSERT(aMaybeReader->GetNextEntry().isSome()); MOZ_RELEASE_ASSERT( aMaybeReader->GetEntryAt(aMaybeReader->NextBlockIndex()).isSome()); MOZ_RELEASE_ASSERT( aMaybeReader->GetNextEntry()->CurrentBlockIndex() == aMaybeReader->GetEntryAt(aMaybeReader->NextBlockIndex()) ->CurrentBlockIndex()); MOZ_RELEASE_ASSERT( aMaybeReader->GetEntryAt(aMaybeReader->NextBlockIndex()) ->ReadObject() == 3); }); // Check that we have `1` to `3`. uint32_t count = 0; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aReader) { MOZ_RELEASE_ASSERT(aReader.ReadObject() == ++count); }); MOZ_RELEASE_ASSERT(count == 3); // Push `4`, store its BlockIndex for later. // This will wrap around, and clear the first entry. BlocksRingBuffer::BlockIndex bi4 = rb.PutObject(uint32_t(4)); // Before: // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16) // - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E // 1. First entry cleared: // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16) // - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ]E // 2. New entry starts at 15 and wraps around: (shown on separate line) // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16) // - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ] // 16 17 18 19 20 21 ... // [4 | int(4) ]E // (collapsed) // 16 17 18 19 20 21 6 7 8 9 10 11 12 13 14 15 (16) // [4 | int(4) ]E ? S[4 | int(2) ] [4 | int(3) ] VERIFY_START_END_PUSHED_CLEARED(6, 21, 4, 1); // Check that we have `2` to `4`. count = 1; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aReader) { MOZ_RELEASE_ASSERT(aReader.ReadObject() == ++count); }); MOZ_RELEASE_ASSERT(count == 4); // Push 5 through Put, no returns. // This will clear the second entry. // Check that the EntryWriter can access bi4 but not bi2. auto bi5_6 = rb.Put(sizeof(uint32_t), [&](BlocksRingBuffer::EntryWriter* aEW) { MOZ_RELEASE_ASSERT(!!aEW); aEW->WriteObject(uint32_t(5)); MOZ_RELEASE_ASSERT(aEW->GetEntryAt(bi2).isNothing()); MOZ_RELEASE_ASSERT(aEW->GetEntryAt(bi4).isSome()); MOZ_RELEASE_ASSERT(aEW->GetEntryAt(bi4)->CurrentBlockIndex() == bi4); MOZ_RELEASE_ASSERT(aEW->GetEntryAt(bi4)->ReadObject() == 4); return MakePair(aEW->CurrentBlockIndex(), aEW->BlockEndIndex()); }); auto& bi5 = bi5_6.first(); auto& bi6 = bi5_6.second(); // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15 (16) // [4 | int(4) ] [4 | int(5) ]E ? S[4 | int(3) ] VERIFY_START_END_PUSHED_CLEARED(11, 26, 5, 2); // Read single entry at bi2, should now gracefully fail. rb.ReadAt(bi2, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isNothing()); }); // Read single entry at bi5. rb.ReadAt(bi5, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isSome()); MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject() == 5); MOZ_RELEASE_ASSERT( aMaybeReader->GetEntryAt(aMaybeReader->NextBlockIndex()).isNothing()); }); rb.Read([&](BlocksRingBuffer::Reader* aReader) { MOZ_RELEASE_ASSERT(!!aReader); // begin() and end() should be at the range edges (verified above). MOZ_RELEASE_ASSERT( ExtractBlockIndex(aReader->begin().CurrentBlockIndex()) == 11); MOZ_RELEASE_ASSERT( ExtractBlockIndex(aReader->end().CurrentBlockIndex()) == 26); // Null BlockIndex clamped to the beginning. MOZ_RELEASE_ASSERT(aReader->At(bi0) == aReader->begin()); // Cleared block index clamped to the beginning. MOZ_RELEASE_ASSERT(aReader->At(bi2) == aReader->begin()); // At(begin) same as begin(). MOZ_RELEASE_ASSERT(aReader->At(aReader->begin().CurrentBlockIndex()) == aReader->begin()); // bi5 at expected position. MOZ_RELEASE_ASSERT( ExtractBlockIndex(aReader->At(bi5).CurrentBlockIndex()) == 21); // bi6 at expected position at the end. MOZ_RELEASE_ASSERT(aReader->At(bi6) == aReader->end()); // At(end) same as end(). MOZ_RELEASE_ASSERT(aReader->At(aReader->end().CurrentBlockIndex()) == aReader->end()); }); // Check that we have `3` to `5`. count = 2; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aReader) { MOZ_RELEASE_ASSERT(aReader.ReadObject() == ++count); }); MOZ_RELEASE_ASSERT(count == 5); // Clear everything before `4`, this should clear `3`. rb.ClearBefore(bi4); // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15 // S[4 | int(4) ] [4 | int(5) ]E ? ? ? ? ? ? VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3); // Check that we have `4` to `5`. count = 3; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aReader) { MOZ_RELEASE_ASSERT(aReader.ReadObject() == ++count); }); MOZ_RELEASE_ASSERT(count == 5); // Clear everything before `4` again, nothing to clear. rb.ClearBefore(bi4); VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3); // Clear everything, this should clear `4` and `5`, and bring the start // index where the end index currently is. rb.ClearBefore(bi6); // 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15 // ? ? ? ? ? ? ? ? ? ? SE? ? ? ? ? ? VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5); // Check that we have nothing to read. rb.ReadEach([&](auto&&) { MOZ_RELEASE_ASSERT(false); }); // Read single entry at bi5, should now gracefully fail. rb.ReadAt(bi5, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isNothing()); }); // Clear everything before now-cleared `4`, nothing to clear. rb.ClearBefore(bi4); VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5); // Push `6` directly. MOZ_RELEASE_ASSERT(rb.PutObject(uint32_t(6)) == bi6); // 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // ? ? ? ? ? ? ? ? ? ? S[4 | int(6) ]E ? VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5); { // Create a 2nd buffer and fill it with `7` and `8`. uint8_t buffer2[MBSize]; BlocksRingBuffer rb2(BlocksRingBuffer::ThreadSafety::WithoutMutex, buffer2, MakePowerOfTwo32()); rb2.PutObject(uint32_t(7)); rb2.PutObject(uint32_t(8)); // Main buffer shouldn't have changed. VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5); // Append contents of rb2 to rb, this should end up being the same as // pushing the two numbers. rb.AppendContents(rb2); // 32 33 34 35 36 37 38 39 40 41 26 27 28 29 30 31 // int(7) ] [4 | int(8) ]E ? S[4 | int(6) ] [4 | VERIFY_START_END_PUSHED_CLEARED(26, 41, 8, 5); // Append contents of rb2 to rb again, to verify that rb2 was not modified // above. This should clear `6` and the first `7`. rb.AppendContents(rb2); // 48 49 50 51 36 37 38 39 40 41 42 43 44 45 46 47 // int(8) ]E ? S[4 | int(8) ] [4 | int(7) ] [4 | VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7); // End of block where rb2 lives, to verify that it is not needed anymore // for its copied values to survive in rb. } VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7); // bi6 should now have been cleared. rb.ReadAt(bi6, [](Maybe&& aMaybeReader) { MOZ_RELEASE_ASSERT(aMaybeReader.isNothing()); }); // Check that we have `8`, `7`, `8`. count = 0; uint32_t expected[3] = {8, 7, 8}; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aReader) { MOZ_RELEASE_ASSERT(count < 3); MOZ_RELEASE_ASSERT(aReader.ReadObject() == expected[count++]); }); MOZ_RELEASE_ASSERT(count == 3); // End of block where rb lives, BlocksRingBuffer destructor should call // entry destructor for remaining entries. } // Check that only the provided stack-based sub-buffer was modified. uint32_t changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } printf("TestBlocksRingBufferAPI done\n"); } void TestBlocksRingBufferUnderlyingBufferChanges() { printf("TestBlocksRingBufferUnderlyingBufferChanges...\n"); // Out-of-session BlocksRingBuffer to start with. BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex); // Block index to read at. Initially "null", but may be changed below. BlocksRingBuffer::BlockIndex bi; // Test all rb APIs when rb is out-of-session and therefore doesn't have an // underlying buffer. auto testOutOfSession = [&]() { MOZ_RELEASE_ASSERT(rb.BufferLength().isNothing()); BlocksRingBuffer::State state = rb.GetState(); // When out-of-session, range start and ends are the same, and there are no // pushed&cleared blocks. MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd); MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0); MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0); // `Put()` functions run the callback with `Nothing`. int32_t ran = 0; rb.Put(1, [&](BlocksRingBuffer::EntryWriter* aMaybeEntryWriter) { MOZ_RELEASE_ASSERT(!aMaybeEntryWriter); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); // `PutFrom` won't do anything, and returns the null BlockIndex. MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) == BlocksRingBuffer::BlockIndex{}); MOZ_RELEASE_ASSERT(rb.PutObject(ran) == BlocksRingBuffer::BlockIndex{}); // `Read()` functions run the callback with `Nothing`. ran = 0; rb.Read([&](BlocksRingBuffer::Reader* aReader) { MOZ_RELEASE_ASSERT(!aReader); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); ran = 0; rb.ReadAt(BlocksRingBuffer::BlockIndex{}, [&](Maybe&& aMaybeEntryReader) { MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing()); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); ran = 0; rb.ReadAt(bi, [&](Maybe&& aMaybeEntryReader) { MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing()); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); // `ReadEach` shouldn't run the callback (nothing to read). rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); }); }; // As `testOutOfSession()` attempts to modify the buffer, we run it twice to // make sure one run doesn't influence the next one. testOutOfSession(); testOutOfSession(); rb.ClearBefore(bi); testOutOfSession(); testOutOfSession(); rb.Clear(); testOutOfSession(); testOutOfSession(); rb.Reset(); testOutOfSession(); testOutOfSession(); constexpr uint32_t MBSize = 32; rb.Set(MakePowerOfTwo()); constexpr bool EMPTY = true; constexpr bool NOT_EMPTY = false; // Test all rb APIs when rb has an underlying buffer. auto testInSession = [&](bool aExpectEmpty) { MOZ_RELEASE_ASSERT(rb.BufferLength().isSome()); BlocksRingBuffer::State state = rb.GetState(); if (aExpectEmpty) { MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd); MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0); MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0); } else { MOZ_RELEASE_ASSERT(state.mRangeStart < state.mRangeEnd); MOZ_RELEASE_ASSERT(state.mPushedBlockCount > 0); MOZ_RELEASE_ASSERT(state.mClearedBlockCount <= state.mPushedBlockCount); } int32_t ran = 0; // The following three `Put...` will write three int32_t of value 1. bi = rb.Put(sizeof(ran), [&](BlocksRingBuffer::EntryWriter* aMaybeEntryWriter) { MOZ_RELEASE_ASSERT(!!aMaybeEntryWriter); ++ran; aMaybeEntryWriter->WriteObject(ran); return aMaybeEntryWriter->CurrentBlockIndex(); }); MOZ_RELEASE_ASSERT(ran == 1); MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) != BlocksRingBuffer::BlockIndex{}); MOZ_RELEASE_ASSERT(rb.PutObject(ran) != BlocksRingBuffer::BlockIndex{}); ran = 0; rb.Read([&](BlocksRingBuffer::Reader* aReader) { MOZ_RELEASE_ASSERT(!!aReader); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); ran = 0; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aEntryReader) { MOZ_RELEASE_ASSERT(aEntryReader.RemainingBytes() == sizeof(ran)); MOZ_RELEASE_ASSERT(aEntryReader.ReadObject() == 1); ++ran; }); MOZ_RELEASE_ASSERT(ran >= 3); ran = 0; rb.ReadAt(BlocksRingBuffer::BlockIndex{}, [&](Maybe&& aMaybeEntryReader) { MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing()); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); ran = 0; rb.ReadAt(bi, [&](Maybe&& aMaybeEntryReader) { MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing() == !bi); ++ran; }); MOZ_RELEASE_ASSERT(ran == 1); }; testInSession(EMPTY); testInSession(NOT_EMPTY); rb.Set(MakePowerOfTwo()); MOZ_RELEASE_ASSERT(rb.BufferLength().isSome()); rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); }); testInSession(EMPTY); testInSession(NOT_EMPTY); rb.Reset(); testOutOfSession(); testOutOfSession(); uint8_t buffer[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } rb.Set(&buffer[MBSize], MakePowerOfTwo()); MOZ_RELEASE_ASSERT(rb.BufferLength().isSome()); rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); }); testInSession(EMPTY); testInSession(NOT_EMPTY); rb.Reset(); testOutOfSession(); testOutOfSession(); rb.Set(&buffer[MBSize], MakePowerOfTwo()); MOZ_RELEASE_ASSERT(rb.BufferLength().isSome()); rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); }); testInSession(EMPTY); testInSession(NOT_EMPTY); // Remove the current underlying buffer, this should clear all entries. rb.Reset(); // Check that only the provided stack-based sub-buffer was modified. uint32_t changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } testOutOfSession(); testOutOfSession(); printf("TestBlocksRingBufferUnderlyingBufferChanges done\n"); } void TestBlocksRingBufferThreading() { printf("TestBlocksRingBufferThreading...\n"); constexpr uint32_t MBSize = 8192; uint8_t buffer[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex, &buffer[MBSize], MakePowerOfTwo32()); // Start reader thread. std::atomic stopReader{false}; std::thread reader([&]() { for (;;) { BlocksRingBuffer::State state = rb.GetState(); printf( "Reader: range=%llu..%llu (%llu bytes) pushed=%llu cleared=%llu " "(alive=%llu)\n", static_cast(ExtractBlockIndex(state.mRangeStart)), static_cast(ExtractBlockIndex(state.mRangeEnd)), static_cast(ExtractBlockIndex(state.mRangeEnd)) - static_cast( ExtractBlockIndex(state.mRangeStart)), static_cast(state.mPushedBlockCount), static_cast(state.mClearedBlockCount), static_cast(state.mPushedBlockCount - state.mClearedBlockCount)); if (stopReader) { break; } ::SleepMilli(1); } }); // Start writer threads. constexpr int ThreadCount = 32; std::thread threads[ThreadCount]; for (int threadNo = 0; threadNo < ThreadCount; ++threadNo) { threads[threadNo] = std::thread( [&](int aThreadNo) { ::SleepMilli(1); constexpr int pushCount = 1024; for (int push = 0; push < pushCount; ++push) { // Reserve as many bytes as the thread number (but at least enough // to store an int), and write an increasing int. rb.Put(std::max(aThreadNo, int(sizeof(push))), [&](BlocksRingBuffer::EntryWriter* aEW) { MOZ_RELEASE_ASSERT(!!aEW); aEW->WriteObject(aThreadNo * 1000000 + push); *aEW += aEW->RemainingBytes(); }); } }, threadNo); } // Wait for all writer threads to die. for (auto&& thread : threads) { thread.join(); } // Stop reader thread. stopReader = true; reader.join(); // Check that only the provided stack-based sub-buffer was modified. uint32_t changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } printf("TestBlocksRingBufferThreading done\n"); } void TestBlocksRingBufferSerialization() { printf("TestBlocksRingBufferSerialization...\n"); constexpr uint32_t MBSize = 64; uint8_t buffer[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex, &buffer[MBSize], MakePowerOfTwo32()); // Will expect literal string to always have the same address. # define THE_ANSWER "The answer is " const char* theAnswer = THE_ANSWER; rb.PutObjects('0', WrapBlocksRingBufferLiteralCStringPointer(THE_ANSWER), 42, std::string(" but pi="), 3.14); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { char c0; const char* answer; int integer; std::string str; double pi; aER.ReadIntoObjects(c0, answer, integer, str, pi); MOZ_RELEASE_ASSERT(c0 == '0'); MOZ_RELEASE_ASSERT(answer == theAnswer); MOZ_RELEASE_ASSERT(integer == 42); MOZ_RELEASE_ASSERT(str == " but pi="); MOZ_RELEASE_ASSERT(pi == 3.14); }); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { char c0 = aER.ReadObject(); MOZ_RELEASE_ASSERT(c0 == '0'); const char* answer = aER.ReadObject(); MOZ_RELEASE_ASSERT(answer == theAnswer); int integer = aER.ReadObject(); MOZ_RELEASE_ASSERT(integer == 42); std::string str = aER.ReadObject(); MOZ_RELEASE_ASSERT(str == " but pi="); double pi = aER.ReadObject(); MOZ_RELEASE_ASSERT(pi == 3.14); }); rb.Clear(); // Write an int and store its BlockIndex. BlocksRingBuffer::BlockIndex blockIndex = rb.PutObject(123); // It should be non-0. MOZ_RELEASE_ASSERT(blockIndex != BlocksRingBuffer::BlockIndex{}); // Write that BlockIndex. rb.PutObject(blockIndex); rb.Read([&](BlocksRingBuffer::Reader* aR) { BlocksRingBuffer::BlockIterator it = aR->begin(); const BlocksRingBuffer::BlockIterator itEnd = aR->end(); MOZ_RELEASE_ASSERT(it != itEnd); MOZ_RELEASE_ASSERT((*it).ReadObject() == 123); ++it; MOZ_RELEASE_ASSERT(it != itEnd); MOZ_RELEASE_ASSERT((*it).ReadObject() == blockIndex); ++it; MOZ_RELEASE_ASSERT(it == itEnd); }); rb.Clear(); rb.PutObjects(std::make_tuple( '0', WrapBlocksRingBufferLiteralCStringPointer(THE_ANSWER), 42, std::string(" but pi="), 3.14)); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { MOZ_RELEASE_ASSERT(aER.ReadObject() == '0'); MOZ_RELEASE_ASSERT(aER.ReadObject() == theAnswer); MOZ_RELEASE_ASSERT(aER.ReadObject() == 42); MOZ_RELEASE_ASSERT(aER.ReadObject() == " but pi="); MOZ_RELEASE_ASSERT(aER.ReadObject() == 3.14); }); rb.Clear(); rb.PutObjects(MakeTuple('0', WrapBlocksRingBufferLiteralCStringPointer(THE_ANSWER), 42, std::string(" but pi="), 3.14)); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { MOZ_RELEASE_ASSERT(aER.ReadObject() == '0'); MOZ_RELEASE_ASSERT(aER.ReadObject() == theAnswer); MOZ_RELEASE_ASSERT(aER.ReadObject() == 42); MOZ_RELEASE_ASSERT(aER.ReadObject() == " but pi="); MOZ_RELEASE_ASSERT(aER.ReadObject() == 3.14); }); rb.Clear(); { UniqueFreePtr ufps(strdup(THE_ANSWER)); rb.PutObjects(ufps); } rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { auto ufps = aER.ReadObject>(); MOZ_RELEASE_ASSERT(!!ufps); MOZ_RELEASE_ASSERT(std::string(THE_ANSWER) == ufps.get()); }); rb.Clear(); int intArray[] = {1, 2, 3, 4, 5}; rb.PutObjects(MakeSpan(intArray)); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { int intArrayOut[sizeof(intArray) / sizeof(intArray[0])] = {0}; auto outSpan = MakeSpan(intArrayOut); aER.ReadIntoObject(outSpan); for (size_t i = 0; i < sizeof(intArray) / sizeof(intArray[0]); ++i) { MOZ_RELEASE_ASSERT(intArrayOut[i] == intArray[i]); } }); rb.Clear(); rb.PutObjects(Maybe(Nothing{}), Maybe(Some(123))); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { Maybe mi0, mi1; aER.ReadIntoObjects(mi0, mi1); MOZ_RELEASE_ASSERT(mi0.isNothing()); MOZ_RELEASE_ASSERT(mi1.isSome()); MOZ_RELEASE_ASSERT(*mi1 == 123); }); rb.Clear(); using V = Variant; V v0(VariantIndex<0>{}, 123); V v1(3.14); V v2(VariantIndex<2>{}, 456); rb.PutObjects(v0, v1, v2); rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { MOZ_RELEASE_ASSERT(aER.ReadObject() == v0); MOZ_RELEASE_ASSERT(aER.ReadObject() == v1); MOZ_RELEASE_ASSERT(aER.ReadObject() == v2); }); // 2nd BlocksRingBuffer to contain the 1st one. It has be be more than twice // the size. constexpr uint32_t MBSize2 = MBSize * 4; uint8_t buffer2[MBSize2 * 3]; for (size_t i = 0; i < MBSize2 * 3; ++i) { buffer2[i] = uint8_t('B' + i); } BlocksRingBuffer rb2(BlocksRingBuffer::ThreadSafety::WithoutMutex, &buffer2[MBSize2], MakePowerOfTwo32()); rb2.PutObject(rb); // 3rd BlocksRingBuffer deserialized from the 2nd one. uint8_t buffer3[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer3[i] = uint8_t('C' + i); } BlocksRingBuffer rb3(BlocksRingBuffer::ThreadSafety::WithoutMutex, &buffer3[MBSize], MakePowerOfTwo32()); rb2.ReadEach( [&](BlocksRingBuffer::EntryReader& aER) { aER.ReadIntoObject(rb3); }); // And a 4th heap-allocated one. UniquePtr rb4up; rb2.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { rb4up = aER.ReadObject>(); }); MOZ_RELEASE_ASSERT(!!rb4up); // Clear 1st and 2nd BlocksRingBuffers, to ensure we have made a deep copy // into the 3rd&4th ones. rb.Clear(); rb2.Clear(); // And now the 3rd one should have the same contents as the 1st one had. rb3.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { MOZ_RELEASE_ASSERT(aER.ReadObject() == v0); MOZ_RELEASE_ASSERT(aER.ReadObject() == v1); MOZ_RELEASE_ASSERT(aER.ReadObject() == v2); }); // And 4th. rb4up->ReadEach([&](BlocksRingBuffer::EntryReader& aER) { MOZ_RELEASE_ASSERT(aER.ReadObject() == v0); MOZ_RELEASE_ASSERT(aER.ReadObject() == v1); MOZ_RELEASE_ASSERT(aER.ReadObject() == v2); }); // In fact, the 3rd and 4th ones should have the same state, because they were // created the same way. MOZ_RELEASE_ASSERT(rb3.GetState().mRangeStart == rb4up->GetState().mRangeStart); MOZ_RELEASE_ASSERT(rb3.GetState().mRangeEnd == rb4up->GetState().mRangeEnd); MOZ_RELEASE_ASSERT(rb3.GetState().mPushedBlockCount == rb4up->GetState().mPushedBlockCount); MOZ_RELEASE_ASSERT(rb3.GetState().mClearedBlockCount == rb4up->GetState().mClearedBlockCount); // Check that only the provided stack-based sub-buffer was modified. uint32_t changed = 0; for (size_t i = MBSize; i < MBSize * 2; ++i) { changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1; } // Expect at least 75% changes. MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8); // Everything around the sub-buffers should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = 0; i < MBSize2; ++i) { MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i)); } for (size_t i = MBSize2 * 2; i < MBSize2 * 3; ++i) { MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i)); } for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i)); } printf("TestBlocksRingBufferSerialization done\n"); } class BaseTestMarkerPayload : public baseprofiler::ProfilerMarkerPayload { public: explicit BaseTestMarkerPayload(int aData) : mData(aData) {} int GetData() const { return mData; } // Exploded DECL_BASE_STREAM_PAYLOAD, but without `MFBT_API`s. static UniquePtr Deserialize( BlocksRingBuffer::EntryReader& aEntryReader); BlocksRingBuffer::Length TagAndSerializationBytes() const override; void SerializeTagAndPayload( BlocksRingBuffer::EntryWriter& aEntryWriter) const override; void StreamPayload( ::mozilla::baseprofiler::SpliceableJSONWriter& aWriter, const ::mozilla::TimeStamp& aProcessStartTime, ::mozilla::baseprofiler::UniqueStacks& aUniqueStacks) const override; private: BaseTestMarkerPayload(CommonProps&& aProps, int aData) : baseprofiler::ProfilerMarkerPayload(std::move(aProps)), mData(aData) {} int mData; }; // static UniquePtr BaseTestMarkerPayload::Deserialize( BlocksRingBuffer::EntryReader& aEntryReader) { CommonProps props = DeserializeCommonProps(aEntryReader); int data = aEntryReader.ReadObject(); return UniquePtr( new BaseTestMarkerPayload(std::move(props), data)); } BlocksRingBuffer::Length BaseTestMarkerPayload::TagAndSerializationBytes() const { return CommonPropsTagAndSerializationBytes() + sizeof(int); } void BaseTestMarkerPayload::SerializeTagAndPayload( BlocksRingBuffer::EntryWriter& aEntryWriter) const { static const DeserializerTag tag = TagForDeserializer(Deserialize); SerializeTagAndCommonProps(tag, aEntryWriter); aEntryWriter.WriteObject(mData); } void BaseTestMarkerPayload::StreamPayload( baseprofiler::SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime, baseprofiler::UniqueStacks& aUniqueStacks) const { aWriter.IntProperty("data", mData); } void TestProfilerMarkerSerialization() { printf("TestProfilerMarkerSerialization...\n"); constexpr uint32_t MBSize = 256; uint8_t buffer[MBSize * 3]; for (size_t i = 0; i < MBSize * 3; ++i) { buffer[i] = uint8_t('A' + i); } BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex, &buffer[MBSize], MakePowerOfTwo32()); constexpr int data = 42; { BaseTestMarkerPayload payload(data); rb.PutObject( static_cast(&payload)); } int read = 0; rb.ReadEach([&](BlocksRingBuffer::EntryReader& aER) { UniquePtr payload = aER.ReadObject>(); MOZ_RELEASE_ASSERT(!!payload); ++read; BaseTestMarkerPayload* testPayload = static_cast(payload.get()); MOZ_RELEASE_ASSERT(testPayload); MOZ_RELEASE_ASSERT(testPayload->GetData() == data); }); MOZ_RELEASE_ASSERT(read == 1); // Everything around the sub-buffer should be unchanged. for (size_t i = 0; i < MBSize; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } for (size_t i = MBSize * 2; i < MBSize * 3; ++i) { MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i)); } printf("TestProfilerMarkerSerialization done\n"); } // Increase the depth, to a maximum (to avoid too-deep recursion). static constexpr size_t NextDepth(size_t aDepth) { constexpr size_t MAX_DEPTH = 128; return (aDepth < MAX_DEPTH) ? (aDepth + 1) : aDepth; } Atomic sStopFibonacci; // Compute fibonacci the hard way (recursively: `f(n)=f(n-1)+f(n-2)`), and // prevent inlining. // The template parameter makes each depth be a separate function, to better // distinguish them in the profiler output. template MOZ_NEVER_INLINE unsigned long long Fibonacci(unsigned long long n) { AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fib", OTHER, std::to_string(DEPTH)); if (n == 0) { return 0; } if (n == 1) { return 1; } if (DEPTH < 5 && sStopFibonacci) { return 1'000'000'000; } TimeStamp start = TimeStamp::NowUnfuzzed(); static constexpr size_t MAX_MARKER_DEPTH = 10; unsigned long long f2 = Fibonacci(n - 2); if (DEPTH == 0) { BASE_PROFILER_ADD_MARKER("Half-way through Fibonacci", OTHER); } unsigned long long f1 = Fibonacci(n - 1); if (DEPTH < MAX_MARKER_DEPTH) { baseprofiler::profiler_add_text_marker( "fib", std::to_string(DEPTH), baseprofiler::ProfilingCategoryPair::OTHER, start, TimeStamp::NowUnfuzzed()); } return f2 + f1; } void TestProfiler() { printf("TestProfiler starting -- pid: %d, tid: %d\n", baseprofiler::profiler_current_process_id(), baseprofiler::profiler_current_thread_id()); // ::SleepMilli(10000); // Test dependencies. TestPowerOfTwoMask(); TestPowerOfTwo(); TestLEB128(); TestModuloBuffer(); TestBlocksRingBufferAPI(); TestBlocksRingBufferUnderlyingBufferChanges(); TestBlocksRingBufferThreading(); TestBlocksRingBufferSerialization(); TestProfilerMarkerSerialization(); { printf("profiler_init()...\n"); AUTO_BASE_PROFILER_INIT; MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active()); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled()); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); printf("profiler_start()...\n"); Vector filters; // Profile all registered threads. MOZ_RELEASE_ASSERT(filters.append("")); const uint32_t features = baseprofiler::ProfilerFeature::Leaf | baseprofiler::ProfilerFeature::StackWalk | baseprofiler::ProfilerFeature::Threads; baseprofiler::profiler_start(baseprofiler::BASE_PROFILER_DEFAULT_ENTRIES, BASE_PROFILER_DEFAULT_INTERVAL, features, filters.begin(), filters.length()); MOZ_RELEASE_ASSERT(baseprofiler::profiler_is_active()); MOZ_RELEASE_ASSERT(baseprofiler::profiler_thread_is_being_profiled()); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); sStopFibonacci = false; std::thread threadFib([]() { AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci"); SleepMilli(5); auto cause = # if defined(__linux__) || defined(__ANDROID__) // Currently disabled on these platforms, so just return a null. decltype(baseprofiler::profiler_get_backtrace()){}; # else baseprofiler::profiler_get_backtrace(); # endif AUTO_BASE_PROFILER_TEXT_MARKER_CAUSE("fibonacci", "First leaf call", OTHER, std::move(cause)); static const unsigned long long fibStart = 37; printf("Fibonacci(%llu)...\n", fibStart); AUTO_BASE_PROFILER_LABEL("Label around Fibonacci", OTHER); unsigned long long f = Fibonacci(fibStart); printf("Fibonacci(%llu) = %llu\n", fibStart, f); }); std::thread threadCancelFib([]() { AUTO_BASE_PROFILER_REGISTER_THREAD("fibonacci canceller"); SleepMilli(5); AUTO_BASE_PROFILER_TEXT_MARKER_CAUSE("fibonacci", "Canceller", OTHER, nullptr); static const int waitMaxSeconds = 10; for (int i = 0; i < waitMaxSeconds; ++i) { if (sStopFibonacci) { AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER, std::to_string(i)); return; } AUTO_BASE_PROFILER_THREAD_SLEEP; SleepMilli(1000); } AUTO_BASE_PROFILER_LABEL_DYNAMIC_STRING("fibCancel", OTHER, "Cancelling!"); sStopFibonacci = true; }); { AUTO_BASE_PROFILER_TEXT_MARKER_CAUSE( "main thread", "joining fibonacci thread", OTHER, nullptr); AUTO_BASE_PROFILER_THREAD_SLEEP; threadFib.join(); } { AUTO_BASE_PROFILER_TEXT_MARKER_CAUSE( "main thread", "joining fibonacci-canceller thread", OTHER, nullptr); sStopFibonacci = true; AUTO_BASE_PROFILER_THREAD_SLEEP; threadCancelFib.join(); } // Just making sure all payloads know how to (de)serialize and stream. baseprofiler::profiler_add_marker( "TracingMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::TracingMarkerPayload("category", baseprofiler::TRACING_EVENT)); auto cause = # if defined(__linux__) || defined(__ANDROID__) // Currently disabled on these platforms, so just return a null. decltype(baseprofiler::profiler_get_backtrace()){}; # else baseprofiler::profiler_get_backtrace(); # endif baseprofiler::profiler_add_marker( "FileIOMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::FileIOMarkerPayload( "operation", "source", "filename", TimeStamp::NowUnfuzzed(), TimeStamp::NowUnfuzzed(), std::move(cause))); baseprofiler::profiler_add_marker( "UserTimingMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::UserTimingMarkerPayload("name", TimeStamp::NowUnfuzzed(), Nothing{}, Nothing{})); baseprofiler::profiler_add_marker( "HangMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::HangMarkerPayload(TimeStamp::NowUnfuzzed(), TimeStamp::NowUnfuzzed())); baseprofiler::profiler_add_marker( "LongTaskMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::LongTaskMarkerPayload(TimeStamp::NowUnfuzzed(), TimeStamp::NowUnfuzzed())); { std::string s = "text payload"; baseprofiler::profiler_add_marker( "TextMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::TextMarkerPayload(s, TimeStamp::NowUnfuzzed(), TimeStamp::NowUnfuzzed())); } baseprofiler::profiler_add_marker( "LogMarkerPayload", baseprofiler::ProfilingCategoryPair::OTHER, baseprofiler::LogMarkerPayload("module", "text", TimeStamp::NowUnfuzzed())); printf("Sleep 1s...\n"); { AUTO_BASE_PROFILER_THREAD_SLEEP; SleepMilli(1000); } Maybe info = baseprofiler::profiler_get_buffer_info(); MOZ_RELEASE_ASSERT(info.isSome()); printf("Profiler buffer range: %llu .. %llu (%llu bytes)\n", static_cast(info->mRangeStart), static_cast(info->mRangeEnd), // sizeof(ProfileBufferEntry) == 9 (static_cast(info->mRangeEnd) - static_cast(info->mRangeStart)) * 9); printf("Stats: min(ns) .. mean(ns) .. max(ns) [count]\n"); printf("- Intervals: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mIntervalsNs.min, info->mIntervalsNs.sum / info->mIntervalsNs.n, info->mIntervalsNs.max, info->mIntervalsNs.n); printf("- Overheads: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mOverheadsNs.min, info->mOverheadsNs.sum / info->mOverheadsNs.n, info->mOverheadsNs.max, info->mOverheadsNs.n); printf(" - Locking: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mLockingsNs.min, info->mLockingsNs.sum / info->mLockingsNs.n, info->mLockingsNs.max, info->mLockingsNs.n); printf(" - Clearning: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mCleaningsNs.min, info->mCleaningsNs.sum / info->mCleaningsNs.n, info->mCleaningsNs.max, info->mCleaningsNs.n); printf(" - Counters: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mCountersNs.min, info->mCountersNs.sum / info->mCountersNs.n, info->mCountersNs.max, info->mCountersNs.n); printf(" - Threads: %7.1f .. %7.1f .. %7.1f [%u]\n", info->mThreadsNs.min, info->mThreadsNs.sum / info->mThreadsNs.n, info->mThreadsNs.max, info->mThreadsNs.n); printf("baseprofiler_save_profile_to_file()...\n"); baseprofiler::profiler_save_profile_to_file("TestProfiler_profile.json"); printf("profiler_stop()...\n"); baseprofiler::profiler_stop(); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_is_active()); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_being_profiled()); MOZ_RELEASE_ASSERT(!baseprofiler::profiler_thread_is_sleeping()); printf("profiler_shutdown()...\n"); } printf("TestProfiler done\n"); } #else // MOZ_BASE_PROFILER // Testing that macros are still #defined (but do nothing) when // MOZ_BASE_PROFILER is disabled. void TestProfiler() { // These don't need to make sense, we just want to know that they're defined // and don't do anything. AUTO_BASE_PROFILER_INIT; // This wouldn't build if the macro did output its arguments. AUTO_BASE_PROFILER_TEXT_MARKER_CAUSE(catch, catch, catch, catch); AUTO_BASE_PROFILER_LABEL(catch, catch); AUTO_BASE_PROFILER_THREAD_SLEEP; } #endif // MOZ_BASE_PROFILER else int main() { // Note that there are two `TestProfiler` functions above, depending on // whether MOZ_BASE_PROFILER is #defined. TestProfiler(); return 0; }