diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index d7b187ce909c..558083033fba 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -148,6 +148,7 @@ public: #if defined(MOZ_GECKO_PROFILER) static void DoStackCapture(const nsACString& aKey); #endif + static void RecordThreadHangStats(Telemetry::ThreadHangStats&& aStats); size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); struct Stat { uint32_t hitCount; @@ -202,6 +203,10 @@ private: KeyedStackCapturer mStackCapturer; #endif + // mThreadHangStats stores recorded, inactive thread hang stats + Vector mThreadHangStats; + Mutex mThreadHangStatsMutex; + CombinedStacks mLateWritesStacks; // This is collected out of the main thread. bool mCachedTelemetryData; uint32_t mLastShutdownTime; @@ -480,6 +485,7 @@ TelemetryImpl::TelemetryImpl() , mHangReportsMutex("Telemetry::mHangReportsMutex") , mCanRecordBase(false) , mCanRecordExtended(false) + , mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex") , mCachedTelemetryData(false) , mLastShutdownTime(0) , mFailedLockCount(0) @@ -496,6 +502,7 @@ TelemetryImpl::~TelemetryImpl() { // We will fix this in bug 1367344. MutexAutoLock hashLock(mHashMutex); MutexAutoLock hangReportsLock(mHangReportsMutex); + MutexAutoLock threadHangsLock(mThreadHangStatsMutex); } void @@ -1058,6 +1065,43 @@ ReadStack(const char *aFileName, Telemetry::ProcessedStack &aStack) aStack = stack; } +NS_IMETHODIMP +TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle ret) +{ + JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0)); + if (!retObj) { + return NS_ERROR_FAILURE; + } + size_t threadIndex = 0; + + if (!BackgroundHangMonitor::IsDisabled()) { + /* First add active threads; we need to hold |iter| (and its lock) + throughout this method to avoid a race condition where a thread can + be recorded twice if the thread is destroyed while this method is + running */ + BackgroundHangMonitor::ThreadHangStatsIterator iter; + for (Telemetry::ThreadHangStats* histogram = iter.GetNext(); + histogram; histogram = iter.GetNext()) { + JS::RootedObject obj(cx, CreateJSThreadHangStats(cx, *histogram)); + if (!JS_DefineElement(cx, retObj, threadIndex++, obj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + + // Add saved threads next + MutexAutoLock autoLock(mThreadHangStatsMutex); + for (auto & stat : mThreadHangStats) { + JS::RootedObject obj(cx, + CreateJSThreadHangStats(cx, stat)); + if (!JS_DefineElement(cx, retObj, threadIndex++, obj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + ret.setObject(*retObj); + return NS_OK; +} + void TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) { @@ -1586,6 +1630,18 @@ TelemetryImpl::CaptureStack(const nsACString& aKey) { return NS_OK; } +void +TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats&& aStats) +{ + if (!sTelemetry || !TelemetryHistogram::CanRecordExtended()) + return; + + MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex); + + // Ignore OOM. + mozilla::Unused << sTelemetry->mThreadHangStats.append(Move(aStats)); +} + bool TelemetryImpl::CanRecordBase() { @@ -1803,6 +1859,10 @@ TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) MutexAutoLock lock(mHangReportsMutex); n += mHangReports.SizeOfExcludingThis(aMallocSizeOf); } + { // Scope for mThreadHangStatsMutex lock + MutexAutoLock lock(mThreadHangStatsMutex); + n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf); + } // It's a bit gross that we measure this other stuff that lives outside of // TelemetryImpl... oh well. @@ -2029,6 +2089,12 @@ void CaptureStack(const nsACString& aKey) } #endif +void RecordThreadHangStats(ThreadHangStats&& aStats) +{ + TelemetryImpl::RecordThreadHangStats(Move(aStats)); +} + + void WriteFailedProfileLock(nsIFile* aProfileDir) { diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index ee5f2c71e19b..14fc45941bc6 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -341,6 +341,20 @@ void RecordChromeHang(uint32_t aDuration, void CaptureStack(const nsCString& aKey); #endif +class ThreadHangStats; + +/** + * Move a ThreadHangStats to Telemetry storage. Normally Telemetry queries + * for active ThreadHangStats through BackgroundHangMonitor, but once a + * thread exits, the thread's copy of ThreadHangStats needs to be moved to + * inside Telemetry using this function. + * + * @param aStats ThreadHangStats to save; the data inside aStats + * will be moved and aStats should be treated as + * invalid after this function returns + */ +void RecordThreadHangStats(ThreadHangStats&& aStats); + /** * Record a failed attempt at locking the user's profile. * diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index 71a819f6542d..0a92c8837331 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -977,6 +977,16 @@ var Impl = { return snapshot; }, + getThreadHangStats: function getThreadHangStats(stats) { + stats.forEach((thread) => { + thread.activity = this.packHistogram(thread.activity); + thread.hangs.forEach((hang) => { + hang.histogram = this.packHistogram(hang.histogram); + }); + }); + return stats; + }, + /** * Descriptive metadata * @@ -1230,6 +1240,7 @@ var Impl = { // Add extended set measurements common to chrome & content processes if (Telemetry.canRecordExtended) { payloadObj.chromeHangs = protect(() => Telemetry.chromeHangs); + payloadObj.threadHangStats = protect(() => this.getThreadHangStats(Telemetry.threadHangStats)); payloadObj.log = protect(() => TelemetryLog.entries()); payloadObj.webrtc = protect(() => Telemetry.webrtcStats); } diff --git a/toolkit/components/telemetry/ThreadHangStats.cpp b/toolkit/components/telemetry/ThreadHangStats.cpp index 65c739d948a0..e567e5ce9eec 100644 --- a/toolkit/components/telemetry/ThreadHangStats.cpp +++ b/toolkit/components/telemetry/ThreadHangStats.cpp @@ -9,9 +9,242 @@ #include "HangReports.h" #include "jsapi.h" +namespace { + +using namespace mozilla; +using namespace mozilla::HangMonitor; +using namespace mozilla::Telemetry; + +static JSObject* +CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time) +{ + /* Create JS representation of TimeHistogram, + in the format of Chromium-style histograms. */ + JS::RootedObject ret(cx, JS_NewPlainObject(cx)); + if (!ret) { + return nullptr; + } + + if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0), + JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "max", + time.GetBucketMax(ArrayLength(time) - 1), + JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "histogram_type", + nsITelemetry::HISTOGRAM_EXPONENTIAL, + JSPROP_ENUMERATE)) { + return nullptr; + } + // TODO: calculate "sum" + if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) { + return nullptr; + } + + JS::RootedObject ranges( + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); + JS::RootedObject counts( + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1)); + if (!ranges || !counts) { + return nullptr; + } + /* In a Chromium-style histogram, the first bucket is an "under" bucket + that represents all values below the histogram's range. */ + if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) || + !JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) { + return nullptr; + } + for (size_t i = 0; i < ArrayLength(time); i++) { + if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i), + JSPROP_ENUMERATE) || + !JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) { + return nullptr; + } + } + if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) { + return nullptr; + } + return ret; +} + +static JSObject* +CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack) +{ + JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length())); + if (!ret) { + return nullptr; + } + for (size_t i = 0; i < stack.length(); i++) { + JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i])); + if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) { + return nullptr; + } + } + return ret; +} + +static void +CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations, + JS::MutableHandleObject returnedObject) +{ + JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0)); + if (!annotationsArray) { + returnedObject.set(nullptr); + return; + } + // We keep track of the annotations we reported in this hash set, so we can + // discard duplicated ones. + nsTHashtable reportedAnnotations; + size_t annotationIndex = 0; + for (const auto & curAnnotations : annotations) { + JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx)); + if (!jsAnnotation) { + continue; + } + // Build a key to index the current annotations in our hash set. + nsAutoString annotationsKey; + nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey); + if (NS_FAILED(rv)) { + continue; + } + // Check if the annotations are in the set. If that's the case, don't double report. + if (reportedAnnotations.GetEntry(annotationsKey)) { + continue; + } + // If not, report them. + reportedAnnotations.PutEntry(annotationsKey); + UniquePtr annotationsEnum = + curAnnotations->GetEnumerator(); + if (!annotationsEnum) { + continue; + } + nsAutoString key; + nsAutoString value; + while (annotationsEnum->Next(key, value)) { + JS::RootedValue jsValue(cx); + jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length())); + if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(), + jsValue, JSPROP_ENUMERATE)) { + returnedObject.set(nullptr); + return; + } + } + if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) { + continue; + } + ++annotationIndex; + } + // Return the array using a |MutableHandleObject| to avoid triggering a false + // positive rooting issue in the hazard analysis build. + returnedObject.set(annotationsArray); +} + +static JSObject* +CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang) +{ + JS::RootedObject ret(cx, JS_NewPlainObject(cx)); + if (!ret) { + return nullptr; + } + + JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack())); + JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang)); + auto& hangAnnotations = hang.GetAnnotations(); + JS::RootedObject annotations(cx); + CreateJSHangAnnotations(cx, hangAnnotations, &annotations); + + if (!stack || + !time || + !annotations || + !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; + } + + return ret; +} + +} // namespace + namespace mozilla { namespace Telemetry { +JSObject* +CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread) +{ + JS::RootedObject ret(cx, JS_NewPlainObject(cx)); + if (!ret) { + return nullptr; + } + JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName())); + if (!name || + !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) { + return nullptr; + } + + JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity)); + if (!activity || + !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) { + return nullptr; + } + + // 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; + } + + JS::RootedString runnableName(cx, JS_NewStringCopyZ(cx, thread.mHangs[i].GetRunnableName())); + if (!runnableName || + !JS_DefineProperty(cx, ret, "runnableName", runnableName, JSPROP_ENUMERATE)) { + return nullptr; + } + + // Check if we have a cached native stack index, and if we do record it. + uint32_t index = thread.mHangs[i].GetNativeStackIndex(); + if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) { + if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) { + return nullptr; + } + } + + if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) { + return nullptr; + } + } + if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) { + return nullptr; + } + + // We should already have a CombinedStacks object on the ThreadHangStats, so + // add that one. + JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks)); + if (!fullReportObj) { + return nullptr; + } + + if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) { + return nullptr; + } + + return ret; +} + +void +TimeHistogram::Add(PRIntervalTime aTime) +{ + uint32_t timeMs = PR_IntervalToMilliseconds(aTime); + size_t index = mozilla::FloorLog2(timeMs); + operator[](index)++; +} + const char* HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength) { @@ -55,5 +288,35 @@ HangStack::AppendViaBuffer(const char* aText, size_t aLength) return InfallibleAppendViaBuffer(aText, aLength); } +uint32_t +HangHistogram::GetHash(const HangStack& aStack) +{ + uint32_t hash = 0; + for (const char* const* label = aStack.begin(); + label != aStack.end(); label++) { + /* If the string is within our buffer, we need to hash its content. + Otherwise, the string is statically allocated, and we only need + to hash the pointer instead of the content. */ + if (aStack.IsInBuffer(*label)) { + hash = AddToHash(hash, HashString(*label)); + } else { + hash = AddToHash(hash, *label); + } + } + return hash; +} + +bool +HangHistogram::operator==(const HangHistogram& aOther) const +{ + if (mHash != aOther.mHash) { + return false; + } + if (mStack.length() != aOther.mStack.length()) { + return false; + } + return mStack == aOther.mStack; +} + } // namespace Telemetry } // namespace mozilla diff --git a/toolkit/components/telemetry/ThreadHangStats.h b/toolkit/components/telemetry/ThreadHangStats.h index f9ce78b3e2e1..6032a27844cf 100644 --- a/toolkit/components/telemetry/ThreadHangStats.h +++ b/toolkit/components/telemetry/ThreadHangStats.h @@ -28,6 +28,31 @@ namespace Telemetry { // 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 + exponential (base 2) buckets; times are accepted in PRIntervalTime and + stored in milliseconds. */ +class TimeHistogram : public mozilla::Array +{ +public: + TimeHistogram() + { + mozilla::PodArrayZero(*this); + } + // Get minimum (inclusive) range of bucket in milliseconds + uint32_t GetBucketMin(size_t aBucket) const { + MOZ_ASSERT(aBucket < ArrayLength(*this)); + return (1u << aBucket) & ~1u; // Bucket 0 starts at 0, not 1 + } + // Get maximum (inclusive) range of bucket in milliseconds + uint32_t GetBucketMax(size_t aBucket) const { + MOZ_ASSERT(aBucket < ArrayLength(*this)); + return (1u << (aBucket + 1u)) - 1u; + } + void Add(PRIntervalTime aTime); +}; + /* A native stack is a simple list of pointers, so rather than building a wrapper type, we typdef the type here. */ typedef std::vector NativeHangStack; @@ -135,6 +160,129 @@ public: const char* AppendViaBuffer(const char* aText, size_t aLength); }; +/* A hang histogram consists of a stack associated with the + hang, along with a time histogram of the hang times. */ +class HangHistogram : public TimeHistogram +{ +public: + // Value used for mNativeStackIndex to represent the absence of a cached + // native stack. + static const uint32_t NO_NATIVE_STACK_INDEX = UINT32_MAX; + +private: + static uint32_t GetHash(const HangStack& aStack); + + HangStack mStack; + // Cached index of the native stack in the mCombinedStacks list in the owning + // ThreadHangStats object. A default value of NO_NATIVE_STACK_INDEX means that + // the ThreadHangStats object which owns this HangHistogram doesn't have a + // cached CombinedStacks with this HangHistogram in it. + uint32_t mNativeStackIndex; + // Use a hash to speed comparisons + const uint32_t mHash; + // Annotations attributed to this stack + HangMonitor::HangAnnotationsVector mAnnotations; + // The name of the runnable on the current thread. + nsCString mRunnableName; + +public: + explicit HangHistogram(HangStack&& aStack, const nsACString& aRunnableName) + : mStack(mozilla::Move(aStack)) + , mNativeStackIndex(NO_NATIVE_STACK_INDEX) + , mHash(GetHash(mStack)) + , mRunnableName(aRunnableName) + { + } + + HangHistogram(HangHistogram&& aOther) + : TimeHistogram(mozilla::Move(aOther)) + , mStack(mozilla::Move(aOther.mStack)) + , mNativeStackIndex(mozilla::Move(aOther.mNativeStackIndex)) + , mHash(mozilla::Move(aOther.mHash)) + , mAnnotations(mozilla::Move(aOther.mAnnotations)) + , mRunnableName(aOther.mRunnableName) + { + } + bool operator==(const HangHistogram& aOther) const; + bool operator!=(const HangHistogram& aOther) const + { + return !operator==(aOther); + } + const HangStack& GetStack() const { + return mStack; + } + uint32_t GetNativeStackIndex() const { + return mNativeStackIndex; + } + void SetNativeStackIndex(uint32_t aIndex) { + MOZ_ASSERT(aIndex != NO_NATIVE_STACK_INDEX); + mNativeStackIndex = aIndex; + } + const char* GetRunnableName() const { + return mRunnableName.get(); + } + const HangMonitor::HangAnnotationsVector& GetAnnotations() const { + return mAnnotations; + } + void Add(PRIntervalTime aTime, HangMonitor::HangAnnotationsPtr aAnnotations) { + TimeHistogram::Add(aTime); + if (aAnnotations) { + if (!mAnnotations.append(Move(aAnnotations))) { + MOZ_CRASH(); + } + } + } +}; + +/* Thread hang stats consist of + - thread name + - time histogram of all task run times + - hang histograms of individual hangs + - annotations for each hang + - combined native stacks for all hangs +*/ +class ThreadHangStats +{ +private: + nsCString mName; + +public: + TimeHistogram mActivity; + mozilla::Vector mHangs; + uint32_t mNativeStackCnt; + CombinedStacks mCombinedStacks; + + explicit ThreadHangStats(const char* aName) + : mName(aName) + , mNativeStackCnt(0) + , mCombinedStacks(Telemetry::kMaximumNativeHangStacks) + { + } + ThreadHangStats(ThreadHangStats&& aOther) + : mName(mozilla::Move(aOther.mName)) + , mActivity(mozilla::Move(aOther.mActivity)) + , mHangs(mozilla::Move(aOther.mHangs)) + , mNativeStackCnt(aOther.mNativeStackCnt) + , mCombinedStacks(mozilla::Move(aOther.mCombinedStacks)) + { + aOther.mNativeStackCnt = 0; + } + const char* GetName() const { + return mName.get(); + } +}; + +/** + * Reflects thread hang stats object as a JS object. + * + * @param JSContext* cx javascript context. + * @param JSContext* cx thread hang statistics. + * + * @return JSObject* Javascript reflection of the statistics. + */ +JSObject* +CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread); + } // namespace Telemetry } // namespace mozilla diff --git a/toolkit/components/telemetry/docs/data/main-ping.rst b/toolkit/components/telemetry/docs/data/main-ping.rst index dab4ddfcbe0b..5955bd5ae62f 100644 --- a/toolkit/components/telemetry/docs/data/main-ping.rst +++ b/toolkit/components/telemetry/docs/data/main-ping.rst @@ -63,6 +63,7 @@ Structure: histograms: {...}, keyedHistograms: {...}, chromeHangs: {...}, + threadHangStats: [...], capturedStacks: {...}, log: [...], webrtc: {...}, @@ -247,6 +248,66 @@ This section contains the keyed histograms available for the current platform. 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 `_ 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: + +* Identical, adjacent "(chrome script)" or "(content script)" stack entries are collapsed together. If a stack is reduced, the "(reduced stack)" frame marker is added as the oldest frame. +* The depth of the reported pseudostacks is limited to 11 entries. This value represents the 99.9th percentile of the thread hangs stack depths reported by Telemetry. +* The native stacks are limited to a depth of 25 stack frames. + +Structure: + +.. code-block:: js + + "threadHangStats" : [ + { + "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" : [ + "Startup::XRE_Main", + "Timer::Fire", + "(content script)", + "IPDL::PPluginScriptableObject::SendGetChildProperty", + ... up to 11 frames ... + ], + "nativeStack": 0, // index into nativeStacks.stacks array + "histogram" : {...}, // the time histogram of the hang times + "annotations" : [ + { + "pluginName" : "Shockwave Flash", + "pluginVersion" : "18.0.0.209" + }, + ... other annotations ... + ] + }, + ], + }, + ... other threads ... + ] + capturedStacks -------------- Contains information about stacks captured on demand via Telemetry API. For more diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index 07697285e6a9..07b87605b184 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -202,6 +202,23 @@ interface nsITelemetry : nsISupports [implicit_jscontext] nsISupports getLoadedModules(); + /* + * An array of thread hang stats, + * [, , ...] + * represents a single thread, + * {"name": "", + * "activity":