Bug 1299770: Implement stack capturing in content processes. r?gfritzsche draft
authorIaroslav (yarik) Sheptykin <yarik.sheptykin@googlemail.com>
Tue, 21 Nov 2017 22:01:02 +0100
changeset 829943 d9af159530958cd7b4175f5aee68529c37fd4f4f
parent 829942 131363332762842ba327166bdc361c6fffc4858f
push id118806
push userbmo:yarik.sheptykin@googlemail.com
push dateFri, 17 Aug 2018 16:05:17 +0000
reviewersgfritzsche
bugs1299770
milestone63.0a1
Bug 1299770: Implement stack capturing in content processes. r?gfritzsche MozReview-Commit-ID: GNPFB2NinIK
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
gfx/ipc/GPUChild.cpp
gfx/ipc/GPUChild.h
gfx/ipc/PGPU.ipdl
toolkit/components/telemetry/KeyedStackCapturer.cpp
toolkit/components/telemetry/KeyedStackCapturer.h
toolkit/components/telemetry/ProcessedStack.cpp
toolkit/components/telemetry/ProcessedStack.h
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/Telemetry.h
toolkit/components/telemetry/TelemetrySession.jsm
toolkit/components/telemetry/docs/collection/stack-capture.rst
toolkit/components/telemetry/docs/data/main-ping.rst
toolkit/components/telemetry/ipc/TelemetryComms.h
toolkit/components/telemetry/ipc/TelemetryIPC.cpp
toolkit/components/telemetry/ipc/TelemetryIPC.h
toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.h
toolkit/components/telemetry/tests/unit/test_ChildStacks.js
toolkit/components/telemetry/tests/unit/test_TelemetryCaptureStack.js
toolkit/components/telemetry/tests/unit/xpcshell.ini
--- 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]