Bug 1312883 - Use MozStackWalk to gather native stacks on Windows. r?jchen, gfritzsche
MozReview-Commit-ID: A2aZAL03FG2
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -278,17 +278,17 @@ HangMonitorChild::HangMonitorChild(Proce
mShutdownDone(false),
mIPCOpen(true)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
mContext = danger::GetJSContext();
mForcePaintMonitor =
MakeUnique<mozilla::BackgroundHangMonitor>("Gecko_Child_ForcePaint",
128, /* ms timeout for microhangs */
- 8192 /* ms timeout for permahangs */,
+ 1024, /* ms timeout for permahangs */
BackgroundHangMonitor::THREAD_PRIVATE);
}
HangMonitorChild::~HangMonitorChild()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
MOZ_ASSERT(sInstance == this);
mForcePaintMonitor = nullptr;
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2074,19 +2074,28 @@ CreateJSHangHistogram(JSContext* cx, con
!JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
(!hangAnnotations.empty() && // <-- Only define annotations when nonempty
!JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
return nullptr;
}
if (!hang.GetNativeStack().empty()) {
- JS::RootedObject native(cx, CreateJSHangStack(cx, hang.GetNativeStack()));
- if (!native ||
- !JS_DefineProperty(cx, ret, "nativeStack", native, JSPROP_ENUMERATE)) {
+ const Telemetry::HangStack& stack = hang.GetNativeStack();
+ const std::vector<uintptr_t>& frames = stack.GetNativeFrames();
+ Telemetry::ProcessedStack processed = Telemetry::GetStackAndModules(frames);
+
+ CombinedStacks singleStack;
+ singleStack.AddStack(processed);
+ JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, singleStack));
+ if (!fullReportObj) {
+ return nullptr;
+ }
+
+ if (!JS_DefineProperty(cx, ret, "nativeStack", fullReportObj, JSPROP_ENUMERATE)) {
return nullptr;
}
}
return ret;
}
static JSObject*
CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
--- a/toolkit/components/telemetry/ThreadHangStats.h
+++ b/toolkit/components/telemetry/ThreadHangStats.h
@@ -46,27 +46,34 @@ public:
};
/* HangStack stores an array of const char pointers,
with optional internal storage for strings. */
class HangStack
{
public:
static const size_t sMaxInlineStorage = 8;
+ // The maximum depth for the native stack frames that we might collect.
+ static const size_t sMaxNativeFrames = 25;
private:
typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
Impl mImpl;
// Stack entries can either be a static const char*
// or a pointer to within this buffer.
mozilla::Vector<char, 0> mBuffer;
+ // When a native stack is gathered, this vector holds the raw program counter
+ // values that MozStackWalk will return to us after it walks the stack.
+ // When gathering the Telemetry payload, Telemetry will take care of mapping
+ // these program counters to proper addresses within modules.
+ std::vector<uintptr_t> mNativeFrames;
public:
- HangStack() { }
+ HangStack() {}
HangStack(HangStack&& aOther)
: mImpl(mozilla::Move(aOther.mImpl))
, mBuffer(mozilla::Move(aOther.mBuffer))
{
}
bool operator==(const HangStack& aOther) const {
@@ -106,16 +113,17 @@ public:
void erase(const char** aEntry) { mImpl.erase(aEntry); }
void erase(const char** aBegin, const char** aEnd) {
mImpl.erase(aBegin, aEnd);
}
void clear() {
mImpl.clear();
mBuffer.clear();
+ mNativeFrames.clear();
}
bool IsInBuffer(const char* aEntry) const {
return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
}
bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
// If the entry came from the buffer, we need to compare its content;
@@ -131,16 +139,31 @@ public:
// aCapacity is the minimal capacity and Vector may make the actual
// capacity larger, in which case we want to use up all the space.
return mBuffer.reserve(aCapacity) &&
mBuffer.reserve(mBuffer.capacity());
}
const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
const char* AppendViaBuffer(const char* aText, size_t aLength);
+
+ void EnsureNativeFrameCapacity(size_t aCapacity) {
+ mNativeFrames.reserve(aCapacity);
+ }
+
+ void AppendNativeFrame(uintptr_t aPc) {
+ MOZ_ASSERT(mNativeFrames.size() <= sMaxNativeFrames);
+ if (mNativeFrames.size() < sMaxNativeFrames) {
+ mNativeFrames.push_back(aPc);
+ }
+ }
+
+ const std::vector<uintptr_t>& GetNativeFrames() const {
+ return mNativeFrames;
+ }
};
/* A hang histogram consists of a stack associated with the
hang, along with a time histogram of the hang times. */
class HangHistogram : public TimeHistogram
{
private:
static uint32_t GetHash(const HangStack& aStack);
--- a/xpcom/threads/ThreadStackHelper.cpp
+++ b/xpcom/threads/ThreadStackHelper.cpp
@@ -158,16 +158,22 @@ public:
ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
~ScopedSetPtr() { mPtr = nullptr; }
};
} // namespace
void
ThreadStackHelper::GetStack(Stack& aStack)
{
+ GetStackInternal(aStack, /* aAppendNativeStack */ false);
+}
+
+void
+ThreadStackHelper::GetStackInternal(Stack& aStack, bool aAppendNativeStack)
+{
// Always run PrepareStackBuffer first to clear aStack
if (!PrepareStackBuffer(aStack)) {
// Skip and return empty aStack
return;
}
ScopedSetPtr<Stack> stackPtr(mStackToFill, &aStack);
@@ -190,30 +196,47 @@ ThreadStackHelper::GetStack(Stack& aStac
}
MOZ_ALWAYS_TRUE(!::sem_wait(&mSem));
#elif defined(XP_WIN)
if (!mInitialized) {
MOZ_ASSERT(false);
return;
}
+
+ if (aAppendNativeStack) {
+ aStack.EnsureNativeFrameCapacity(Telemetry::HangStack::sMaxNativeFrames);
+ }
+
if (::SuspendThread(mThreadID) == DWORD(-1)) {
MOZ_ASSERT(false);
return;
}
// SuspendThread is asynchronous, so the thread may still be running. Use
// GetThreadContext to ensure it's really suspended.
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
if (::GetThreadContext(mThreadID, &context)) {
FillStackBuffer();
}
+ if (aAppendNativeStack) {
+ auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
+ Stack* stack = static_cast<Stack*>(aClosure);
+ stack->AppendNativeFrame(reinterpret_cast<uintptr_t>(aPC));
+ };
+
+ MozStackWalk(callback, /* skipFrames */ 0,
+ /* maxFrames */ Telemetry::HangStack::sMaxNativeFrames,
+ reinterpret_cast<void*>(&aStack),
+ reinterpret_cast<uintptr_t>(mThreadID), nullptr);
+ }
+
MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
#elif defined(XP_MACOSX)
# if defined(MOZ_VALGRIND) && defined(RUNNING_ON_VALGRIND)
if (RUNNING_ON_VALGRIND) {
/* thread_suspend and thread_resume sometimes hang runs on Valgrind,
for unknown reasons. So, just avoid them. See bug 1100911. */
return;
@@ -231,20 +254,17 @@ ThreadStackHelper::GetStack(Stack& aStac
#endif
}
void
ThreadStackHelper::GetNativeStack(Stack& aStack)
{
#ifdef MOZ_THREADSTACKHELPER_NATIVE
- // Get pseudostack first and fill the thread context.
- GetStack(aStack);
-
- // TODO: walk the saved stack frames.
+ GetStackInternal(aStack, /* aAppendNativeStack */ true);
#endif // MOZ_THREADSTACKHELPER_NATIVE
}
#ifdef XP_LINUX
int ThreadStackHelper::sInitialized;
int ThreadStackHelper::sFillStackSignum;
--- a/xpcom/threads/ThreadStackHelper.h
+++ b/xpcom/threads/ThreadStackHelper.h
@@ -104,16 +104,18 @@ public:
/**
* Retrieve the current native stack of the thread associated
* with this ThreadStackHelper.
*
* @param aNativeStack Stack instance to be filled.
*/
void GetNativeStack(Stack& aStack);
+private:
+ void GetStackInternal(Stack& aStack, bool aAppendNativeStack);
#if defined(XP_LINUX)
private:
static int sInitialized;
static int sFillStackSignum;
static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext);
sem_t mSem;