Bug 783154 - Refactor the chrome hang code to use the same class as write poisoning. r=taras,benwa,vladan.
This commit is contained in:
@@ -25,6 +25,7 @@ EXPORTS_NAMESPACES = mozilla
|
||||
|
||||
EXPORTS_mozilla = \
|
||||
Telemetry.h \
|
||||
ProcessedStack.h \
|
||||
TelemetryHistograms.h \
|
||||
$(NULL)
|
||||
|
||||
|
||||
79
toolkit/components/telemetry/ProcessedStack.h
Normal file
79
toolkit/components/telemetry/ProcessedStack.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/* -*- Mode: C++; tab-width: 2; 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 ProcessedStack_h__
|
||||
#define ProcessedStack_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
namespace Telemetry {
|
||||
|
||||
// This class represents a stack trace and the modules referenced in that trace.
|
||||
// It is designed to be easy to read and write to disk or network and doesn't
|
||||
// include any logic on how to collect or read the information it stores.
|
||||
class ProcessedStack
|
||||
{
|
||||
public:
|
||||
ProcessedStack();
|
||||
size_t GetStackSize() const;
|
||||
size_t GetNumModules() const;
|
||||
|
||||
struct Frame
|
||||
{
|
||||
// The offset of this program counter in its module or an absolute pc.
|
||||
uintptr_t mOffset;
|
||||
// The index to pass to GetModule to get the module this program counter
|
||||
// was in.
|
||||
uint16_t mModIndex;
|
||||
};
|
||||
struct Module
|
||||
{
|
||||
// The file name, /foo/bar/libxul.so for example.
|
||||
std::string mName;
|
||||
|
||||
// The address it was loaded to.
|
||||
// FIXME: remove this once chrome hang has switched to using offsets.
|
||||
uintptr_t mStart;
|
||||
|
||||
// The size of this mapping. May or may not be the entire file.
|
||||
// FIXME: remove this. It was only used as a sanity check.
|
||||
size_t mMappingSize;
|
||||
// Windows specific fields. On other platforms they are 0/empty.
|
||||
int mPdbAge;
|
||||
std::string mPdbSignature;
|
||||
std::string mPdbName;
|
||||
|
||||
bool operator==(const Module& other) const;
|
||||
};
|
||||
|
||||
const Frame &GetFrame(unsigned aIndex) const;
|
||||
void AddFrame(const Frame& aFrame);
|
||||
const Module &GetModule(unsigned aIndex) const;
|
||||
void AddModule(const Module& aFrame);
|
||||
|
||||
void Clear();
|
||||
|
||||
// FIXME: remove these once chrome hang has switched to using offsets.
|
||||
bool HasModule(const Module &aModule) const;
|
||||
void RemoveModule(unsigned aIndex);
|
||||
|
||||
private:
|
||||
std::vector<Module> mModules;
|
||||
std::vector<Frame> mStack;
|
||||
};
|
||||
|
||||
// Get the current list of loaded modules, filter and pair it to the provided
|
||||
// stack. We let the caller collect the stack since different callers have
|
||||
// different needs (current thread X main thread, stopping the thread, etc).
|
||||
// FIXME: remove the aRelative option once chrome hang has switched to using
|
||||
// offsets.
|
||||
ProcessedStack
|
||||
GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative);
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
#endif // ProcessedStack_h__
|
||||
@@ -3,6 +3,7 @@
|
||||
* 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 <algorithm>
|
||||
#include "base/histogram.h"
|
||||
#include "base/pickle.h"
|
||||
#include "nsIComponentManager.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "nsBaseHashtable.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
@@ -102,8 +104,7 @@ public:
|
||||
uint32_t delay);
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
static void RecordChromeHang(uint32_t duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap);
|
||||
Telemetry::ProcessedStack &aStack);
|
||||
#endif
|
||||
static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
|
||||
struct Stat {
|
||||
@@ -117,10 +118,7 @@ public:
|
||||
typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
|
||||
struct HangReport {
|
||||
uint32_t duration;
|
||||
Telemetry::HangStack callStack;
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
SharedLibraryInfo moduleMap;
|
||||
#endif
|
||||
Telemetry::ProcessedStack mStack;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -1021,6 +1019,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
|
||||
// Each hang report is an object in the 'chromeHangs' array
|
||||
for (size_t i = 0; i < mHangReports.Length(); ++i) {
|
||||
Telemetry::ProcessedStack &stack = mHangReports[i].mStack;
|
||||
JSObject *reportObj = JS_NewObject(cx, NULL, NULL, NULL);
|
||||
if (!reportObj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@@ -1050,10 +1049,11 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const uint32_t pcCount = mHangReports[i].callStack.Length();
|
||||
const uint32_t pcCount = stack.GetStackSize();
|
||||
for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
|
||||
nsCAutoString pcString;
|
||||
pcString.AppendPrintf("0x%p", mHangReports[i].callStack[pcIndex]);
|
||||
const Telemetry::ProcessedStack::Frame &Frame = stack.GetFrame(pcIndex);
|
||||
pcString.AppendPrintf("0x%p", Frame.mOffset);
|
||||
JSString *str = JS_NewStringCopyZ(cx, pcString.get());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@@ -1076,12 +1076,11 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
const uint32_t moduleCount = mHangReports[i].moduleMap.GetSize();
|
||||
const uint32_t moduleCount = stack.GetNumModules();
|
||||
for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
|
||||
// Current module
|
||||
const SharedLibrary &module =
|
||||
mHangReports[i].moduleMap.GetEntry(moduleIndex);
|
||||
const Telemetry::ProcessedStack::Module &module =
|
||||
stack.GetModule(moduleIndex);
|
||||
|
||||
JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr);
|
||||
if (!moduleInfoArray) {
|
||||
@@ -1094,7 +1093,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
|
||||
// Start address
|
||||
nsCAutoString addressString;
|
||||
addressString.AppendPrintf("0x%p", module.GetStart());
|
||||
addressString.AppendPrintf("0x%p", module.mStart);
|
||||
JSString *str = JS_NewStringCopyZ(cx, addressString.get());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@@ -1105,7 +1104,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
}
|
||||
|
||||
// Module name
|
||||
str = JS_NewStringCopyZ(cx, module.GetName());
|
||||
str = JS_NewStringCopyZ(cx, module.mName.c_str());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -1115,26 +1114,19 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
}
|
||||
|
||||
// Module size in memory
|
||||
val = INT_TO_JSVAL(int32_t(module.GetEnd() - module.GetStart()));
|
||||
val = INT_TO_JSVAL(int32_t(module.mMappingSize));
|
||||
if (!JS_SetElement(cx, moduleInfoArray, 2, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// "PDB Age" identifier
|
||||
val = INT_TO_JSVAL(0);
|
||||
#if defined(MOZ_PROFILING) && defined(XP_WIN)
|
||||
val = INT_TO_JSVAL(module.GetPdbAge());
|
||||
#endif
|
||||
val = INT_TO_JSVAL(module.mPdbAge);
|
||||
if (!JS_SetElement(cx, moduleInfoArray, 3, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// "PDB Signature" GUID
|
||||
char guidString[NSID_LENGTH] = { 0 };
|
||||
#if defined(MOZ_PROFILING) && defined(XP_WIN)
|
||||
module.GetPdbSignature().ToProvidedString(guidString);
|
||||
#endif
|
||||
str = JS_NewStringCopyZ(cx, guidString);
|
||||
str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -1144,11 +1136,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
}
|
||||
|
||||
// Name of associated PDB file
|
||||
const char *pdbName = "";
|
||||
#if defined(MOZ_PROFILING) && defined(XP_WIN)
|
||||
pdbName = module.GetPdbName();
|
||||
#endif
|
||||
str = JS_NewStringCopyZ(cx, pdbName);
|
||||
str = JS_NewStringCopyZ(cx, module.mPdbName.c_str());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -1157,7 +1145,6 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@@ -1424,8 +1411,7 @@ TelemetryImpl::RecordSlowStatement(const nsACString &sql,
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void
|
||||
TelemetryImpl::RecordChromeHang(uint32_t duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap)
|
||||
Telemetry::ProcessedStack &aStack)
|
||||
{
|
||||
MOZ_ASSERT(sTelemetry);
|
||||
if (!sTelemetry->mCanRecord) {
|
||||
@@ -1436,17 +1422,19 @@ TelemetryImpl::RecordChromeHang(uint32_t duration,
|
||||
|
||||
// Only report the modules which changed since the first hang report
|
||||
if (sTelemetry->mHangReports.Length()) {
|
||||
SharedLibraryInfo &firstModuleMap =
|
||||
sTelemetry->mHangReports[0].moduleMap;
|
||||
for (size_t i = 0; i < moduleMap.GetSize(); ++i) {
|
||||
if (firstModuleMap.Contains(moduleMap.GetEntry(i))) {
|
||||
moduleMap.RemoveEntries(i, i + 1);
|
||||
Telemetry::ProcessedStack &firstStack =
|
||||
sTelemetry->mHangReports[0].mStack;
|
||||
const uint32_t moduleCount = aStack.GetNumModules();
|
||||
for (size_t i = 0; i < moduleCount; ++i) {
|
||||
const Telemetry::ProcessedStack::Module &module = aStack.GetModule(i);
|
||||
if (firstStack.HasModule(module)) {
|
||||
aStack.RemoveModule(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HangReport newReport = { duration, callStack, moduleMap };
|
||||
HangReport newReport = { duration, aStack };
|
||||
sTelemetry->mHangReports.AppendElement(newReport);
|
||||
}
|
||||
#endif
|
||||
@@ -1534,13 +1522,195 @@ void Init()
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void RecordChromeHang(uint32_t duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap)
|
||||
ProcessedStack &aStack)
|
||||
{
|
||||
TelemetryImpl::RecordChromeHang(duration, callStack, moduleMap);
|
||||
TelemetryImpl::RecordChromeHang(duration, aStack);
|
||||
}
|
||||
#endif
|
||||
|
||||
ProcessedStack::ProcessedStack()
|
||||
{
|
||||
}
|
||||
|
||||
size_t ProcessedStack::GetStackSize() const
|
||||
{
|
||||
return mStack.size();
|
||||
}
|
||||
|
||||
const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
|
||||
{
|
||||
MOZ_ASSERT(aIndex < mStack.size());
|
||||
return mStack[aIndex];
|
||||
}
|
||||
|
||||
void ProcessedStack::AddFrame(const Frame &aFrame)
|
||||
{
|
||||
mStack.push_back(aFrame);
|
||||
}
|
||||
|
||||
size_t ProcessedStack::GetNumModules() const
|
||||
{
|
||||
return mModules.size();
|
||||
}
|
||||
|
||||
const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
|
||||
{
|
||||
MOZ_ASSERT(aIndex < mModules.size());
|
||||
return mModules[aIndex];
|
||||
}
|
||||
|
||||
bool ProcessedStack::HasModule(const Module &aModule) const {
|
||||
return mModules.end() !=
|
||||
std::find(mModules.begin(), mModules.end(), aModule);
|
||||
}
|
||||
|
||||
void ProcessedStack::RemoveModule(unsigned aIndex) {
|
||||
mModules.erase(mModules.begin() + aIndex);
|
||||
}
|
||||
|
||||
void ProcessedStack::AddModule(const Module &aModule)
|
||||
{
|
||||
mModules.push_back(aModule);
|
||||
}
|
||||
|
||||
void ProcessedStack::Clear() {
|
||||
mModules.clear();
|
||||
mStack.clear();
|
||||
}
|
||||
|
||||
bool ProcessedStack::Module::operator==(const Module& aOther) const {
|
||||
return mName == aOther.mName &&
|
||||
mStart == aOther.mStart &&
|
||||
mMappingSize == aOther.mMappingSize &&
|
||||
mPdbAge == aOther.mPdbAge &&
|
||||
mPdbSignature == aOther.mPdbSignature &&
|
||||
mPdbName == aOther.mPdbName;
|
||||
}
|
||||
|
||||
struct StackFrame
|
||||
{
|
||||
uintptr_t mPC; // The program counter at this position in the call stack.
|
||||
uint16_t mIndex; // The number of this frame in the call stack.
|
||||
uint16_t mModIndex; // The index of module that has this program counter.
|
||||
};
|
||||
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mPC < b.mPC;
|
||||
}
|
||||
|
||||
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mIndex < b.mIndex;
|
||||
}
|
||||
#endif
|
||||
|
||||
ProcessedStack GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative)
|
||||
{
|
||||
std::vector<StackFrame> rawStack;
|
||||
for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(),
|
||||
e = aPCs.end(); i != e; ++i) {
|
||||
uintptr_t aPC = *i;
|
||||
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
|
||||
std::numeric_limits<uint16_t>::max()};
|
||||
rawStack.push_back(Frame);
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
// Remove all modules not referenced by a PC on the stack
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
|
||||
|
||||
size_t moduleIndex = 0;
|
||||
size_t stackIndex = 0;
|
||||
size_t stackSize = rawStack.size();
|
||||
|
||||
SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
|
||||
rawModules.SortByAddress();
|
||||
|
||||
while (moduleIndex < rawModules.GetSize()) {
|
||||
const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
|
||||
uintptr_t moduleStart = module.GetStart();
|
||||
uintptr_t moduleEnd = module.GetEnd() - 1;
|
||||
// the interval is [moduleStart, moduleEnd)
|
||||
|
||||
bool moduleReferenced = false;
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
uintptr_t pc = rawStack[stackIndex].mPC;
|
||||
if (pc >= moduleEnd)
|
||||
break;
|
||||
|
||||
if (pc >= moduleStart) {
|
||||
// If the current PC is within the current module, mark
|
||||
// module as used
|
||||
moduleReferenced = true;
|
||||
if (aRelative)
|
||||
rawStack[stackIndex].mPC -= moduleStart;
|
||||
rawStack[stackIndex].mModIndex = moduleIndex;
|
||||
} else {
|
||||
// PC does not belong to any module. It is probably from
|
||||
// the JIT. Use a fixed mPC so that we don't get different
|
||||
// stacks on different runs.
|
||||
rawStack[stackIndex].mPC =
|
||||
std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleReferenced) {
|
||||
++moduleIndex;
|
||||
} else {
|
||||
// Remove module if no PCs within its address range
|
||||
rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
// These PCs are past the last module.
|
||||
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
|
||||
#endif
|
||||
|
||||
// Copy the information to the return value.
|
||||
ProcessedStack Ret;
|
||||
for (std::vector<StackFrame>::iterator i = rawStack.begin(),
|
||||
e = rawStack.end(); i != e; ++i) {
|
||||
const StackFrame &rawFrame = *i;
|
||||
ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex };
|
||||
Ret.AddFrame(frame);
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
|
||||
const SharedLibrary &info = rawModules.GetEntry(i);
|
||||
ProcessedStack::Module module = {
|
||||
info.GetName(),
|
||||
info.GetStart(),
|
||||
info.GetEnd() - info.GetStart(),
|
||||
#ifdef XP_WIN
|
||||
info.GetPdbAge(),
|
||||
"", // mPdbSignature
|
||||
info.GetPdbName(),
|
||||
#else
|
||||
0, // mPdbAge
|
||||
"", // mPdbSignature
|
||||
"" // mPdbName
|
||||
#endif
|
||||
};
|
||||
#ifdef XP_WIN
|
||||
char guidString[NSID_LENGTH] = { 0 };
|
||||
info.GetPdbSignature().ToProvidedString(guidString);
|
||||
module.mPdbSignature = guidString;
|
||||
#endif
|
||||
Ret.AddModule(module);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -127,10 +127,7 @@ void RecordSlowSQLStatement(const nsACString &statement,
|
||||
*/
|
||||
const PRUint32 kSlowStatementThreshold = 100;
|
||||
|
||||
/**
|
||||
* nsTArray of pointers representing PCs on a call stack
|
||||
*/
|
||||
typedef nsTArray<uintptr_t> HangStack;
|
||||
class ProcessedStack;
|
||||
|
||||
/**
|
||||
* Record the main thread's call stack after it hangs.
|
||||
@@ -141,8 +138,7 @@ typedef nsTArray<uintptr_t> HangStack;
|
||||
*/
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
void RecordChromeHang(PRUint32 duration,
|
||||
const HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap);
|
||||
ProcessedStack &aStack);
|
||||
#endif
|
||||
|
||||
} // namespace Telemetry
|
||||
|
||||
@@ -59,7 +59,7 @@ nsProfiler::GetProfile(char **aProfile)
|
||||
}
|
||||
|
||||
static void
|
||||
AddSharedLibraryInfoToStream(std::ostream& aStream, SharedLibrary& aLib)
|
||||
AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
|
||||
{
|
||||
aStream << "{";
|
||||
aStream << "\"start\":" << aLib.GetStart();
|
||||
|
||||
@@ -140,7 +140,7 @@ public:
|
||||
mEntries.push_back(entry);
|
||||
}
|
||||
|
||||
SharedLibrary& GetEntry(size_t i)
|
||||
const SharedLibrary& GetEntry(size_t i) const
|
||||
{
|
||||
return mEntries[i];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "mach_override.h"
|
||||
@@ -38,131 +39,11 @@ struct FuncData {
|
||||
// 'Function' after it has been replaced.
|
||||
};
|
||||
|
||||
|
||||
// FIXME: duplicated code. The HangMonitor could also report processed addresses,
|
||||
// this class should be moved somewhere it can be shared.
|
||||
class ProcessedStack
|
||||
{
|
||||
public:
|
||||
ProcessedStack() : mProcessed(false)
|
||||
{
|
||||
}
|
||||
void Reserve(unsigned int n)
|
||||
{
|
||||
mStack.reserve(n);
|
||||
}
|
||||
size_t GetStackSize()
|
||||
{
|
||||
return mStack.size();
|
||||
}
|
||||
struct ProcessedStackFrame
|
||||
{
|
||||
uintptr_t mOffset;
|
||||
uint16_t mModIndex;
|
||||
};
|
||||
ProcessedStackFrame GetFrame(unsigned aIndex)
|
||||
{
|
||||
const StackFrame &Frame = mStack[aIndex];
|
||||
ProcessedStackFrame Ret = { Frame.mPC, Frame.mModIndex };
|
||||
return Ret;
|
||||
}
|
||||
size_t GetNumModules()
|
||||
{
|
||||
MOZ_ASSERT(mProcessed);
|
||||
return mModules.GetSize();
|
||||
}
|
||||
const char *GetModuleName(unsigned aIndex)
|
||||
{
|
||||
MOZ_ASSERT(mProcessed);
|
||||
return mModules.GetEntry(aIndex).GetName();
|
||||
}
|
||||
void AddStackFrame(uintptr_t aPC)
|
||||
{
|
||||
MOZ_ASSERT(!mProcessed);
|
||||
StackFrame Frame = {aPC, static_cast<uint16_t>(mStack.size()),
|
||||
std::numeric_limits<uint16_t>::max()};
|
||||
mStack.push_back(Frame);
|
||||
}
|
||||
void Process()
|
||||
{
|
||||
mProcessed = true;
|
||||
mModules = SharedLibraryInfo::GetInfoForSelf();
|
||||
mModules.SortByAddress();
|
||||
|
||||
// Remove all modules not referenced by a PC on the stack
|
||||
std::sort(mStack.begin(), mStack.end(), CompareByPC);
|
||||
|
||||
size_t moduleIndex = 0;
|
||||
size_t stackIndex = 0;
|
||||
size_t stackSize = mStack.size();
|
||||
|
||||
while (moduleIndex < mModules.GetSize()) {
|
||||
SharedLibrary& module = mModules.GetEntry(moduleIndex);
|
||||
uintptr_t moduleStart = module.GetStart();
|
||||
uintptr_t moduleEnd = module.GetEnd() - 1;
|
||||
// the interval is [moduleStart, moduleEnd)
|
||||
|
||||
bool moduleReferenced = false;
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
uintptr_t pc = mStack[stackIndex].mPC;
|
||||
if (pc >= moduleEnd)
|
||||
break;
|
||||
|
||||
if (pc >= moduleStart) {
|
||||
// If the current PC is within the current module, mark
|
||||
// module as used
|
||||
moduleReferenced = true;
|
||||
mStack[stackIndex].mPC -= moduleStart;
|
||||
mStack[stackIndex].mModIndex = moduleIndex;
|
||||
} else {
|
||||
// PC does not belong to any module. It is probably from
|
||||
// the JIT. Use a fixed mPC so that we don't get different
|
||||
// stacks on different runs.
|
||||
mStack[stackIndex].mPC =
|
||||
std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleReferenced) {
|
||||
++moduleIndex;
|
||||
} else {
|
||||
// Remove module if no PCs within its address range
|
||||
mModules.RemoveEntries(moduleIndex, moduleIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (;stackIndex < stackSize; ++stackIndex) {
|
||||
// These PCs are past the last module.
|
||||
mStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
|
||||
std::sort(mStack.begin(), mStack.end(), CompareByIndex);
|
||||
}
|
||||
|
||||
private:
|
||||
struct StackFrame
|
||||
{
|
||||
uintptr_t mPC; // The program counter at this position in the call stack.
|
||||
uint16_t mIndex; // The number of this frame in the call stack.
|
||||
uint16_t mModIndex; // The index of module that has this program counter.
|
||||
};
|
||||
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mPC < b.mPC;
|
||||
}
|
||||
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
|
||||
{
|
||||
return a.mIndex < b.mIndex;
|
||||
}
|
||||
SharedLibraryInfo mModules;
|
||||
std::vector<StackFrame> mStack;
|
||||
bool mProcessed;
|
||||
};
|
||||
|
||||
void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
|
||||
{
|
||||
ProcessedStack *stack = static_cast<ProcessedStack*>(aClosure);
|
||||
stack->AddStackFrame(reinterpret_cast<uintptr_t>(aPC));
|
||||
std::vector<uintptr_t> *stack =
|
||||
static_cast<std::vector<uintptr_t>*>(aClosure);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
char *sProfileDirectory = NULL;
|
||||
@@ -177,9 +58,10 @@ bool ValidWriteAssert(bool ok)
|
||||
|
||||
// Write the stack and loaded libraries to a file. We can get here
|
||||
// concurrently from many writes, so we use multiple temporary files.
|
||||
ProcessedStack stack;
|
||||
NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&stack), 0);
|
||||
stack.Process();
|
||||
std::vector<uintptr_t> rawStack;
|
||||
|
||||
NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&rawStack), 0);
|
||||
Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack, true);
|
||||
|
||||
nsPrintfCString nameAux("%s%s", sProfileDirectory,
|
||||
"/Telemetry.LateWriteTmpXXXXXX");
|
||||
@@ -192,14 +74,15 @@ bool ValidWriteAssert(bool ok)
|
||||
size_t numModules = stack.GetNumModules();
|
||||
fprintf(f, "%zu\n", numModules);
|
||||
for (int i = 0; i < numModules; ++i) {
|
||||
const char *name = stack.GetModuleName(i);
|
||||
fprintf(f, "%s\n", name ? name : "");
|
||||
Telemetry::ProcessedStack::Module module = stack.GetModule(i);
|
||||
fprintf(f, "%s\n", module.mName.c_str());
|
||||
}
|
||||
|
||||
size_t numFrames = stack.GetStackSize();
|
||||
fprintf(f, "%zu\n", numFrames);
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
const ProcessedStack::ProcessedStackFrame &frame = stack.GetFrame(i);
|
||||
const Telemetry::ProcessedStack::Frame &frame =
|
||||
stack.GetFrame(i);
|
||||
// NOTE: We write the offsets, while the atos tool expects a value with
|
||||
// the virtual address added. For example, running otool -l on the the firefox
|
||||
// binary shows
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsStackWalk.h"
|
||||
@@ -112,75 +113,32 @@ static void
|
||||
ChromeStackWalker(void *aPC, void *aSP, void *aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClosure);
|
||||
Telemetry::HangStack *callStack =
|
||||
reinterpret_cast< Telemetry::HangStack* >(aClosure);
|
||||
|
||||
if (callStack->Length() < MAX_CALL_STACK_PCS)
|
||||
callStack->AppendElement(reinterpret_cast<uintptr_t>(aPC));
|
||||
std::vector<uintptr_t> *stack =
|
||||
static_cast<std::vector<uintptr_t>*>(aClosure);
|
||||
if (stack->size() == MAX_CALL_STACK_PCS)
|
||||
return;
|
||||
MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
static void
|
||||
GetChromeHangReport(Telemetry::HangStack &callStack, SharedLibraryInfo &moduleMap)
|
||||
GetChromeHangReport(Telemetry::ProcessedStack &aStack)
|
||||
{
|
||||
MOZ_ASSERT(winMainThreadHandle);
|
||||
|
||||
// The thread we're about to suspend might have the alloc lock
|
||||
// so allocate ahead of time
|
||||
callStack.SetCapacity(MAX_CALL_STACK_PCS);
|
||||
|
||||
std::vector<uintptr_t> rawStack;
|
||||
rawStack.reserve(MAX_CALL_STACK_PCS);
|
||||
DWORD ret = ::SuspendThread(winMainThreadHandle);
|
||||
if (ret == -1) {
|
||||
callStack.Clear();
|
||||
moduleMap.Clear();
|
||||
if (ret == -1)
|
||||
return;
|
||||
}
|
||||
NS_StackWalk(ChromeStackWalker, 0, &callStack,
|
||||
NS_StackWalk(ChromeStackWalker, 0, reinterpret_cast<void*>(&rawStack),
|
||||
reinterpret_cast<uintptr_t>(winMainThreadHandle));
|
||||
ret = ::ResumeThread(winMainThreadHandle);
|
||||
if (ret == -1) {
|
||||
callStack.Clear();
|
||||
moduleMap.Clear();
|
||||
if (ret == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
moduleMap = SharedLibraryInfo::GetInfoForSelf();
|
||||
moduleMap.SortByAddress();
|
||||
|
||||
// Remove all modules not referenced by a PC on the stack
|
||||
Telemetry::HangStack sortedStack = callStack;
|
||||
sortedStack.Sort();
|
||||
|
||||
size_t moduleIndex = 0;
|
||||
size_t stackIndex = 0;
|
||||
bool unreferencedModule = true;
|
||||
while (stackIndex < sortedStack.Length() && moduleIndex < moduleMap.GetSize()) {
|
||||
uintptr_t pc = sortedStack[stackIndex];
|
||||
SharedLibrary& module = moduleMap.GetEntry(moduleIndex);
|
||||
uintptr_t moduleStart = module.GetStart();
|
||||
uintptr_t moduleEnd = module.GetEnd() - 1;
|
||||
if (moduleStart <= pc && pc <= moduleEnd) {
|
||||
// If the current PC is within the current module, mark module as used
|
||||
unreferencedModule = false;
|
||||
++stackIndex;
|
||||
} else if (pc > moduleEnd) {
|
||||
if (unreferencedModule) {
|
||||
// Remove module if no PCs within its address range
|
||||
moduleMap.RemoveEntries(moduleIndex, moduleIndex + 1);
|
||||
} else {
|
||||
// Module was referenced on stack, but current PC belongs to later module
|
||||
unreferencedModule = true;
|
||||
++moduleIndex;
|
||||
}
|
||||
} else {
|
||||
// PC does not belong to any module
|
||||
++stackIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up remaining unreferenced modules, i.e. module addresses > max(pc)
|
||||
if (moduleIndex + 1 < moduleMap.GetSize()) {
|
||||
moduleMap.RemoveEntries(moduleIndex + 1, moduleMap.GetSize());
|
||||
}
|
||||
aStack = Telemetry::GetStackAndModules(rawStack, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -198,8 +156,7 @@ ThreadMain(void*)
|
||||
int waitCount = 0;
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
Telemetry::HangStack hangStack;
|
||||
SharedLibraryInfo hangModuleMap;
|
||||
Telemetry::ProcessedStack stack;
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
@@ -224,7 +181,7 @@ ThreadMain(void*)
|
||||
++waitCount;
|
||||
if (waitCount == 2) {
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
GetChromeHangReport(hangStack, hangModuleMap);
|
||||
GetChromeHangReport(stack);
|
||||
#else
|
||||
PRInt32 delay =
|
||||
PRInt32(PR_IntervalToSeconds(now - timestamp));
|
||||
@@ -239,9 +196,8 @@ ThreadMain(void*)
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
if (waitCount >= 2) {
|
||||
PRUint32 hangDuration = PR_IntervalToSeconds(now - lastTimestamp);
|
||||
Telemetry::RecordChromeHang(hangDuration, hangStack, hangModuleMap);
|
||||
hangStack.Clear();
|
||||
hangModuleMap.Clear();
|
||||
Telemetry::RecordChromeHang(hangDuration, stack);
|
||||
stack.Clear();
|
||||
}
|
||||
#endif
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
Reference in New Issue
Block a user