author | Jim Chen <nchen@mozilla.com> |
Fri, 22 Nov 2013 14:17:31 -0500 | |
changeset 157180 | 4c31b0875e214c33a4abce64b9e8e27d2ef135f6 |
parent 157179 | c32c0526e7b56e40eb2d4cab666a9634850039e6 |
child 157181 | 5365478bdea9ca9e1eb8299a5f3705a907e34955 |
push id | 25703 |
push user | philringnalda@gmail.com |
push date | Sat, 23 Nov 2013 16:19:02 +0000 |
treeherder | mozilla-central@ad6589ed742c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | vladan |
bugs | 932865 |
milestone | 28.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -41,16 +41,17 @@ #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsXULAppAPI.h" #include "nsThreadUtils.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "plstr.h" #include "nsAppDirectoryServiceDefs.h" +#include "mozilla/BackgroundHangMonitor.h" #include "mozilla/ThreadHangStats.h" #include "mozilla/ProcessedStack.h" #include "mozilla/Mutex.h" #include "mozilla/FileUtils.h" #include "mozilla/Preferences.h" #include "mozilla/PoisonIOInterposer.h" #if defined(MOZ_ENABLE_PROFILER_SPS) #include "shared-libraries.h" @@ -1708,16 +1709,190 @@ ReadStack(const char *aFileName, Telemet index }; stack.AddFrame(frame); } aStack = stack; } +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_NewObject(cx, nullptr, nullptr, nullptr)); + if (!ret) { + return nullptr; + } + + if (!JS_DefineProperty(cx, ret, "min", + UINT_TO_JSVAL(time.GetBucketMin(0)), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "max", + UINT_TO_JSVAL(time.GetBucketMax( + ArrayLength(time) - 1)), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "histogram_type", + INT_TO_JSVAL(nsITelemetry::HISTOGRAM_EXPONENTIAL), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + // TODO: calculate "sum", "log_sum", and "log_sum_squares" + if (!JS_DefineProperty(cx, ret, "sum", INT_TO_JSVAL(0), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "log_sum", DOUBLE_TO_JSVAL(0.0), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "log_sum_squares", DOUBLE_TO_JSVAL(0.0), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + + JS::RootedObject ranges( + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1, nullptr)); + JS::RootedObject counts( + cx, JS_NewArrayObject(cx, ArrayLength(time) + 1, nullptr)); + 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. */ + JS::RootedValue underRange(cx, INT_TO_JSVAL(time.GetBucketMin(0))); + JS::RootedValue underCount(cx, INT_TO_JSVAL(0)); + if (!JS_SetElement(cx, ranges, 0, &underRange) || + !JS_SetElement(cx, counts, 0, &underCount)) { + return nullptr; + } + for (size_t i = 0; i < ArrayLength(time); i++) { + JS::RootedValue range(cx, UINT_TO_JSVAL(time.GetBucketMax(i))); + JS::RootedValue count(cx, UINT_TO_JSVAL(time[i])); + if (!JS_SetElement(cx, ranges, i + 1, &range) || + !JS_SetElement(cx, counts, i + 1, &count)) { + return nullptr; + } + } + if (!JS_DefineProperty(cx, ret, "ranges", OBJECT_TO_JSVAL(ranges), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "counts", OBJECT_TO_JSVAL(counts), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + return ret; +} + +static JSObject* +CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang) +{ + JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, nullptr, nullptr)); + if (!ret) { + return nullptr; + } + + const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack(); + JS::RootedObject stack(cx, + JS_NewArrayObject(cx, hangStack.length(), nullptr)); + if (!ret) { + return nullptr; + } + for (size_t i = 0; i < hangStack.length(); i++) { + JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i])); + JS::RootedValue frame(cx, STRING_TO_JSVAL(string)); + if (!JS_SetElement(cx, stack, i, &frame)) { + return nullptr; + } + } + + JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang)); + if (!time || + !JS_DefineProperty(cx, ret, "stack", OBJECT_TO_JSVAL(stack), + nullptr, nullptr, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, ret, "histogram", OBJECT_TO_JSVAL(time), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + return ret; +} + +static JSObject* +CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread) +{ + JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, nullptr, nullptr)); + if (!ret) { + return nullptr; + } + JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName())); + if (!name || + !JS_DefineProperty(cx, ret, "name", STRING_TO_JSVAL(name), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + + JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity)); + if (!activity || + !JS_DefineProperty(cx, ret, "activity", OBJECT_TO_JSVAL(activity), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + + JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0, nullptr)); + if (!hangs) { + return nullptr; + } + for (size_t i = 0; i < thread.mHangs.length(); i++) { + JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i])); + JS::RootedValue hang(cx, OBJECT_TO_JSVAL(obj)); + if (!JS_SetElement(cx, hangs, i, &hang)) { + return nullptr; + } + } + if (!JS_DefineProperty(cx, ret, "hangs", OBJECT_TO_JSVAL(hangs), + nullptr, nullptr, JSPROP_ENUMERATE)) { + return nullptr; + } + return ret; +} + +NS_IMETHODIMP +TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::Value* ret) +{ + JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0, nullptr)); + if (!retObj) { + return NS_ERROR_FAILURE; + } + size_t threadIndex = 0; + + /* 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)); + JS::RootedValue thread(cx, OBJECT_TO_JSVAL(obj)); + if (!JS_SetElement(cx, retObj, threadIndex++, &thread)) { + return NS_ERROR_FAILURE; + } + } + + // Add saved threads next + MutexAutoLock autoLock(mThreadHangStatsMutex); + for (size_t i = 0; i < mThreadHangStats.length(); i++) { + JS::RootedObject obj(cx, + CreateJSThreadHangStats(cx, mThreadHangStats[i])); + JS::RootedValue thread(cx, OBJECT_TO_JSVAL(obj)); + if (!JS_SetElement(cx, retObj, threadIndex++, &thread)) { + return NS_ERROR_FAILURE; + } + } + *ret = OBJECT_TO_JSVAL(retObj); + return NS_OK; +} + void TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) { nsAutoCString nativePath; nsresult rv = aProfileDir->GetNativePath(nativePath); if (NS_FAILED(rv)) { return; }
--- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -306,16 +306,26 @@ TelemetryPing.prototype = { } if (Object.keys(packedHistograms).length != 0) ret[addonName] = packedHistograms; } return ret; }, + 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 * * @param reason * The reason for the telemetry ping, this will be included in the * returned metadata, * @return The metadata as a JS object */ @@ -543,16 +553,17 @@ TelemetryPing.prototype = { */ assemblePayloadWithMeasurements: function assemblePayloadWithMeasurements(simpleMeasurements, info) { let payloadObj = { ver: PAYLOAD_VERSION, simpleMeasurements: simpleMeasurements, histograms: this.getHistograms(Telemetry.histogramSnapshots), slowSQL: Telemetry.slowSQL, chromeHangs: Telemetry.chromeHangs, + threadHangStats: this.getThreadHangStats(Telemetry.threadHangStats), lateWrites: Telemetry.lateWrites, addonHistograms: this.getAddonHistograms(), addonDetails: AddonManagerPrivate.getTelemetryDetails(), info: info }; if (Object.keys(this._slowSQLStartup).length != 0 && (Object.keys(this._slowSQLStartup.mainThread).length ||
--- a/toolkit/components/telemetry/ThreadHangStats.h +++ b/toolkit/components/telemetry/ThreadHangStats.h @@ -2,16 +2,17 @@ /* 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 mozilla_BackgroundHangTelemetry_h #define mozilla_BackgroundHangTelemetry_h #include "mozilla/Array.h" +#include "mozilla/Assertions.h" #include "mozilla/Move.h" #include "mozilla/Mutex.h" #include "mozilla/PodOperations.h" #include "mozilla/Vector.h" #include "nsString.h" #include "prinrval.h" @@ -25,16 +26,26 @@ static const size_t kTimeHistogramBucket stored in milliseconds. */ class TimeHistogram : public mozilla::Array<uint32_t, kTimeHistogramBuckets> { 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 hang histogram consists of a stack associated with the hang, along with a time histogram of the hang times. */ class HangHistogram : public TimeHistogram { public:
--- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -95,16 +95,33 @@ interface nsITelemetry : nsISupports * 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 array of thread hang stats, + * [<thread>, <thread>, ...] + * <thread> represents a single thread, + * {"name": "<name>", + * "activity": <time>, + * "hangs": [<hang>, <hang>, ...]} + * <time> represents a histogram of time intervals in milliseconds, + * with the same format as histogramSnapshots + * <hang> represents a particular hang, + * {"stack": <stack>, "histogram": <time>} + * <stack> represents the hang's stack, + * ["<frame_0>", "<frame_1>", ...] + */ + [implicit_jscontext] + readonly attribute jsval threadHangStats; + + /* * An object with two fields: memoryMap and stacks. * * memoryMap is a list of loaded libraries. * * stacks is a list of stacks. Each stack is a list of pairs of the form * [moduleIndex, offset]. The moduleIndex is an index into the memoryMap and * offset is an offset in the library at memoryMap[moduleIndex]. * This format is used to make it easier to send the stacks to the * symbolication server. */