bug 1218576 - Accumulate child histograms in the parent process r=froydnj
authorChris H-C <chutten@mozilla.com>
Wed, 22 Jun 2016 10:16:40 -0400
changeset 314436 9b792ae11734c602485896031a6d3400a3f603b3
parent 314404 d6809e466fe6c6cdeebeebe8878ee0d03e59d4f6
child 314437 832793e20c54a1fc6a2c6573e9205e2e783a3676
push id20574
push usercbook@mozilla.com
push dateTue, 20 Sep 2016 10:05:16 +0000
treeherderfx-team@14705f779a46 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1218576
milestone51.0a1
bug 1218576 - Accumulate child histograms in the parent process r=froydnj Batch the accumulations to only transmit every so often, so we don't incur too much in the way of IPC overhead penalties. What this doesn't do: * remove or restructure child telemetry code to adapt to the new way * send the telemetry anywhere * allow for the child process to clear child histograms * support anything but histograms (but this is expected and okay) MozReview-Commit-ID: JnUkcmN3Ya7
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/Telemetry.h
toolkit/components/telemetry/TelemetryComms.h
toolkit/components/telemetry/TelemetryHistogram.cpp
toolkit/components/telemetry/TelemetryHistogram.h
toolkit/components/telemetry/moz.build
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5408,8 +5408,24 @@ ContentParent::SendGetFilesResponseAndFo
 void
 ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
 {
   if (!mHangMonitorActor) {
     return;
   }
   ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
 }
