Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/tools/profiler/tests/gtest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 204 kB image not shown  

Quelle  GeckoProfiler.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; 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/. */


// This file tests a lot of the profiler_*() functions in GeckoProfiler.h.
// Most of the tests just check that nothing untoward (e.g. crashes, deadlocks)
// happens when calling these functions. They don't do much inspection of
// profiler internals.

#include "mozilla/ProfilerThreadPlatformData.h"
#include "mozilla/ProfilerThreadRegistration.h"
#include "mozilla/ProfilerThreadRegistrationInfo.h"
#include "mozilla/ProfilerThreadRegistry.h"
#include "mozilla/ProfilerUtils.h"
#include "mozilla/ProgressLogger.h"
#include "mozilla/UniquePtrExtensions.h"

#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "prthread.h"
#include "nsHttp.h"
#include "nsIClassOfService.h"

#include "gtest/gtest.h"
#include "mozilla/gtest/MozAssertions.h"

#include <thread>

#if defined(_MSC_VER) || defined(__MINGW32__)
#  include <processthreadsapi.h>
#  include <realtimeapiset.h>
#elif defined(__APPLE__)
#  include <mach/thread_act.h>
#endif

#ifdef MOZ_GECKO_PROFILER

#  include "GeckoProfiler.h"
#  include "mozilla/ProfilerMarkerTypes.h"
#  include "mozilla/ProfilerMarkers.h"
#  include "NetworkMarker.h"
#  include "platform.h"
#  include "ProfileBuffer.h"
#  include "ProfilerControl.h"

#  include "js/Initialization.h"
#  include "js/Printf.h"
#  include "jsapi.h"
#  include "json/json.h"
#  include "mozilla/Atomics.h"
#  include "mozilla/DataMutex.h"
#  include "mozilla/ProfileBufferEntrySerializationGeckoExtensions.h"
#  include "mozilla/ProfileJSONWriter.h"
#  include "mozilla/ScopeExit.h"
#  include "mozilla/net/HttpBaseChannel.h"
#  include "nsIChannelEventSink.h"
#  include "nsIThread.h"
#  include "nsThreadUtils.h"

#  include <cstring>
#  include <set>

#endif  // MOZ_GECKO_PROFILER

// Note: profiler_init() has already been called in XRE_main(), so we can't
// test it here. Likewise for profiler_shutdown(), and AutoProfilerInit
// (which is just an RAII wrapper for profiler_init() and profiler_shutdown()).

using namespace mozilla;

TEST(GeckoProfiler, ProfilerUtils)
{
  profiler_init_main_thread_id();

  static_assert(std::is_same_v<decltype(profiler_current_process_id()),
                               ProfilerProcessId>);
  static_assert(
      std::is_same_v<decltype(profiler_current_process_id()),
                     decltype(baseprofiler::profiler_current_process_id())>);
  ProfilerProcessId processId = profiler_current_process_id();
  EXPECT_TRUE(processId.IsSpecified());
  EXPECT_EQ(processId, baseprofiler::profiler_current_process_id());

  static_assert(
      std::is_same_v<decltype(profiler_current_thread_id()), ProfilerThreadId>);
  static_assert(
      std::is_same_v<decltype(profiler_current_thread_id()),
                     decltype(baseprofiler::profiler_current_thread_id())>);
  EXPECT_EQ(profiler_current_thread_id(),
            baseprofiler::profiler_current_thread_id());

  ProfilerThreadId mainTestThreadId = profiler_current_thread_id();
  EXPECT_TRUE(mainTestThreadId.IsSpecified());

  ProfilerThreadId mainThreadId = profiler_main_thread_id();
  EXPECT_TRUE(mainThreadId.IsSpecified());

  EXPECT_EQ(mainThreadId, mainTestThreadId)
      << "Test should run on the main thread";
  EXPECT_TRUE(profiler_is_main_thread());

  std::thread testThread([&]() {
    EXPECT_EQ(profiler_current_process_id(), processId);

    const ProfilerThreadId testThreadId = profiler_current_thread_id();
    EXPECT_TRUE(testThreadId.IsSpecified());
    EXPECT_NE(testThreadId, mainThreadId);
    EXPECT_FALSE(profiler_is_main_thread());

    EXPECT_EQ(baseprofiler::profiler_current_process_id(), processId);
    EXPECT_EQ(baseprofiler::profiler_current_thread_id(), testThreadId);
    EXPECT_EQ(baseprofiler::profiler_main_thread_id(), mainThreadId);
    EXPECT_FALSE(baseprofiler::profiler_is_main_thread());
  });
  testThread.join();
}

TEST(GeckoProfiler, ThreadRegistrationInfo)
{
  profiler_init_main_thread_id();

  TimeStamp ts = TimeStamp::Now();
  {
    profiler::ThreadRegistrationInfo trInfo{
        "name", ProfilerThreadId::FromNumber(123), false, ts};
    EXPECT_STREQ(trInfo.Name(), "name");
    EXPECT_NE(trInfo.Name(), "name")
        << "ThreadRegistrationInfo should keep its own copy of the name";
    EXPECT_EQ(trInfo.RegisterTime(), ts);
    EXPECT_EQ(trInfo.ThreadId(), ProfilerThreadId::FromNumber(123));
    EXPECT_EQ(trInfo.IsMainThread(), false);
  }

  // Make sure the next timestamp will be different from `ts`.
  while (TimeStamp::Now() == ts) {
  }

  {
    profiler::ThreadRegistrationInfo trInfoHere{"Here"};
    EXPECT_STREQ(trInfoHere.Name(), "Here");
    EXPECT_NE(trInfoHere.Name(), "Here")
        << "ThreadRegistrationInfo should keep its own copy of the name";
    TimeStamp baseRegistrationTime;
#ifdef MOZ_GECKO_PROFILER
    baseRegistrationTime = baseprofiler::detail::GetThreadRegistrationTime();
#endif
    if (baseRegistrationTime) {
      EXPECT_EQ(trInfoHere.RegisterTime(), baseRegistrationTime);
    } else {
      EXPECT_GT(trInfoHere.RegisterTime(), ts);
    }
    EXPECT_EQ(trInfoHere.ThreadId(), profiler_current_thread_id());
    EXPECT_EQ(trInfoHere.ThreadId(), profiler_main_thread_id())
        << "Gtests are assumed to run on the main thread";
    EXPECT_EQ(trInfoHere.IsMainThread(), true)
        << "Gtests are assumed to run on the main thread";
  }

  {
    // Sub-thread test.
    // These will receive sub-thread data (to test move at thread end).
    TimeStamp tsThread;
    ProfilerThreadId threadThreadId;
    UniquePtr<profiler::ThreadRegistrationInfo> trInfoThreadPtr;

    std::thread testThread([&]() {
      profiler::ThreadRegistrationInfo trInfoThread{"Thread"};
      EXPECT_STREQ(trInfoThread.Name(), "Thread");
      EXPECT_NE(trInfoThread.Name(), "Thread")
          << "ThreadRegistrationInfo should keep its own copy of the name";
      EXPECT_GT(trInfoThread.RegisterTime(), ts);
      EXPECT_EQ(trInfoThread.ThreadId(), profiler_current_thread_id());
      EXPECT_NE(trInfoThread.ThreadId(), profiler_main_thread_id());
      EXPECT_EQ(trInfoThread.IsMainThread(), false);

      tsThread = trInfoThread.RegisterTime();
      threadThreadId = trInfoThread.ThreadId();
      trInfoThreadPtr =
          MakeUnique<profiler::ThreadRegistrationInfo>(std::move(trInfoThread));
    });
    testThread.join();

    ASSERT_NE(trInfoThreadPtr, nullptr);
    EXPECT_STREQ(trInfoThreadPtr->Name(), "Thread");
    EXPECT_EQ(trInfoThreadPtr->RegisterTime(), tsThread);
    EXPECT_EQ(trInfoThreadPtr->ThreadId(), threadThreadId);
    EXPECT_EQ(trInfoThreadPtr->IsMainThread(), false)
        << "Gtests are assumed to run on the main thread";
  }
}

