--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5677,16 +5677,25 @@ ContentParent::RecvUpdateChildKeyedScala
mozilla::ipc::IPCResult
ContentParent::RecvRecordChildEvents(nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents)
{
TelemetryIPC::RecordChildEvents(GetTelemetryProcessID(mRemoteType), aEvents);
return IPC_OK();
}
mozilla::ipc::IPCResult
+ContentParent::RecvRecordChildStacks(nsTArray<mozilla::Telemetry::ChildStackData>&& aStacks)
+{
+#if defined(MOZ_GECKO_PROFILER)
+ TelemetryIPC::RecordChildStacks(GetTelemetryProcessID(mRemoteType), aStacks);
+#endif
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
ContentParent::RecvRecordDiscardedData(
const mozilla::Telemetry::DiscardedData& aDiscardedData)
{
TelemetryIPC::RecordDiscardedData(GetTelemetryProcessID(mRemoteType),
aDiscardedData);
return IPC_OK();
}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1243,16 +1243,18 @@ public:
virtual mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(
InfallibleTArray<KeyedHistogramAccumulation>&& aAccumulations) override;
virtual mozilla::ipc::IPCResult RecvUpdateChildScalars(
InfallibleTArray<ScalarAction>&& aScalarActions) override;
virtual mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
InfallibleTArray<KeyedScalarAction>&& aScalarActions) override;
virtual mozilla::ipc::IPCResult RecvRecordChildEvents(
nsTArray<ChildEventData>&& events) override;
+ virtual mozilla::ipc::IPCResult RecvRecordChildStacks(
+ nsTArray<ChildStackData>&& stacks) override;
virtual mozilla::ipc::IPCResult RecvRecordDiscardedData(
const DiscardedData& aDiscardedData) override;
virtual mozilla::ipc::IPCResult RecvBHRThreadHang(
const HangDetails& aHangDetails) override;
virtual mozilla::ipc::IPCResult
RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -92,16 +92,17 @@ using struct mozilla::layers::TextureFac
using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h";
using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::DynamicScalarDefinition from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ChildStackData from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
union ChromeRegistryItem
{
ChromePackage;
@@ -1146,16 +1147,17 @@ parent:
/**
* Messages for communicating child Telemetry to the parent process
*/
async AccumulateChildHistograms(HistogramAccumulation[] accumulations);
async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations);
async UpdateChildScalars(ScalarAction[] updates);
async UpdateChildKeyedScalars(KeyedScalarAction[] updates);
async RecordChildEvents(ChildEventData[] events);
+ async RecordChildStacks(ChildStackData[] events);
async RecordDiscardedData(DiscardedData data);
sync GetA11yContentId() returns (uint32_t aContentId);
async A11yHandlerControl(uint32_t aPid,
IHandlerControlHolder aHandlerControl);
async AddMemoryReport(MemoryReport aReport);
async FinishMemoryReport(uint32_t aGeneration);
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -212,16 +212,25 @@ GPUChild::RecvUpdateChildKeyedScalars(In
mozilla::ipc::IPCResult
GPUChild::RecvRecordChildEvents(nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents)
{
TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Gpu, aEvents);
return IPC_OK();
}
mozilla::ipc::IPCResult
+GPUChild::RecvRecordChildStacks(nsTArray<mozilla::Telemetry::ChildStackData>&& aStacks)
+{
+#if defined(MOZ_GECKO_PROFILER)
+ TelemetryIPC::RecordChildStacks(Telemetry::ProcessID::Gpu, aStacks);
+#endif
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
GPUChild::RecvRecordDiscardedData(const mozilla::Telemetry::DiscardedData& aDiscardedData)
{
TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Gpu, aDiscardedData);
return IPC_OK();
}
mozilla::ipc::IPCResult
GPUChild::RecvNotifyDeviceReset(const GPUDeviceData& aData)
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -48,16 +48,17 @@ public:
mozilla::ipc::IPCResult RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog) override;
mozilla::ipc::IPCResult RecvInitCrashReporter(Shmem&& shmem, const NativeThreadId& aThreadId) override;
mozilla::ipc::IPCResult RecvAccumulateChildHistograms(InfallibleTArray<HistogramAccumulation>&& aAccumulations) override;
mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(InfallibleTArray<KeyedHistogramAccumulation>&& aAccumulations) override;
mozilla::ipc::IPCResult RecvUpdateChildScalars(InfallibleTArray<ScalarAction>&& aScalarActions) override;
mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(InfallibleTArray<KeyedScalarAction>&& aScalarActions) override;
mozilla::ipc::IPCResult RecvRecordChildEvents(nsTArray<ChildEventData>&& events) override;
+ mozilla::ipc::IPCResult RecvRecordChildStacks(nsTArray<ChildStackData>&& stacks) override;
mozilla::ipc::IPCResult RecvRecordDiscardedData(const DiscardedData& aDiscardedData) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override;
mozilla::ipc::IPCResult RecvNotifyUiObservers(const nsCString& aTopic) override;
mozilla::ipc::IPCResult RecvNotifyDeviceReset(const GPUDeviceData& aData) override;
mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -17,16 +17,17 @@ include protocol PVideoDecoderManager;
using base::ProcessId from "base/process.h";
using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ChildStackData from "mozilla/TelemetryComms.h";
using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
using mozilla::gfx::Feature from "gfxFeature.h";
using mozilla::gfx::Fallback from "gfxFallback.h";
using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h";
namespace mozilla {
namespace gfx {
@@ -122,16 +123,17 @@ child:
async NotifyUiObservers(nsCString aTopic);
// Messages for reporting telemetry to the UI process.
async AccumulateChildHistograms(HistogramAccumulation[] accumulations);
async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations);
async UpdateChildScalars(ScalarAction[] actions);
async UpdateChildKeyedScalars(KeyedScalarAction[] actions);
async RecordChildEvents(ChildEventData[] events);
+ async RecordChildStacks(ChildStackData[] stacks);
async RecordDiscardedData(DiscardedData data);
async NotifyDeviceReset(GPUDeviceData status);
async AddMemoryReport(MemoryReport aReport);
async FinishMemoryReport(uint32_t aGeneration);
// Update the UI process after a feature's status has changed. This is used
--- a/toolkit/components/telemetry/KeyedStackCapturer.cpp
+++ b/toolkit/components/telemetry/KeyedStackCapturer.cpp
@@ -4,16 +4,21 @@
* 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 "KeyedStackCapturer.h"
#include "nsPrintfCString.h"
#include "mozilla/StackWalk.h"
#include "ProcessedStack.h"
#include "jsapi.h"
+#include "ipc/TelemetryIPCAccumulator.h"
+#include "ipc/TelemetryComms.h"
+#include "TelemetryCommon.h"
+
+namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
namespace {
/** Defines the size of the keyed stack dictionary. */
const uint8_t kMaxKeyLength = 50;
/** The maximum number of captured stacks that we're keeping. */
const size_t kMaxCapturedStacksKept = 50;
@@ -25,17 +30,17 @@ const size_t kMaxCapturedStacksKept = 50
* @return True, if the char is valid, False - otherwise.
*/
bool
IsKeyCharValid(const char aChar)
{
return (aChar >= 'A' && aChar <= 'Z')
|| (aChar >= 'a' && aChar <= 'z')
|| (aChar >= '0' && aChar <= '9')
- || aChar == '-';
+ || aChar == '-';
}
/**
* Checks if a given string is a valid telemetry key.
*
* @param aKey is the key string.
* @return True, if the key is valid, False - otherwise.
*/
@@ -47,125 +52,203 @@ IsKeyValid(const nsACString& aKey)
return false;
}
// Check key characters.
const char* cur = aKey.BeginReading();
const char* end = aKey.EndReading();
for (; cur < end; ++cur) {
- if (!IsKeyCharValid(*cur)) {
- return false;
- }
+ if (!IsKeyCharValid(*cur)) {
+ return false;
+ }
}
return true;
}
} // anonymous namespace
namespace mozilla {
namespace Telemetry {
-void KeyedStackCapturer::Capture(const nsACString& aKey) {
- MutexAutoLock captureStackMutex(mStackCapturerMutex);
+KeyedStackCapturer::KeyedStackCapturer()
+ : mCapturesByProcess(static_cast<size_t>(ProcessID::Count))
+ , mStackCapturerMutex("Telemetry::StackCapturerMutex")
+{
+ // Pre-populating the array of process captures with empty capture data.
+ for (size_t i = 0; i < this->mCapturesByProcess.Capacity(); ++i) {
+ this->mCapturesByProcess.AppendElement(Captures());
+ }
+}
+void
+KeyedStackCapturer::Capture(const nsACString& aKey) {
// Check if the key is ok.
if (!IsKeyValid(aKey)) {
NS_WARNING(nsPrintfCString(
"Invalid key is used to capture stack in telemetry: '%s'",
PromiseFlatCString(aKey).get()
).get());
return;
}
+ // If we are in the child process we delegate captured stack
+ // to the parent process for storage.
+ if (!XRE_IsParentProcess()) {
+ ProcessedStack stack = this->CaptureStackAndModules();
+ TelemetryIPCAccumulator::RecordCapturedStack(aKey, stack);
+ return;
+ }
+
+ ProcessedStack stack;
+ if (!this->StackKeyExists(aKey, Telemetry::ProcessID::Parent))
+ {
+ stack = this->CaptureStackAndModules();
+ }
+
+ this->AddStack(aKey, &stack, Telemetry::ProcessID::Parent);
+}
+
+bool
+KeyedStackCapturer::StackKeyExists(const nsACString& aKey,
+ Telemetry::ProcessID aProcessId) {
+ return mCapturesByProcess[static_cast<size_t>(aProcessId)]
+ .mStackInfos.Get(aKey) != nullptr;
+}
+
+ProcessedStack
+KeyedStackCapturer::CaptureStackAndModules()
+{
+ std::vector<uintptr_t> rawStack;
+ auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
+ std::vector<uintptr_t>* stack =
+ static_cast<std::vector<uintptr_t>*>(aClosure);
+ stack->push_back(reinterpret_cast<uintptr_t>(aPC));
+ };
+
+ MozStackWalk(callback, /* skipFrames */ 0, /* maxFrames */ 0, &rawStack);
+ return GetStackAndModules(rawStack);
+}
+
+void
+KeyedStackCapturer::AddStack(const nsACString& aKey,
+ const ProcessedStack* aStack,
+ const Telemetry::ProcessID aProcessId) {
+ MutexAutoLock captureStackMutex(mStackCapturerMutex);
+
+ // A shortcut for the process captures in the current process.
+ Captures & captures = mCapturesByProcess[static_cast<size_t>(aProcessId)];
+
// Trying to find and update the stack information.
- StackFrequencyInfo* info = mStackInfos.Get(aKey);
+ StackFrequencyInfo* info = captures.mStackInfos.Get(aKey);
if (info) {
// We already recorded this stack before, only increase the count.
info->mCount++;
return;
}
// Check if we have room for new captures.
- if (mStackInfos.Count() >= kMaxCapturedStacksKept) {
+ if (captures.mStacks.GetStackCount() >= kMaxCapturedStacksKept) {
// Addressed by Bug 1316793.
return;
}
- // We haven't captured a stack for this key before, do it now.
- // Note that this does a stackwalk and is an expensive operation.
- std::vector<uintptr_t> rawStack;
- auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
- std::vector<uintptr_t>* stack =
- static_cast<std::vector<uintptr_t>*>(aClosure);
- stack->push_back(reinterpret_cast<uintptr_t>(aPC));
- };
- MozStackWalk(callback, /* skipFrames */ 0, /* maxFrames */ 0, &rawStack);
- ProcessedStack stack = GetStackAndModules(rawStack);
+ // Store the new stack info.
+ size_t stackIndex = captures.mStacks.AddStack((*aStack));
+ captures.mStackInfos.Put(aKey, new StackFrequencyInfo(1, stackIndex));
+}
+
+JSObject*
+KeyedStackCapturer::CreateJSCapturesObject(JSContext* aCx, const Captures& captures)
+{
+ // this adds the memoryMap and stacks properties.
+ JS::RootedObject fullReportObj(aCx, CreateJSStackObject(aCx, captures.mStacks));
+ if (!fullReportObj) {
+ return nullptr;
+ }
+
+ // Create array for storing keys and cardinalities of captures stacks.
+ JS::RootedObject keysArray(aCx, JS_NewArrayObject(aCx, 0));
+ if (!keysArray) {
+ return nullptr;
+ }
- // Store the new stack info.
- size_t stackIndex = mStacks.AddStack(stack);
- mStackInfos.Put(aKey, new StackFrequencyInfo(1, stackIndex));
+ bool ok = JS_DefineProperty(aCx, fullReportObj, "captures",
+ keysArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return nullptr;
+ }
+
+ size_t keyIndex = 0;
+ for (auto iter = captures.mStackInfos.ConstIter(); !iter.Done(); iter.Next(), ++keyIndex) {
+ const StackFrequencyInfo* info = iter.Data();
+
+ JS::RootedObject infoArray(aCx, JS_NewArrayObject(aCx, 0));
+ if (!keysArray) {
+ return nullptr;
+ }
+ JS::RootedString str(aCx, JS_NewStringCopyZ(aCx,
+ PromiseFlatCString(iter.Key()).get()));
+ if (!str ||
+ !JS_DefineElement(aCx, infoArray, 0, str, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(aCx, infoArray, 1, info->mIndex, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(aCx, infoArray, 2, info->mCount, JSPROP_ENUMERATE) ||
+ !JS_DefineElement(aCx, keysArray, keyIndex, infoArray, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ return fullReportObj;
}
NS_IMETHODIMP
-KeyedStackCapturer::ReflectCapturedStacks(JSContext *cx, JS::MutableHandle<JS::Value> ret)
+KeyedStackCapturer::ReflectCapturedStacks(JSContext* aCx, JS::MutableHandle<JS::Value> aRet)
{
MutexAutoLock capturedStackMutex(mStackCapturerMutex);
- // this adds the memoryMap and stacks properties.
- JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, mStacks));
- if (!fullReportObj) {
- return NS_ERROR_FAILURE;
- }
-
- JS::RootedObject keysArray(cx, JS_NewArrayObject(cx, 0));
- if (!keysArray) {
- return NS_ERROR_FAILURE;
- }
+ // Resulting JS object containing full capture report.
+ JS::RootedObject fullReportObject(aCx, JS_NewPlainObject(aCx));
- bool ok = JS_DefineProperty(cx, fullReportObj, "captures",
- keysArray, JSPROP_ENUMERATE);
- if (!ok) {
- return NS_ERROR_FAILURE;
- }
-
- size_t keyIndex = 0;
- for (auto iter = mStackInfos.ConstIter(); !iter.Done(); iter.Next(), ++keyIndex) {
- const StackFrequencyInfo* info = iter.Data();
-
- JS::RootedObject infoArray(cx, JS_NewArrayObject(cx, 0));
- if (!keysArray) {
- return NS_ERROR_FAILURE;
- }
- JS::RootedString str(cx, JS_NewStringCopyZ(cx,
- PromiseFlatCString(iter.Key()).get()));
- if (!str ||
- !JS_DefineElement(cx, infoArray, 0, str, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, infoArray, 1, info->mIndex, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, infoArray, 2, info->mCount, JSPROP_ENUMERATE) ||
- !JS_DefineElement(cx, keysArray, keyIndex, infoArray, JSPROP_ENUMERATE)) {
+ // Loop over processes, render and add captures as JS objects to the report.
+ for (size_t i = 0; i < mCapturesByProcess.Length(); ++i) {
+ const Captures & captures = mCapturesByProcess.ElementAt(i);
+ JS::RootedObject capturesJSObject(aCx, CreateJSCapturesObject(aCx, captures));
+ bool ok = JS_DefineProperty(aCx, fullReportObject, Common::GetNameForProcessID(static_cast<ProcessID>(i)),
+ capturesJSObject, JSPROP_ENUMERATE);
+ if (!ok) {
return NS_ERROR_FAILURE;
}
}
- ret.setObject(*fullReportObj);
+ aRet.setObject(*fullReportObject);
return NS_OK;
}
void
KeyedStackCapturer::Clear()
{
MutexAutoLock captureStackMutex(mStackCapturerMutex);
- mStackInfos.Clear();
- mStacks.Clear();
+ for (auto& captures : mCapturesByProcess) {
+ captures.mStackInfos.Clear();
+ captures.mStacks.Clear();
+ }
+}
+
+void
+KeyedStackCapturer::RecordChildStacks(Telemetry::ProcessID aProcessType,
+ const nsTArray<Telemetry::ChildStackData>& aStacks) {
+ for (auto& stackData : aStacks) {
+ this->AddStack(stackData.key, &stackData.stack, aProcessType);
+ }
}
size_t
KeyedStackCapturer::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
size_t n = 0;
- n += mStackInfos.SizeOfExcludingThis(aMallocSizeOf);
- n += mStacks.SizeOfExcludingThis();
+ for (auto& captures : mCapturesByProcess) {
+ n += captures.mStackInfos.SizeOfExcludingThis(aMallocSizeOf);
+ n += captures.mStacks.SizeOfExcludingThis();
+ }
return n;
}
} // namespace Telemetry
} // namespace mozilla
--- a/toolkit/components/telemetry/KeyedStackCapturer.h
+++ b/toolkit/components/telemetry/KeyedStackCapturer.h
@@ -6,72 +6,155 @@
#ifndef KeyedStackCapturer_h__
#define KeyedStackCapturer_h__
#include "Telemetry.h"
#include "nsString.h"
#include "nsClassHashtable.h"
#include "mozilla/Mutex.h"
#include "CombinedStacks.h"
+#include "TelemetryProcessEnums.h"
struct JSContext;
namespace mozilla {
namespace Telemetry {
+struct ChildStackData;
+
/**
* Allows taking a snapshot of a call stack on demand. Captured stacks are
* indexed by a string key in a hash table. The stack is only captured Once
* for each key. Consequent captures with the same key result in incrementing
* capture counter without re-capturing the stack.
*/
class KeyedStackCapturer
{
public:
- KeyedStackCapturer()
- : mStackCapturerMutex("Telemetry::StackCapturerMutex")
- {}
+ KeyedStackCapturer();
/**
* Captures a stack for the given key.
*/
void Capture(const nsACString& aKey);
/**
* Transforms captured stacks into a JS object.
*/
NS_IMETHODIMP ReflectCapturedStacks(
- JSContext *cx, JS::MutableHandle<JS::Value> ret);
+ JSContext* aCx, JS::MutableHandle<JS::Value> aRet);
/**
* Resets captured stacks and the information related to them.
*/
void Clear();
+ /**
+ * Record captured stacks for the given process type with the data coming
+ * from child process.
+ *
+ * @param aProcessType - the process type to record the events for
+ * @param aStacks - stacks to record
+ */
+ void RecordChildStacks(Telemetry::ProcessID aProcessType,
+ const nsTArray<Telemetry::ChildStackData>& aStacks);
+
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
private:
/**
* Describes how often a stack was captured.
*/
struct StackFrequencyInfo {
// A number of times the stack was captured.
uint32_t mCount;
// Index of the stack inside stacks array.
uint32_t mIndex;
+ // ID of the process in which the stack has been captured.
+ Telemetry::ProcessID mProcessId;
StackFrequencyInfo(uint32_t aCount, uint32_t aIndex)
: mCount(aCount)
, mIndex(aIndex)
{}
};
typedef nsClassHashtable<nsCStringHashKey, StackFrequencyInfo> FrequencyInfoMapType;
- FrequencyInfoMapType mStackInfos;
- CombinedStacks mStacks;
+ /**
+ * Bundles all information about captured stacks in a single
+ * object.
+ */
+ struct Captures {
+ // Recorded stacks.
+ CombinedStacks mStacks;
+
+ // Additional stack info.
+ FrequencyInfoMapType mStackInfos;
+ };
+
+ typedef nsTArray<Captures> CapturesByProcess;
+
+ CapturesByProcess mCapturesByProcess;
+
Mutex mStackCapturerMutex;
+
+ /**
+ * Walks the stack and creates a ProcessedStack object with the resulting
+ * raw stack.
+ *
+ * @returns constructed ProcessedStack object.
+ */
+ ProcessedStack CaptureStackAndModules();
+
+ /**
+ * Adds a stack to the internal stack collection under the given key.
+ * If the collection contains the key already only the counter will be
+ * increased.
+ *
+ * @param aKey - key for storing the stack under.
+ * @param aStack - the stack to store. If nullptr given the
+ * stack will be captured first.
+ @ @param arocessId - Id of the process where the stack has been captured.
+ */
+ void AddStack(const nsACString& aKey,
+ const ProcessedStack* aStack,
+ const Telemetry::ProcessID aProcessId);
+
+ /**
+ * Creates a JS representation of stack captures. The resulting object
+ * has the following structure:
+ *
+ * <code>
+ *
+ * {
+ * “stacks”: [],
+ * “captures”: [[“key”, index-in-stacks]]
+ * }
+ *
+ * </code>
+ *
+ * @param cx - JS context object.
+ * @param aCaptures - object storing information on captures.
+ *
+ * @returns constructed rooted JSObject or a null pointer if construction
+ * failed.
+ */
+ JSObject* CreateJSCapturesObject(JSContext* aCx,
+ const Captures& aCaptures);
+
+ /**
+ * Verifies if the stack dictionary already contains a given key.
+ *
+ * @param aKey - stack key to look up in the dictionary.
+ * @param aProcessId - ID of the process for looking up the key.
+ *
+ * @returns True if the key exists and False - otherwise.
+ */
+ bool StackKeyExists(const nsACString& aKey,
+ Telemetry::ProcessID aProcessId);
+
};
} // namespace Telemetry
} // namespace mozilla
#endif // KeyedStackCapturer_h__
--- a/toolkit/components/telemetry/ProcessedStack.cpp
+++ b/toolkit/components/telemetry/ProcessedStack.cpp
@@ -36,54 +36,76 @@ namespace mozilla {
namespace Telemetry {
const size_t kMaxChromeStackDepth = 50;
ProcessedStack::ProcessedStack() = default;
size_t ProcessedStack::GetStackSize() const
{
- return mStack.size();
+ return mStack.Length();
}
size_t ProcessedStack::GetNumModules() const
{
- return mModules.size();
+ return mModules.Length();
}
-bool ProcessedStack::Module::operator==(const Module& aOther) const {
+bool ProcessedStack::Module::operator==(const Module& aOther) const
+{
return mName == aOther.mName &&
mBreakpadId == aOther.mBreakpadId;
}
const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
{
- MOZ_ASSERT(aIndex < mStack.size());
+ MOZ_ASSERT(aIndex < mStack.Length());
return mStack[aIndex];
}
void ProcessedStack::AddFrame(const Frame &aFrame)
{
- mStack.push_back(aFrame);
+ mStack.AppendElement(aFrame);
}
const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
{
- MOZ_ASSERT(aIndex < mModules.size());
+ MOZ_ASSERT(aIndex < mModules.Length());
return mModules[aIndex];
}
void ProcessedStack::AddModule(const Module &aModule)
{
- mModules.push_back(aModule);
+ mModules.AppendElement(aModule);
+}
+
+void ProcessedStack::Clear()
+{
+ mModules.Clear();
+ mStack.Clear();
+}
+
+const nsTArray<ProcessedStack::Module>& ProcessedStack::GetModules() const
+{
+ return this->mModules;
}
-void ProcessedStack::Clear() {
- mModules.clear();
- mStack.clear();
+const nsTArray<ProcessedStack::Frame>& ProcessedStack::GetFrames() const
+{
+ return this->mStack;
+}
+
+void ProcessedStack::SetModules(const nsTArray<Module>& aFrames)
+{
+ this->mModules = aFrames;
+}
+
+void ProcessedStack::SetFrames(const nsTArray<Frame>& aFrames)
+{
+ this->mStack = aFrames;
}
ProcessedStack
GetStackAndModules(const std::vector<uintptr_t>& aPCs)
{
std::vector<StackFrame> rawStack;
auto stackEnd = aPCs.begin() + std::min(aPCs.size(), kMaxChromeStackDepth);
for (auto i = aPCs.begin(); i != stackEnd; ++i) {
--- a/toolkit/components/telemetry/ProcessedStack.h
+++ b/toolkit/components/telemetry/ProcessedStack.h
@@ -3,16 +3,17 @@
* 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>
+#include "nsTArray.h"
#include "nsString.h"
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.
@@ -38,24 +39,31 @@ public:
nsString mName;
nsCString mBreakpadId;
bool operator==(const Module& other) const;
};
const Frame &GetFrame(unsigned aIndex) const;
void AddFrame(const Frame& aFrame);
- const Module &GetModule(unsigned aIndex) const;
+
+ const nsTArray<Frame>& GetFrames() const;
+ void SetFrames(const nsTArray<Frame>& aFrames);
+
+ const Module& GetModule(unsigned aIndex) const;
void AddModule(const Module& aFrame);
+ const nsTArray<Module>& GetModules() const;
+ void SetModules(const nsTArray<Module>& aModules);
+
void Clear();
private:
- std::vector<Module> mModules;
- std::vector<Frame> mStack;
+ nsTArray<Module> mModules;
+ nsTArray<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).
ProcessedStack
GetStackAndModules(const std::vector<uintptr_t> &aPCs);
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -108,16 +108,18 @@ using Telemetry::Common::SetCurrentProdu
using Telemetry::Common::SupportedProduct;
using mozilla::dom::Promise;
using mozilla::dom::AutoJSAPI;
using mozilla::Telemetry::CombinedStacks;
using mozilla::Telemetry::TelemetryIOInterposeObserver;
#if defined(MOZ_GECKO_PROFILER)
using mozilla::Telemetry::KeyedStackCapturer;
+using mozilla::Telemetry::ProcessID;
+using mozilla::Telemetry::ChildStackData;
#endif
// This is not a member of TelemetryImpl because we want to record I/O during
// startup.
StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
void
ClearIOReporting()
@@ -142,16 +144,19 @@ public:
void InitMemoryReporter();
static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
static void ShutdownTelemetry();
static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
uint32_t delay);
#if defined(MOZ_GECKO_PROFILER)
static void DoStackCapture(const nsACString& aKey);
+ static void RecordChildStacks(ProcessID aProcessType,
+ const nsTArray<ChildStackData>& aStacks);
+
#endif
struct Stat {
uint32_t hitCount;
uint32_t totalTime;
};
struct StmtStats {
struct Stat mainThread;
struct Stat otherThreads;
@@ -1524,20 +1529,28 @@ TelemetryImpl::RecordIceCandidates(const
sTelemetry->mWebrtcTelemetry.RecordIceCandidateMask(iceCandidateBitmask, success);
}
#if defined(MOZ_GECKO_PROFILER)
void
TelemetryImpl::DoStackCapture(const nsACString& aKey) {
- if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
+ if (Telemetry::CanRecordPrereleaseData()) {
sTelemetry->mStackCapturer.Capture(aKey);
}
}
+
+void
+TelemetryImpl::RecordChildStacks(ProcessID aProcessType,
+ const nsTArray<ChildStackData>& aStacks) {
+ if (Telemetry::CanRecordPrereleaseData()) {
+ sTelemetry->mStackCapturer.RecordChildStacks(aProcessType, aStacks);
+ }
+}
#endif
nsresult
TelemetryImpl::CaptureStack(const nsACString& aKey) {
#ifdef MOZ_GECKO_PROFILER
TelemetryImpl::DoStackCapture(aKey);
#endif
return NS_OK;
@@ -2053,16 +2066,22 @@ void Init()
MOZ_ASSERT(telemetryService);
}
#if defined(MOZ_GECKO_PROFILER)
void CaptureStack(const nsACString& aKey)
{
TelemetryImpl::DoStackCapture(aKey);
}
+
+void RecordChildStacks(ProcessID aProcessType,
+ const nsTArray<ChildStackData>& aStacks)
+{
+ TelemetryImpl::RecordChildStacks(aProcessType, aStacks);
+}
#endif
void
WriteFailedProfileLock(nsIFile* aProfileDir)
{
nsCOMPtr<nsIFile> file;
nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir);
NS_ENSURE_SUCCESS_VOID(rv);
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -11,16 +11,20 @@
#include "mozilla/StartupTimeline.h"
#include "nsTArray.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TelemetryScalarEnums.h"
+#if defined(MOZ_GECKO_PROFILER)
+#include "mozilla/TelemetryProcessEnums.h"
+#endif
+
/******************************************************************************
* This implements the Telemetry system.
* It allows recording into histograms as well some more specialized data
* points and gives access to the data.
*
* For documentation on how to add and use new Telemetry probes, see:
* https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe
*
@@ -490,16 +494,35 @@ class ProcessedStack;
* only captured once. Subsequent calls result in incrementing the capture
* counter.
*
* @param aKey - A user defined key associated with the captured stack.
*
* NOTE: Unwinding call stacks is an expensive operation performance-wise.
*/
void CaptureStack(const nsCString& aKey);
+
+struct ChildStackData;
+
+/**
+ * Adds stacks captured in the child process to the stack collection
+ * in the parrent process.
+ *
+ * NOTE: This is intented to be called by IPC and reserved for internal
+ * usage by the telementy components only.
+ *
+ * @deprecated Please do not base your implementation on this method
+ * as it will be removed soon.
+ *
+ * @param aProcessType - Type of the process in which the stacks were
+ * captured.
+ * @param aStacks - Array of captured stacks.
+ */
+void RecordChildStacks(ProcessID aProcessType,
+ const nsTArray<ChildStackData>& aStacks);
#endif
/**
* Record a failed attempt at locking the user's profile.
*
* @param aProfileDir The profile directory whose lock attempt failed
*/
void WriteFailedProfileLock(nsIFile* aProfileDir);
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1192,18 +1192,22 @@ var Impl = {
if (!this._isClassicReason(reason)) {
payloadObj.processes.parent.gc = protect(() => GCTelemetry.entries("main", clearSubsession));
payloadObj.processes.content.gc = protect(() => GCTelemetry.entries("content", clearSubsession));
}
// Adding captured stacks to the payload only if any exist and clearing
// captures for this sub-session.
let stacks = protect(() => Telemetry.snapshotCapturedStacks(true));
- if (stacks && ("captures" in stacks) && (stacks.captures.length > 0)) {
- payloadObj.processes.parent.capturedStacks = stacks;
+ if (stacks) {
+ for (const [process, capturesInfo] of Object.entries(stacks)) {
+ if (("captures" in capturesInfo) && (capturesInfo.captures.length > 0)) {
+ payloadObj.processes[process].capturedStacks = capturesInfo;
+ }
+ }
}
}
return payloadObj;
},
/**
* Start a new subsession.
--- a/toolkit/components/telemetry/docs/collection/stack-capture.rst
+++ b/toolkit/components/telemetry/docs/collection/stack-capture.rst
@@ -16,18 +16,17 @@ the same key are combined in order to re
used to reflect the frequency of identical stacks.
The serialized stack data is submitted with the :doc:`main pings <../data/main-ping>`.
The API
=======
Capturing stacks is available either via the `nsITelemetry interface <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/nsITelemetry.idl>`_
or the `C++ API <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Telemetry.h>`_.
-Note that current implementation of the API is not thread safe. Also, capturing
-stacks in the content process is not supported yet.
+Capturing stacks is supported in parent, content, and GPU processes.
JS API
------
Privileged JavaScript code can capture stacks using the following function:
.. code-block:: js
Services.telemetry.captureStack(aKey);
--- a/toolkit/components/telemetry/docs/data/main-ping.rst
+++ b/toolkit/components/telemetry/docs/data/main-ping.rst
@@ -113,23 +113,25 @@ Structure:
.. code-block:: js
"processes" : {
// ... other processes ...
"parent": {
scalars: {...},
keyedScalars: {...},
+ capturedStacks: {...},
// parent process histograms and keyedHistograms are in main payload
},
"content": {
scalars: {...},
keyedScalars: {...},
histograms: {...},
keyedHistograms: {...},
+ capturedStacks: {...},
},
"gpu": {
// ...
}
}
histograms and keyedHistograms
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -292,18 +294,18 @@ Structure:
]
capturedStacks
--------------
Contains information about stacks captured on demand via Telemetry API. For more
information see :doc:`stack capture <../collection/stack-capture>`.
This is similar to :ref:`chromeHangs`, but only Precise C++ stacks on the main thread of
-the parent process are reported. This data is only available on Windows, either
-in Firefox Nightly or in builds using ``--enable-profiling`` switch.
+the calling process are reported. This data is available either in Firefox Nightly
+or in builds using ``--enable-profiling`` switch on pre Release channels.
Limits for captured stacks are the same as for chromeHangs (see below). Furthermore:
* the key length is limited to 50 characters,
* keys are restricted to alpha-numeric characters and `-`.
The module names can contain unicode characters.
--- a/toolkit/components/telemetry/ipc/TelemetryComms.h
+++ b/toolkit/components/telemetry/ipc/TelemetryComms.h
@@ -6,16 +6,17 @@
#ifndef Telemetry_Comms_h__
#define Telemetry_Comms_h__
#include "ipc/IPCMessageUtils.h"
#include "nsITelemetry.h"
#include "nsVariant.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/TelemetryProcessEnums.h"
+#include "ProcessedStack.h"
namespace mozilla {
namespace Telemetry {
// Histogram accumulation types.
enum HistogramID : uint32_t;
struct HistogramAccumulation
@@ -97,22 +98,30 @@ struct ChildEventData {
mozilla::TimeStamp timestamp;
nsCString category;
nsCString method;
nsCString object;
mozilla::Maybe<nsCString> value;
nsTArray<EventExtraEntry> extra;
};
+struct ChildStackData {
+ nsCString key;
+ mozilla::Telemetry::ProcessedStack stack;
+};
+
struct DiscardedData {
uint32_t mDiscardedHistogramAccumulations;
uint32_t mDiscardedKeyedHistogramAccumulations;
uint32_t mDiscardedScalarActions;
uint32_t mDiscardedKeyedScalarActions;
uint32_t mDiscardedChildEvents;
+#if defined(MOZ_GECKO_PROFILER)
+ uint32_t mDiscardedChildStacks;
+#endif
};
} // namespace Telemetry
} // namespace mozilla
namespace IPC {
template<>
@@ -423,15 +432,107 @@ ParamTraits<mozilla::Telemetry::EventExt
}
return true;
}
};
template<>
struct
+ParamTraits<mozilla::Telemetry::ProcessedStack::Frame>
+{
+ typedef mozilla::Telemetry::ProcessedStack::Frame paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, aParam.mModIndex);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter, &(aResult->mOffset)) &&
+ ReadParam(aMsg, aIter, &(aResult->mModIndex));
+ }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::ProcessedStack::Module>
+{
+ typedef mozilla::Telemetry::ProcessedStack::Module paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mName);
+ WriteParam(aMsg, aParam.mBreakpadId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter, &(aResult->mName)) &&
+ ReadParam(aMsg, aIter, &(aResult->mBreakpadId));
+ }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::ProcessedStack>
+{
+ typedef mozilla::Telemetry::ProcessedStack paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.GetModules());
+ WriteParam(aMsg, aParam.GetFrames());
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ nsTArray<paramType::Module> modules;
+ nsTArray<paramType::Frame> frames;
+
+ if (!ReadParam(aMsg, aIter, &modules) ||
+ !ReadParam(aMsg, aIter, &frames)
+ ) {
+ return false;
+ }
+
+ aResult->SetModules(modules);
+ aResult->SetFrames(frames);
+
+ return true;
+ }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::ChildStackData>
+{
+ typedef mozilla::Telemetry::ChildStackData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.key);
+ WriteParam(aMsg, aParam.stack);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &(aResult->key)) ||
+ !ReadParam(aMsg, aIter, &(aResult->stack))
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template<>
+struct
ParamTraits<mozilla::Telemetry::DiscardedData>
: public PlainOldDataSerializer<mozilla::Telemetry::DiscardedData>
{ };
} // namespace IPC
#endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/ipc/TelemetryIPC.cpp
+++ b/toolkit/components/telemetry/ipc/TelemetryIPC.cpp
@@ -3,16 +3,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/. */
#include "TelemetryIPC.h"
#include "../TelemetryScalar.h"
#include "../TelemetryHistogram.h"
#include "../TelemetryEvent.h"
+#include "../KeyedStackCapturer.h"
namespace mozilla {
void
TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID aProcessType,
const nsTArray<Telemetry::HistogramAccumulation>& aAccumulations)
{
TelemetryHistogram::AccumulateChild(aProcessType, aAccumulations);
@@ -52,15 +53,25 @@ TelemetryIPC::AddDynamicScalarDefinition
}
void
TelemetryIPC::RecordChildEvents(Telemetry::ProcessID aProcessType, const nsTArray<Telemetry::ChildEventData>& aEvents)
{
TelemetryEvent::RecordChildEvents(aProcessType, aEvents);
}
+#if defined(MOZ_GECKO_PROFILER)
+
+void
+TelemetryIPC::RecordChildStacks(Telemetry::ProcessID aProcessType, const nsTArray<Telemetry::ChildStackData>& aStacks)
+{
+ Telemetry::RecordChildStacks(aProcessType, aStacks);
+}
+
+#endif
+
void
TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID aProcessType,
const Telemetry::DiscardedData& aDiscardedData)
{
TelemetryScalar::RecordDiscardedData(aProcessType, aDiscardedData);
}
}
--- a/toolkit/components/telemetry/ipc/TelemetryIPC.h
+++ b/toolkit/components/telemetry/ipc/TelemetryIPC.h
@@ -19,16 +19,19 @@ namespace mozilla {
namespace Telemetry {
struct HistogramAccumulation;
struct KeyedHistogramAccumulation;
struct ScalarAction;
struct KeyedScalarAction;
struct DynamicScalarDefinition;
struct ChildEventData;
+#if defined(MOZ_GECKO_PROFILER)
+struct ChildStackData;
+#endif
struct DiscardedData;
}
namespace TelemetryIPC {
/**
* Accumulate child process data into histograms for the given process type.
@@ -70,16 +73,30 @@ void UpdateChildKeyedScalars(Telemetry::
* Record events for the given process type with the data coming from child process.
*
* @param aProcessType - the process type to record the events for
* @param aEvents - events to record
*/
void RecordChildEvents(Telemetry::ProcessID aProcessType,
const nsTArray<Telemetry::ChildEventData>& aEvents);
+#if defined(MOZ_GECKO_PROFILER)
+
+/**
+ * Record captured stacks for the given process type with the data coming
+ * from child process.
+ *
+ * @param aProcessType - the process type to record the events for
+ * @param aStacks - stacks to record
+ */
+void RecordChildStacks(Telemetry::ProcessID aProcessType,
+ const nsTArray<Telemetry::ChildStackData>& aStacks);
+
+#endif
+
/**
* Record the counts of data the child process had to discard
*
* @param aProcessType - the process reporting the discarded data
* @param aDiscardedData - stats about the discarded data
*/
void RecordDiscardedData(Telemetry::ProcessID aProcessType,
const Telemetry::DiscardedData& aDiscardedData);
--- a/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
+++ b/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
@@ -28,32 +28,40 @@ using mozilla::Telemetry::HistogramAccum
using mozilla::Telemetry::DiscardedData;
using mozilla::Telemetry::KeyedHistogramAccumulation;
using mozilla::Telemetry::ScalarActionType;
using mozilla::Telemetry::ScalarAction;
using mozilla::Telemetry::KeyedScalarAction;
using mozilla::Telemetry::ScalarVariant;
using mozilla::Telemetry::ChildEventData;
+#if defined(MOZ_GECKO_PROFILER)
+using mozilla::Telemetry::ChildStackData;
+#endif
+
namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
// Sending each remote accumulation immediately places undue strain on the
// IPC subsystem. Batch the remote accumulations for a period of time before
// sending them all at once. This value was chosen as a balance between data
// timeliness and performance (see bug 1218576)
const uint32_t kBatchTimeoutMs = 2000;
// To stop growing unbounded in memory while waiting for kBatchTimeoutMs to
// drain the probe accumulation arrays, we request an immediate flush if the
// arrays manage to reach certain high water mark of elements.
const size_t kHistogramAccumulationsArrayHighWaterMark = 5 * 1024;
const size_t kScalarActionsArrayHighWaterMark = 10000;
// With the current limits, events cost us about 1100 bytes each.
// This limits memory use to about 10MB.
const size_t kEventsArrayHighWaterMark = 10000;
+// Currently, stacks captures cost us about 7300 bytes each.
+// This limits memory use to about 10MB based on the following formula:
+// 7300 * kStacksArrayHighWaterMark * kWaterMarkDiscardFactor <= 10MB.
+const size_t kStacksArrayHighWaterMark = 200;
// If we are starved we can overshoot the watermark.
// This is the multiplier over which we will discard data.
const size_t kWaterMarkDiscardFactor = 5;
// Counts of how many pieces of data we have discarded.
DiscardedData gDiscardedData = {0};
// This timer is used for batching and sending child process accumulations to the parent.
@@ -63,16 +71,20 @@ mozilla::Atomic<bool, mozilla::Relaxed>
// This batches child process accumulations that should be sent to the parent.
StaticAutoPtr<nsTArray<HistogramAccumulation>> gHistogramAccumulations;
StaticAutoPtr<nsTArray<KeyedHistogramAccumulation>> gKeyedHistogramAccumulations;
StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions;
StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions;
StaticAutoPtr<nsTArray<ChildEventData>> gChildEvents;
+#if defined(MOZ_GECKO_PROFILER)
+StaticAutoPtr<nsTArray<ChildStackData>> gChildStacks;
+#endif
+
// This is a StaticMutex rather than a plain Mutex so that (1)
// it gets initialised in a thread-safe manner the first time
// it is used, and (2) because it is never de-initialised, and
// a normal Mutex would show up as a leak in BloatView. StaticMutex
// also has the "OffTheBooks" property, so it won't show as a leak
// in BloatView.
static StaticMutex gTelemetryIPCAccumulatorMutex;
@@ -248,29 +260,60 @@ TelemetryIPCAccumulator::RecordChildEven
// Store the event.
gChildEvents->AppendElement(ChildEventData{timestamp, nsCString(category),
nsCString(method), nsCString(object),
value,
nsTArray<mozilla::Telemetry::EventExtraEntry>(extra)});
ArmIPCTimer(locker);
}
+#if defined(MOZ_GECKO_PROFILER)
+
+void
+TelemetryIPCAccumulator::RecordCapturedStack(const nsACString& aKey,
+ mozilla::Telemetry::ProcessedStack& stack)
+{
+ StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+
+ if (!gChildStacks) {
+ gChildStacks = new nsTArray<ChildStackData>();
+ }
+
+ if (gChildStacks->Length() >=
+ kWaterMarkDiscardFactor * kStacksArrayHighWaterMark) {
+ gDiscardedData.mDiscardedChildStacks++;
+ return;
+ }
+
+ if (gChildStacks->Length() == kStacksArrayHighWaterMark) {
+ DispatchIPCTimerFired();
+ }
+
+ gChildStacks->AppendElement(ChildStackData{nsCString(aKey), stack});
+ ArmIPCTimer(locker);
+}
+
+#endif
+
// This method takes the lock only to double-buffer the batched telemetry.
// It releases the lock before calling out to IPC code which can (and does)
// Accumulate (which would deadlock)
template<class TActor>
static void
SendAccumulatedData(TActor* ipcActor)
{
// Get the accumulated data and free the storage buffers.
nsTArray<HistogramAccumulation> histogramsToSend;
nsTArray<KeyedHistogramAccumulation> keyedHistogramsToSend;
nsTArray<ScalarAction> scalarsToSend;
nsTArray<KeyedScalarAction> keyedScalarsToSend;
nsTArray<ChildEventData> eventsToSend;
+ #if defined(MOZ_GECKO_PROFILER)
+ nsTArray<ChildStackData> stacksToSend;
+ #endif
DiscardedData discardedData;
{
StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
if (gHistogramAccumulations) {
histogramsToSend.SwapElements(*gHistogramAccumulations);
}
if (gKeyedHistogramAccumulations) {
@@ -280,16 +323,21 @@ SendAccumulatedData(TActor* ipcActor)
scalarsToSend.SwapElements(*gChildScalarsActions);
}
if (gChildKeyedScalarsActions) {
keyedScalarsToSend.SwapElements(*gChildKeyedScalarsActions);
}
if (gChildEvents) {
eventsToSend.SwapElements(*gChildEvents);
}
+#if defined(MOZ_GECKO_PROFILER)
+ if (gChildStacks) {
+ stacksToSend.SwapElements(*gChildStacks);
+ }
+#endif
discardedData = gDiscardedData;
gDiscardedData = {0};
}
// Send the accumulated data to the parent process.
MOZ_ASSERT(ipcActor);
if (histogramsToSend.Length()) {
mozilla::Unused <<
@@ -306,16 +354,22 @@ SendAccumulatedData(TActor* ipcActor)
if (keyedScalarsToSend.Length()) {
mozilla::Unused <<
NS_WARN_IF(!ipcActor->SendUpdateChildKeyedScalars(keyedScalarsToSend));
}
if (eventsToSend.Length()) {
mozilla::Unused <<
NS_WARN_IF(!ipcActor->SendRecordChildEvents(eventsToSend));
}
+#if defined(MOZ_GECKO_PROFILER)
+ if (stacksToSend.Length()) {
+ mozilla::Unused <<
+ NS_WARN_IF(!ipcActor->SendRecordChildStacks(stacksToSend));
+ }
+#endif
mozilla::Unused <<
NS_WARN_IF(!ipcActor->SendRecordDiscardedData(discardedData));
}
// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
// unset gIPCTimerArmed until the IPC completes
//
--- a/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.h
+++ b/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.h
@@ -6,16 +6,20 @@
#ifndef TelemetryIPCAccumulator_h__
#define TelemetryIPCAccumulator_h__
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Maybe.h"
#include "nsStringFwd.h"
#include "TelemetryComms.h"
+#if defined(MOZ_GECKO_PROFILER)
+#include "../ProcessedStack.h"
+#endif
+
class nsIRunnable;
class nsITimer;
namespace mozilla {
class TimeStamp;
namespace TelemetryIPCAccumulator {
@@ -41,12 +45,19 @@ void RecordChildEvent(const mozilla::Tim
const mozilla::Maybe<nsCString>& value,
const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra);
void IPCTimerFired(nsITimer* aTimer, void* aClosure);
void DeInitializeGlobalState();
void DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent);
+#if defined(MOZ_GECKO_PROFILER)
+
+// Accumulation of stacks captured in child process.
+void RecordCapturedStack(const nsACString& aKey, mozilla::Telemetry::ProcessedStack& stack);
+
+#endif
+
}
}
#endif // TelemetryIPCAccumulator_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_ChildStacks.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
+ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", this);
+ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this);
+ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this);
+ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
+
+const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
+
+const PLATFORM_VERSION = "1.9.2";
+const APP_VERSION = "1";
+const APP_ID = "xpcshell@tests.mozilla.org";
+const APP_NAME = "XPCShell";
+
+const SINGLE_STACK_CAPTURE = "SINGLE-STACK-CAPTURE";
+const MULTIPLE_STACK_CAPTURE = "MULTIPLE-STACK-CAPTURE";
+const PARENT_ONLY_STACK = "PARENT-ONLY-STACK";
+
+/**
+ * Wait until stacks captured in child appear in the parent process.
+ */
+async function waitForContentStacks() {
+ await ContentTaskUtils.waitForCondition(() => {
+ const clearCapturedStacks = false;
+ const stacks =
+ Telemetry.snapshotCapturedStacks(clearCapturedStacks);
+ return stacks.content.captures.length > 0;
+ });
+}
+
+/**
+ * A wrapper for capturing stack for a given key several times.
+ *
+ * @param String key name of the key to capture stack under.
+ * @param Int times number of times to perform stack capturing.
+ *
+ * @return undefined
+ */
+function captureStack(key, times) {
+ times = typeof times === "undefined" ? 1 : times;
+ while (times--) {
+ Telemetry.captureStack(key);
+ }
+}
+
+/**
+ * Loops throught the contents of the test ping and searches for the capture
+ * information by the given key name.
+ */
+function findStackByKey(stacks, key) {
+ let found = stacks.captures.filter(captureInfo => { return captureInfo[0] === key; });
+ return found.length >= 1 ? found[0] : false;
+}
+
+const TestSuite = new Map([
+ [
+ "Ensure a single stack capture arrives into parent.",
+ {
+ child: () => { captureStack(SINGLE_STACK_CAPTURE); },
+ parent: () => {},
+ checkPayload: (payload) => {
+ // Validate the data for captured stacks.
+ Assert.ok("processes" in payload, "Should have processes section");
+ Assert.ok("content" in payload.processes, "Should have child process section");
+ Assert.ok("capturedStacks" in payload.processes.content, "Child process section should have stacks.");
+
+ let stacks = payload.processes.content.capturedStacks;
+ Assert.equal(SINGLE_STACK_CAPTURE, stacks.captures[0][0], "Stack should be captured under test-stack key.");
+ Assert.equal(1, stacks.captures[0][2], "Test stack should be captured once");
+ Assert.ok(stacks.stacks[stacks.captures[0][1]].length > 0, "Captured stack should not be empty");
+ },
+ },
+ ],
+ [
+ "Capture 2 stacks in child and 2 in parent. Ensure that ping structure reflects these counts.",
+ {
+ child: () => { captureStack(MULTIPLE_STACK_CAPTURE, 2); },
+ parent: () => {
+ captureStack(MULTIPLE_STACK_CAPTURE, 2);
+ },
+ checkPayload: (payload) => {
+ let childStacks = findStackByKey(payload.processes.content.capturedStacks, MULTIPLE_STACK_CAPTURE);
+ Assert.equal(2, childStacks[2], "The stack should be capture twice in child.");
+
+ let parentStacks = findStackByKey(payload.processes.parent.capturedStacks, MULTIPLE_STACK_CAPTURE);
+ Assert.equal(2, parentStacks[2], "The stack should be capture twice in parent.");
+ },
+ },
+ ],
+ [
+ "Ensures that stacks captured only in parent are correctly reflected in ping.",
+ {
+ child: () => {},
+ parent: () => {
+ captureStack(PARENT_ONLY_STACK);
+ },
+ checkPayload: (payload) => {
+ let parentStacks = findStackByKey(payload.processes.parent.capturedStacks, PARENT_ONLY_STACK);
+ Assert.equal(1, parentStacks[1], "One stack should be captured in parent.");
+
+ let childStacks = findStackByKey(payload.processes.content.capturedStacks, PARENT_ONLY_STACK);
+ Assert.ok(!childStacks, "Child stacks should be empty.");
+ },
+ },
+ ]
+]);
+
+add_task({
+ // Only execute tests in this file if the build contains profiler.
+ // Without profiler stack capturing is disabled.
+ skip_if: () => !AppConstants.MOZ_GECKO_PROFILER
+}, async function test_ChildStacksArriveInParent() {
+ if (!runningInParent) {
+ // Setup child telementry and capture a stack.
+ TelemetryController.testSetupContent();
+ TestSuite.forEach(handler => { handler.child() });
+ do_send_remote_message(MESSAGE_CHILD_TEST_DONE);
+ return;
+ }
+
+ // Setup parent telemetry.
+ do_get_profile(true);
+ loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
+ finishAddonManagerStartup();
+ await TelemetryController.testSetup();
+ // Make sure we don't generate unexpected pings due to pref changes.
+ await setEmptyPrefWatchlist();
+
+ // Run test in child, don't wait for it to finish: just wait for the
+ // MESSAGE_CHILD_TEST_DONE.
+ run_test_in_child("test_ChildStacks.js");
+ await do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
+
+ // Make sure captured stacks have arrived into parent.
+ await waitForContentStacks();
+
+ TestSuite.forEach(handler => { handler.parent() });
+
+ // Get an "environment-changed" ping rather than a "test-ping", as
+ // captured stacks are only reported in subsession pings.
+ const payload = TelemetrySession.getPayload("test-ping");
+
+ TestSuite.forEach(handler => {
+ handler.checkPayload(payload);
+ });
+
+ // This is needed due to run_test_in_child.
+ do_test_finished();
+});
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryCaptureStack.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryCaptureStack.js
@@ -49,23 +49,28 @@ function checkObjectStructure(obj) {
}
/**
* A helper for triggering a stack capture and returning the new state of stacks.
*
* @param {String} key The key for capturing stack.
* @param {Boolean} clear True to reset captured stacks, False - otherwise.
*
- * @return {Object} captured stacks.
+ * @return {Object} captured stacks for the parent process.
*/
function captureStacks(key, clear = true) {
Telemetry.captureStack(key);
let stacks = Telemetry.snapshotCapturedStacks(clear);
- Assert.ok(checkObjectStructure(stacks));
- return stacks;
+ ["parent", "content", "gpu"].forEach(process => {
+ // Ensure that the informaiton for the process type exists in the stacks
+ // object and has the expected structure.
+ Assert.ok(stacks.hasOwnProperty(process));
+ Assert.ok(checkObjectStructure(stacks[process]));
+ });
+ return stacks.parent;
}
const TEST_STACK_KEYS = ["TEST-KEY1", "TEST-KEY2"];
/**
* Ensures that captured stacks appear in pings, if any were captured.
*/
add_task({
@@ -87,17 +92,17 @@ add_task({
*/
add_task({
skip_if: () => !ENABLE_TESTS
}, function test_CaptureStacksIncreasesNumberOfCapturedStacks() {
// Construct a unique key for this test.
let key = TEST_STACK_KEYS[0] + "-UNIQUE-KEY-1";
// Ensure that no captures for the key exist.
- let original = Telemetry.snapshotCapturedStacks();
+ let original = (Telemetry.snapshotCapturedStacks()).parent;
Assert.equal(undefined, original.captures.find(capture => capture[0] === key));
// Capture stack and find updated capture stats for TEST_STACK_KEYS[0].
let updated = captureStacks(key);
// Ensure that a new element has been appended to both stacks and captures.
Assert.equal(original.stacks.length + 1, updated.stacks.length);
Assert.equal(original.captures.length + 1, updated.captures.length);
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -71,16 +71,17 @@ skip-if = os == "android" # Disabled due
[test_TelemetryReportingPolicy.js]
skip-if = os == "android" # Disabled due to crashes (see bug 1367762)
tags = addons
[test_TelemetryScalars.js]
[test_TelemetryScalars_buildFaster.js]
[test_TelemetryTimestamps.js]
skip-if = toolkit == 'android'
[test_TelemetryCaptureStack.js]
+[test_ChildStacks.js]
[test_TelemetryChildEvents_buildFaster.js]
skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
[test_TelemetryEvents.js]
[test_TelemetryEvents_buildFaster.js]
[test_ChildEvents.js]
skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
[test_TelemetryModules.js]
[test_PingSender.js]