+
+bool
+ContentParent::RecvAccumulateChildHistogram(
+                InfallibleTArray<Accumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChild(aAccumulations);
+  return true;
+}
+
+bool
+ContentParent::RecvAccumulateChildKeyedHistogram(
+                InfallibleTArray<KeyedAccumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChildKeyed(aAccumulations);
+  return true;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1132,16 +1132,20 @@ private:
   virtual bool RecvNotifyLowMemory() override;
 
   virtual bool RecvGetFilesRequest(const nsID& aID,
                                    const nsString& aDirectoryPath,
                                    const bool& aRecursiveFlag) override;
 
   virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override;
 
+  virtual bool RecvAccumulateChildHistogram(
+                  InfallibleTArray<Accumulation>&& aAccumulations) override;
+  virtual bool RecvAccumulateChildKeyedHistogram(
+                  InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
 public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
 private:
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -95,16 +95,18 @@ using mozilla::dom::ContentParentId from
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
 using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h";
 using mozilla::DocShellOriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h";
+using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1211,16 +1213,22 @@ parent:
      async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
      async DeleteGetFilesRequest(nsID aID);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
+    /**
+     * Messages for communicating child Telemetry to the parent process
+     */
+    async AccumulateChildHistogram(Accumulation[] accumulations);
+    async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2789,16 +2789,28 @@ AccumulateCategorical(ID id, const nsCSt
 void
 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
 {
   Accumulate(aHistogram,
              static_cast<uint32_t>((end - start).ToMilliseconds()));
 }
 
 void
+AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChild(aAccumulations);
+}
+
+void
+AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChildKeyed(aAccumulations);
+}
+
+void
 ClearHistogram(ID aId)
 {
   TelemetryHistogram::ClearHistogram(aId);
 }
 
 const char*
 GetHistogramName(ID id)
 {
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -28,16 +28,19 @@
  *****************************************************************************/
 
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
+struct Accumulation;
+struct KeyedAccumulation;
+
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Create and destroy the underlying base::StatisticsRecorder singleton.
  * Creation has to be done very early in the startup sequence.
@@ -121,16 +124,30 @@ void AccumulateCategorical(ID id, const 
  *
  * @param id - histogram id
  * @param start - start time
  * @param end - end time
  */
 void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now());
 
 /**
+ * Accumulate child data into child histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChild(const nsTArray<Accumulation>& aAccumulations);
+
+/**
+ * Accumulate child data into child keyed histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations);
+
+/**
  * This clears the data for a histogram in TelemetryHistogramEnums.h.
  *
  * @param id - histogram id
  */
 void ClearHistogram(ID id);
 
 /**
  * Enable/disable recording for this histogram at runtime.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryComms.h
@@ -0,0 +1,84 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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
+ */
+
+#ifndef Telemetry_Comms_h__
+#define Telemetry_Comms_h__
+
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+enum ID : uint32_t;
+
+struct Accumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+};
+
+struct KeyedAccumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+  nsCString mKey;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+namespace IPC {
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::Accumulation>
+{
+  typedef mozilla::Telemetry::Accumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::KeyedAccumulation>
+{
+  typedef mozilla::Telemetry::KeyedAccumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+    WriteParam(aMsg, aParam.mKey);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mKey))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+} // namespace IPC
+
+#endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -9,33 +9,39 @@
 #include "js/GCAPI.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsITelemetry.h"
 
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/unused.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryHistogram.h"
 
 #include "base/histogram.h"
 
 using base::Histogram;
 using base::StatisticsRecorder;
 using base::BooleanHistogram;
 using base::CountHistogram;
 using base::FlagHistogram;
 using base::LinearHistogram;
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
+using mozilla::StaticAutoPtr;
+using mozilla::Telemetry::Accumulation;
+using mozilla::Telemetry::KeyedAccumulation;
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
 // * Functions named internal_*: these can only be reached via an
@@ -87,16 +93,17 @@ using mozilla::StaticMutexAutoLock;
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
 #define EXPIRED_ID "__expired__"
 #define SUBSESSION_HISTOGRAM_PREFIX "sub#"
 #define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
+#define CHILD_HISTOGRAM_SUFFIX "#content"
 
 namespace {
 
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::IsInDataset;
 
@@ -180,16 +187,22 @@ bool gCorruptHistograms[mozilla::Telemet
 // This is for gHistograms, gHistogramStringTable
 #include "TelemetryHistogramData.inc"
 
 AddonMapType gAddonMap;
 
 // The singleton StatisticsRecorder object for this process.
 base::StatisticsRecorder* gStatisticsRecorder = nullptr;
 
+// For batching and sending child process accumulations to the parent
+nsITimer* gIPCTimer = nullptr;
+bool gIPCTimerArmed = false;
+StaticAutoPtr<nsTArray<Accumulation>> gAccumulations;
+StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedAccumulations;
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE CONSTANTS
 
@@ -199,16 +212,22 @@ namespace {
 const mozilla::Telemetry::ID kRecordingInitiallyDisabledIDs[] = {
   mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
 
   // The array must not be empty. Leave these item here.
   mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
   mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
 };
 
+// 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;
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Misc small helpers
 
@@ -407,32 +426,41 @@ internal_GetHistogramEnumId(const char *
     return NS_ERROR_INVALID_ARG;
   }
   *id = entry->mData;
   return NS_OK;
 }
 
 // O(1) histogram lookup by numeric id
 nsresult
-internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret)
+internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret,
+                              bool child = false)
 {
   static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  Histogram *h = knownHistograms[id];
+  static Histogram* knownChildHistograms[mozilla::Telemetry::HistogramCount] = {0};
+  Histogram *h = child ? knownChildHistograms[id] : knownHistograms[id];
   if (h) {
     *ret = h;
     return NS_OK;
   }
 
   const HistogramInfo &p = gHistograms[id];
   if (p.keyed) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = internal_HistogramGet(p.id(), p.expiration(), p.histogramType,
-                                      p.min, p.max, p.bucketCount, true, &h);
+  nsCString histogramName;
+  histogramName.Append(p.id());
+  if (child) {
+    histogramName.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  }
+
+  nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
+                                      p.histogramType, p.min, p.max,
+                                      p.bucketCount, true, &h);
   if (NS_FAILED(rv))
     return rv;
 
 #ifdef DEBUG
   // Check that the C++ Histogram code computes the same ranges as the
   // Python histogram code.
   if (!IsExpiredVersion(p.expiration())) {
     const struct bounds &b = gBucketLowerBoundIndex[id];
@@ -442,17 +470,21 @@ internal_GetHistogramByEnumId(mozilla::T
       for (int i = 0; i < b.length; ++i) {
         MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
                    "C++/Python bucket mismatch");
       }
     }
   }
 #endif
 