static constexpr ThreadProfilingFeatures scEachAndAnyThreadProfilingFeatures[] =
    {ThreadProfilingFeatures::CPUUtilization, ThreadProfilingFeatures::Sampling,
     ThreadProfilingFeatures::Markers, ThreadProfilingFeatures::Any};

TEST(GeckoProfiler, ThreadProfilingFeaturesType)
{
  ASSERT_EQ(static_cast<uint32_t>(ThreadProfilingFeatures::Any), 1u + 2u + 4u)
      << "This test assumes that there are 3 binary choices 1+2+4; "
         "Is this test up to date?";

  EXPECT_EQ(Combine(ThreadProfilingFeatures::CPUUtilization,
                    ThreadProfilingFeatures::Sampling,
                    ThreadProfilingFeatures::Markers),
            ThreadProfilingFeatures::Any);

  constexpr ThreadProfilingFeatures allThreadProfilingFeatures[] = {
      ThreadProfilingFeatures::NotProfiled,
      ThreadProfilingFeatures::CPUUtilization,
      ThreadProfilingFeatures::Sampling, ThreadProfilingFeatures::Markers,
      ThreadProfilingFeatures::Any};

  for (ThreadProfilingFeatures f1 : allThreadProfilingFeatures) {
    // Combine and Intersect are commutative.
    for (ThreadProfilingFeatures f2 : allThreadProfilingFeatures) {
      EXPECT_EQ(Combine(f1, f2), Combine(f2, f1));
      EXPECT_EQ(Intersect(f1, f2), Intersect(f2, f1));
    }

    // Combine works like OR.
    EXPECT_EQ(Combine(f1, f1), f1);
    EXPECT_EQ(Combine(f1, f1, f1), f1);

    // 'OR NotProfiled' doesn't change anything.
    EXPECT_EQ(Combine(f1, ThreadProfilingFeatures::NotProfiled), f1);

    // 'OR Any' makes Any.
    EXPECT_EQ(Combine(f1, ThreadProfilingFeatures::Any),
              ThreadProfilingFeatures::Any);

    // Intersect works like AND.
    EXPECT_EQ(Intersect(f1, f1), f1);
    EXPECT_EQ(Intersect(f1, f1, f1), f1);

    // 'AND NotProfiled' erases anything.
    EXPECT_EQ(Intersect(f1, ThreadProfilingFeatures::NotProfiled),
              ThreadProfilingFeatures::NotProfiled);

    // 'AND Any' doesn't change anything.
    EXPECT_EQ(Intersect(f1, ThreadProfilingFeatures::Any), f1);
  }

  for (ThreadProfilingFeatures f1 : scEachAndAnyThreadProfilingFeatures) {
    EXPECT_TRUE(DoFeaturesIntersect(f1, f1));

    // NotProfiled doesn't intersect with any feature.
    EXPECT_FALSE(DoFeaturesIntersect(f1, ThreadProfilingFeatures::NotProfiled));

    // Any intersects with any feature.
    EXPECT_TRUE(DoFeaturesIntersect(f1, ThreadProfilingFeatures::Any));
  }
}

static void TestConstUnlockedConstReader(
    const profiler::ThreadRegistration::UnlockedConstReader& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  EXPECT_STREQ(aData.Info().Name(), "Test thread");
  EXPECT_GE(aData.Info().RegisterTime(), aBeforeRegistration);
  EXPECT_LE(aData.Info().RegisterTime(), aAfterRegistration);
  EXPECT_EQ(aData.Info().ThreadId(), aThreadId);
  EXPECT_FALSE(aData.Info().IsMainThread());

#if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(MOZ_GECKO_PROFILER)
  HANDLE threadHandle = aData.PlatformDataCRef().ProfiledThread();
  EXPECT_NE(threadHandle, nullptr);
  EXPECT_EQ(ProfilerThreadId::FromNumber(::GetThreadId(threadHandle)),
            aThreadId);
  // Test calling QueryThreadCycleTime, we cannot assume that it will always
  // work, but at least it shouldn't crash.
  ULONG64 cycles;
  (void)QueryThreadCycleTime(threadHandle, &cycles);
#elif defined(__APPLE__) && defined(MOZ_GECKO_PROFILER)
  // Test calling thread_info, we cannot assume that it will always work, but at
  // least it shouldn't crash.
  thread_basic_info_data_t threadBasicInfo;
  mach_msg_type_number_t basicCount = THREAD_BASIC_INFO_COUNT;
  (void)thread_info(
      aData.PlatformDataCRef().ProfiledThread(), THREAD_BASIC_INFO,
      reinterpret_cast<thread_info_t>(&threadBasicInfo), &basicCount);
#elif (defined(__linux__) || defined(__ANDROID__) || defined(__FreeBSD__)) && \
    defined(MOZ_GECKO_PROFILER)
  // Test calling GetClockId, we cannot assume that it will always work, but at
  // least it shouldn't crash.
  Maybe<clockid_t> maybeClockId = aData.PlatformDataCRef().GetClockId();
  if (maybeClockId) {
    // Test calling clock_gettime, we cannot assume that it will always work,
    // but at least it shouldn't crash.
    timespec ts;
    (void)clock_gettime(*maybeClockId, &ts);
  }
#else
  (void)aData.PlatformDataCRef();
#endif

  EXPECT_GE(aData.StackTop(), aOnStackObject)
      << "StackTop should be at &onStackChar, or higher on some "
         "platforms";
};

static void TestConstUnlockedConstReaderAndAtomicRW(
    const profiler::ThreadRegistration::UnlockedConstReaderAndAtomicRW& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedConstReader(aData, aBeforeRegistration, aAfterRegistration,
                               aOnStackObject, aThreadId);

  (void)aData.ProfilingStackCRef();

  EXPECT_EQ(aData.ProfilingFeatures(), ThreadProfilingFeatures::NotProfiled);

  EXPECT_FALSE(aData.IsSleeping());
};

static void TestUnlockedConstReaderAndAtomicRW(
    profiler::ThreadRegistration::UnlockedConstReaderAndAtomicRW& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedConstReaderAndAtomicRW(aData, aBeforeRegistration,
                                          aAfterRegistration, aOnStackObject,
                                          aThreadId);

  (void)aData.ProfilingStackRef();

  EXPECT_FALSE(aData.IsSleeping());
  aData.SetSleeping();
  EXPECT_TRUE(aData.IsSleeping());
  aData.SetAwake();
  EXPECT_FALSE(aData.IsSleeping());

  aData.ReinitializeOnResume();

  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
  aData.SetSleeping();
  // After sleeping, the 2nd+ calls can duplicate.
  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_TRUE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_TRUE(aData.CanDuplicateLastSampleDueToSleep());
  aData.ReinitializeOnResume();
  // After reinit (and sleeping), the 2nd+ calls can duplicate.
  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_TRUE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_TRUE(aData.CanDuplicateLastSampleDueToSleep());
  aData.SetAwake();
  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
  EXPECT_FALSE(aData.CanDuplicateLastSampleDueToSleep());
};

static void TestConstUnlockedRWForLockedProfiler(
    const profiler::ThreadRegistration::UnlockedRWForLockedProfiler& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedConstReaderAndAtomicRW(aData, aBeforeRegistration,
                                          aAfterRegistration, aOnStackObject,
                                          aThreadId);

  // We can't create a PSAutoLock here, so just verify that the call would
  // compile and return the expected type.
  static_assert(std::is_same_v<decltype(aData.GetProfiledThreadData(
                                   std::declval<PSAutoLock>())),
                               const ProfiledThreadData*>);
};

static void TestConstUnlockedReaderAndAtomicRWOnThread(
    const profiler::ThreadRegistration::UnlockedReaderAndAtomicRWOnThread&
        aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedRWForLockedProfiler(aData, aBeforeRegistration,
                                       aAfterRegistration, aOnStackObject,
                                       aThreadId);

  EXPECT_EQ(aData.GetJSContext(), nullptr);
};

static void TestUnlockedRWForLockedProfiler(
    profiler::ThreadRegistration::UnlockedRWForLockedProfiler& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedRWForLockedProfiler(aData, aBeforeRegistration,
                                       aAfterRegistration, aOnStackObject,
                                       aThreadId);
  TestUnlockedConstReaderAndAtomicRW(aData, aBeforeRegistration,
                                     aAfterRegistration, aOnStackObject,
                                     aThreadId);

  // No functions to test here.
};

