mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
Bug 712109 - Implement chrome hang reporting on profiling branch. r=ehsan
This commit is contained in:
parent
9617e4bf4f
commit
a6bb287122
@ -133,12 +133,20 @@ public:
|
||||
static void RecordSlowStatement(const nsACString &statement,
|
||||
const nsACString &dbName,
|
||||
PRUint32 delay);
|
||||
static void RecordChromeHang(PRUint32 duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap);
|
||||
static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
|
||||
struct StmtStats {
|
||||
PRUint32 hitCount;
|
||||
PRUint32 totalTime;
|
||||
};
|
||||
typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
|
||||
struct HangReport {
|
||||
PRUint32 duration;
|
||||
Telemetry::HangStack callStack;
|
||||
SharedLibraryInfo moduleMap;
|
||||
};
|
||||
|
||||
private:
|
||||
static bool StatementReflector(SlowSQLEntryType *entry, JSContext *cx,
|
||||
@ -181,6 +189,8 @@ private:
|
||||
// AutoHashtable here.
|
||||
nsTHashtable<nsCStringHashKey> mTrackedDBs;
|
||||
Mutex mHashMutex;
|
||||
nsTArray<HangReport> mHangReports;
|
||||
Mutex mHangReportsMutex;
|
||||
};
|
||||
|
||||
TelemetryImpl* TelemetryImpl::sTelemetry = NULL;
|
||||
@ -460,7 +470,8 @@ WrapAndReturnHistogram(Histogram *h, JSContext *cx, jsval *ret)
|
||||
TelemetryImpl::TelemetryImpl():
|
||||
mHistogramMap(Telemetry::HistogramCount),
|
||||
mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default),
|
||||
mHashMutex("Telemetry::mHashMutex")
|
||||
mHashMutex("Telemetry::mHashMutex"),
|
||||
mHangReportsMutex("Telemetry::mHangReportsMutex")
|
||||
{
|
||||
// A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs
|
||||
const char *trackedDBs[] = {
|
||||
@ -942,6 +953,144 @@ TelemetryImpl::GetSlowSQL(JSContext *cx, jsval *ret)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
|
||||
{
|
||||
MutexAutoLock hangReportMutex(mHangReportsMutex);
|
||||
JSObject *reportArray = JS_NewArrayObject(cx, 0, nsnull);
|
||||
if (!reportArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
*ret = OBJECT_TO_JSVAL(reportArray);
|
||||
|
||||
// Each hang report is an object in the 'chromeHangs' array
|
||||
for (size_t i = 0; i < mHangReports.Length(); ++i) {
|
||||
JSObject *reportObj = JS_NewObject(cx, NULL, NULL, NULL);
|
||||
if (!reportObj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
jsval reportObjVal = OBJECT_TO_JSVAL(reportObj);
|
||||
if (!JS_SetElement(cx, reportArray, i, &reportObjVal)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Record the hang duration (expressed in seconds)
|
||||
JSBool ok = JS_DefineProperty(cx, reportObj, "duration",
|
||||
INT_TO_JSVAL(mHangReports[i].duration),
|
||||
NULL, NULL, JSPROP_ENUMERATE);
|
||||
if (!ok) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Represent call stack PCs as strings
|
||||
// (JS can't represent all 64-bit integer values)
|
||||
JSObject *pcArray = JS_NewArrayObject(cx, 0, nsnull);
|
||||
if (!pcArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
ok = JS_DefineProperty(cx, reportObj, "stack", OBJECT_TO_JSVAL(pcArray),
|
||||
NULL, NULL, JSPROP_ENUMERATE);
|
||||
if (!ok) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const PRUint32 pcCount = mHangReports[i].callStack.Length();
|
||||
for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
|
||||
nsCAutoString pcString;
|
||||
pcString.AppendPrintf("0x%p", mHangReports[i].callStack[pcIndex]);
|
||||
JSString *str = JS_NewStringCopyZ(cx, pcString.get());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
jsval v = STRING_TO_JSVAL(str);
|
||||
if (!JS_SetElement(cx, pcArray, pcIndex, &v)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Record memory map info
|
||||
JSObject *moduleArray = JS_NewArrayObject(cx, 0, nsnull);
|
||||
if (!moduleArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
ok = JS_DefineProperty(cx, reportObj, "memoryMap",
|
||||
OBJECT_TO_JSVAL(moduleArray),
|
||||
NULL, NULL, JSPROP_ENUMERATE);
|
||||
if (!ok) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const PRUint32 moduleCount = mHangReports[i].moduleMap.GetSize();
|
||||
for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
|
||||
// Current module
|
||||
const SharedLibrary &module =
|
||||
mHangReports[i].moduleMap.GetEntry(moduleIndex);
|
||||
|
||||
JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nsnull);
|
||||
if (!moduleInfoArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
jsval val = OBJECT_TO_JSVAL(moduleInfoArray);
|
||||
if (!JS_SetElement(cx, moduleArray, moduleIndex, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Start address
|
||||
nsCAutoString addressString;
|
||||
addressString.AppendPrintf("0x%p", module.GetStart());
|
||||
JSString *str = JS_NewStringCopyZ(cx, addressString.get());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
val = STRING_TO_JSVAL(str);
|
||||
if (!JS_SetElement(cx, moduleInfoArray, 0, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Module name
|
||||
str = JS_NewStringCopyZ(cx, module.GetName());
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
val = STRING_TO_JSVAL(str);
|
||||
if (!JS_SetElement(cx, moduleInfoArray, 1, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Module size in memory
|
||||
val = INT_TO_JSVAL(int32_t(module.GetEnd() - module.GetStart()));
|
||||
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
|
||||
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);
|
||||
if (!str) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
val = STRING_TO_JSVAL(str);
|
||||
if (!JS_SetElement(cx, moduleInfoArray, 4, &val)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::GetRegisteredHistograms(JSContext *cx, jsval *ret)
|
||||
{
|
||||
@ -1378,6 +1527,34 @@ TelemetryImpl::RecordSlowStatement(const nsACString &statement,
|
||||
entry->mData.totalTime += delay;
|
||||
}
|
||||
|
||||
void
|
||||
TelemetryImpl::RecordChromeHang(PRUint32 duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap)
|
||||
{
|
||||
MOZ_ASSERT(sTelemetry);
|
||||
if (!sTelemetry->mCanRecord) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
|
||||
|
||||
// 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);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HangReport newReport = { duration, callStack, moduleMap };
|
||||
sTelemetry->mHangReports.AppendElement(newReport);
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
|
||||
|
||||
@ -1459,6 +1636,13 @@ void Init()
|
||||
MOZ_ASSERT(telemetryService);
|
||||
}
|
||||
|
||||
void RecordChromeHang(PRUint32 duration,
|
||||
const Telemetry::HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap)
|
||||
{
|
||||
TelemetryImpl::RecordChromeHang(duration, callStack, moduleMap);
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -42,6 +42,9 @@
|
||||
#include "mozilla/GuardObjects.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/StartupTimeline.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "shared-libraries.h"
|
||||
|
||||
namespace base {
|
||||
class Histogram;
|
||||
@ -156,6 +159,22 @@ void RecordSlowSQLStatement(const nsACString &statement,
|
||||
*/
|
||||
const PRUint32 kSlowStatementThreshold = 100;
|
||||
|
||||
/**
|
||||
* nsTArray of pointers representing PCs on a call stack
|
||||
*/
|
||||
typedef nsTArray<uintptr_t> HangStack;
|
||||
|
||||
/**
|
||||
* Record the main thread's call stack after it hangs.
|
||||
*
|
||||
* @param duration - Approximate duration of main thread hang in seconds
|
||||
* @param callStack - Array of PCs from the hung call stack
|
||||
* @param moduleMap - Array of info about modules in memory (for symbolication)
|
||||
*/
|
||||
void RecordChromeHang(PRUint32 duration,
|
||||
const HangStack &callStack,
|
||||
SharedLibraryInfo &moduleMap);
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
#endif // Telemetry_h__
|
||||
|
@ -453,6 +453,7 @@ TelemetryPing.prototype = {
|
||||
payloadObj.simpleMeasurements = getSimpleMeasurements();
|
||||
payloadObj.histograms = this.getHistograms(Telemetry.histogramSnapshots);
|
||||
payloadObj.slowSQL = Telemetry.slowSQL;
|
||||
payloadObj.chromeHangs = Telemetry.chromeHangs;
|
||||
payloadObj.addonHistograms = this.getAddonHistograms();
|
||||
}
|
||||
if (Object.keys(this._slowSQLStartup.mainThread).length
|
||||
|
@ -70,7 +70,7 @@ interface nsITelemetrySaveSessionDataCallback : nsISupports
|
||||
void handle(in bool success);
|
||||
};
|
||||
|
||||
[scriptable, uuid(db854295-478d-4de9-8211-d73ed7d81cd0)]
|
||||
[scriptable, uuid(f23a2c8d-9286-42e9-ab1b-ed287eeade6d)]
|
||||
interface nsITelemetry : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -118,6 +118,14 @@ interface nsITelemetry : nsISupports
|
||||
[implicit_jscontext]
|
||||
readonly attribute jsval slowSQL;
|
||||
|
||||
/*
|
||||
* An array of chrome hang reports. Each element is a hang report represented
|
||||
* as an object containing the hang duration, call stack PCs and information
|
||||
* about modules in memory.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
readonly attribute jsval chromeHangs;
|
||||
|
||||
/**
|
||||
* An object whose properties are the names of histograms defined in
|
||||
* TelemetryHistograms.h and whose corresponding values are the textual
|
||||
|
@ -48,6 +48,7 @@ EXPORTS = \
|
||||
sampler.h \
|
||||
sps_sampler.h \
|
||||
thread_helper.h \
|
||||
shared-libraries.h \
|
||||
$(NULL)
|
||||
|
||||
LOCAL_INCLUDES += \
|
||||
|
@ -54,6 +54,10 @@ interface nsIProfiler : nsISupports
|
||||
* Every object has three properties: start, end, and name.
|
||||
* start and end are integers describing the address range that the library
|
||||
* occupies in memory. name is the path of the library as a string.
|
||||
*
|
||||
* On Windows profiling builds, the shared library objects will have
|
||||
* additional pdbSignature and pdbAge properties for uniquely identifying
|
||||
* shared library versions for stack symbolication.
|
||||
*/
|
||||
AString getSharedLibraryInformation();
|
||||
};
|
||||
|
@ -96,6 +96,10 @@ AddSharedLibraryInfoToStream(std::ostream& aStream, SharedLibrary& aLib)
|
||||
aStream << "\"start\":" << aLib.GetStart();
|
||||
aStream << ",\"end\":" << aLib.GetEnd();
|
||||
aStream << ",\"name\":\"" << aLib.GetName() << "\"";
|
||||
#ifdef XP_WIN
|
||||
aStream << ",\"pdbSignature\":\"" << aLib.GetPdbSignature().ToString() << "\"";
|
||||
aStream << ",\"pdbAge\":" << aLib.GetPdbAge();
|
||||
#endif
|
||||
aStream << "}";
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jeff Muizelaar <jmuizelaar@mozilla.com>
|
||||
* Vladan Djeric <vdjeric@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
@ -38,10 +39,65 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <Dbghelp.h>
|
||||
|
||||
#include "shared-libraries.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#define CV_SIGNATURE 0x53445352 // 'SDSR'
|
||||
|
||||
struct CodeViewRecord70
|
||||
{
|
||||
uint32_t signature;
|
||||
GUID pdbSignature;
|
||||
uint32_t pdbAge;
|
||||
uint8_t pdbFileName[1];
|
||||
};
|
||||
|
||||
static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge)
|
||||
{
|
||||
if (!aStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(aStart);
|
||||
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(
|
||||
aStart + dosHeader->e_lfanew);
|
||||
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t relativeVirtualAddress =
|
||||
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
|
||||
if (!relativeVirtualAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DEBUG_DIRECTORY debugDirectory =
|
||||
reinterpret_cast<PIMAGE_DEBUG_DIRECTORY>(aStart + relativeVirtualAddress);
|
||||
if (!debugDirectory || debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CodeViewRecord70 *debugInfo = reinterpret_cast<CodeViewRecord70 *>(
|
||||
aStart + debugDirectory->AddressOfRawData);
|
||||
if (!debugInfo || debugInfo->signature != CV_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aAge = debugInfo->pdbAge;
|
||||
GUID& pdbSignature = debugInfo->pdbSignature;
|
||||
aSignature.m0 = pdbSignature.Data1;
|
||||
aSignature.m1 = pdbSignature.Data2;
|
||||
aSignature.m2 = pdbSignature.Data3;
|
||||
memcpy(aSignature.m3, pdbSignature.Data4, sizeof(pdbSignature.Data4));
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo sharedLibraryInfo;
|
||||
@ -52,11 +108,17 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
module.dwSize = sizeof(MODULEENTRY32);
|
||||
if (Module32First(snap, &module)) {
|
||||
do {
|
||||
SharedLibrary shlib((uintptr_t)module.modBaseAddr,
|
||||
(uintptr_t)module.modBaseAddr+module.modBaseSize,
|
||||
0, // DLLs are always mapped at offset 0 on Windows
|
||||
module.szModule);
|
||||
sharedLibraryInfo.AddSharedLibrary(shlib);
|
||||
nsID pdbSig;
|
||||
uint32_t pdbAge;
|
||||
if (GetPdbInfo((uintptr_t)module.modBaseAddr, pdbSig, pdbAge)) {
|
||||
SharedLibrary shlib((uintptr_t)module.modBaseAddr,
|
||||
(uintptr_t)module.modBaseAddr+module.modBaseSize,
|
||||
0, // DLLs are always mapped at offset 0 on Windows
|
||||
pdbSig,
|
||||
pdbAge,
|
||||
module.szModule);
|
||||
sharedLibraryInfo.AddSharedLibrary(shlib);
|
||||
}
|
||||
} while (Module32Next(snap, &module));
|
||||
}
|
||||
|
||||
|
@ -36,17 +36,31 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <mozilla/StandardInteger.h>
|
||||
#include <nsID.h>
|
||||
|
||||
class SharedLibrary {
|
||||
public:
|
||||
SharedLibrary(unsigned long aStart, unsigned long aEnd, unsigned long aOffset, char *aName)
|
||||
|
||||
SharedLibrary(unsigned long aStart,
|
||||
unsigned long aEnd,
|
||||
unsigned long aOffset,
|
||||
#ifdef XP_WIN
|
||||
nsID aPdbSignature,
|
||||
unsigned long aPdbAge,
|
||||
#endif
|
||||
char *aName)
|
||||
: mStart(aStart)
|
||||
, mEnd(aEnd)
|
||||
, mOffset(aOffset)
|
||||
#ifdef XP_WIN
|
||||
, mPdbSignature(aPdbSignature)
|
||||
, mPdbAge(aPdbAge)
|
||||
#endif
|
||||
, mName(strdup(aName))
|
||||
{}
|
||||
|
||||
@ -54,6 +68,10 @@ public:
|
||||
: mStart(aEntry.mStart)
|
||||
, mEnd(aEntry.mEnd)
|
||||
, mOffset(aEntry.mOffset)
|
||||
#ifdef XP_WIN
|
||||
, mPdbSignature(aEntry.mPdbSignature)
|
||||
, mPdbAge(aEntry.mPdbAge)
|
||||
#endif
|
||||
, mName(strdup(aEntry.mName))
|
||||
{}
|
||||
|
||||
@ -65,20 +83,43 @@ public:
|
||||
mStart = aEntry.mStart;
|
||||
mEnd = aEntry.mEnd;
|
||||
mOffset = aEntry.mOffset;
|
||||
#ifdef XP_WIN
|
||||
mPdbSignature = aEntry.mPdbSignature;
|
||||
mPdbAge = aEntry.mPdbAge;
|
||||
#endif
|
||||
if (mName)
|
||||
free(mName);
|
||||
mName = strdup(aEntry.mName);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const SharedLibrary& other) const
|
||||
{
|
||||
bool equal = ((mStart == other.mStart) &&
|
||||
(mEnd == other.mEnd) &&
|
||||
(mOffset == other.mOffset) &&
|
||||
(mName && other.mName && (strcmp(mName, other.mName) == 0)));
|
||||
#ifdef XP_WIN
|
||||
equal = equal &&
|
||||
(mPdbSignature.Equals(other.mPdbSignature)) &&
|
||||
(mPdbAge == other.mPdbAge);
|
||||
#endif
|
||||
return equal;
|
||||
}
|
||||
|
||||
~SharedLibrary()
|
||||
{
|
||||
free(mName);
|
||||
mName = NULL;
|
||||
}
|
||||
|
||||
uintptr_t GetStart() { return mStart; }
|
||||
uintptr_t GetEnd() { return mEnd; }
|
||||
char* GetName() { return mName; }
|
||||
uintptr_t GetStart() const { return mStart; }
|
||||
uintptr_t GetEnd() const { return mEnd; }
|
||||
#ifdef XP_WIN
|
||||
nsID GetPdbSignature() const { return mPdbSignature; }
|
||||
uint32_t GetPdbAge() const { return mPdbAge; }
|
||||
#endif
|
||||
char* GetName() const { return mName; }
|
||||
|
||||
private:
|
||||
explicit SharedLibrary() {}
|
||||
@ -86,9 +127,20 @@ private:
|
||||
uintptr_t mStart;
|
||||
uintptr_t mEnd;
|
||||
uintptr_t mOffset;
|
||||
#ifdef XP_WIN
|
||||
// Windows-specific PDB file identifiers
|
||||
nsID mPdbSignature;
|
||||
uint32_t mPdbAge;
|
||||
#endif
|
||||
char *mName;
|
||||
};
|
||||
|
||||
static bool
|
||||
CompareAddresses(const SharedLibrary& first, const SharedLibrary& second)
|
||||
{
|
||||
return first.GetStart() < second.GetStart();
|
||||
}
|
||||
|
||||
class SharedLibraryInfo {
|
||||
public:
|
||||
static SharedLibraryInfo GetInfoForSelf();
|
||||
@ -104,10 +156,34 @@ public:
|
||||
return mEntries[i];
|
||||
}
|
||||
|
||||
size_t GetSize()
|
||||
// Removes items in the range [first, last)
|
||||
// i.e. element at the "last" index is not removed
|
||||
void RemoveEntries(size_t first, size_t last)
|
||||
{
|
||||
mEntries.erase(mEntries.begin() + first, mEntries.begin() + last);
|
||||
}
|
||||
|
||||
bool Contains(const SharedLibrary& searchItem) const
|
||||
{
|
||||
return (mEntries.end() !=
|
||||
std::find(mEntries.begin(), mEntries.end(), searchItem));
|
||||
}
|
||||
|
||||
size_t GetSize() const
|
||||
{
|
||||
return mEntries.size();
|
||||
}
|
||||
|
||||
void SortByAddress()
|
||||
{
|
||||
std::sort(mEntries.begin(), mEntries.end(), CompareAddresses);
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
mEntries.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SharedLibrary> mEntries;
|
||||
};
|
||||
|
@ -38,8 +38,10 @@
|
||||
#include "mozilla/HangMonitor.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsStackWalk.h"
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
#include "nsExceptionHandler.h"
|
||||
@ -49,6 +51,10 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_PROFILING) && defined(XP_WIN)
|
||||
#define REPORT_CHROME_HANGS
|
||||
#endif
|
||||
|
||||
namespace mozilla { namespace HangMonitor {
|
||||
|
||||
/**
|
||||
@ -59,6 +65,8 @@ volatile bool gDebugDisableHangMonitor = false;
|
||||
|
||||
const char kHangMonitorPrefName[] = "hangmonitor.timeout";
|
||||
|
||||
const char kTelemetryPrefName[] = "toolkit.telemetry.enabled";
|
||||
|
||||
// Monitor protects gShutdown and gTimeout, but not gTimestamp which rely on
|
||||
// being atomically set by the processor; synchronization doesn't really matter
|
||||
// in this use case.
|
||||
@ -76,11 +84,28 @@ bool gShutdown;
|
||||
// we're currently not processing events.
|
||||
volatile PRIntervalTime gTimestamp;
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
// Main thread ID used in reporting chrome hangs under Windows
|
||||
static HANDLE winMainThreadHandle = NULL;
|
||||
|
||||
// Default timeout for reporting chrome hangs to Telemetry (10 seconds)
|
||||
static const PRInt32 DEFAULT_CHROME_HANG_INTERVAL = 10;
|
||||
#endif
|
||||
|
||||
// PrefChangedFunc
|
||||
int
|
||||
PrefChanged(const char*, void*)
|
||||
{
|
||||
PRInt32 newval = Preferences::GetInt(kHangMonitorPrefName);
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
// Monitor chrome hangs on the profiling branch if Telemetry enabled
|
||||
if (newval == 0) {
|
||||
PRBool telemetryEnabled = Preferences::GetBool(kTelemetryPrefName);
|
||||
if (telemetryEnabled) {
|
||||
newval = DEFAULT_CHROME_HANG_INTERVAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
if (newval != gTimeout) {
|
||||
gTimeout = newval;
|
||||
@ -111,6 +136,76 @@ Crash()
|
||||
NS_RUNTIMEABORT("HangMonitor triggered");
|
||||
}
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
static void
|
||||
ChromeStackWalker(void *aPC, void *aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClosure);
|
||||
Telemetry::HangStack *callStack =
|
||||
reinterpret_cast< Telemetry::HangStack* >(aClosure);
|
||||
callStack->AppendElement(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
static void
|
||||
GetChromeHangReport(Telemetry::HangStack &callStack, SharedLibraryInfo &moduleMap)
|
||||
{
|
||||
MOZ_ASSERT(winMainThreadHandle);
|
||||
moduleMap = SharedLibraryInfo::GetInfoForSelf();
|
||||
moduleMap.SortByAddress();
|
||||
|
||||
DWORD ret = ::SuspendThread(winMainThreadHandle);
|
||||
if (ret == -1) {
|
||||
callStack.Clear();
|
||||
moduleMap.Clear();
|
||||
return;
|
||||
}
|
||||
NS_StackWalk(ChromeStackWalker, 0, &callStack,
|
||||
reinterpret_cast<uintptr_t>(winMainThreadHandle));
|
||||
ret = ::ResumeThread(winMainThreadHandle);
|
||||
if (ret == -1) {
|
||||
callStack.Clear();
|
||||
moduleMap.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
ThreadMain(void*)
|
||||
{
|
||||
@ -122,6 +217,9 @@ ThreadMain(void*)
|
||||
PRIntervalTime lastTimestamp = 0;
|
||||
int waitCount = 0;
|
||||
|
||||
Telemetry::HangStack hangStack;
|
||||
SharedLibraryInfo hangModuleMap;
|
||||
|
||||
while (true) {
|
||||
if (gShutdown) {
|
||||
return; // Exit the thread
|
||||
@ -143,15 +241,27 @@ ThreadMain(void*)
|
||||
gTimeout > 0) {
|
||||
++waitCount;
|
||||
if (waitCount == 2) {
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
GetChromeHangReport(hangStack, hangModuleMap);
|
||||
#else
|
||||
PRInt32 delay =
|
||||
PRInt32(PR_IntervalToSeconds(now - timestamp));
|
||||
if (delay > gTimeout) {
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
Crash();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
if (waitCount >= 2) {
|
||||
PRUint32 hangDuration = PR_IntervalToSeconds(now - lastTimestamp);
|
||||
Telemetry::RecordChromeHang(hangDuration, hangStack, hangModuleMap);
|
||||
hangStack.Clear();
|
||||
hangModuleMap.Clear();
|
||||
}
|
||||
#endif
|
||||
lastTimestamp = timestamp;
|
||||
waitCount = 0;
|
||||
}
|
||||
@ -182,6 +292,14 @@ Startup()
|
||||
Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, NULL);
|
||||
PrefChanged(NULL, NULL);
|
||||
|
||||
#ifdef REPORT_CHROME_HANGS
|
||||
Preferences::RegisterCallback(PrefChanged, kTelemetryPrefName, NULL);
|
||||
winMainThreadHandle =
|
||||
OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId());
|
||||
if (!winMainThreadHandle)
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Don't actually start measuring hangs until we hit the main event loop.
|
||||
// This potentially misses a small class of really early startup hangs,
|
||||
// but avoids dealing with some xpcshell tests and other situations which
|
||||
|
Loading…
Reference in New Issue
Block a user