Bug 1363883 - Share memory maps between BHR native stacks, dr=bsmedberg, r=froydnj

MozReview-Commit-ID: IQ1fMXUD0ch
This commit is contained in:
Michael Layzell 2017-05-10 17:50:47 -04:00
parent d365f6d96f
commit 590b8f81b4
5 changed files with 80 additions and 53 deletions

View File

@ -109,7 +109,11 @@ const size_t kMaxChromeStackDepth = 50;
// more efficiently by keeping a single global list of modules.
class CombinedStacks {
public:
CombinedStacks() : mNextIndex(0) {}
explicit CombinedStacks(size_t aMaxStacksCount = kMaxChromeStacksKept)
: mNextIndex(0)
, mMaxStacksCount(aMaxStacksCount)
{}
typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
size_t GetModuleCount() const;
@ -128,6 +132,8 @@ private:
std::vector<Stack> mStacks;
// The index of the next buffer element to write to in mStacks.
size_t mNextIndex;
// The maximum number of stacks to keep in the CombinedStacks object.
size_t mMaxStacksCount;
};
static JSObject *
@ -146,9 +152,9 @@ CombinedStacks::GetModule(unsigned aIndex) const {
size_t
CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
// Advance the indices of the circular queue holding the stacks.
size_t index = mNextIndex++ % kMaxChromeStacksKept;
size_t index = mNextIndex++ % mMaxStacksCount;
// Grow the vector up to the maximum size, if needed.
if (mStacks.size() < kMaxChromeStacksKept) {
if (mStacks.size() < mMaxStacksCount) {
mStacks.resize(mStacks.size() + 1);
}
// Get a reference to the location holding the new stack.
@ -2043,21 +2049,6 @@ CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
return nullptr;
}
if (!hang.GetNativeStack().empty()) {
const Telemetry::NativeHangStack& stack = hang.GetNativeStack();
Telemetry::ProcessedStack processed = Telemetry::GetStackAndModules(stack);
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;
}
@ -2080,12 +2071,34 @@ CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
return nullptr;
}
// Create a CombinedStacks instance which will contain all of the stacks
// collected by the BHR. Specify a custom maxStacksCount to ensure that all of
// our native hang stacks fit.
CombinedStacks combinedStacks(Telemetry::kMaximumNativeHangStacks);
// Process the hangs into a hangs object.
JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
if (!hangs) {
return nullptr;
}
for (size_t i = 0; i < thread.mHangs.length(); i++) {
JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
if (!ret) {
return nullptr;
}
// Check if we have a native stack, and if we do, add it to combinedStacks,
// and store its index in the 'nativeStack' member of the hang object.
const Telemetry::NativeHangStack& stack = thread.mHangs[i].GetNativeStack();
if (!stack.empty() &&
combinedStacks.GetStackCount() < Telemetry::kMaximumNativeHangStacks) {
Telemetry::ProcessedStack processed = Telemetry::GetStackAndModules(stack);
uint32_t index = combinedStacks.AddStack(processed);
if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
return nullptr;
}
}
if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
return nullptr;
}
@ -2094,6 +2107,15 @@ CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
return nullptr;
}
JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, combinedStacks));
if (!fullReportObj) {
return nullptr;
}
if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
return nullptr;
}
return ret;
}

View File