static void TestUnlockedReaderAndAtomicRWOnThread(
    profiler::ThreadRegistration::UnlockedReaderAndAtomicRWOnThread& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedReaderAndAtomicRWOnThread(aData, aBeforeRegistration,
                                             aAfterRegistration, aOnStackObject,
                                             aThreadId);
  TestUnlockedRWForLockedProfiler(aData, aBeforeRegistration,
                                  aAfterRegistration, aOnStackObject,
                                  aThreadId);

  // No functions to test here.
};

static void TestConstLockedRWFromAnyThread(
    const profiler::ThreadRegistration::LockedRWFromAnyThread& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstUnlockedReaderAndAtomicRWOnThread(aData, aBeforeRegistration,
                                             aAfterRegistration, aOnStackObject,
                                             aThreadId);

  EXPECT_EQ(aData.GetJsFrameBuffer(), nullptr);
  EXPECT_EQ(aData.GetEventTarget(), nullptr);
};

static void TestLockedRWFromAnyThread(
    profiler::ThreadRegistration::LockedRWFromAnyThread& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstLockedRWFromAnyThread(aData, aBeforeRegistration, aAfterRegistration,
                                 aOnStackObject, aThreadId);
  TestUnlockedReaderAndAtomicRWOnThread(aData, aBeforeRegistration,
                                        aAfterRegistration, aOnStackObject,
                                        aThreadId);

  // We can't create a ProfiledThreadData nor PSAutoLock here, so just verify
  // that the call would compile and return the expected type.
  static_assert(std::is_same_v<decltype(aData.SetProfilingFeaturesAndData(
                                   std::declval<ThreadProfilingFeatures>(),
                                   std::declval<ProfiledThreadData*>(),
                                   std::declval<PSAutoLock>())),
                               void>);

  aData.ResetMainThread(nullptr);

  TimeDuration delay = TimeDuration::FromSeconds(1);
  TimeDuration running = TimeDuration::FromSeconds(1);
  aData.GetRunningEventDelay(TimeStamp::Now(), delay, running);
  EXPECT_TRUE(delay.IsZero());
  EXPECT_TRUE(running.IsZero());

  aData.StartJSSampling(123u);
  aData.StopJSSampling();
};

static void TestConstLockedRWOnThread(
    const profiler::ThreadRegistration::LockedRWOnThread& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstLockedRWFromAnyThread(aData, aBeforeRegistration, aAfterRegistration,
                                 aOnStackObject, aThreadId);

  // No functions to test here.
};

static void TestLockedRWOnThread(
    profiler::ThreadRegistration::LockedRWOnThread& aData,
    const TimeStamp& aBeforeRegistration, const TimeStamp& aAfterRegistration,
    const void* aOnStackObject,
    ProfilerThreadId aThreadId = profiler_current_thread_id()) {
  TestConstLockedRWOnThread(aData, aBeforeRegistration, aAfterRegistration,
                            aOnStackObject, aThreadId);
  TestLockedRWFromAnyThread(aData, aBeforeRegistration, aAfterRegistration,
                            aOnStackObject, aThreadId);

  // We don't want to really call SetCycleCollectedJSContext here, so just
  // verify that the call would compile and return the expected type.
  static_assert(std::is_same_v<decltype(aData.SetCycleCollectedJSContext(
                                   std::declval<CycleCollectedJSContext*>())),
                               void>);
  aData.ClearCycleCollectedJSContext();
  aData.PollJSSampling();
};

TEST(GeckoProfiler, ThreadRegistration_DataAccess)
{
  using TR = profiler::ThreadRegistration;

  profiler_init_main_thread_id();
  ASSERT_TRUE(profiler_is_main_thread())
  << "This test assumes it runs on the main thread";

  // Note that the main thread could already be registered, so we work in a new
  // thread to test an actual registration that we control.

  std::thread testThread([&]() {
    ASSERT_FALSE(TR::IsRegistered())
    << "A new std::thread should not start registered";
    EXPECT_FALSE(TR::GetOnThreadPtr());
    EXPECT_FALSE(TR::WithOnThreadRefOr([&](auto) { return true; }, false));

    char onStackChar;

    TimeStamp beforeRegistration = TimeStamp::Now();
    TR tr{"Test thread", &onStackChar};
    TimeStamp afterRegistration = TimeStamp::Now();

    ASSERT_TRUE(TR::IsRegistered());

    // Note: This test will mostly be about checking the correct access to
    // thread data, depending on how it's obtained. Not all the functionality
    // related to that data is tested (e.g., because it involves JS or other
    // external dependencies that would be difficult to control here.)

    auto TestOnThreadRef = [&](TR::OnThreadRef aOnThreadRef) {
      // To test const-qualified member functions.
      const TR::OnThreadRef& onThreadCRef = aOnThreadRef;

      // const UnlockedConstReader (always const)

      TestConstUnlockedConstReader(onThreadCRef.UnlockedConstReaderCRef(),
                                   beforeRegistration, afterRegistration,
                                   &onStackChar);
      onThreadCRef.WithUnlockedConstReader(
          [&](const TR::UnlockedConstReader& aData) {
            TestConstUnlockedConstReader(aData, beforeRegistration,
                                         afterRegistration, &onStackChar);
          });

      // const UnlockedConstReaderAndAtomicRW

      TestConstUnlockedConstReaderAndAtomicRW(
          onThreadCRef.UnlockedConstReaderAndAtomicRWCRef(), beforeRegistration,
          afterRegistration, &onStackChar);
      onThreadCRef.WithUnlockedConstReaderAndAtomicRW(
          [&](const TR::UnlockedConstReaderAndAtomicRW& aData) {
            TestConstUnlockedConstReaderAndAtomicRW(
                aData, beforeRegistration, afterRegistration, &onStackChar);
          });

      // non-const UnlockedConstReaderAndAtomicRW

      TestUnlockedConstReaderAndAtomicRW(
          aOnThreadRef.UnlockedConstReaderAndAtomicRWRef(), beforeRegistration,
          afterRegistration, &onStackChar);
      aOnThreadRef.WithUnlockedConstReaderAndAtomicRW(
          [&](TR::UnlockedConstReaderAndAtomicRW& aData) {
            TestUnlockedConstReaderAndAtomicRW(aData, beforeRegistration,
                                               afterRegistration, &onStackChar);
          });

      // const UnlockedRWForLockedProfiler

      TestConstUnlockedRWForLockedProfiler(
          onThreadCRef.UnlockedRWForLockedProfilerCRef(), beforeRegistration,
          afterRegistration, &onStackChar);
      onThreadCRef.WithUnlockedRWForLockedProfiler(
          [&](const TR::UnlockedRWForLockedProfiler& aData) {
            TestConstUnlockedRWForLockedProfiler(
                aData, beforeRegistration, afterRegistration, &onStackChar);
          });

      // non-const UnlockedRWForLockedProfiler

      TestUnlockedRWForLockedProfiler(
          aOnThreadRef.UnlockedRWForLockedProfilerRef(), beforeRegistration,
          afterRegistration, &onStackChar);
      aOnThreadRef.WithUnlockedRWForLockedProfiler(
          [&](TR::UnlockedRWForLockedProfiler& aData) {
            TestUnlockedRWForLockedProfiler(aData, beforeRegistration,
                                            afterRegistration, &onStackChar);
          });

      // const UnlockedReaderAndAtomicRWOnThread

      TestConstUnlockedReaderAndAtomicRWOnThread(
          onThreadCRef.UnlockedReaderAndAtomicRWOnThreadCRef(),
          beforeRegistration, afterRegistration, &onStackChar);
      onThreadCRef.WithUnlockedReaderAndAtomicRWOnThread(
          [&](const TR::UnlockedReaderAndAtomicRWOnThread& aData) {
            TestConstUnlockedReaderAndAtomicRWOnThread(
                aData, beforeRegistration, afterRegistration, &onStackChar);
          });

      // non-const UnlockedReaderAndAtomicRWOnThread

      TestUnlockedReaderAndAtomicRWOnThread(
          aOnThreadRef.UnlockedReaderAndAtomicRWOnThreadRef(),
          beforeRegistration, afterRegistration, &onStackChar);
      aOnThreadRef.WithUnlockedReaderAndAtomicRWOnThread(
          [&](TR::UnlockedReaderAndAtomicRWOnThread& aData) {
            TestUnlockedReaderAndAtomicRWOnThread(
                aData, beforeRegistration, afterRegistration, &onStackChar);
          });

      // LockedRWFromAnyThread
      // Note: It cannot directly be accessed on the thread, this will be
      // tested through LockedRWOnThread.

      // const LockedRWOnThread

      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
      {
        TR::OnThreadRef::ConstRWOnThreadWithLock constRWOnThreadWithLock =
            onThreadCRef.ConstLockedRWOnThread();
        EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
        TestConstLockedRWOnThread(constRWOnThreadWithLock.DataCRef(),
                                  beforeRegistration, afterRegistration,
                                  &onStackChar);
      }
      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
      onThreadCRef.WithConstLockedRWOnThread(
          [&](const TR::LockedRWOnThread& aData) {
            EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
            TestConstLockedRWOnThread(aData, beforeRegistration,
                                      afterRegistration, &onStackChar);
          });
      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());

      // non-const LockedRWOnThread

      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
      {
        TR::OnThreadRef::RWOnThreadWithLock rwOnThreadWithLock =
            aOnThreadRef.GetLockedRWOnThread();
        EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
        TestConstLockedRWOnThread(rwOnThreadWithLock.DataCRef(),
                                  beforeRegistration, afterRegistration,
                                  &onStackChar);
        TestLockedRWOnThread(rwOnThreadWithLock.DataRef(), beforeRegistration,
                             afterRegistration, &onStackChar);
      }
      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
      aOnThreadRef.WithLockedRWOnThread([&](TR::LockedRWOnThread& aData) {
        EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
        TestLockedRWOnThread(aData, beforeRegistration, afterRegistration,
                             &onStackChar);
      });
      EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
    };

    TR::OnThreadPtr onThreadPtr = TR::GetOnThreadPtr();
    ASSERT_TRUE(onThreadPtr);
    TestOnThreadRef(*onThreadPtr);

    TR::WithOnThreadRef(
        [&](TR::OnThreadRef aOnThreadRef) { TestOnThreadRef(aOnThreadRef); });

    EXPECT_TRUE(TR::WithOnThreadRefOr(
        [&](TR::OnThreadRef aOnThreadRef) {
          TestOnThreadRef(aOnThreadRef);
          return true;
        },
        false));
  });
  testThread.join();
}