-  *ret = knownHistograms[id] = h;
+  if (child) {
+    *ret = knownChildHistograms[id] = h;
+  } else {
+    *ret = knownHistograms[id] = h;
+  }
   return NS_OK;
 }
 
 nsresult
 internal_GetHistogramByName(const nsACString &name, Histogram **ret)
 {
   mozilla::Telemetry::ID id;
   nsresult rv
@@ -1676,42 +1708,125 @@ internal_SetHistogramRecordingEnabled(mo
       h->SetRecordingEnabled(aEnabled);
       return;
     }
   }
 
   MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
 }
 
+void internal_armIPCTimer()
+{
+  if (gIPCTimerArmed) {
+    return;
+  }
+  if (!gIPCTimer) {
+    CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer);
+  }
+  if (gIPCTimer) {
+    gIPCTimer->InitWithFuncCallback(TelemetryHistogram::IPCTimerFired,
+                                    nullptr, kBatchTimeoutMs,
+                                    nsITimer::TYPE_ONE_SHOT);
+    gIPCTimerArmed = true;
+  }
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gAccumulations) {
+    gAccumulations = new nsTArray<Accumulation>();
+  }
+  gAccumulations->AppendElement(Accumulation{aId, aSample});
+  internal_armIPCTimer();
+  return true;
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gKeyedAccumulations) {
+    gKeyedAccumulations = new nsTArray<KeyedAccumulation>();
+  }
+  gKeyedAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
+  internal_armIPCTimer();
+  return true;
+}
+
 void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
 {
-  if (!internal_CanRecordBase()) {
+  if (!internal_CanRecordBase() ||
+      internal_RemoteAccumulate(aHistogram, aSample)) {
     return;
   }
   Histogram *h;
   nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
   if (NS_SUCCEEDED(rv)) {
     internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
   }
 }
 
 void
 internal_Accumulate(mozilla::Telemetry::ID aID,
                     const nsCString& aKey, uint32_t aSample)
 {
-  if (!gInitDone || !internal_CanRecordBase()) {
+  if (!gInitDone || !internal_CanRecordBase() ||
+      internal_RemoteAccumulate(aID, aKey, aSample)) {
     return;
   }
   const HistogramInfo& th = gHistograms[aID];
   KeyedHistogram* keyed
      = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
   MOZ_ASSERT(keyed);
   keyed->Add(aKey, aSample);
 }
 
+void
+internal_AccumulateChild(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  Histogram* h;
+  nsresult rv = internal_GetHistogramByEnumId(aId, &h, true);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
+  } else {
+    NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
+  }
+}
+
+void
+internal_AccumulateChildKeyed(mozilla::Telemetry::ID aId,
+                              const nsCString& aKey, uint32_t aSample)
+{
+  if (!gInitDone || !internal_CanRecordBase()) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aId];
+  nsCString id;
+  id.Append(th.id());
+  id.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
+  if (!keyed) {
+    const nsDependentCString expiration(th.expiration());
+    keyed = new KeyedHistogram(id, expiration, th.histogramType, th.min, th.max,
+                               th.bucketCount, th.dataset);
+    gKeyedHistograms.Put(id, keyed);
+  }
+  keyed->Add(aKey, aSample);
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
 
@@ -1811,16 +1926,21 @@ void TelemetryHistogram::InitializeGloba
 void TelemetryHistogram::DeInitializeGlobalState()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
   gCanRecordExtended = false;
   gHistogramMap.Clear();
   gKeyedHistograms.Clear();
   gAddonMap.Clear();
+  gAccumulations = nullptr;
+  gKeyedAccumulations = nullptr;
+  if (gIPCTimer) {
+    NS_RELEASE(gIPCTimer);
+  }
   gInitDone = false;
 }
 
 #ifdef DEBUG
 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gInitDone;
 }
@@ -1916,22 +2036,17 @@ TelemetryHistogram::Accumulate(const cha
   if (!internal_CanRecordBase()) {
     return;
   }
   mozilla::Telemetry::ID id;
   nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_FAILED(rv)) {
     return;
   }
-
-  Histogram *h;
-  rv = internal_GetHistogramByEnumId(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, sample, gHistograms[id].dataset);
-  }
+  internal_Accumulate(id, sample);
 }
 
 void
 TelemetryHistogram::Accumulate(const char* name,
                                const nsCString& key, uint32_t sample)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