@ -20,6 +20,12 @@
namespace mozilla {
namespace Telemetry {
// This variable controls the maximum number of native hang stacks which may be
// attached to a ping. This is due to how large native stacks can be. We want to
// reduce the chance of a ping being discarded due to it exceeding the maximum
// ping size.
static const uint32_t kMaximumNativeHangStacks = 300;
static const size_t kTimeHistogramBuckets = 8 * sizeof(PRIntervalTime);
/* TimeHistogram is an efficient histogram that puts time durations into

View File

@ -250,7 +250,7 @@ As of Firefox 48, this section does not contain empty keyed histograms anymore.
threadHangStats
---------------
Contains the statistics about the hangs in main and background threads. Note that hangs in this section capture the `C++ pseudostack <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler#Native_stack_vs._Pseudo_stack>`_ and an incomplete JS stack, which is not 100% precise. For particularly egregious hangs, an unsymbolicated native stack is also captured. The amount of time that is considered "egregious" is different from thread to thread, and is set when the BackgroundHangMonitor is constructed for that thread. In general though, hangs from 5 - 10 seconds are generally considered egregious. Shorter hangs (1 - 2s) are considered egregious for other threads (the compositor thread, and the hang monitor that is only enabled during tab switch).
Contains the statistics about the hangs in main and background threads. Note that hangs in this section capture the `C++ pseudostack <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler#Native_stack_vs._Pseudo_stack>`_ and an incomplete JS stack, which is not 100% precise. For particularly egregious hangs, and on nightly, an unsymbolicated native stack is also captured. The amount of time that is considered "egregious" is different from thread to thread, and is set when the BackgroundHangMonitor is constructed for that thread. In general though, hangs from 5 - 10 seconds are generally considered egregious. Shorter hangs (1 - 2s) are considered egregious for other threads (the compositor thread, and the hang monitor that is only enabled during tab switch).
To avoid submitting overly large payloads, some limits are applied:
@ -266,6 +266,24 @@ Structure:
{
"name" : "Gecko",
"activity" : {...}, // a time histogram of all task run times
"nativeStacks": { // captured for all hangs on nightly, or egregious hangs on beta
"memoryMap": [
["wgdi32.pdb", "08A541B5942242BDB4AEABD8C87E4CFF2"],
["igd10iumd32.pdb", "D36DEBF2E78149B5BE1856B772F1C3991"],
// ... other entries in the format ["module name", "breakpad identifier"] ...
],
"stacks": [
[
[
0, // the module index or -1 for invalid module indices
190649 // the offset of this program counter in its module or an absolute pc
],
[1, 2540075],
// ... other frames ...
],
// ... other stacks ...
]
},
"hangs" : [
{
"stack" : [
@ -275,24 +293,7 @@ Structure:
"IPDL::PPluginScriptableObject::SendGetChildProperty",
... up to 11 frames ...
],
"nativeStack": { // only captured for egregious hangs
"memoryMap": [
["wgdi32.pdb", "08A541B5942242BDB4AEABD8C87E4CFF2"],
["igd10iumd32.pdb", "D36DEBF2E78149B5BE1856B772F1C3991"],
// ... other entries in the format ["module name", "breakpad identifier"] ...
],
"stacks": [
[
[
0, // the module index or -1 for invalid module indices
190649 // the offset of this program counter in its module or an absolute pc
],
[1, 2540075],
// ... other frames ...
],
// ... other stacks ...
]
},
"nativeStack": 0, // index into nativeStacks.stacks array
"histogram" : {...}, // the time histogram of the hang times
"annotations" : [
{
@ -305,7 +306,7 @@ Structure:
],
},
... other threads ...
]
]
capturedStacks
--------------

View File

@ -86,13 +86,17 @@ function run_test() {
// Native stack gathering is only enabled on Windows x86.
if (mozinfo.os == "win" && mozinfo.bits == 32) {
// Make sure one of the hangs is a permanent
// hang containing a native stack.
ok(endHangs.hangs.some((hang) => (
hang.nativeStack &&
Array.isArray(hang.nativeStack.memoryMap) &&
Array.isArray(hang.nativeStack.stacks)
)));
// Make sure there is a nativeStacks field with native stacks in it.
ok(Array.isArray(endHangs.nativeStacks.memoryMap));
ok(Array.isArray(endHangs.nativeStacks.stacks));
ok(endHangs.nativeStacks.stacks.length > 0);
// Make sure that at least one of the hangs contains a native stack.
ok(endHangs.hangs.some((hang) => {
return typeof hang.nativeStack == "number" &&
hang.nativeStack < endHangs.nativeStacks.stacks.length &&
Array.isArray(endHangs.nativeStacks.stacks[hang.nativeStack]);
}));
}
check_histogram(endHangs.hangs[0].histogram);

View File

@ -32,12 +32,6 @@
// It can be scaled back again in the future
#define BHR_BETA_MOD 1;
// This variable controls the maximum number of native hang stacks which may be
// attached to a ping. This is due to how large native stacks can be. We want to
// reduce the chance of a ping being discarded due to it exceeding the maximum
// ping size.
static const uint32_t kMaximumNativeHangStacks = 300;
// Maximum depth of the call stack in the reported thread hangs. This value represents
// the 99.9th percentile of the thread hangs stack depths reported by Telemetry.
static const size_t kMaxThreadHangStackDepth = 30;
@ -341,7 +335,7 @@ BackgroundHangManager::RunMonitorThread()
if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) {
// A hang started
#ifdef NIGHTLY_BUILD
if (currentThread->mStats.mNativeStackCnt < kMaximumNativeHangStacks) {
if (currentThread->mStats.mNativeStackCnt < Telemetry::kMaximumNativeHangStacks) {
// NOTE: In nightly builds of firefox we want to collect native stacks
// for all hangs, not just permahangs.
currentThread->mStats.mNativeStackCnt += 1;
@ -498,7 +492,7 @@ BackgroundHangThread::ReportPermaHang()
Telemetry::NativeHangStack& stack = hang.GetNativeStack();
if (stack.empty()) {
mStats.mNativeStackCnt += 1;
if (mStats.mNativeStackCnt <= kMaximumNativeHangStacks) {
if (mStats.mNativeStackCnt <= Telemetry::kMaximumNativeHangStacks) {
mStackHelper.GetNativeStack(stack);
}
}