// Thread name if registered, nullptr otherwise.
static const char* GetThreadName() {
  return profiler::ThreadRegistration::WithOnThreadRefOr(
      [](profiler::ThreadRegistration::OnThreadRef onThreadRef) {
        return onThreadRef.WithUnlockedConstReader(
            [](const profiler::ThreadRegistration::UnlockedConstReader& aData) {
              return aData.Info().Name();
            });
      },
      nullptr);
}

// Get the thread name, as registered in the PRThread, nullptr on failure.
static const char* GetPRThreadName() {
  nsIThread* nsThread = NS_GetCurrentThread();
  if (!nsThread) {
    return nullptr;
  }
  PRThread* prThread = nullptr;
  if (NS_FAILED(nsThread->GetPRThread(&prThread))) {
    return nullptr;
  }
  if (!prThread) {
    return nullptr;
  }
  return PR_GetThreadName(prThread);
}

TEST(GeckoProfiler, ThreadRegistration_MainThreadName)
{
  EXPECT_TRUE(profiler::ThreadRegistration::IsRegistered());
  EXPECT_STREQ(GetThreadName(), "GeckoMain");

  // Check that the real thread name (outside the profiler) is *not* GeckoMain.
  EXPECT_STRNE(GetPRThreadName(), "GeckoMain");
}

TEST(GeckoProfiler, ThreadRegistration_NestedRegistrations)
{
  using TR = profiler::ThreadRegistration;

  profiler_init_main_thread_id();
  ASSERT_TRUE(profiler_is_main_thread())
  << "This test assumes it runs on the main thread";

  // Note that the main thread could already be registered, so we work in a new
  // thread to test actual registrations that we control.

  std::thread testThread([&]() {
    ASSERT_FALSE(TR::IsRegistered())
    << "A new std::thread should not start registered";

    char onStackChar;

    // Blocks {} are mostly for clarity, but some control on-stack registration
    // lifetimes.

    // On-stack registration.
    {
      TR rt{"Test thread #1", &onStackChar};
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #1");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #1");
    }
    ASSERT_FALSE(TR::IsRegistered());

    // Off-stack registration.
    {
      TR::RegisterThread("Test thread #2", &onStackChar);
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #2");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #2");

      TR::UnregisterThread();
      ASSERT_FALSE(TR::IsRegistered());
    }

    // Extra un-registration should be ignored.
    TR::UnregisterThread();
    ASSERT_FALSE(TR::IsRegistered());

    // Nested on-stack.
    {
      TR rt2{"Test thread #3", &onStackChar};
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #3");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #3");

      {
        TR rt3{"Test thread #4", &onStackChar};
        ASSERT_TRUE(TR::IsRegistered());
        EXPECT_STREQ(GetThreadName(), "Test thread #3")
            << "Nested registration shouldn't change the name";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #3")
            << "Nested registration shouldn't change the PRThread name";
      }
      ASSERT_TRUE(TR::IsRegistered())
      << "Thread should still be registered after nested un-registration";
      EXPECT_STREQ(GetThreadName(), "Test thread #3")
          << "Thread should still be registered after nested un-registration";
      EXPECT_STREQ(GetPRThreadName(), "Test thread #3");
    }
    ASSERT_FALSE(TR::IsRegistered());

    // Nested off-stack.
    {
      TR::RegisterThread("Test thread #5", &onStackChar);
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #5");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #5");

      {
        TR::RegisterThread("Test thread #6", &onStackChar);
        ASSERT_TRUE(TR::IsRegistered());
        EXPECT_STREQ(GetThreadName(), "Test thread #5")
            << "Nested registration shouldn't change the name";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #5")
            << "Nested registration shouldn't change the PRThread name";

        TR::UnregisterThread();
        ASSERT_TRUE(TR::IsRegistered())
        << "Thread should still be registered after nested un-registration";
        EXPECT_STREQ(GetThreadName(), "Test thread #5")
            << "Thread should still be registered after nested un-registration";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #5");
      }

      TR::UnregisterThread();
      ASSERT_FALSE(TR::IsRegistered());
    }

    // Nested on- and off-stack.
    {
      TR rt2{"Test thread #7", &onStackChar};
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #7");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #7");

      {
        TR::RegisterThread("Test thread #8", &onStackChar);
        ASSERT_TRUE(TR::IsRegistered());
        EXPECT_STREQ(GetThreadName(), "Test thread #7")
            << "Nested registration shouldn't change the name";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #7")
            << "Nested registration shouldn't change the PRThread name";

        TR::UnregisterThread();
        ASSERT_TRUE(TR::IsRegistered())
        << "Thread should still be registered after nested un-registration";
        EXPECT_STREQ(GetThreadName(), "Test thread #7")
            << "Thread should still be registered after nested un-registration";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #7");
      }
    }
    ASSERT_FALSE(TR::IsRegistered());

    // Nested off- and on-stack.
    {
      TR::RegisterThread("Test thread #9", &onStackChar);
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #9");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #9");

      {
        TR rt3{"Test thread #10", &onStackChar};
        ASSERT_TRUE(TR::IsRegistered());
        EXPECT_STREQ(GetThreadName(), "Test thread #9")
            << "Nested registration shouldn't change the name";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #9")
            << "Nested registration shouldn't change the PRThread name";
      }
      ASSERT_TRUE(TR::IsRegistered())
      << "Thread should still be registered after nested un-registration";
      EXPECT_STREQ(GetThreadName(), "Test thread #9")
          << "Thread should still be registered after nested un-registration";
      EXPECT_STREQ(GetPRThreadName(), "Test thread #9");

      TR::UnregisterThread();
      ASSERT_FALSE(TR::IsRegistered());
    }

    // Excess UnregisterThread with on-stack TR.
    {
      TR rt2{"Test thread #11", &onStackChar};
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #11");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #11");

      TR::UnregisterThread();
      ASSERT_TRUE(TR::IsRegistered())
      << "On-stack thread should still be registered after off-stack "
         "un-registration";
      EXPECT_STREQ(GetThreadName(), "Test thread #11")
          << "On-stack thread should still be registered after off-stack "
             "un-registration";
      EXPECT_STREQ(GetPRThreadName(), "Test thread #11");
    }
    ASSERT_FALSE(TR::IsRegistered());

    // Excess on-thread TR destruction with already-unregistered root off-thread
    // registration.
    {
      TR::RegisterThread("Test thread #12", &onStackChar);
      ASSERT_TRUE(TR::IsRegistered());
      EXPECT_STREQ(GetThreadName(), "Test thread #12");
      EXPECT_STREQ(GetPRThreadName(), "Test thread #12");

      {
        TR rt3{"Test thread #13", &onStackChar};
        ASSERT_TRUE(TR::IsRegistered());
        EXPECT_STREQ(GetThreadName(), "Test thread #12")
            << "Nested registration shouldn't change the name";
        EXPECT_STREQ(GetPRThreadName(), "Test thread #12")
            << "Nested registration shouldn't change the PRThread name";

        // Note that we unregister the root registration, while nested `rt3` is
        // still alive.
        TR::UnregisterThread();
        ASSERT_FALSE(TR::IsRegistered())
        << "UnregisterThread() of the root RegisterThread() should always work";

        // At this end of this block, `rt3` will be destroyed, but nothing
        // should happen.
      }
      ASSERT_FALSE(TR::IsRegistered());
    }

    ASSERT_FALSE(TR::IsRegistered());
  });
  testThread.join();
}