@@ -1948,16 +2063,44 @@ void
 TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::ID aId,
                                           const nsCString& label)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   internal_HistogramAddCategorical(aId, label);
 }
 
 void
+TelemetryHistogram::AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    internal_AccumulateChild(aAccumulations[i].mId, aAccumulations[i].mSample);
+  }
+}
+
+void
+TelemetryHistogram::AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    internal_AccumulateChildKeyed(aAccumulations[i].mId,
+                                  aAccumulations[i].mKey,
+                                  aAccumulations[i].mSample);
+  }
+}
+
+void
 TelemetryHistogram::ClearHistogram(mozilla::Telemetry::ID aId)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
   }
 
   Histogram *h;
@@ -2053,16 +2196,18 @@ TelemetryHistogram::CreateHistogramSnaps
     }
     const uint32_t type = gHistograms[i].histogramType;
     if (type == nsITelemetry::HISTOGRAM_FLAG ||
         type == nsITelemetry::HISTOGRAM_COUNT) {
       Histogram *h;
       mozilla::DebugOnly<nsresult> rv
          = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
+      rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h, true);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   StatisticsRecorder::Histograms hs;
   StatisticsRecorder::GetHistograms(&hs);
 
   // We identify corrupt histograms first, rather than interspersing it
   // in the loop below, to ensure that our corruption statistics don't
@@ -2316,8 +2461,45 @@ TelemetryHistogram::GetHistogramSizesofI
   StatisticsRecorder::GetHistograms(&hs);
   size_t n = 0;
   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
     Histogram *h = *it;
     n += h->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
+
+// 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)
+//
+// To ensure non-reentrancy, the timer is not released until the method
+// completes
+void
+TelemetryHistogram::IPCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+  nsTArray<Accumulation> accumulationsToSend;
+  nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    if (gAccumulations) {
+      accumulationsToSend.SwapElements(*gAccumulations);
+    }
+    if (gKeyedAccumulations) {
+      keyedAccumulationsToSend.SwapElements(*gKeyedAccumulations);
+    }
+  }
+
+  mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
+  mozilla::Unused << NS_WARN_IF(!contentChild);
+  if (contentChild) {
+    if (accumulationsToSend.Length()) {
+      mozilla::Unused <<
+        NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
+    }
+    if (keyedAccumulationsToSend.Length()) {
+      mozilla::Unused <<
+        NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
+    }
+  }
+
+  gIPCTimerArmed = false;
+}
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -3,16 +3,18 @@
  * 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 TelemetryHistogram_h__
 #define TelemetryHistogram_h__
 
 #include "mozilla/TelemetryHistogramEnums.h"
 
+#include "mozilla/TelemetryComms.h"
+
 // This module is internal to Telemetry.  It encapsulates Telemetry's
 // histogram accumulation and storage logic.  It should only be used by
 // Telemetry.cpp.  These functions should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace TelemetryHistogram {
 
 void CreateStatisticsRecorder();
@@ -37,16 +39,19 @@ nsresult SetHistogramRecordingEnabled(co
 void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
 void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey,
                                             uint32_t aSample);
 void Accumulate(const char* name, uint32_t sample);
 void Accumulate(const char* name, const nsCString& key, uint32_t sample);
 
 void AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& aLabel);
 
+void AccumulateChild(const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations);
+void AccumulateChildKeyed(const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations);
+
 void
 ClearHistogram(mozilla::Telemetry::ID aId);
 
 nsresult
 GetHistogramById(const nsACString &name, JSContext *cx,
                  JS::MutableHandle<JS::Value> ret);
 
 nsresult
@@ -91,11 +96,13 @@ nsresult
 GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret);
 
 size_t
 GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 size_t
 GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
+void
+IPCTimerFired(nsITimer* aTimer, void* aClosure);
 } // namespace TelemetryHistogram
 
 #endif // TelemetryHistogram_h__
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -14,16 +14,17 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'telemetry'
 
 EXPORTS.mozilla += [
     '!TelemetryHistogramEnums.h',
     '!TelemetryScalarEnums.h',
     'ProcessedStack.h',
     'Telemetry.h',
+    'TelemetryComms.h',
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
     'TelemetryCommon.cpp',
     'TelemetryHistogram.cpp',
     'TelemetryScalar.cpp',