Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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,
#include "BaseProfiler.h"
#include "mozilla/Attributes.h"
#include "mozilla/BaseAndGeckoProfilerDetail.h"
#include "mozilla/BaseProfileJSONWriter.h"
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/FailureLatch.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/NotNull.h"
#include "mozilla/ProgressLogger.h"
#include "mozilla/ProportionValue.h"
#ifdef MOZ_GECKO_PROFILER
# include "mozilla/BaseProfilerMarkerTypes.h"
# include "mozilla/leb128iterator.h"
# include "mozilla/ModuloBuffer.h"
# include "mozilla/mozalloc.h"
# include "mozilla/PowerOfTwo.h"
# include "mozilla/ProfileBufferChunk.h"
# include "mozilla/ProfileBufferChunkManagerSingle.h"
# include "mozilla/ProfileBufferChunkManagerWithLocalLimit.h"
# include "mozilla/ProfileBufferControlledChunkManager.h"
# include "mozilla/ProfileChunkedBuffer.h"
# include "mozilla/Vector.h"
#endif // MOZ_GECKO_PROFILER
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <windows.h>
# include <mmsystem.h>
# include <process.h>
#else
# include <errno.h>
# include <time.h>
#endif
#include <algorithm>
#include <atomic>
#include <iostream>
#include <random>
#include <thread>
#include <type_traits>
#include <utility>
void TestFailureLatch() {
printf("TestFailureLatch...\n");
// Test infallible latch.
{
mozilla::FailureLatchInfallibleSource& infallibleLatch =
mozilla::FailureLatchInfallibleSource::Singleton();
MOZ_RELEASE_ASSERT(!infallibleLatch.Fallible());
MOZ_RELEASE_ASSERT(!infallibleLatch.Failed());
MOZ_RELEASE_ASSERT(!infallibleLatch.GetFailure());
MOZ_RELEASE_ASSERT(&infallibleLatch.SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
MOZ_RELEASE_ASSERT(&std::as_const(infallibleLatch).SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
}
// Test failure latch basic functions.
{
mozilla::FailureLatchSource failureLatch;
MOZ_RELEASE_ASSERT(failureLatch.Fallible());
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
MOZ_RELEASE_ASSERT(&failureLatch.SourceFailureLatch() == &failureLatch);
MOZ_RELEASE_ASSERT(&std::as_const(failureLatch).SourceFailureLatch() ==
&failureLatch);
failureLatch.SetFailure("error");
MOZ_RELEASE_ASSERT(failureLatch.Fallible());
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
failureLatch.SetFailure("later error");
MOZ_RELEASE_ASSERT(failureLatch.Fallible());
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(failureLatch.GetFailure());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
}
// Test SetFailureFrom.
{
mozilla::FailureLatchSource failureLatch;
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
failureLatch.SetFailureFrom(failureLatch);
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
// SetFailureFrom with no error.
{
mozilla::FailureLatchSource failureLatchInnerOk;
MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
failureLatch.SetFailureFrom(failureLatchInnerOk);
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
MOZ_RELEASE_ASSERT(!failureLatchInnerOk.Failed());
MOZ_RELEASE_ASSERT(!failureLatchInnerOk.GetFailure());
}
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
MOZ_RELEASE_ASSERT(!failureLatch.GetFailure());
// SetFailureFrom with error.
{
mozilla::FailureLatchSource failureLatchInnerError;
MOZ_RELEASE_ASSERT(!failureLatchInnerError.Failed());
MOZ_RELEASE_ASSERT(!failureLatchInnerError.GetFailure());
failureLatchInnerError.SetFailure("inner error");
MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
MOZ_RELEASE_ASSERT(
strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);
MOZ_RELEASE_ASSERT(!failureLatch.Failed());
failureLatch.SetFailureFrom(failureLatchInnerError);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
MOZ_RELEASE_ASSERT(
strcmp(failureLatchInnerError.GetFailure(), "inner error") == 0);
}
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
failureLatch.SetFailureFrom(failureLatch);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
// SetFailureFrom with error again, ignored.
{
mozilla::FailureLatchSource failureLatchInnerError;
failureLatchInnerError.SetFailure("later inner error");
MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
"later inner error") == 0);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
failureLatch.SetFailureFrom(failureLatchInnerError);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(failureLatchInnerError.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatchInnerError.GetFailure(),
"later inner error") == 0);
}
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "inner error") == 0);
}
// Test FAILURELATCH_IMPL_PROXY
{
class Proxy final : public mozilla::FailureLatch {
public:
explicit Proxy(mozilla::FailureLatch& aFailureLatch)
: mFailureLatch(WrapNotNull(&aFailureLatch)) {}
void Set(mozilla::FailureLatch& aFailureLatch) {
mFailureLatch = WrapNotNull(&aFailureLatch);
}
FAILURELATCH_IMPL_PROXY(*mFailureLatch)
private:
mozilla::NotNull<mozilla::FailureLatch*> mFailureLatch;
};
Proxy proxy{mozilla::FailureLatchInfallibleSource::Singleton()};
MOZ_RELEASE_ASSERT(!proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
// Error from proxy.
{
mozilla::FailureLatchSource failureLatch;
proxy.Set(failureLatch);
MOZ_RELEASE_ASSERT(proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&failureLatch);
proxy.SetFailure("error");
MOZ_RELEASE_ASSERT(proxy.Failed());
MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
// Don't forget to stop pointing at soon-to-be-destroyed object.
proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
}
// Error from proxy's origin.
{
mozilla::FailureLatchSource failureLatch;
proxy.Set(failureLatch);
MOZ_RELEASE_ASSERT(proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&failureLatch);
failureLatch.SetFailure("error");
MOZ_RELEASE_ASSERT(proxy.Failed());
MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
// Don't forget to stop pointing at soon-to-be-destroyed object.
proxy.Set(mozilla::FailureLatchInfallibleSource::Singleton());
}
MOZ_RELEASE_ASSERT(!proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
}
// Test FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE
{
class ProxyOrNull final : public mozilla::FailureLatch {
public:
ProxyOrNull() = default;
void Set(mozilla::FailureLatch* aFailureLatchOrNull) {
mFailureLatchOrNull = aFailureLatchOrNull;
}
FAILURELATCH_IMPL_PROXY_OR_INFALLIBLE(mFailureLatchOrNull, ProxyOrNull)
private:
mozilla::FailureLatch* mFailureLatchOrNull = nullptr;
};
ProxyOrNull proxy;
MOZ_RELEASE_ASSERT(!proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
// Error from proxy.
{
mozilla::FailureLatchSource failureLatch;
proxy.Set(&failureLatch);
MOZ_RELEASE_ASSERT(proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&failureLatch);
proxy.SetFailure("error");
MOZ_RELEASE_ASSERT(proxy.Failed());
MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
// Don't forget to stop pointing at soon-to-be-destroyed object.
proxy.Set(nullptr);
}
// Error from proxy's origin.
{
mozilla::FailureLatchSource failureLatch;
proxy.Set(&failureLatch);
MOZ_RELEASE_ASSERT(proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() == &failureLatch);
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&failureLatch);
failureLatch.SetFailure("error");
MOZ_RELEASE_ASSERT(proxy.Failed());
MOZ_RELEASE_ASSERT(strcmp(proxy.GetFailure(), "error") == 0);
MOZ_RELEASE_ASSERT(failureLatch.Failed());
MOZ_RELEASE_ASSERT(strcmp(failureLatch.GetFailure(), "error") == 0);
// Don't forget to stop pointing at soon-to-be-destroyed object.
proxy.Set(nullptr);
}
MOZ_RELEASE_ASSERT(!proxy.Fallible());
MOZ_RELEASE_ASSERT(!proxy.Failed());
MOZ_RELEASE_ASSERT(!proxy.GetFailure());
MOZ_RELEASE_ASSERT(&proxy.SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
MOZ_RELEASE_ASSERT(&std::as_const(proxy).SourceFailureLatch() ==
&mozilla::FailureLatchInfallibleSource::Singleton());
}
printf("TestFailureLatch done\n");
}
void TestProfilerUtils() {
printf("TestProfilerUtils...\n");
{
using mozilla::baseprofiler::BaseProfilerProcessId;
using Number = BaseProfilerProcessId::NumberType;
static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();
static_assert(
BaseProfilerProcessId{}.ToNumber() == 0,
"These tests assume that the unspecified process id number is 0; "
"if this fails, please update these tests accordingly");
static_assert(!BaseProfilerProcessId{}.IsSpecified());
static_assert(!BaseProfilerProcessId::FromNumber(0).IsSpecified());
static_assert(BaseProfilerProcessId::FromNumber(1).IsSpecified());
static_assert(BaseProfilerProcessId::FromNumber(123).IsSpecified());
static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).IsSpecified());
static_assert(BaseProfilerProcessId::FromNumber(Number(1)).ToNumber() ==
Number(1));
static_assert(BaseProfilerProcessId::FromNumber(Number(123)).ToNumber() ==
Number(123));
static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber).ToNumber() ==
scMaxNumber);
static_assert(BaseProfilerProcessId{} == BaseProfilerProcessId{});
static_assert(BaseProfilerProcessId::FromNumber(Number(123)) ==
BaseProfilerProcessId::FromNumber(Number(123)));
static_assert(BaseProfilerProcessId{} !=
BaseProfilerProcessId::FromNumber(Number(123)));
static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
BaseProfilerProcessId{});
static_assert(BaseProfilerProcessId::FromNumber(Number(123)) !=
BaseProfilerProcessId::FromNumber(scMaxNumber));
static_assert(BaseProfilerProcessId::FromNumber(scMaxNumber) !=
BaseProfilerProcessId::FromNumber(Number(123)));
// Verify trivial-copyability by memcpy'ing to&from same-size storage.
static_assert(std::is_trivially_copyable_v<BaseProfilerProcessId>);
BaseProfilerProcessId pid;
MOZ_RELEASE_ASSERT(!pid.IsSpecified());
Number pidStorage;
static_assert(sizeof(pidStorage) == sizeof(pid));
// Copy from BaseProfilerProcessId to storage. Note: We cannot assume that
// this is equal to what ToNumber() gives us. All we can do is verify that
// copying from storage back to BaseProfilerProcessId works as expected.
std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
BaseProfilerProcessId pid2 = BaseProfilerProcessId::FromNumber(2);
MOZ_RELEASE_ASSERT(pid2.IsSpecified());
std::memcpy(&pid2, &pidStorage, sizeof(pid));
MOZ_RELEASE_ASSERT(!pid2.IsSpecified());
pid = BaseProfilerProcessId::FromNumber(123);
std::memcpy(&pidStorage, &pid, sizeof(pidStorage));
pid2 = BaseProfilerProcessId{};
MOZ_RELEASE_ASSERT(!pid2.IsSpecified());
std::memcpy(&pid2, &pidStorage, sizeof(pid));
MOZ_RELEASE_ASSERT(pid2.IsSpecified());
MOZ_RELEASE_ASSERT(pid2.ToNumber() == 123);
// No conversions to/from numbers.
static_assert(!std::is_constructible_v<BaseProfilerProcessId, Number>);
static_assert(!std::is_assignable_v<BaseProfilerProcessId, Number>);
static_assert(!std::is_constructible_v<Number, BaseProfilerProcessId>);
static_assert(!std::is_assignable_v<Number, BaseProfilerProcessId>);
static_assert(
std::is_same_v<
decltype(mozilla::baseprofiler::profiler_current_process_id()),
BaseProfilerProcessId>);
MOZ_RELEASE_ASSERT(
mozilla::baseprofiler::profiler_current_process_id().IsSpecified());
}
{
mozilla::baseprofiler::profiler_init_main_thread_id();
using mozilla::baseprofiler::BaseProfilerThreadId;
using Number = BaseProfilerThreadId::NumberType;
static constexpr Number scMaxNumber = std::numeric_limits<Number>::max();
static_assert(
BaseProfilerThreadId{}.ToNumber() == 0,
"These tests assume that the unspecified thread id number is 0; "
"if this fails, please update these tests accordingly");
static_assert(!BaseProfilerThreadId{}.IsSpecified());
static_assert(!BaseProfilerThreadId::FromNumber(0).IsSpecified());
static_assert(BaseProfilerThreadId::FromNumber(1).IsSpecified());
static_assert(BaseProfilerThreadId::FromNumber(123).IsSpecified());
static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).IsSpecified());
static_assert(BaseProfilerThreadId::FromNumber(Number(1)).ToNumber() ==
Number(1));
static_assert(BaseProfilerThreadId::FromNumber(Number(123)).ToNumber() ==
Number(123));
static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber).ToNumber() ==
scMaxNumber);
static_assert(BaseProfilerThreadId{} == BaseProfilerThreadId{});
static_assert(BaseProfilerThreadId::FromNumber(Number(123)) ==
BaseProfilerThreadId::FromNumber(Number(123)));
static_assert(BaseProfilerThreadId{} !=
BaseProfilerThreadId::FromNumber(Number(123)));
static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
BaseProfilerThreadId{});
static_assert(BaseProfilerThreadId::FromNumber(Number(123)) !=
BaseProfilerThreadId::FromNumber(scMaxNumber));
static_assert(BaseProfilerThreadId::FromNumber(scMaxNumber) !=
BaseProfilerThreadId::FromNumber(Number(123)));
// Verify trivial-copyability by memcpy'ing to&from same-size storage.
static_assert(std::is_trivially_copyable_v<BaseProfilerThreadId>);
BaseProfilerThreadId tid;
MOZ_RELEASE_ASSERT(!tid.IsSpecified());
Number tidStorage;
static_assert(sizeof(tidStorage) == sizeof(tid));
// Copy from BaseProfilerThreadId to storage. Note: We cannot assume that
// this is equal to what ToNumber() gives us. All we can do is verify that
// copying from storage back to BaseProfilerThreadId works as expected.
std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
BaseProfilerThreadId tid2 = BaseProfilerThreadId::FromNumber(2);
MOZ_RELEASE_ASSERT(tid2.IsSpecified());
std::memcpy(&tid2, &tidStorage, sizeof(tid));
MOZ_RELEASE_ASSERT(!tid2.IsSpecified());
tid = BaseProfilerThreadId::FromNumber(Number(123));
std::memcpy(&tidStorage, &tid, sizeof(tidStorage));
tid2 = BaseProfilerThreadId{};
MOZ_RELEASE_ASSERT(!tid2.IsSpecified());
std::memcpy(&tid2, &tidStorage, sizeof(tid));
MOZ_RELEASE_ASSERT(tid2.IsSpecified());
MOZ_RELEASE_ASSERT(tid2.ToNumber() == Number(123));
// No conversions to/from numbers.
static_assert(!std::is_constructible_v<BaseProfilerThreadId, Number>);
static_assert(!std::is_assignable_v<BaseProfilerThreadId, Number>);
static_assert(!std::is_constructible_v<Number, BaseProfilerThreadId>);
static_assert(!std::is_assignable_v<Number, BaseProfilerThreadId>);
static_assert(std::is_same_v<
decltype(mozilla::baseprofiler::profiler_current_thread_id()),
BaseProfilerThreadId>);
BaseProfilerThreadId mainTestThreadId =
mozilla::baseprofiler::profiler_current_thread_id();
MOZ_RELEASE_ASSERT(mainTestThreadId.IsSpecified());
BaseProfilerThreadId mainThreadId =
mozilla::baseprofiler::profiler_main_thread_id();
MOZ_RELEASE_ASSERT(mainThreadId.IsSpecified());
MOZ_RELEASE_ASSERT(mainThreadId == mainTestThreadId,
"Test should run on the main thread");
MOZ_RELEASE_ASSERT(mozilla::baseprofiler::profiler_is_main_thread());
std::thread testThread([&]() {
const BaseProfilerThreadId testThreadId =
mozilla::baseprofiler::profiler_current_thread_id();
MOZ_RELEASE_ASSERT(testThreadId.IsSpecified());
MOZ_RELEASE_ASSERT(testThreadId != mainThreadId);
MOZ_RELEASE_ASSERT(!mozilla::baseprofiler::profiler_is_main_thread());
});
testThread.join();
}
// No conversions between processes and threads.
static_assert(
!std::is_constructible_v<mozilla::baseprofiler::BaseProfilerThreadId,
mozilla::baseprofiler::BaseProfilerProcessId>);
static_assert(
!std::is_assignable_v<mozilla::baseprofiler::BaseProfilerThreadId,
mozilla::baseprofiler::BaseProfilerProcessId>);
static_assert(
!std::is_constructible_v<mozilla::baseprofiler::BaseProfilerProcessId,
mozilla::baseprofiler::BaseProfilerThreadId>);
static_assert(
!std::is_assignable_v<mozilla::baseprofiler::BaseProfilerProcessId,
mozilla::baseprofiler::BaseProfilerThreadId>);
printf("TestProfilerUtils done\n");
}
void TestBaseAndProfilerDetail() {
printf("TestBaseAndProfilerDetail...\n");
{
using mozilla::profiler::detail::FilterHasPid;
const auto pid123 =
mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);
MOZ_RELEASE_ASSERT(FilterHasPid("pid:123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid(" ", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid=123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:123 ", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid: 123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0000000000000000000000123", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:12", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:1234", pid123));
MOZ_RELEASE_ASSERT(!FilterHasPid("pid:0", pid123));
using PidNumber = mozilla::baseprofiler::BaseProfilerProcessId::NumberType;
const PidNumber maxNumber = std::numeric_limits<PidNumber>::max();
const auto maxPid =
mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(maxNumber);
const std::string maxPidString = "pid:" + std::to_string(maxNumber);
MOZ_RELEASE_ASSERT(FilterHasPid(maxPidString.c_str(), maxPid));
const std::string tooBigPidString = maxPidString + "0";
MOZ_RELEASE_ASSERT(!FilterHasPid(tooBigPidString.c_str(), maxPid));
}
{
using mozilla::profiler::detail::FiltersExcludePid;
const auto pid123 =
mozilla::baseprofiler::BaseProfilerProcessId::FromNumber(123);
MOZ_RELEASE_ASSERT(
!FiltersExcludePid(mozilla::Span<const char*>{}, pid123));
{
const char* const filters[] = {"main"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"main", "pid:123"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"main", "pid:456"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"pid:123"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"pid:123", "pid:456"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"pid:456", "pid:123"};
MOZ_RELEASE_ASSERT(!FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"pid:456"};
MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
}
{
const char* const filters[] = {"pid:456", "pid:789"};
MOZ_RELEASE_ASSERT(FiltersExcludePid(filters, pid123));
}
}
printf("TestBaseAndProfilerDetail done\n");
}
void TestSharedMutex() {
printf("TestSharedMutex...\n");
mozilla::baseprofiler::detail::BaseProfilerSharedMutex sm;
// First round of minimal tests in this thread.
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
sm.LockExclusive();
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
sm.UnlockExclusive();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
sm.LockShared();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
sm.UnlockShared();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
{
mozilla::baseprofiler::detail::BaseProfilerAutoLockExclusive exclusiveLock{
sm};
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
}
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
{
mozilla::baseprofiler::detail::BaseProfilerAutoLockShared sharedLock{sm};
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
}
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
// The following will run actions between two threads, to verify that
// exclusive and shared locks work as expected.
// These actions will happen from top to bottom.
// This will test all possible lock interactions.
enum NextAction { // State of the lock:
t1Starting, // (x=exclusive, s=shared, ?=blocked)
t2Starting, // t1 t2
t1LockExclusive, // x
t2LockExclusiveAndBlock, // x x? - Can't have two exclusives.
t1UnlockExclusive, // x
t2UnblockedAfterT1Unlock, // x
t1LockSharedAndBlock, // s? x - Can't have shared during excl
t2UnlockExclusive, // s
t1UnblockedAfterT2Unlock, // s
t2LockShared, // s s - Can have multiple shared locks
t1UnlockShared, // s
t2StillLockedShared, // s
t1LockExclusiveAndBlock, // x? s - Can't have excl during shared
t2UnlockShared, // x
t1UnblockedAfterT2UnlockShared, // x
t2CheckAfterT1Lock, // x
t1LastUnlockExclusive, // (unlocked)
done
};
// Each thread will repeatedly read this `nextAction`, and run actions that
// target it...
std::atomic<NextAction> nextAction{static_cast<NextAction>(0)};
// ... and advance to the next available action (which should usually be for
// the other thread).
auto AdvanceAction = [&nextAction]() {
MOZ_RELEASE_ASSERT(nextAction <= done);
nextAction = static_cast<NextAction>(static_cast<int>(nextAction) + 1);
};
std::thread t1{[&]() {
for (;;) {
switch (nextAction) {
case t1Starting:
AdvanceAction();
break;
case t1LockExclusive:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
sm.LockExclusive();
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case t1UnlockExclusive:
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
// Advance first, before unlocking, so that t2 sees the new state.
AdvanceAction();
sm.UnlockExclusive();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
break;
case t1LockSharedAndBlock:
// Advance action before attempting to lock after t2's exclusive lock.
AdvanceAction();
sm.LockShared();
// We will only acquire the lock after t1 unlocks.
MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2Unlock);
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case t1UnlockShared:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
// Advance first, before unlocking, so that t2 sees the new state.
AdvanceAction();
sm.UnlockShared();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
break;
case t1LockExclusiveAndBlock:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
// Advance action before attempting to lock after t2's shared lock.
AdvanceAction();
sm.LockExclusive();
// We will only acquire the lock after t2 unlocks.
MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2UnlockShared);
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case t1LastUnlockExclusive:
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
// Advance first, before unlocking, so that t2 sees the new state.
AdvanceAction();
sm.UnlockExclusive();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
break;
case done:
return;
default:
// Ignore other actions intended for t2.
break;
}
}
}};
std::thread t2{[&]() {
for (;;) {
switch (nextAction) {
case t2Starting:
AdvanceAction();
break;
case t2LockExclusiveAndBlock:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
// Advance action before attempting to lock after t1's exclusive lock.
AdvanceAction();
sm.LockExclusive();
// We will only acquire the lock after t1 unlocks.
MOZ_RELEASE_ASSERT(nextAction == t2UnblockedAfterT1Unlock);
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case t2UnlockExclusive:
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
// Advance first, before unlocking, so that t1 sees the new state.
AdvanceAction();
sm.UnlockExclusive();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
break;
case t2LockShared:
sm.LockShared();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case t2StillLockedShared:
AdvanceAction();
break;
case t2UnlockShared:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
// Advance first, before unlocking, so that t1 sees the new state.
AdvanceAction();
sm.UnlockShared();
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
break;
case t2CheckAfterT1Lock:
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
AdvanceAction();
break;
case done:
return;
default:
// Ignore other actions intended for t1.
break;
}
}
}};
t1.join();
t2.join();
printf("TestSharedMutex done\n");
}
void TestProportionValue() {
printf("TestProportionValue...\n");
using mozilla::ProportionValue;
#define STATIC_ASSERT_EQ(a, b) \
static_assert((a) == (b)); \
MOZ_RELEASE_ASSERT((a) == (b));
#define STATIC_ASSERT(e) STATIC_ASSERT_EQ(e, true)
// Conversion from&to double.
STATIC_ASSERT_EQ(ProportionValue().ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(0.0).ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(0.5).ToDouble(), 0.5);
STATIC_ASSERT_EQ(ProportionValue(1.0).ToDouble(), 1.0);
// Clamping.
STATIC_ASSERT_EQ(
ProportionValue(std::numeric_limits<double>::min()).ToDouble(), 0.0);
STATIC_ASSERT_EQ(
ProportionValue(std::numeric_limits<long double>::min()).ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(-1.0).ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(-0.01).ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(-0.0).ToDouble(), 0.0);
STATIC_ASSERT_EQ(ProportionValue(1.01).ToDouble(), 1.0);
STATIC_ASSERT_EQ(
ProportionValue(std::numeric_limits<double>::max()).ToDouble(), 1.0);
// User-defined literal.
{
using namespace mozilla::literals::ProportionValue_literals;
STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
}
{
// ProportionValue_literals is an inline namespace of mozilla::literals, so
// it's optional.
using namespace mozilla::literals;
STATIC_ASSERT_EQ(0_pc, ProportionValue(0.0));
STATIC_ASSERT_EQ(0._pc, ProportionValue(0.0));
STATIC_ASSERT_EQ(50_pc, ProportionValue(0.5));
STATIC_ASSERT_EQ(50._pc, ProportionValue(0.5));
STATIC_ASSERT_EQ(100_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(100._pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(101_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(100.01_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(1000_pc, ProportionValue(1.0));
STATIC_ASSERT_EQ(1000._pc, ProportionValue(1.0));
}
// Invalid construction, conversion to double NaN.
MOZ_RELEASE_ASSERT(std::isnan(ProportionValue::MakeInvalid().ToDouble()));
using namespace mozilla::literals::ProportionValue_literals;
// Conversion to&from underlying integral number.
STATIC_ASSERT_EQ(
ProportionValue::FromUnderlyingType((0_pc).ToUnderlyingType()).ToDouble(),
0.0);
STATIC_ASSERT_EQ(
ProportionValue::FromUnderlyingType((50_pc).ToUnderlyingType())
.ToDouble(),
0.5);
STATIC_ASSERT_EQ(
ProportionValue::FromUnderlyingType((100_pc).ToUnderlyingType())
.ToDouble(),
1.0);
STATIC_ASSERT(ProportionValue::FromUnderlyingType(
ProportionValue::MakeInvalid().ToUnderlyingType())
.IsInvalid());
// IsExactlyZero.
STATIC_ASSERT(ProportionValue().IsExactlyZero());
STATIC_ASSERT((0_pc).IsExactlyZero());
STATIC_ASSERT(!(50_pc).IsExactlyZero());
STATIC_ASSERT(!(100_pc).IsExactlyZero());
STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyZero());
// IsExactlyOne.
STATIC_ASSERT(!ProportionValue().IsExactlyOne());
STATIC_ASSERT(!(0_pc).IsExactlyOne());
STATIC_ASSERT(!(50_pc).IsExactlyOne());
STATIC_ASSERT((100_pc).IsExactlyOne());
STATIC_ASSERT(!ProportionValue::MakeInvalid().IsExactlyOne());
// IsValid.
STATIC_ASSERT(ProportionValue().IsValid());
STATIC_ASSERT((0_pc).IsValid());
STATIC_ASSERT((50_pc).IsValid());
STATIC_ASSERT((100_pc).IsValid());
STATIC_ASSERT(!ProportionValue::MakeInvalid().IsValid());
// IsInvalid.
STATIC_ASSERT(!ProportionValue().IsInvalid());
STATIC_ASSERT(!(0_pc).IsInvalid());
STATIC_ASSERT(!(50_pc).IsInvalid());
STATIC_ASSERT(!(100_pc).IsInvalid());
STATIC_ASSERT(ProportionValue::MakeInvalid().IsInvalid());
// Addition.
STATIC_ASSERT_EQ((0_pc + 0_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((0_pc + 100_pc).ToDouble(), 1.0);
STATIC_ASSERT_EQ((100_pc + 0_pc).ToDouble(), 1.0);
STATIC_ASSERT_EQ((100_pc + 100_pc).ToDouble(), 1.0);
STATIC_ASSERT((ProportionValue::MakeInvalid() + 50_pc).IsInvalid());
STATIC_ASSERT((50_pc + ProportionValue::MakeInvalid()).IsInvalid());
// Subtraction.
STATIC_ASSERT_EQ((0_pc - 0_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((0_pc - 100_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((100_pc - 0_pc).ToDouble(), 1.0);
STATIC_ASSERT_EQ((100_pc - 100_pc).ToDouble(), 0.0);
STATIC_ASSERT((ProportionValue::MakeInvalid() - 50_pc).IsInvalid());
STATIC_ASSERT((50_pc - ProportionValue::MakeInvalid()).IsInvalid());
// Multiplication.
STATIC_ASSERT_EQ((0_pc * 0_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((0_pc * 100_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((50_pc * 50_pc).ToDouble(), 0.25);
STATIC_ASSERT_EQ((50_pc * 100_pc).ToDouble(), 0.5);
STATIC_ASSERT_EQ((100_pc * 50_pc).ToDouble(), 0.5);
STATIC_ASSERT_EQ((100_pc * 0_pc).ToDouble(), 0.0);
STATIC_ASSERT_EQ((100_pc * 100_pc).ToDouble(), 1.0);
STATIC_ASSERT((ProportionValue::MakeInvalid() * 50_pc).IsInvalid());
STATIC_ASSERT((50_pc * ProportionValue::MakeInvalid()).IsInvalid());
// Division by a positive integer value.
STATIC_ASSERT_EQ((100_pc / 1u).ToDouble(), 1.0);
STATIC_ASSERT_EQ((100_pc / 2u).ToDouble(), 0.5);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(6u) / 2u).ToUnderlyingType(), 3u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(5u) / 2u).ToUnderlyingType(), 2u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(1u) / 2u).ToUnderlyingType(), 0u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(0u) / 2u).ToUnderlyingType(), 0u);
STATIC_ASSERT((100_pc / 0u).IsInvalid());
STATIC_ASSERT((ProportionValue::MakeInvalid() / 2u).IsInvalid());
// Multiplication by a positive integer value.
STATIC_ASSERT_EQ((100_pc * 1u).ToDouble(), 1.0);
STATIC_ASSERT_EQ((50_pc * 1u).ToDouble(), 0.5);
STATIC_ASSERT_EQ((50_pc * 2u).ToDouble(), 1.0);
STATIC_ASSERT_EQ((50_pc * 3u).ToDouble(), 1.0); // Clamped.
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(1u) * 2u).ToUnderlyingType(), 2u);
STATIC_ASSERT((ProportionValue::MakeInvalid() * 2u).IsInvalid());
// Verifying PV - u < (PV / u) * u <= PV, with n=3, PV between 6 and 9 :
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(6u) / 3u).ToUnderlyingType(), 2u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(7u) / 3u).ToUnderlyingType(), 2u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(8u) / 3u).ToUnderlyingType(), 2u);
STATIC_ASSERT_EQ(
(ProportionValue::FromUnderlyingType(9u) / 3u).ToUnderlyingType(), 3u);
// Direct comparisons.
STATIC_ASSERT_EQ(0_pc, 0_pc);
STATIC_ASSERT(0_pc == 0_pc);
STATIC_ASSERT(!(0_pc == 100_pc));
STATIC_ASSERT(0_pc != 100_pc);
STATIC_ASSERT(!(0_pc != 0_pc));
STATIC_ASSERT(0_pc < 100_pc);
STATIC_ASSERT(!(0_pc < 0_pc));
STATIC_ASSERT(0_pc <= 0_pc);
STATIC_ASSERT(0_pc <= 100_pc);
STATIC_ASSERT(!(100_pc <= 0_pc));
STATIC_ASSERT(100_pc > 0_pc);
STATIC_ASSERT(!(100_pc > 100_pc));
STATIC_ASSERT(100_pc >= 0_pc);
STATIC_ASSERT(100_pc >= 100_pc);
STATIC_ASSERT(!(0_pc >= 100_pc));
// 0.5 is binary-friendly, so we can double it and compare it exactly.
STATIC_ASSERT_EQ(50_pc + 50_pc, 100_pc);
#undef STATIC_ASSERT_EQ
printf("TestProportionValue done\n");
}
template <typename Arg0, typename... Args>
bool AreAllEqual(Arg0&& aArg0, Args&&... aArgs) {
return ((aArg0 == aArgs) && ...);
}
void TestProgressLogger() {
printf("TestProgressLogger...\n");
using mozilla::ProgressLogger;
using mozilla::ProportionValue;
using namespace mozilla::literals::ProportionValue_literals;
auto progressRefPtr = mozilla::MakeRefPtr<ProgressLogger::SharedProgress>();
MOZ_RELEASE_ASSERT(progressRefPtr);
MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
{
ProgressLogger pl(progressRefPtr, "Started", "All done");
MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(), "Started"));
// At this top level, the scale is 1:1.
pl.SetLocalProgress(10_pc, "Top 10%");
MOZ_RELEASE_ASSERT(
AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 10_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(), "Top 10%"));
pl.SetLocalProgress(0_pc, "Restarted");
MOZ_RELEASE_ASSERT(
AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(), 0_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(), "Restarted"));
{
// Create a sub-logger for the whole global range. Notice that this is
// moving the current progress back to 0.
ProgressLogger plSub1 =
pl.CreateSubLoggerFromTo(0_pc, "Sub1 started", 100_pc, "Sub1 ended");
MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyZero());
MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyZero());
MOZ_RELEASE_ASSERT(plSub1.GetGlobalProgress().IsExactlyZero());
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), "Sub1 started"));
// At this level, the scale is still 1:1.
plSub1.SetLocalProgress(10_pc, "Sub1 10%");
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), 10_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), "Sub1 10%"));
{
// Create a sub-logger half the global range.
// 0 0.25 0.375 0.5 0.625 0.75 1
// |---------------|-------|-------|-------|-------|---------------|
// plSub2: 0 0.25 0.5 0.75 1
ProgressLogger plSub2 = plSub1.CreateSubLoggerFromTo(
25_pc, "Sub2 started", 75_pc, "Sub2 ended");
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 25_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
"Sub2 started"));
plSub2.SetLocalProgress(25_pc, "Sub2 25%");
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 37.5_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
"Sub2 25%"));
plSub2.SetLocalProgress(50_pc, "Sub2 50%");
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 50_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
"Sub2 50%"));
{
// Create a sub-logger half the parent range.
// 0 0.25 0.375 0.5 0.625 0.75 1
// |---------------|-------|-------|-------|-------|---------------|
// plSub2: 0 0.25 0.5 0.75 1
// plSub3: 0 0.5 1
ProgressLogger plSub3 = plSub2.CreateSubLoggerTo(
"Sub3 started", 100_pc, ProgressLogger::NO_LOCATION_UPDATE);
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
plSub3.GetGlobalProgress(), 50_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
plSub3.GetLastGlobalLocation(), "Sub3 started"));
plSub3.SetLocalProgress(50_pc, "Sub3 50%");
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(),
plSub3.GetGlobalProgress(), 62.5_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
plSub3.GetLastGlobalLocation(), "Sub3 50%"));
} // End of plSub3
// When plSub3 ends, progress moves to its 100%, which is also plSub2's
// 100%, which is plSub1's and the global progress of 75%
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->Progress(), pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), plSub2.GetGlobalProgress(), 75_pc));
// But location is still at the last explicit update.
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), plSub2.GetLastGlobalLocation(),
"Sub3 50%"));
} // End of plSub2
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
pl.GetGlobalProgress(),
plSub1.GetGlobalProgress(), 75_pc));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
plSub1.GetLastGlobalLocation(), "Sub2 ended"));
} // End of plSub1
MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
MOZ_RELEASE_ASSERT(pl.GetGlobalProgress().IsExactlyOne());
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(), "Sub1 ended"));
const auto loopStart = 75_pc;
const auto loopEnd = 87.5_pc;
const uint32_t loopCount = 8;
uint32_t expectedIndex = 0u;
auto expectedIterationStart = loopStart;
const auto iterationIncrement = (loopEnd - loopStart) / loopCount;
for (auto&& [index, loopPL] : pl.CreateLoopSubLoggersFromTo(
loopStart, loopEnd, loopCount, "looping...")) {
MOZ_RELEASE_ASSERT(index == expectedIndex);
++expectedIndex;
MOZ_RELEASE_ASSERT(
AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
loopPL.GetGlobalProgress(), expectedIterationStart));
MOZ_RELEASE_ASSERT(AreAllEqual(
progressRefPtr->LastLocation(), pl.GetLastGlobalLocation(),
loopPL.GetLastGlobalLocation(), "looping..."));
loopPL.SetLocalProgress(50_pc, "half");
MOZ_RELEASE_ASSERT(loopPL.GetGlobalProgress() ==
expectedIterationStart + iterationIncrement / 2u);
MOZ_RELEASE_ASSERT(
AreAllEqual(progressRefPtr->Progress(), pl.GetGlobalProgress(),
loopPL.GetGlobalProgress(),
expectedIterationStart + iterationIncrement / 2u));
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(),
loopPL.GetLastGlobalLocation(), "half"));
expectedIterationStart = expectedIterationStart + iterationIncrement;
}
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->Progress(),
pl.GetGlobalProgress(),
expectedIterationStart));
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(),
pl.GetLastGlobalLocation(), "looping..."));
} // End of pl
MOZ_RELEASE_ASSERT(progressRefPtr->Progress().IsExactlyOne());
MOZ_RELEASE_ASSERT(AreAllEqual(progressRefPtr->LastLocation(), "All done"));
printf("TestProgressLogger done\n");
}
#ifdef MOZ_GECKO_PROFILER
MOZ_MAYBE_UNUSED static void SleepMilli(unsigned aMilliseconds) {
# if defined(_MSC_VER) || defined(__MINGW32__)
Sleep(aMilliseconds);
# else
struct timespec ts = {/* .tv_sec */ static_cast<time_t>(aMilliseconds / 1000),
/* ts.tv_nsec */ long(aMilliseconds % 1000) * 1000000};
struct timespec tr = {0, 0};
while (nanosleep(&ts, &tr)) {
if (errno == EINTR) {
ts = tr;
} else {
printf("nanosleep() -> %s\n", strerror(errno));
exit(1);
}
}
# endif
}
MOZ_MAYBE_UNUSED static void WaitUntilTimeStampChanges(
const mozilla::TimeStamp& aTimeStampToCompare = mozilla::TimeStamp::Now()) {
while (aTimeStampToCompare == mozilla::TimeStamp::Now()) {
SleepMilli(1);
}
}
using namespace mozilla;
void TestPowerOfTwoMask() {
printf("TestPowerOfTwoMask...\n");
static_assert(MakePowerOfTwoMask<uint32_t, 0>().MaskValue() == 0);
constexpr PowerOfTwoMask<uint32_t> c0 = MakePowerOfTwoMask<uint32_t, 0>();
MOZ_RELEASE_ASSERT(c0.MaskValue() == 0);
static_assert(MakePowerOfTwoMask<uint32_t, 0xFFu>().MaskValue() == 0xFFu);
constexpr PowerOfTwoMask<uint32_t> cFF =
MakePowerOfTwoMask<uint32_t, 0xFFu>();
MOZ_RELEASE_ASSERT(cFF.MaskValue() == 0xFFu);
static_assert(MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>().MaskValue() ==
0xFFFFFFFFu);
constexpr PowerOfTwoMask<uint32_t> cFFFFFFFF =
MakePowerOfTwoMask<uint32_t, 0xFFFFFFFFu>();
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<uint32_t> 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<uint32_t, 1>().Value() == 1);
constexpr PowerOfTwo<uint32_t> c1 = MakePowerOfTwo<uint32_t, 1>();
MOZ_RELEASE_ASSERT(c1.Value() == 1);
static_assert(MakePowerOfTwo<uint32_t, 1>().Mask().MaskValue() == 0);
static_assert(MakePowerOfTwo<uint32_t, 128>().Value() == 128);
constexpr PowerOfTwo<uint32_t> c128 = MakePowerOfTwo<uint32_t, 128>();
MOZ_RELEASE_ASSERT(c128.Value() == 128);
static_assert(MakePowerOfTwo<uint32_t, 128>().Mask().MaskValue() == 127);
static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().Value() == 0x80000000u);
constexpr PowerOfTwo<uint32_t> cMax = MakePowerOfTwo<uint32_t, 0x80000000u>();
MOZ_RELEASE_ASSERT(cMax.Value() == 0x80000000u);
static_assert(MakePowerOfTwo<uint32_t, 0x80000000u>().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<uint32_t> p2(test.mInput);
MOZ_RELEASE_ASSERT(p2.Value() == test.mValue);
MOZ_RELEASE_ASSERT(p2.MaskValue() == test.mMask);
PowerOfTwoMask<uint32_t> 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<uint8_t>() == 2);
MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint16_t>() == 3);
MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint32_t>() == 5);
MOZ_RELEASE_ASSERT(ULEB128MaxSize<uint64_t>() == 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<uint64_t>()];
// 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<uint64_t>(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);
// Testing ULEB128 reader.
ULEB128Reader<uint64_t> reader;
MOZ_RELEASE_ASSERT(!reader.IsComplete());
// Move pointer back to start of buffer.
p = buffer;
for (;;) {
// Read a byte and feed it to the reader.
if (reader.FeedByteIsComplete(*p++)) {
break;
}
// Not complete yet, we shouldn't have reached the end pointer.
MOZ_RELEASE_ASSERT(!reader.IsComplete());
MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
}
MOZ_RELEASE_ASSERT(reader.IsComplete());
// Pointer should have advanced just past the expected LEB128 size.
MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
// And check the read value.
MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);
// And again after a Reset.
reader.Reset();
MOZ_RELEASE_ASSERT(!reader.IsComplete());
p = buffer;
for (;;) {
if (reader.FeedByteIsComplete(*p++)) {
break;
}
MOZ_RELEASE_ASSERT(!reader.IsComplete());
MOZ_RELEASE_ASSERT(p < buffer + test.mSize);
}
MOZ_RELEASE_ASSERT(reader.IsComplete());
MOZ_RELEASE_ASSERT(p == buffer + test.mSize);
MOZ_RELEASE_ASSERT(reader.Value() == test.mValue);
}
printf("TestLEB128 done\n");
}
struct StringWriteFunc final : public JSONWriteFunc {
std::string mString;
void Write(const mozilla::Span<const char>& aStr) final {
mString.append(aStr.data(), aStr.size());
}
};
void CheckJSON(mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
const char* aExpected, int aLine) {
const std::string& actual =
static_cast<StringWriteFunc&>(aWriter.WriteFunc()).mString;
if (strcmp(aExpected, actual.c_str()) != 0) {
fprintf(stderr,
"---- EXPECTED ---- (line %d)\n<<<%s>>>\n"
"---- ACTUAL ----\n<<<%s>>>\n",
aLine, aExpected, actual.c_str());
MOZ_RELEASE_ASSERT(false, "expected and actual output don't match");
}
}
void TestJSONTimeOutput() {
printf("TestJSONTimeOutput...\n");
# define TEST(in, out) \
do { \
mozilla::baseprofiler::SpliceableJSONWriter writer( \
mozilla::MakeUnique<StringWriteFunc>(), \
FailureLatchInfallibleSource::Singleton()); \
writer.Start(); \
writer.TimeDoubleMsProperty("time_ms", (in)); \
writer.End(); \
CheckJSON(writer, "{\"time_ms\":" out "}", __LINE__); \
} while (false);
TEST(0, "0");
TEST(0.000'000'1, "0");
TEST(0.000'000'4, "0");
TEST(0.000'000'499, "0");
TEST(0.000'000'5, "0.000001");
TEST(0.000'001, "0.000001");
TEST(0.000'01, "0.00001");
TEST(0.000'1, "0.0001");
TEST(0.001, "0.001");
TEST(0.01, "0.01");
TEST(0.1, "0.1");
TEST(1, "1");
TEST(2, "2");
TEST(10, "10");
TEST(100, "100");
TEST(1'000, "1000");
TEST(10'000, "10000");
TEST(100'000, "100000");
TEST(1'000'000, "1000000");
// 2^53-2 ns in ms. 2^53-1 is the highest integer value representable in
// double, -1 again because we're adding 0.5 before truncating.
// That's 104 days, after which the nanosecond precision would decrease.
TEST(9'007'199'254.740'990, "9007199254.74099");
TEST(-0.000'000'1, "0");
TEST(-0.000'000'4, "0");
TEST(-0.000'000'499, "0");
TEST(-0.000'000'5, "-0.000001");
TEST(-0.000'001, "-0.000001");
TEST(-0.000'01, "-0.00001");
TEST(-0.000'1, "-0.0001");
TEST(-0.001, "-0.001");
TEST(-0.01, "-0.01");
TEST(-0.1, "-0.1");
TEST(-1, "-1");
TEST(-2, "-2");
TEST(-10, "-10");
TEST(-100, "-100");
TEST(-1'000, "-1000");
TEST(-10'000, "-10000");
TEST(-100'000, "-100000");
TEST(-1'000'000, "-1000000");
TEST(-9'007'199'254.740'990, "-9007199254.74099");
# undef TEST
printf("TestJSONTimeOutput done\n");
}
template <uint8_t byte, uint8_t... tail>
constexpr bool TestConstexprULEB128Reader(ULEB128Reader<uint64_t>& aReader) {
if (aReader.IsComplete()) {
return false;
}
const bool isComplete = aReader.FeedByteIsComplete(byte);
if (aReader.IsComplete() != isComplete) {
return false;
}
if constexpr (sizeof...(tail) == 0) {
return isComplete;
} else {
if (isComplete) {
return false;
}
return TestConstexprULEB128Reader<tail...>(aReader);
}
}
template <uint64_t expected, uint8_t... bytes>
constexpr bool TestConstexprULEB128Reader() {
ULEB128Reader<uint64_t> reader;
if (!TestConstexprULEB128Reader<bytes...>(reader)) {
return false;
}
if (!reader.IsComplete()) {
return false;
}
if (reader.Value() != expected) {
return false;
}
reader.Reset();
if (!TestConstexprULEB128Reader<bytes...>(reader)) {
return false;
}
if (!reader.IsComplete()) {
return false;
}
if (reader.Value() != expected) {
return false;
}
return true;
}
static_assert(TestConstexprULEB128Reader<0x0u, 0x0u>());
static_assert(!TestConstexprULEB128Reader<0x0u, 0x0u, 0x0u>());
static_assert(TestConstexprULEB128Reader<0x1u, 0x1u>());
static_assert(TestConstexprULEB128Reader<0x7Fu, 0x7Fu>());
static_assert(TestConstexprULEB128Reader<0x80u, 0x80u, 0x01u>());
static_assert(!TestConstexprULEB128Reader<0x80u, 0x80u>());
static_assert(!TestConstexprULEB128Reader<0x80u, 0x01u>());
static_assert(TestConstexprULEB128Reader<0x81u, 0x81u, 0x01u>());
static_assert(TestConstexprULEB128Reader<0xFFu, 0xFFu, 0x01u>());
static_assert(TestConstexprULEB128Reader<0x100u, 0x80u, 0x02u>());
static_assert(TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
0xFFu, 0x0Fu>());
static_assert(
!TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());
static_assert(!TestConstexprULEB128Reader<0xFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu,
0xFFu, 0xFFu, 0x0Fu>());
static_assert(
TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0x01u>());
static_assert(
!TestConstexprULEB128Reader<0xFFFFFFFFFFFFFFFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu>());
static void TestChunk() {
printf("TestChunk...\n");
static_assert(!std::is_default_constructible_v<ProfileBufferChunk>,
"ProfileBufferChunk should not be default-constructible");
static_assert(
!std::is_constructible_v<ProfileBufferChunk, ProfileBufferChunk::Length>,
"ProfileBufferChunk should not be constructible from Length");
static_assert(
sizeof(ProfileBufferChunk::Header) ==
sizeof(ProfileBufferChunk::Header::mOffsetFirstBlock) +
sizeof(ProfileBufferChunk::Header::mOffsetPastLastBlock) +
sizeof(ProfileBufferChunk::Header::mStartTimeStamp) +
sizeof(ProfileBufferChunk::Header::mDoneTimeStamp) +
sizeof(ProfileBufferChunk::Header::mBufferBytes) +
sizeof(ProfileBufferChunk::Header::mBlockCount) +
sizeof(ProfileBufferChunk::Header::mRangeStart) +
sizeof(ProfileBufferChunk::Header::mProcessId) +
sizeof(ProfileBufferChunk::Header::mPADDING),
"ProfileBufferChunk::Header may have unwanted padding, please review");
// Note: The above static_assert is an attempt at keeping
// ProfileBufferChunk::Header tightly packed, but some changes could make this
// impossible to achieve (most probably due to alignment) -- Just do your
// best!
constexpr ProfileBufferChunk::Length TestLen = 1000;
// Basic allocations of different sizes.
for (ProfileBufferChunk::Length len = 0; len <= TestLen; ++len) {
auto chunk = ProfileBufferChunk::Create(len);
static_assert(
std::is_same_v<decltype(chunk), UniquePtr<ProfileBufferChunk>>,
"ProfileBufferChunk::Create() should return a "
"UniquePtr<ProfileBufferChunk>");
MOZ_RELEASE_ASSERT(!!chunk, "OOM!?");
MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= len);
MOZ_RELEASE_ASSERT(chunk->ChunkBytes() >=
len + ProfileBufferChunk::SizeofChunkMetadata());
MOZ_RELEASE_ASSERT(chunk->RemainingBytes() == chunk->BufferBytes());
MOZ_RELEASE_ASSERT(chunk->OffsetFirstBlock() == 0);
MOZ_RELEASE_ASSERT(chunk->OffsetPastLastBlock() == 0);
MOZ_RELEASE_ASSERT(chunk->BlockCount() == 0);
MOZ_RELEASE_ASSERT(chunk->ProcessId() == 0);
MOZ_RELEASE_ASSERT(chunk->RangeStart() == 0);
MOZ_RELEASE_ASSERT(chunk->BufferSpan().LengthBytes() ==
chunk->BufferBytes());
MOZ_RELEASE_ASSERT(!chunk->GetNext());
MOZ_RELEASE_ASSERT(!chunk->ReleaseNext());
MOZ_RELEASE_ASSERT(chunk->Last() == chunk.get());
}
// Allocate the main test Chunk.
auto chunkA = ProfileBufferChunk::Create(TestLen);
MOZ_RELEASE_ASSERT(!!chunkA, "OOM!?");
MOZ_RELEASE_ASSERT(chunkA->BufferBytes() >= TestLen);
MOZ_RELEASE_ASSERT(chunkA->ChunkBytes() >=
TestLen + ProfileBufferChunk::SizeofChunkMetadata());
MOZ_RELEASE_ASSERT(!chunkA->GetNext());
MOZ_RELEASE_ASSERT(!chunkA->ReleaseNext());
constexpr ProfileBufferIndex chunkARangeStart = 12345;
chunkA->SetRangeStart(chunkARangeStart);
MOZ_RELEASE_ASSERT(chunkA->RangeStart() == chunkARangeStart);
// Get a read-only span over its buffer.
auto bufferA = chunkA->BufferSpan();
static_assert(
std::is_same_v<decltype(bufferA), Span<const ProfileBufferChunk::Byte>>,
"BufferSpan() should return a Span<const Byte>");
MOZ_RELEASE_ASSERT(bufferA.LengthBytes() == chunkA->BufferBytes());
// Add the initial tail block.
constexpr ProfileBufferChunk::Length initTailLen = 10;
auto initTail = chunkA->ReserveInitialBlockAsTail(initTailLen);
static_assert(
std::is_same_v<decltype(initTail), Span<ProfileBufferChunk::Byte>>,
"ReserveInitialBlockAsTail() should return a Span<Byte>");
MOZ_RELEASE_ASSERT(initTail.LengthBytes() == initTailLen);
MOZ_RELEASE_ASSERT(initTail.Elements() == bufferA.Elements());
MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen);
// Add the first complete block.
constexpr ProfileBufferChunk::Length block1Len = 20;
auto block1 = chunkA->ReserveBlock(block1Len);
static_assert(
std::is_same_v<decltype(block1), ProfileBufferChunk::ReserveReturn>,
"ReserveBlock() should return a ReserveReturn");
MOZ_RELEASE_ASSERT(block1.mBlockRangeIndex.ConvertToProfileBufferIndex() ==
chunkARangeStart + initTailLen);
MOZ_RELEASE_ASSERT(block1.mSpan.LengthBytes() == block1Len);
MOZ_RELEASE_ASSERT(block1.mSpan.Elements() ==
bufferA.Elements() + initTailLen);
MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == initTailLen + block1Len);
MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() != 0);
// Add another block to over-fill the ProfileBufferChunk.
const ProfileBufferChunk::Length remaining =
chunkA->BufferBytes() - (initTailLen + block1Len);
constexpr ProfileBufferChunk::Length overfill = 30;
const ProfileBufferChunk::Length block2Len = remaining + overfill;
ProfileBufferChunk::ReserveReturn block2 = chunkA->ReserveBlock(block2Len);
MOZ_RELEASE_ASSERT(block2.mBlockRangeIndex.ConvertToProfileBufferIndex() ==
chunkARangeStart + initTailLen + block1Len);
MOZ_RELEASE_ASSERT(block2.mSpan.LengthBytes() == remaining);
MOZ_RELEASE_ASSERT(block2.mSpan.Elements() ==
bufferA.Elements() + initTailLen + block1Len);
MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == initTailLen);
MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == chunkA->BufferBytes());
MOZ_RELEASE_ASSERT(chunkA->RemainingBytes() == 0);
// Block must be marked "done" before it can be recycled.
chunkA->MarkDone();
// It must be marked "recycled" before data can be added to it again.
chunkA->MarkRecycled();
// Add an empty initial tail block.
Span<ProfileBufferChunk::Byte> initTail2 =
chunkA->ReserveInitialBlockAsTail(0);
MOZ_RELEASE_ASSERT(initTail2.LengthBytes() == 0);
MOZ_RELEASE_ASSERT(initTail2.Elements() == bufferA.Elements());
MOZ_RELEASE_ASSERT(chunkA->OffsetFirstBlock() == 0);
MOZ_RELEASE_ASSERT(chunkA->OffsetPastLastBlock() == 0);
// Block must be marked "done" before it can be destroyed.
chunkA->MarkDone();
chunkA->SetProcessId(123);
MOZ_RELEASE_ASSERT(chunkA->ProcessId() == 123);
printf("TestChunk done\n");
}
static void TestChunkManagerSingle() {
printf("TestChunkManagerSingle...\n");
// Construct a ProfileBufferChunkManagerSingle for one chunk of size >=1000.
constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 1000;
ProfileBufferChunkManagerSingle cms{ChunkMinBufferBytes};
// Reference to base class, to exercize virtual methods.
ProfileBufferChunkManager& cm = cms;
# ifdef DEBUG
const char* chunkManagerRegisterer = "TestChunkManagerSingle";
cm.RegisteredWith(chunkManagerRegisterer);
# endif // DEBUG
const auto maxTotalSize = cm.MaxTotalSize();
MOZ_RELEASE_ASSERT(maxTotalSize >= ChunkMinBufferBytes);
cm.SetChunkDestroyedCallback([](const ProfileBufferChunk&) {
MOZ_RELEASE_ASSERT(
false,
"ProfileBufferChunkManagerSingle should never destroy its one chunk");
});
UniquePtr<ProfileBufferChunk> extantReleasedChunks =
cm.GetExtantReleasedChunks();
MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
// First request.
UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();