TEST(GeckoProfiler, ThreadRegistry_DataAccess)
{
  using TR = profiler::ThreadRegistration;
  using TRy = profiler::ThreadRegistry;

  profiler_init_main_thread_id();
  ASSERT_TRUE(profiler_is_main_thread())
  << "This test assumes it runs on the main thread";

  // Note that the main thread could already be registered, so we work in a new
  // thread to test an actual registration that we control.

  std::thread testThread([&]() {
    ASSERT_FALSE(TR::IsRegistered())
    << "A new std::thread should not start registered";
    EXPECT_FALSE(TR::GetOnThreadPtr());
    EXPECT_FALSE(TR::WithOnThreadRefOr([&](auto) { return true; }, false));

    char onStackChar;

    TimeStamp beforeRegistration = TimeStamp::Now();
    TR tr{"Test thread", &onStackChar};
    TimeStamp afterRegistration = TimeStamp::Now();

    ASSERT_TRUE(TR::IsRegistered());

    // Note: This test will mostly be about checking the correct access to
    // thread data, depending on how it's obtained. Not all the functionality
    // related to that data is tested (e.g., because it involves JS or other
    // external dependencies that would be difficult to control here.)

    const ProfilerThreadId testThreadId = profiler_current_thread_id();

    auto testThroughRegistry = [&]() {
      auto TestOffThreadRef = [&](TRy::OffThreadRef aOffThreadRef) {
        // To test const-qualified member functions.
        const TRy::OffThreadRef& offThreadCRef = aOffThreadRef;

        // const UnlockedConstReader (always const)

        TestConstUnlockedConstReader(offThreadCRef.UnlockedConstReaderCRef(),
                                     beforeRegistration, afterRegistration,
                                     &onStackChar, testThreadId);
        offThreadCRef.WithUnlockedConstReader(
            [&](const TR::UnlockedConstReader& aData) {
              TestConstUnlockedConstReader(aData, beforeRegistration,
                                           afterRegistration, &onStackChar,
                                           testThreadId);
            });

        // const UnlockedConstReaderAndAtomicRW

        TestConstUnlockedConstReaderAndAtomicRW(
            offThreadCRef.UnlockedConstReaderAndAtomicRWCRef(),
            beforeRegistration, afterRegistration, &onStackChar, testThreadId);
        offThreadCRef.WithUnlockedConstReaderAndAtomicRW(
            [&](const TR::UnlockedConstReaderAndAtomicRW& aData) {
              TestConstUnlockedConstReaderAndAtomicRW(
                  aData, beforeRegistration, afterRegistration, &onStackChar,
                  testThreadId);
            });

        // non-const UnlockedConstReaderAndAtomicRW

        TestUnlockedConstReaderAndAtomicRW(
            aOffThreadRef.UnlockedConstReaderAndAtomicRWRef(),
            beforeRegistration, afterRegistration, &onStackChar, testThreadId);
        aOffThreadRef.WithUnlockedConstReaderAndAtomicRW(
            [&](TR::UnlockedConstReaderAndAtomicRW& aData) {
              TestUnlockedConstReaderAndAtomicRW(aData, beforeRegistration,
                                                 afterRegistration,
                                                 &onStackChar, testThreadId);
            });

        // const UnlockedRWForLockedProfiler

        TestConstUnlockedRWForLockedProfiler(
            offThreadCRef.UnlockedRWForLockedProfilerCRef(), beforeRegistration,
            afterRegistration, &onStackChar, testThreadId);
        offThreadCRef.WithUnlockedRWForLockedProfiler(
            [&](const TR::UnlockedRWForLockedProfiler& aData) {
              TestConstUnlockedRWForLockedProfiler(aData, beforeRegistration,
                                                   afterRegistration,
                                                   &onStackChar, testThreadId);
            });

        // non-const UnlockedRWForLockedProfiler

        TestUnlockedRWForLockedProfiler(
            aOffThreadRef.UnlockedRWForLockedProfilerRef(), beforeRegistration,
            afterRegistration, &onStackChar, testThreadId);
        aOffThreadRef.WithUnlockedRWForLockedProfiler(
            [&](TR::UnlockedRWForLockedProfiler& aData) {
              TestUnlockedRWForLockedProfiler(aData, beforeRegistration,
                                              afterRegistration, &onStackChar,
                                              testThreadId);
            });

        // UnlockedReaderAndAtomicRWOnThread
        // Note: It cannot directly be accessed off the thread, this will be
        // tested through LockedRWFromAnyThread.

        // const LockedRWFromAnyThread

        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
        {
          TRy::OffThreadRef::ConstRWFromAnyThreadWithLock
              constRWFromAnyThreadWithLock =
                  offThreadCRef.ConstLockedRWFromAnyThread();
          if (profiler_current_thread_id() == testThreadId) {
            EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
          }
          TestConstLockedRWFromAnyThread(
              constRWFromAnyThreadWithLock.DataCRef(), beforeRegistration,
              afterRegistration, &onStackChar, testThreadId);
        }
        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
        offThreadCRef.WithConstLockedRWFromAnyThread(
            [&](const TR::LockedRWFromAnyThread& aData) {
              if (profiler_current_thread_id() == testThreadId) {
                EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
              }
              TestConstLockedRWFromAnyThread(aData, beforeRegistration,
                                             afterRegistration, &onStackChar,
                                             testThreadId);
            });
        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());

        // non-const LockedRWFromAnyThread

        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
        {
          TRy::OffThreadRef::RWFromAnyThreadWithLock rwFromAnyThreadWithLock =
              aOffThreadRef.GetLockedRWFromAnyThread();
          if (profiler_current_thread_id() == testThreadId) {
            EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
          }
          TestLockedRWFromAnyThread(rwFromAnyThreadWithLock.DataRef(),
                                    beforeRegistration, afterRegistration,
                                    &onStackChar, testThreadId);
        }
        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());
        aOffThreadRef.WithLockedRWFromAnyThread(
            [&](TR::LockedRWFromAnyThread& aData) {
              if (profiler_current_thread_id() == testThreadId) {
                EXPECT_TRUE(TR::IsDataMutexLockedOnCurrentThread());
              }
              TestLockedRWFromAnyThread(aData, beforeRegistration,
                                        afterRegistration, &onStackChar,
                                        testThreadId);
            });
        EXPECT_FALSE(TR::IsDataMutexLockedOnCurrentThread());

        // LockedRWOnThread
        // Note: It can never be accessed off the thread.
      };

      int ranTest = 0;
      TRy::WithOffThreadRef(testThreadId, [&](TRy::OffThreadRef aOffThreadRef) {
        TestOffThreadRef(aOffThreadRef);
        ++ranTest;
      });
      EXPECT_EQ(ranTest, 1);

      EXPECT_TRUE(TRy::WithOffThreadRefOr(
          testThreadId,
          [&](TRy::OffThreadRef aOffThreadRef) {
            TestOffThreadRef(aOffThreadRef);
            return true;
          },
          false));

      ranTest = 0;
      EXPECT_FALSE(TRy::IsRegistryMutexLockedOnCurrentThread());
      for (TRy::OffThreadRef offThreadRef : TRy::LockedRegistry{}) {
        EXPECT_TRUE(TRy::IsRegistryMutexLockedOnCurrentThread() ||
                    !TR::IsRegistered());
        if (offThreadRef.UnlockedConstReaderCRef().Info().ThreadId() ==
            testThreadId) {
          TestOffThreadRef(offThreadRef);
          ++ranTest;
        }
      }
      EXPECT_EQ(ranTest, 1);
      EXPECT_FALSE(TRy::IsRegistryMutexLockedOnCurrentThread());

      {
        ranTest = 0;
        EXPECT_FALSE(TRy::IsRegistryMutexLockedOnCurrentThread());
        TRy::LockedRegistry lockedRegistry{};
        EXPECT_TRUE(TRy::IsRegistryMutexLockedOnCurrentThread() ||
                    !TR::IsRegistered());
        for (TRy::OffThreadRef offThreadRef : lockedRegistry) {
          if (offThreadRef.UnlockedConstReaderCRef().Info().ThreadId() ==
              testThreadId) {
            TestOffThreadRef(offThreadRef);
            ++ranTest;
          }
        }
        EXPECT_EQ(ranTest, 1);
      }
      EXPECT_FALSE(TRy::IsRegistryMutexLockedOnCurrentThread());
    };

    // Test on the current thread.
    testThroughRegistry();

    // Test from another thread.
    std::thread otherThread([&]() {
      ASSERT_NE(profiler_current_thread_id(), testThreadId);
      testThroughRegistry();

      // Test that this unregistered thread is really not registered.
      int ranTest = 0;
      TRy::WithOffThreadRef(
          profiler_current_thread_id(),
          [&](TRy::OffThreadRef aOffThreadRef) { ++ranTest; });
      EXPECT_EQ(ranTest, 0);

      EXPECT_FALSE(TRy::WithOffThreadRefOr(
          profiler_current_thread_id(),
          [&](TRy::OffThreadRef aOffThreadRef) {
            ++ranTest;
            return true;
          },
          false));
      EXPECT_EQ(ranTest, 0);
    });
    otherThread.join();
  });
  testThread.join();
}

