summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/ProfilerThreadRegistration.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/core/ProfilerThreadRegistration.cpp')
-rw-r--r--tools/profiler/core/ProfilerThreadRegistration.cpp198
1 files changed, 198 insertions, 0 deletions
diff --git a/tools/profiler/core/ProfilerThreadRegistration.cpp b/tools/profiler/core/ProfilerThreadRegistration.cpp
new file mode 100644
index 0000000000..c81d00573d
--- /dev/null
+++ b/tools/profiler/core/ProfilerThreadRegistration.cpp
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ProfilerThreadRegistration.h"
+
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ProfilerThreadRegistry.h"
+#include "nsString.h"
+#ifdef MOZ_GECKO_PROFILER
+# include "platform.h"
+#else
+# define profiler_mark_thread_awake()
+# define profiler_mark_thread_asleep()
+#endif
+
+namespace mozilla::profiler {
+
+/* static */
+MOZ_THREAD_LOCAL(ThreadRegistration*) ThreadRegistration::tlsThreadRegistration;
+
+ThreadRegistration::ThreadRegistration(const char* aName, const void* aStackTop)
+ : mData(aName, aStackTop) {
+ auto* tls = GetTLS();
+ if (MOZ_UNLIKELY(!tls)) {
+ // No TLS, nothing can be done without it.
+ return;
+ }
+
+ if (ThreadRegistration* rootRegistration = tls->get(); rootRegistration) {
+ // This is a nested ThreadRegistration object, so the thread is already
+ // registered in the TLS and ThreadRegistry and we don't need to register
+ // again.
+ MOZ_ASSERT(
+ mData.Info().ThreadId() == rootRegistration->mData.Info().ThreadId(),
+ "Thread being re-registered has changed its TID");
+ // TODO: Use new name. This is currently not possible because the
+ // TLS-stored RegisteredThread's ThreadInfo cannot be changed.
+ // In the meantime, we record a marker that could be used in the frontend.
+ PROFILER_MARKER_TEXT("Nested ThreadRegistration()", OTHER_Profiling,
+ MarkerOptions{},
+ ProfilerString8View::WrapNullTerminatedString(aName));
+ return;
+ }
+
+ tls->set(this);
+ ThreadRegistry::Register(OnThreadRef{*this});
+ profiler_mark_thread_awake();
+}
+
+ThreadRegistration::~ThreadRegistration() {
+ MOZ_ASSERT(profiler_current_thread_id() == mData.mInfo.ThreadId(),
+ "ThreadRegistration must be destroyed on its thread");
+ MOZ_ASSERT(!mDataMutex.IsLockedOnCurrentThread(),
+ "Mutex shouldn't be locked here, as it's about to be destroyed "
+ "in ~ThreadRegistration()");
+ auto* tls = GetTLS();
+ if (MOZ_UNLIKELY(!tls)) {
+ // No TLS, nothing can be done without it.
+ return;
+ }
+
+ if (ThreadRegistration* rootRegistration = tls->get(); rootRegistration) {
+ if (rootRegistration != this) {
+ // `this` is not in the TLS, so it was a nested registration, there is
+ // nothing to unregister yet.
+ PROFILER_MARKER_TEXT(
+ "Nested ~ThreadRegistration()", OTHER_Profiling, MarkerOptions{},
+ ProfilerString8View::WrapNullTerminatedString(mData.Info().Name()));
+ return;
+ }
+
+ profiler_mark_thread_asleep();
+#ifdef NIGHTLY_BUILD
+ mData.RecordWakeCount();
+#endif
+ ThreadRegistry::Unregister(OnThreadRef{*this});
+#ifdef DEBUG
+ // After ThreadRegistry::Unregister, other threads should not be able to
+ // find this ThreadRegistration, and shouldn't have kept any reference to
+ // it across the ThreadRegistry mutex.
+ MOZ_ASSERT(mDataMutex.TryLock(),
+ "Mutex shouldn't be locked in any thread, as it's about to be "
+ "destroyed in ~ThreadRegistration()");
+ // Undo the above successful TryLock.
+ mDataMutex.Unlock();
+#endif // DEBUG
+
+ tls->set(nullptr);
+ return;
+ }
+
+ // Already removed from the TLS!? This could happen with improperly-nested
+ // register/unregister calls, and the first ThreadRegistration has already
+ // been unregistered.
+ // We cannot record a marker on this thread because it was already
+ // unregistered. Send it to the main thread (unless this *is* already the
+ // main thread, which has been unregistered); this may be useful to catch
+ // mismatched register/unregister pairs in Firefox.
+ if (!profiler_is_main_thread()) {
+ nsAutoCString threadId("thread id: ");
+ threadId.AppendInt(profiler_current_thread_id().ToNumber());
+ threadId.AppendLiteral(", name: \"");
+ threadId.AppendASCII(mData.Info().Name());
+ threadId.AppendLiteral("\"");
+ PROFILER_MARKER_TEXT(
+ "~ThreadRegistration() but TLS is empty", OTHER_Profiling,
+ MarkerOptions(MarkerThreadId::MainThread(), MarkerStack::Capture()),
+ threadId);
+ }
+}
+
+/* static */
+ProfilingStack* ThreadRegistration::RegisterThread(const char* aName,
+ const void* aStackTop) {
+ auto* tls = GetTLS();
+ if (MOZ_UNLIKELY(!tls)) {
+ // No TLS, nothing can be done without it.
+ return nullptr;
+ }
+
+ if (ThreadRegistration* rootRegistration = tls->get(); rootRegistration) {
+ // Already registered, record the extra depth to ignore the matching
+ // UnregisterThread.
+ ++rootRegistration->mOtherRegistrations;
+ // TODO: Use new name. This is currently not possible because the
+ // TLS-stored RegisteredThread's ThreadInfo cannot be changed.
+ // In the meantime, we record a marker that could be used in the frontend.
+ PROFILER_MARKER_TEXT("Nested ThreadRegistration::RegisterThread()",
+ OTHER_Profiling, MarkerOptions{},
+ ProfilerString8View::WrapNullTerminatedString(aName));
+ return &rootRegistration->mData.mProfilingStack;
+ }
+
+ // Create on heap, it self-registers with the TLS (its effective owner, so
+ // we can forget the pointer after this), and with the Profiler.
+ ThreadRegistration* tr = new ThreadRegistration(aName, aStackTop);
+ tr->mIsOnHeap = true;
+ return &tr->mData.mProfilingStack;
+}
+
+/* static */
+void ThreadRegistration::UnregisterThread() {
+ auto* tls = GetTLS();
+ if (MOZ_UNLIKELY(!tls)) {
+ // No TLS, nothing can be done without it.
+ return;
+ }
+
+ if (ThreadRegistration* rootRegistration = tls->get(); rootRegistration) {
+ if (rootRegistration->mOtherRegistrations != 0) {
+ // This is assumed to be a matching UnregisterThread() for a nested
+ // RegisterThread(). Decrease depth and we're done.
+ --rootRegistration->mOtherRegistrations;
+ // We don't know what name was used in the related RegisterThread().
+ PROFILER_MARKER_UNTYPED("Nested ThreadRegistration::UnregisterThread()",
+ OTHER_Profiling);
+ return;
+ }
+
+ if (!rootRegistration->mIsOnHeap) {
+ // The root registration was not added by `RegisterThread()`, so it
+ // shouldn't be deleted!
+ // This could happen if there are un-paired `UnregisterThread` calls when
+ // the initial registration (still alive) was done on the stack. We don't
+ // know what name was used in the related RegisterThread().
+ PROFILER_MARKER_UNTYPED("Excess ThreadRegistration::UnregisterThread()",
+ OTHER_Profiling, MarkerStack::Capture());
+ return;
+ }
+
+ // This is the last `UnregisterThread()` that should match the first
+ // `RegisterThread()` that created this ThreadRegistration on the heap.
+ // Just delete this root registration, it will de-register itself from the
+ // TLS (and from the Profiler).
+ delete rootRegistration;
+ return;
+ }
+
+ // There is no known ThreadRegistration for this thread, ignore this
+ // request. We cannot record a marker on this thread because it was already
+ // unregistered. Send it to the main thread (unless this *is* already the
+ // main thread, which has been unregistered); this may be useful to catch
+ // mismatched register/unregister pairs in Firefox.
+ if (!profiler_is_main_thread()) {
+ nsAutoCString threadId("thread id: ");
+ threadId.AppendInt(profiler_current_thread_id().ToNumber());
+ PROFILER_MARKER_TEXT(
+ "ThreadRegistration::UnregisterThread() but TLS is empty",
+ OTHER_Profiling,
+ MarkerOptions(MarkerThreadId::MainThread(), MarkerStack::Capture()),
+ threadId);
+ }
+}
+
+} // namespace mozilla::profiler