Bug 935092 - Add ThreadStackHelper to get a thread's pesudo-stack; r=BenWa
authorJim Chen <nchen@mozilla.com>
Fri, 22 Nov 2013 14:17:30 -0500
changeset 172755 288456164d94cf77ac4eab6f12c16da3194aa30a
parent 172754 18ba8691786d4ce7ccde7ecd00f33ccb633f49dc
child 172756 c4834a9ec4c8eaaf2c92df6e0fb0f37ff9744025
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa
bugs935092
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 935092 - Add ThreadStackHelper to get a thread's pesudo-stack; r=BenWa
xpcom/threads/ThreadStackHelper.cpp
xpcom/threads/ThreadStackHelper.h
xpcom/threads/moz.build
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadStackHelper.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "ThreadStackHelper.h"
+#include "MainThreadUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+
+#ifdef XP_LINUX
+#include <unistd.h>
+#include <sys/syscall.h>
+#endif
+
+#ifdef ANDROID
+#ifndef SYS_gettid
+#define SYS_gettid __NR_gettid
+#endif
+#ifndef SYS_tgkill
+#define SYS_tgkill __NR_tgkill
+#endif
+#endif
+
+namespace mozilla {
+
+void
+ThreadStackHelper::Startup()
+{
+#if defined(XP_LINUX)
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!sInitialized) {
+    MOZ_ALWAYS_TRUE(!::sem_init(&sSem, 0, 0));
+  }
+  sInitialized++;
+#endif
+}
+
+void
+ThreadStackHelper::Shutdown()
+{
+#if defined(XP_LINUX)
+  MOZ_ASSERT(NS_IsMainThread());
+  if (sInitialized == 1) {
+    MOZ_ALWAYS_TRUE(!::sem_destroy(&sSem));
+  }
+  sInitialized--;
+#endif
+}
+
+ThreadStackHelper::ThreadStackHelper()
+  : mPseudoStack(mozilla_get_pseudo_stack())
+  , mStackBuffer()
+  , mMaxStackSize(mStackBuffer.capacity())
+{
+#if defined(XP_LINUX)
+  mThreadID = ::syscall(SYS_gettid);
+#elif defined(XP_WIN)
+  mInitialized = !!::DuplicateHandle(
+    ::GetCurrentProcess(), ::GetCurrentThread(),
+    ::GetCurrentProcess(), &mThreadID,
+    THREAD_SUSPEND_RESUME, FALSE, 0);
+  MOZ_ASSERT(mInitialized);
+#elif defined(XP_MACOSX)
+  mThreadID = mach_thread_self();
+#endif
+}
+
+ThreadStackHelper::~ThreadStackHelper()
+{
+#if defined(XP_WIN)
+  if (mInitialized) {
+    MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID));
+  }
+#endif
+}
+
+void
+ThreadStackHelper::GetStack(Stack& aStack)
+{
+  // Always run PrepareStackBuffer first to clear aStack
+  if (!PrepareStackBuffer(aStack)) {
+    MOZ_ASSERT(false);
+    return;
+  }
+
+#if defined(XP_LINUX)
+  if (!sInitialized) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  sCurrent = this;
+  struct sigaction sigact = {};
+  sigact.sa_sigaction = SigAction;
+  sigemptyset(&sigact.sa_mask);
+  sigact.sa_flags = SA_SIGINFO | SA_RESTART;
+  if (::sigaction(SIGPROF, &sigact, &sOldSigAction)) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  MOZ_ALWAYS_TRUE(!::syscall(SYS_tgkill, getpid(), mThreadID, SIGPROF));
+  MOZ_ALWAYS_TRUE(!::sem_wait(&sSem));
+
+#elif defined(XP_WIN)
+  if (!mInitialized) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  if (::SuspendThread(mThreadID) == DWORD(-1)) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  FillStackBuffer();
+  MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
+
+#elif defined(XP_MACOSX)
+  if (::thread_suspend(mThreadID) != KERN_SUCCESS) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  FillStackBuffer();
+  MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS);
+
+#endif
+  aStack = Move(mStackBuffer);
+}
+
+#ifdef XP_LINUX
+
+int ThreadStackHelper::sInitialized;
+sem_t ThreadStackHelper::sSem;
+struct sigaction ThreadStackHelper::sOldSigAction;
+ThreadStackHelper* ThreadStackHelper::sCurrent;
+
+void
+ThreadStackHelper::SigAction(int aSignal, siginfo_t* aInfo, void* aContext)
+{
+  ::sigaction(SIGPROF, &sOldSigAction, nullptr);
+  sCurrent->FillStackBuffer();
+  sCurrent = nullptr;
+  ::sem_post(&sSem);
+}
+
+#endif // XP_LINUX
+
+bool
+ThreadStackHelper::PrepareStackBuffer(Stack& aStack) {
+  aStack.clear();
+  if (!mPseudoStack) {
+    return false;
+  }
+  mStackBuffer.clear();
+  return mStackBuffer.reserve(mMaxStackSize);
+}
+
+void
+ThreadStackHelper::FillStackBuffer() {
+  size_t reservedSize = mMaxStackSize;
+
+  // Go from front to back
+  const volatile StackEntry* entry = mPseudoStack->mStack;
+  const volatile StackEntry* end = entry + mPseudoStack->stackSize();
+  for (; reservedSize-- && entry != end; entry++) {
+    /* We only accept non-copy labels, because
+       we are unable to actually copy labels here */
+    if (!entry->isCopyLabel()) {
+      mStackBuffer.infallibleAppend(entry->label());
+    }
+  }
+  // If we exited early due to buffer size, expand the buffer for next time
+  mMaxStackSize += (end - entry);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadStackHelper.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_ThreadStackHelper_h
+#define mozilla_ThreadStackHelper_h
+
+#include "mozilla/ThreadHangStats.h"
+
+#include "GeckoProfiler.h"
+
+#include <stddef.h>
+
+#if defined(XP_LINUX)
+#include <signal.h>
+#include <semaphore.h>
+#include <sys/types.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#elif defined(XP_MACOSX)
+#include <mach/mach.h>
+#endif
+
+namespace mozilla {
+
+/**
+ * ThreadStackHelper is used to retrieve the profiler pseudo-stack of a
+ * thread, as an alternative of using the profiler to take a profile.
+ * The target thread first declares an ThreadStackHelper instance;
+ * then another thread can call ThreadStackHelper::GetStack to retrieve
+ * the pseudo-stack of the target thread at that instant.
+ *
+ * Only non-copying labels are included in the stack, which means labels
+ * with custom text and markers are not included.
+ */
+class ThreadStackHelper
+{
+public:
+  typedef Telemetry::HangHistogram::Stack Stack;
+
+private:
+  const PseudoStack* const mPseudoStack;
+  Stack mStackBuffer;
+  size_t mMaxStackSize;
+
+  bool PrepareStackBuffer(Stack& aStack);
+  void FillStackBuffer();
+
+public:
+  /**
+   * Initialize ThreadStackHelper. Must be called from main thread.
+   */
+  static void Startup();
+  /**
+   * Uninitialize ThreadStackHelper. Must be called from main thread.
+   */
+  static void Shutdown();
+
+  /**
+   * Create a ThreadStackHelper instance targeting the current thread.
+   */
+  ThreadStackHelper();
+
+  ~ThreadStackHelper();
+
+  /**
+   * Retrieve the current pseudostack of the thread associated
+   * with this ThreadStackHelper.
+   *
+   * @param aStack Stack instance to be filled.
+   */
+  void GetStack(Stack& aStack);
+
+#if defined(XP_LINUX)
+private:
+  static int sInitialized;
+  static sem_t sSem;
+  static struct sigaction sOldSigAction;
+  static ThreadStackHelper* sCurrent;
+
+  static void SigAction(int aSignal, siginfo_t* aInfo, void* aContext);
+
+  pid_t mThreadID;
+
+#elif defined(XP_WIN)
+private:
+  bool mInitialized;
+  HANDLE mThreadID;
+
+#elif defined(XP_MACOSX)
+private:
+  thread_act_t mThreadID;
+
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadStackHelper_h
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -41,16 +41,17 @@ UNIFIED_SOURCES += [
     'nsEnvironment.cpp',
     'nsEventQueue.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsThread.cpp',
     'nsThreadManager.cpp',
     'nsThreadPool.cpp',
     'nsTimerImpl.cpp',
+    'ThreadStackHelper.cpp',
     'TimerThread.cpp',
 ]
 
 MSVC_ENABLE_PGO = True
 
 LOCAL_INCLUDES += [
     '../build',
 ]