TEST(GeckoProfiler, ThreadRegistration_RegistrationEdgeCases)
{
  using TR = profiler::ThreadRegistration;
  using TRy = profiler::ThreadRegistry;

  profiler_init_main_thread_id();
  ASSERT_TRUE(profiler_is_main_thread())
  << "This test assumes it runs on the main thread";

  // Note that the main thread could already be registered, so we work in a new
  // thread to test an actual registration that we control.

  int registrationCount = 0;
  int otherThreadLoops = 0;
  int otherThreadReads = 0;

  // This thread will register and unregister in a loop, with some pauses.
  // Another thread will attempty to access the test thread, and lock its data.
  // The main goal is to check edges cases around (un)registrations.
  std::thread testThread([&]() {
    const ProfilerThreadId testThreadId = profiler_current_thread_id();

    const TimeStamp endTestAt = TimeStamp::Now() + TimeDuration::FromSeconds(1);

    std::thread otherThread([&]() {
      // Initial sleep so that testThread can start its loop.
      PR_Sleep(PR_MillisecondsToInterval(1));

      while (TimeStamp::Now() < endTestAt) {
        ++otherThreadLoops;

        TRy::WithOffThreadRef(testThreadId, [&](TRy::OffThreadRef
                                                    aOffThreadRef) {
          if (otherThreadLoops % 1000 == 0) {
            PR_Sleep(PR_MillisecondsToInterval(1));
          }
          TRy::OffThreadRef::RWFromAnyThreadWithLock rwFromAnyThreadWithLock =
              aOffThreadRef.GetLockedRWFromAnyThread();
          ++otherThreadReads;
          if (otherThreadReads % 1000 == 0) {
            PR_Sleep(PR_MillisecondsToInterval(1));
          }
        });
      }
    });

    while (TimeStamp::Now() < endTestAt) {
      ASSERT_FALSE(TR::IsRegistered())
      << "A new std::thread should not start registered";
      EXPECT_FALSE(TR::GetOnThreadPtr());
      EXPECT_FALSE(TR::WithOnThreadRefOr([&](auto) { return true; }, false));

      char onStackChar;

      TR tr{"Test thread", &onStackChar};
      ++registrationCount;

      ASSERT_TRUE(TR::IsRegistered());

      int ranTest = 0;
      TRy::WithOffThreadRef(testThreadId, [&](TRy::OffThreadRef aOffThreadRef) {
        if (registrationCount % 2000 == 0) {
          PR_Sleep(PR_MillisecondsToInterval(1));
        }
        ++ranTest;
      });
      EXPECT_EQ(ranTest, 1);

      if (registrationCount % 1000 == 0) {
        PR_Sleep(PR_MillisecondsToInterval(1));
      }
    }

    otherThread.join();
  });

  testThread.join();

  // It's difficult to guess what these numbers should be, but they definitely
  // should be non-zero. The main goal was to test that nothing goes wrong.
  EXPECT_GT(registrationCount, 0);
  EXPECT_GT(otherThreadLoops, 0);
  EXPECT_GT(otherThreadReads, 0);
}

#ifdef MOZ_GECKO_PROFILER

// Common JSON checks.

// Check that the given JSON string include no JSON whitespace characters
// (excluding those in property names and strings).
void JSONWhitespaceCheck(const char* aOutput) {
  ASSERT_NE(aOutput, nullptr);

  enum class State { Data, String, StringEscaped };
  State state = State::Data;
  size_t length = 0;
  size_t whitespaces = 0;
  for (const char* p = aOutput; *p != '\0'; ++p) {
    ++length;
    const char c = *p;

    switch (state) {
      case State::Data:
        if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
          ++whitespaces;
        } else if (c == '"') {
          state = State::String;
        }
        break;

      case State::String:
        if (c == '"') {
          state = State::Data;
        } else if (c == '\\') {
          state = State::StringEscaped;
        }
        break;

      case State::StringEscaped:
        state = State::String;
        break;
    }
  }

  EXPECT_EQ(whitespaces, 0u);
  EXPECT_GT(length, 0u);
}

// Does the GETTER return a non-null TYPE? (Non-critical)
#  define EXPECT_HAS_JSON(GETTER, TYPE)              \
    do {                                             \
      if ((GETTER).isNull()) {                       \
        EXPECT_FALSE((GETTER).isNull())              \
            << #GETTER " doesn't exist or is null";  \
      } else if (!(GETTER).is##TYPE()) {             \
        EXPECT_TRUE((GETTER).is##TYPE())             \
            << #GETTER " didn't return type " #TYPE; \
      }                                              \
    } while (false)

// Does the GETTER return a non-null TYPE? (Critical)
#  define ASSERT_HAS_JSON(GETTER, TYPE) \
    do {                                \
      ASSERT_FALSE((GETTER).isNull());  \
      ASSERT_TRUE((GETTER).is##TYPE()); \
    } while (false)

// Does the GETTER return a non-null TYPE? (Critical)
// If yes, store the reference to Json::Value into VARIABLE.
#  define GET_JSON(VARIABLE, GETTER, TYPE) \
    ASSERT_HAS_JSON(GETTER, TYPE);         \
    const Json::Value& VARIABLE = (GETTER)

// Does the GETTER return a non-null TYPE? (Critical)
// If yes, store the value as `const TYPE` into VARIABLE.
#  define GET_JSON_VALUE(VARIABLE, GETTER, TYPE) \
    ASSERT_HAS_JSON(GETTER, TYPE);               \
    const auto VARIABLE = (GETTER).as##TYPE()

// Non-const GET_JSON_VALUE.
#  define GET_JSON_MUTABLE_VALUE(VARIABLE, GETTER, TYPE) \
    ASSERT_HAS_JSON(GETTER, TYPE);                       \
    auto VARIABLE = (GETTER).as##TYPE()

// Checks that the GETTER's value is present, is of the expected TYPE, and has
// the expected VALUE. (Non-critical)
#  define EXPECT_EQ_JSON(GETTER, TYPE, VALUE)        \
    do {                                             \
      if ((GETTER).isNull()) {                       \
        EXPECT_FALSE((GETTER).isNull())              \
            << #GETTER " doesn't exist or is null";  \
      } else if (!(GETTER).is##TYPE()) {             \
        EXPECT_TRUE((GETTER).is##TYPE())             \
            << #GETTER " didn't return type " #TYPE; \
      } else {                                       \
        EXPECT_EQ((GETTER).as##TYPE(), (VALUE));     \
      }                                              \
    } while (false)

// Checks that the GETTER's value is present, and is a valid index into the
// STRINGTABLE array, pointing at the expected STRING.
#  define EXPECT_EQ_STRINGTABLE(GETTER, STRINGTABLE, STRING)                 \
    do {                                                                     \
      if ((GETTER).isNull()) {                                               \
        EXPECT_FALSE((GETTER).isNull())                                      \
            << #GETTER " doesn't exist or is null";                          \
      } else if (!(GETTER).isUInt()) {                                       \
        EXPECT_TRUE((GETTER).isUInt()) << #GETTER " didn't return an index"; \
      } else {                                                               \
        EXPECT_LT((GETTER).asUInt(), (STRINGTABLE).size());                  \
        EXPECT_EQ_JSON((STRINGTABLE)[(GETTER).asUInt()], String, (STRING));  \
      }                                                                      \
    } while (false)

#  define EXPECT_JSON_ARRAY_CONTAINS(GETTER, TYPE, VALUE)                     \
    do {                                                                      \
      if ((GETTER).isNull()) {                                                \
        EXPECT_FALSE((GETTER).isNull())                                       \
            << #GETTER " doesn't exist or is null";                           \
      } else if (!(GETTER).isArray()) {                                       \
        EXPECT_TRUE((GETTER).is##TYPE()) << #GETTER " is not an array";       \
      } else if (const Json::ArrayIndex size = (GETTER).size(); size == 0u) { \
        EXPECT_NE(size, 0u) << #GETTER " is an empty array";                  \
      } else {                                                                \
        bool found = false;                                                   \
        for (Json::ArrayIndex i = 0; i < size; ++i) {                         \
          if (!(GETTER)[i].is##TYPE()) {                                      \
            EXPECT_TRUE((GETTER)[i].is##TYPE())                               \
                << #GETTER "[" << i << "] is not " #TYPE;                     \
            break;                                                            \
          }                                                                   \
          if ((GETTER)[i].as##TYPE() == (VALUE)) {                            \
            found = true;                                                     \
            break;                                                            \
          }                                                                   \
        }                                                                     \
        EXPECT_TRUE(found) << #GETTER " doesn't contain " #VALUE;             \
      }                                                                       \
    } while (false)

#  define EXPECT_JSON_ARRAY_EXCLUDES(GETTER, TYPE, VALUE)               \
    do {                                                                \
      if ((GETTER).isNull()) {                                          \
        EXPECT_FALSE((GETTER).isNull())                                 \
            << #GETTER " doesn't exist or is null";                     \
      } else if (!(GETTER).isArray()) {                                 \
        EXPECT_TRUE((GETTER).is##TYPE()) << #GETTER " is not an array"; \
      } else {                                                          \
        const Json::ArrayIndex size = (GETTER).size();                  \
        for (Json::ArrayIndex i = 0; i < size; ++i) {                   \
          if (!(GETTER)[i].is##TYPE()) {                                \
            EXPECT_TRUE((GETTER)[i].is##TYPE())                         \
                << #GETTER "[" << i << "] is not " #TYPE;               \
            break;                                                      \
          }                                                             \
          if ((GETTER)[i].as##TYPE() == (VALUE)) {                      \
            EXPECT_TRUE((GETTER)[i].as##TYPE() != (VALUE))              \
                << #GETTER " contains " #VALUE;                         \
            break;                                                      \
          }                                                             \
        }                                                               \
      }                                                                 \
    } while (false)

// Check that the given process root contains all the expected properties.
static void JSONRootCheck(const Json::Value& aRoot,
                          bool aWithMainThread = true) {
  ASSERT_TRUE(aRoot.isObject());

  EXPECT_HAS_JSON(aRoot["libs"], Array);

  GET_JSON(meta, aRoot["meta"], Object);
  EXPECT_HAS_JSON(meta["version"], UInt);
  EXPECT_HAS_JSON(meta["startTime"], Double);
  EXPECT_HAS_JSON(meta["profilingStartTime"], Double);
  EXPECT_HAS_JSON(meta["contentEarliestTime"], Double);
  EXPECT_HAS_JSON(meta["profilingEndTime"], Double);

  EXPECT_HAS_JSON(aRoot["pages"], Array);

  // "counters" is only present if there is any data to report.
  // Test that expect "counters" should test for its presence first.
  if (aRoot.isMember("counters")) {
    // We have "counters", test their overall validity.
    GET_JSON(counters, aRoot["counters"], Array);
    for (const Json::Value& counter : counters) {
      ASSERT_TRUE(counter.isObject());
      EXPECT_HAS_JSON(counter["name"], String);
      EXPECT_HAS_JSON(counter["category"], String);
      EXPECT_HAS_JSON(counter["description"], String);
      GET_JSON(samples, counter["samples"], Object);
      GET_JSON(samplesSchema, samples["schema"], Object);
      EXPECT_GE(samplesSchema.size(), 3u);
      GET_JSON_VALUE(samplesTime, samplesSchema["time"], UInt);
      GET_JSON_VALUE(samplesNumber, samplesSchema["number"], UInt);
      GET_JSON_VALUE(samplesCount, samplesSchema["count"], UInt);
      GET_JSON(samplesData, samples["data"], Array);
      double previousTime = 0.0;
      for (const Json::Value& sample : samplesData) {
        ASSERT_TRUE(sample.isArray());
        GET_JSON_VALUE(time, sample[samplesTime], Double);
        EXPECT_GE(time, previousTime);
        previousTime = time;
        if (sample.isValidIndex(samplesNumber)) {
          EXPECT_HAS_JSON(sample[samplesNumber], UInt64);
        }
        if (sample.isValidIndex(samplesCount)) {
          EXPECT_HAS_JSON(sample[samplesCount], Int64);
        }
      }
    }
  }

  GET_JSON(threads, aRoot["threads"], Array);
  const Json::ArrayIndex threadCount = threads.size();
  for (Json::ArrayIndex i = 0; i < threadCount; ++i) {
    GET_JSON(thread, threads[i], Object);
    EXPECT_HAS_JSON(thread["processType"], String);
    EXPECT_HAS_JSON(thread["name"], String);
    EXPECT_HAS_JSON(thread["registerTime"], Double);
    GET_JSON(samples, thread["samples"], Object);
    EXPECT_HAS_JSON(thread["markers"], Object);
    EXPECT_HAS_JSON(thread["pid"], Int64);
    EXPECT_HAS_JSON(thread["tid"], Int64);
    GET_JSON(stackTable, thread["stackTable"], Object);
    GET_JSON(frameTable, thread["frameTable"], Object);
    GET_JSON(stringTable, thread["stringTable"], Array);

    GET_JSON(stackTableSchema, stackTable["schema"], Object);
    EXPECT_GE(stackTableSchema.size(), 2u);
    GET_JSON_VALUE(stackTablePrefix, stackTableSchema["prefix"], UInt);
    GET_JSON_VALUE(stackTableFrame, stackTableSchema["frame"], UInt);
    GET_JSON(stackTableData, stackTable["data"], Array);

    GET_JSON(frameTableSchema, frameTable["schema"], Object);
    EXPECT_GE(frameTableSchema.size(), 1u);
    GET_JSON_VALUE(frameTableLocation, frameTableSchema["location"], UInt);
    GET_JSON(frameTableData, frameTable["data"], Array);

    GET_JSON(samplesSchema, samples["schema"], Object);
    GET_JSON_VALUE(sampleStackIndex, samplesSchema["stack"], UInt);
    GET_JSON(samplesData, samples["data"], Array);
    for (const Json::Value& sample : samplesData) {
      ASSERT_TRUE(sample.isArray());
      if (sample.isValidIndex(sampleStackIndex)) {
        if (!sample[sampleStackIndex].isNull()) {
          GET_JSON_MUTABLE_VALUE(stack, sample[sampleStackIndex], UInt);
          EXPECT_TRUE(stackTableData.isValidIndex(stack));
          for (;;) {
            // `stack` (from the sample, or from the callee frame's "prefix" in
            // the previous loop) points into the stackTable.
            GET_JSON(stackTableEntry, stackTableData[stack], Array);
            GET_JSON_VALUE(frame, stackTableEntry[stackTableFrame], UInt);

            // The stackTable entry's "frame" points into the frameTable.
            EXPECT_TRUE(frameTableData.isValidIndex(frame));
            GET_JSON(frameTableEntry, frameTableData[frame], Array);
            GET_JSON_VALUE(location, frameTableEntry[frameTableLocation], UInt);

            // The frameTable entry's "location" points at a string.
            EXPECT_TRUE(stringTable.isValidIndex(location));

            // The stackTable entry's "prefix" is null for the root frame.
            if (stackTableEntry[stackTablePrefix].isNull()) {
              break;
            }
            // Otherwise it recursively points at the caller in the stackTable.
            GET_JSON_VALUE(prefix, stackTableEntry[stackTablePrefix], UInt);
            EXPECT_TRUE(stackTableData.isValidIndex(prefix));
            stack = prefix;
          }
        }
      }
    }
  }

  if (aWithMainThread) {
    ASSERT_GT(threadCount, 0u);
    GET_JSON(thread0, threads[0], Object);
    EXPECT_EQ_JSON(thread0["name"], String, "GeckoMain");
  }

  EXPECT_HAS_JSON(aRoot["pausedRanges"], Array);

  const Json::Value& processes = aRoot["processes"];
  if (!processes.isNull()) {
    ASSERT_TRUE(processes.isArray());
    const Json::ArrayIndex processCount = processes.size();
    for (Json::ArrayIndex i = 0; i < processCount; ++i) {
      GET_JSON(process, processes[i], Object);
      JSONRootCheck(process, aWithMainThread);
    }
  }

  GET_JSON(profilingLog, aRoot["profilingLog"], Object);
  EXPECT_EQ(profilingLog.size(), 1u);
  for (auto it = profilingLog.begin(); it != profilingLog.end(); ++it) {
    // The key should be a pid.
    const auto key = it.name();
    for (const auto letter : key) {
      EXPECT_GE(letter, '0');
      EXPECT_LE(letter, '9');
    }
    // And the value should be an object.
    GET_JSON(logForPid, profilingLog[key], Object);
    // Its content is not defined, but we expect at least these:
    EXPECT_HAS_JSON(logForPid["profilingLogBegin_TSms"], Double);
    EXPECT_HAS_JSON(logForPid["profilingLogEnd_TSms"], Double);
  }
}

// Check that various expected top properties are in the JSON, and then call the
// provided `aJSONCheckFunction` with the JSON root object.
template <typename JSONCheckFunction>
void JSONOutputCheck(const char* aOutput,
                     JSONCheckFunction&& aJSONCheckFunction) {
  ASSERT_NE(aOutput, nullptr);

  JSONWhitespaceCheck(aOutput);

  // Extract JSON.
  Json::Value parsedRoot;
  Json::CharReaderBuilder builder;
  const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
  ASSERT_TRUE(
      reader->parse(aOutput, strchr(aOutput, '\0'), &parsedRoot, nullptr));

  JSONRootCheck(parsedRoot);

  std::forward<JSONCheckFunction>(aJSONCheckFunction)(parsedRoot);
}

// Returns `static_cast<SamplingState>(-1)` if callback could not be installed.
static SamplingState WaitForSamplingState() {
  Atomic<int> samplingState{-1};

  if (!profiler_callback_after_sampling([&](SamplingState aSamplingState) {
        samplingState = static_cast<int>(aSamplingState);
      })) {
    return static_cast<SamplingState>(-1);
  }

  while (samplingState == -1) {
  }

  return static_cast<SamplingState>(static_cast<int>(samplingState));
}

typedef Vector<const char*> StrVec;

static void InactiveFeaturesAndParamsCheck() {
  int entries;
  Maybe<double> duration;
  double interval;
  uint32_t features;
  StrVec filters;
  uint64_t activeTabID;

  ASSERT_TRUE(!profiler_is_active());
  ASSERT_TRUE(!profiler_feature_active(ProfilerFeature::MainThreadIO));
  ASSERT_TRUE(!profiler_feature_active(ProfilerFeature::NativeAllocations));

  profiler_get_start_params(&entries, &duration, &interval, &features, &filters,
                            &activeTabID);

  ASSERT_TRUE(entries == 0);
  ASSERT_TRUE(duration == Nothing());
  ASSERT_TRUE(interval == 0);
  ASSERT_TRUE(features == 0);
  ASSERT_TRUE(filters.empty());
  ASSERT_TRUE(activeTabID == 0);
}

static void ActiveParamsCheck(int aEntries, double aInterval,
                              uint32_t aFeatures, const char** aFilters,
                              size_t aFiltersLen, uint64_t aActiveTabID,
                              const Maybe<double>& aDuration = Nothing()) {
  int entries;
  Maybe<double> duration;
  double interval;
  uint32_t features;
  StrVec filters;
  uint64_t activeTabID;

  profiler_get_start_params(&entries, &duration, &interval, &features, &filters,
                            &activeTabID);

  ASSERT_TRUE(entries == aEntries);
  ASSERT_TRUE(duration == aDuration);
  ASSERT_TRUE(interval == aInterval);
  ASSERT_TRUE(features == aFeatures);
  ASSERT_TRUE(filters.length() == aFiltersLen);
  ASSERT_TRUE(activeTabID == aActiveTabID);
  for (size_t i = 0; i < aFiltersLen; i++) {
    ASSERT_TRUE(strcmp(filters[i], aFilters[i]) == 0);
  }
}

TEST(GeckoProfiler, FeaturesAndParams)
{
  InactiveFeaturesAndParamsCheck();

  // Try a couple of features and filters.
  {
    uint32_t features = ProfilerFeature::JS;
    const char* filters[] = {"GeckoMain""Compositor"};

#  define PROFILER_DEFAULT_DURATION 20 /* seconds, for tests only */
    profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                   features, filters, std::size(filters), 100,
                   Some(PROFILER_DEFAULT_DURATION));

    ASSERT_TRUE(profiler_is_active());
    ASSERT_TRUE(!profiler_feature_active(ProfilerFeature::MainThreadIO));
    ASSERT_TRUE(!profiler_feature_active(ProfilerFeature::IPCMessages));

    ActiveParamsCheck(PROFILER_DEFAULT_ENTRIES.Value(),
                      PROFILER_DEFAULT_INTERVAL, features, filters,
                      std::size(filters), 100, Some(PROFILER_DEFAULT_DURATION));

    profiler_stop();

    InactiveFeaturesAndParamsCheck();
  }

  // Try some different features and filters.
  {
    uint32_t features =
        ProfilerFeature::MainThreadIO | ProfilerFeature::IPCMessages;
    const char* filters[] = {"GeckoMain""Foo""Bar"};

    // Testing with some arbitrary buffer size (as could be provided by
    // external code), which we convert to the appropriate power of 2.
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=95 G=93

¤ Dauer der Verarbeitung: 0.7 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.