Bug 1278556 - Enable child process scalar recording. r=chutten, f=gfritzsche,froydnj
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Mon, 16 Jan 2017 05:12:00 +0100
changeset 462240 2ae922a0d331c978a184592fe3e281a24bc641f6
parent 462239 850f95f34e6dcd00611c95bcd398edee45515ec1
child 462241 94b3eae2ea2b1ca56097ef4306f911a5f67e0cfd
push id41683
push userbmo:steffen.wilberg@web.de
push dateMon, 16 Jan 2017 21:50:32 +0000
reviewerschutten
bugs1278556
milestone53.0a1
Bug 1278556 - Enable child process scalar recording. r=chutten, f=gfritzsche,froydnj MozReview-Commit-ID: IddrszBX0f5
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/Telemetry.cpp
toolkit/components/telemetry/Telemetry.h
toolkit/components/telemetry/TelemetryComms.h
toolkit/components/telemetry/TelemetryIPCAccumulator.cpp
toolkit/components/telemetry/TelemetryIPCAccumulator.h
toolkit/components/telemetry/TelemetryScalar.cpp
toolkit/components/telemetry/TelemetryScalar.h
toolkit/components/telemetry/nsITelemetry.idl
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4760,16 +4760,32 @@ ContentParent::RecvAccumulateChildHistog
 mozilla::ipc::IPCResult
 ContentParent::RecvAccumulateChildKeyedHistogram(
                 InfallibleTArray<KeyedAccumulation>&& aAccumulations)
 {
   Telemetry::AccumulateChildKeyed(GeckoProcessType_Content, aAccumulations);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvUpdateChildScalars(
+                InfallibleTArray<ScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildScalars(GeckoProcessType_Content, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvUpdateChildKeyedScalars(
+                InfallibleTArray<KeyedScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildKeyedScalars(GeckoProcessType_Content, aScalarActions);
+  return IPC_OK();
+}
+
 PURLClassifierParent*
 ContentParent::AllocPURLClassifierParent(const Principal& aPrincipal,
                                          const bool& aUseTrackingProtection,
                                          bool* aSuccess)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   *aSuccess = true;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1077,16 +1077,20 @@ private:
                                                       const bool& aRecursiveFlag) override;
 
   virtual mozilla::ipc::IPCResult RecvDeleteGetFilesRequest(const nsID& aID) override;
 
   virtual mozilla::ipc::IPCResult RecvAccumulateChildHistogram(
     InfallibleTArray<Accumulation>&& aAccumulations) override;
   virtual mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram(
     InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateChildScalars(
+    InfallibleTArray<ScalarAction>&& aScalarActions) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
+    InfallibleTArray<KeyedScalarAction>&& aScalarActions) 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
@@ -90,16 +90,18 @@ using struct LookAndFeelInt from "mozill
 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::OriginAttributes 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";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1145,16 +1147,18 @@ parent:
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
     /**
      * Messages for communicating child Telemetry to the parent process
      */
     async AccumulateChildHistogram(Accumulation[] accumulations);
     async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+    async UpdateChildScalars(ScalarAction[] updates);
+    async UpdateChildKeyedScalars(KeyedScalarAction[] updates);
 
     sync GetA11yContentId() returns (uint32_t aContentId);
 
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -149,16 +149,30 @@ GPUChild::RecvAccumulateChildHistogram(I
 mozilla::ipc::IPCResult
 GPUChild::RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations)
 {
   Telemetry::AccumulateChildKeyed(GeckoProcessType_GPU, aAccumulations);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+GPUChild::RecvUpdateChildScalars(InfallibleTArray<ScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildScalars(GeckoProcessType_GPU, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+GPUChild::RecvUpdateChildKeyedScalars(InfallibleTArray<KeyedScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildKeyedScalars(GeckoProcessType_GPU, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 GPUChild::RecvNotifyDeviceReset()
 {
   mHost->mListener->OnProcessDeviceReset(mHost);
   return IPC_OK();
 }
 
 void
 GPUChild::ActorDestroy(ActorDestroyReason aWhy)
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -35,16 +35,18 @@ public:
   void OnVarChanged(const GfxVarUpdate& aVar) override;
 
   // PGPUChild overrides.
   mozilla::ipc::IPCResult RecvInitComplete(const GPUDeviceData& aData) override;
   mozilla::ipc::IPCResult RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog) override;
   mozilla::ipc::IPCResult RecvInitCrashReporter(Shmem&& shmem) override;
   mozilla::ipc::IPCResult RecvAccumulateChildHistogram(InfallibleTArray<Accumulation>&& aAccumulations) override;
   mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
+  mozilla::ipc::IPCResult RecvUpdateChildScalars(InfallibleTArray<ScalarAction>&& aScalarActions) override;
+  mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(InfallibleTArray<KeyedScalarAction>&& aScalarActions) 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() override;
 
   static void Destroy(UniquePtr<GPUChild>&& aChild);
 
 private:
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -12,16 +12,18 @@ include protocol PVideoDecoderManager;
 
 using base::ProcessId from "base/process.h";
 using mozilla::TimeDuration from "mozilla/TimeStamp.h";
 using mozilla::CSSToLayoutDeviceScale from "Units.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 
 namespace mozilla {
 namespace gfx {
 
 union GfxPrefValue {
   bool;
   int32_t;
   uint32_t;
@@ -96,14 +98,16 @@ child:
 
   // Have a message be broadcasted to the UI process by the UI process
   // observer service.
   async NotifyUiObservers(nsCString aTopic);
 
   // Messages for reporting telemetry to the UI process.
   async AccumulateChildHistogram(Accumulation[] accumulations);
   async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+  async UpdateChildScalars(ScalarAction[] actions);
+  async UpdateChildKeyedScalars(KeyedScalarAction[] actions);
 
   async NotifyDeviceReset();
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2127,19 +2127,17 @@ TelemetryImpl::CreateTelemetryInstance()
       XRE_IsContentProcess() ||
       XRE_IsGPUProcess())
   {
     useTelemetry = true;
   }
 
   // First, initialize the TelemetryHistogram and TelemetryScalar global states.
   TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry);
-
-  // Only record scalars from the parent process.
-  TelemetryScalar::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
+  TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry);
 
   // Only record events from the parent process.
   TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
 
   // Now, create and initialize the Telemetry global state.
   sTelemetry = new TelemetryImpl();
 
   // AddRef for the local reference
@@ -3102,16 +3100,30 @@ AccumulateChild(GeckoProcessType aProces
 
 void
 AccumulateChildKeyed(GeckoProcessType aProcessType,
                      const nsTArray<KeyedAccumulation>& aAccumulations)
 {
   TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations);
 }
 
+void
+UpdateChildScalars(GeckoProcessType aProcessType,
+                   const nsTArray<ScalarAction>& aScalarActions)
+{
+  TelemetryScalar::UpdateChildData(aProcessType, aScalarActions);
+}
+
+void
+UpdateChildKeyedScalars(GeckoProcessType aProcessType,
+                        const nsTArray<KeyedScalarAction>& aScalarActions)
+{
+  TelemetryScalar::UpdateChildKeyedData(aProcessType, aScalarActions);
+}
+
 const char*
 GetHistogramName(ID id)
 {
   return TelemetryHistogram::GetHistogramName(id);
 }
 
 bool
 CanRecordBase()
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -31,16 +31,18 @@
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
 struct Accumulation;
 struct KeyedAccumulation;
+struct ScalarAction;
+struct KeyedScalarAction;
 
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Create and destroy the underlying base::StatisticsRecorder singleton.
@@ -139,16 +141,30 @@ void AccumulateChild(GeckoProcessType aP
 /**
  * Accumulate child process data into keyed histograms for the given process type.
  *
  * @param aAccumulations - accumulation actions to perform
  */
 void AccumulateChildKeyed(GeckoProcessType aProcessType, const nsTArray<KeyedAccumulation>& aAccumulations);
 
 /**
+ * Update scalars for the given process type with the data coming from child process.
+ *
+ * @param aScalarActions - actions to update the scalar data
+ */
+void UpdateChildScalars(GeckoProcessType aProcessType, const nsTArray<ScalarAction>& aScalarActions);
+
+/**
+ * Update keyed  scalars for the given process type with the data coming from child process.
+ *
+ * @param aScalarActions - actions to update the keyed scalar data
+ */
+void UpdateChildKeyedScalars(GeckoProcessType aProcessType, const nsTArray<KeyedScalarAction>& aScalarActions);
+
+/**
  * Enable/disable recording for this histogram at runtime.
  * Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[].
  * id must be a valid telemetry enum, otherwise an assertion is triggered.
  *
  * @param id - histogram id
  * @param enabled - whether or not to enable recording from now on.
  */
 void SetHistogramRecordingEnabled(ID id, bool enabled);
--- a/toolkit/components/telemetry/TelemetryComms.h
+++ b/toolkit/components/telemetry/TelemetryComms.h
@@ -2,35 +2,64 @@
 /* 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"
+#include "nsITelemetry.h"
+#include "nsVariant.h"
 
 namespace mozilla {
 namespace Telemetry {
 
+// Histogram accumulation types.
 enum ID : uint32_t;
 
 struct Accumulation
 {
   mozilla::Telemetry::ID mId;
   uint32_t mSample;
 };
 
 struct KeyedAccumulation
 {
   mozilla::Telemetry::ID mId;
   uint32_t mSample;
   nsCString mKey;
 };
 
+// Scalar accumulation types.
+enum class ScalarID : uint32_t;
+
+enum class ScalarActionType : uint32_t {
+  eSet = 0,
+  eAdd = 1,
+  eSetMaximum = 2
+};
+
+struct ScalarAction
+{
+  ScalarID mId;
+  uint32_t mScalarType;
+  ScalarActionType mActionType;
+  nsCOMPtr<nsIVariant> mData;
+};
+
+struct KeyedScalarAction
+{
+  ScalarID mId;
+  uint32_t mScalarType;
+  ScalarActionType mActionType;
+  nsCString mKey;
+  nsCOMPtr<nsIVariant> mData;
+};
+
 } // namespace Telemetry
 } // namespace mozilla
 
 namespace IPC {
 
 template<>
 struct
 ParamTraits<mozilla::Telemetry::Accumulation>
@@ -74,11 +103,222 @@ ParamTraits<mozilla::Telemetry::KeyedAcc
         !ReadParam(aMsg, aIter, &(aResult->mKey))) {
       return false;
     }
 
     return true;
   }
 };
 
+/**
+ * IPC scalar data message serialization and de-serialization.
+ */
+template<>
+struct
+ParamTraits<mozilla::Telemetry::ScalarAction>
+{
+  typedef mozilla::Telemetry::ScalarAction paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    // Write the message type
+    aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mId));
+    WriteParam(aMsg, aParam.mScalarType);
+    WriteParam(aMsg, static_cast<uint32_t>(aParam.mActionType));
+
+    switch(aParam.mScalarType) {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t val = 0;
+          nsresult rv = aParam.mData->GetAsUint32(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Count Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          nsString val;
+          nsresult rv = aParam.mData->GetAsAString(val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Conversion failed.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool val = 0;
+          nsresult rv = aParam.mData->GetAsBool(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Boolean Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown scalar type.");
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    // Read the scalar ID and the scalar type.
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mScalarType)) ||
+        !ReadParam(aMsg, aIter, reinterpret_cast<uint32_t*>(&(aResult->mActionType)))) {
+      return false;
+    }
+
+    // De-serialize the data based on the scalar type.
+    nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+    switch (aResult->mScalarType)
+    {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t data = 0;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsUint32(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          nsString data;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsAString(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool data = false;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsBool(data))) {
+            return false;
+          }
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown scalar type.");
+        return false;
+    }
+
+    aResult->mData = outVar.forget();
+    return true;
+  }
+};
+
+/**
+ * IPC keyed scalar data message serialization and de-serialization.
+ */
+template<>
+struct
+ParamTraits<mozilla::Telemetry::KeyedScalarAction>
+{
+  typedef mozilla::Telemetry::KeyedScalarAction paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    // Write the message type
+    aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mId));
+    WriteParam(aMsg, aParam.mScalarType);
+    WriteParam(aMsg, static_cast<uint32_t>(aParam.mActionType));
+    WriteParam(aMsg, aParam.mKey);
+
+    switch(aParam.mScalarType) {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t val = 0;
+          nsresult rv = aParam.mData->GetAsUint32(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Keyed Count Scalar unable to convert variant to uint from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          // Keyed string scalars are not supported.
+          MOZ_ASSERT(false, "Keyed String Scalar unable to be write from child process. Not supported.");
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool val = 0;
+          nsresult rv = aParam.mData->GetAsBool(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Keyed Boolean Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown keyed scalar type.");
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    // Read the scalar ID and the scalar type.
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mScalarType)) ||
+        !ReadParam(aMsg, aIter, reinterpret_cast<uint32_t*>(&(aResult->mActionType))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mKey))) {
+      return false;
+    }
+
+    // De-serialize the data based on the scalar type.
+    nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+    switch (aResult->mScalarType)
+    {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t data = 0;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsUint32(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          // Keyed string scalars are not supported.
+          MOZ_ASSERT(false, "Keyed String Scalar unable to be read from child process. Not supported.");
+          return false;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool data = false;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsBool(data))) {
+            return false;
+          }
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown keyed scalar type.");
+        return false;
+    }
+
+    aResult->mData = outVar.forget();
+    return true;
+  }
+};
+
 } // namespace IPC
 
 #endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp
+++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp
@@ -11,22 +11,26 @@
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsITimer.h"
 #include "nsThreadUtils.h"
 #include "TelemetryHistogram.h"
+#include "TelemetryScalar.h"
 
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
 using mozilla::StaticAutoPtr;
 using mozilla::Telemetry::Accumulation;
 using mozilla::Telemetry::KeyedAccumulation;
+using mozilla::Telemetry::ScalarActionType;
+using mozilla::Telemetry::ScalarAction;
+using mozilla::Telemetry::KeyedScalarAction;
 
 // 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
@@ -37,16 +41,18 @@ const size_t kHistogramAccumulationsArra
 // For batching and sending child process accumulations to the parent
 nsITimer* gIPCTimer = nullptr;
 mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false);
 mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false);
 
 // For batching and sending child process accumulations to the parent
 StaticAutoPtr<nsTArray<Accumulation>> gHistogramAccumulations;
 StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedHistogramAccumulations;
+StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions;
+StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions;
 
 // 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;
@@ -122,67 +128,120 @@ TelemetryIPCAccumulator::AccumulateChild
     TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
       TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
     }));
   }
   gKeyedHistogramAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
   ArmIPCTimer(locker);
 }
 
+void
+TelemetryIPCAccumulator::RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind,
+                                                 ScalarActionType aAction, nsIVariant* aValue)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  // Make sure to have the storage.
+  if (!gChildScalarsActions) {
+    gChildScalarsActions = new nsTArray<ScalarAction>();
+  }
+  // Store the action.
+  gChildScalarsActions->AppendElement(ScalarAction{aId, aKind, aAction, aValue});
+  ArmIPCTimer(locker);
+}
+
+void
+TelemetryIPCAccumulator::RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId,
+                                                      const nsAString& aKey, uint32_t aKind,
+                                                      ScalarActionType aAction, nsIVariant* aValue)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  // Make sure to have the storage.
+  if (!gChildKeyedScalarsActions) {
+    gChildKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
+  }
+  // Store the action.
+  gChildKeyedScalarsActions->AppendElement(
+    KeyedScalarAction{aId, aKind, aAction, NS_ConvertUTF16toUTF8(aKey), aValue});
+  ArmIPCTimer(locker);
+}
+
 // 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 we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
 // unset gIPCTimerArmed until the IPC completes
 //
 // This function must be called on the main thread, otherwise IPC will fail.
 void
 TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Get the accumulated data and free the storage buffer.
   nsTArray<Accumulation> accumulationsToSend;
   nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
+  nsTArray<ScalarAction> scalarsToSend;
+  nsTArray<KeyedScalarAction> keyedScalarsToSend;
   {
     StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
     if (gHistogramAccumulations) {
       accumulationsToSend.SwapElements(*gHistogramAccumulations);
     }
     if (gKeyedHistogramAccumulations) {
       keyedAccumulationsToSend.SwapElements(*gKeyedHistogramAccumulations);
     }
+    // Copy the scalar actions.
+    if (gChildScalarsActions) {
+      scalarsToSend.SwapElements(*gChildScalarsActions);
+    }
+    if (gChildKeyedScalarsActions) {
+      keyedScalarsToSend.SwapElements(*gChildKeyedScalarsActions);
+    }
   }
 
   // Send the accumulated data to the parent process.
   switch (XRE_GetProcessType()) {
     case GeckoProcessType_Content: {
       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));
         }
+        if (scalarsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendUpdateChildScalars(scalarsToSend));
+        }
+        if (keyedScalarsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendUpdateChildKeyedScalars(keyedScalarsToSend));
+        }
       }
       break;
     }
     case GeckoProcessType_GPU: {
       if (mozilla::gfx::GPUParent* gpu = mozilla::gfx::GPUParent::GetSingleton()) {
         if (accumulationsToSend.Length()) {
           mozilla::Unused << gpu->SendAccumulateChildHistogram(accumulationsToSend);
         }
         if (keyedAccumulationsToSend.Length()) {
           mozilla::Unused << gpu->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend);
         }
+        if (scalarsToSend.Length()) {
+          mozilla::Unused << gpu->SendUpdateChildScalars(scalarsToSend);
+        }
+        if (keyedScalarsToSend.Length()) {
+          mozilla::Unused << gpu->SendUpdateChildKeyedScalars(keyedScalarsToSend);
+        }
       }
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported process type");
       break;
   }
 
@@ -194,16 +253,18 @@ TelemetryIPCAccumulator::DeInitializeGlo
 {
   StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
   if (gIPCTimer) {
     NS_RELEASE(gIPCTimer);
   }
 
   gHistogramAccumulations = nullptr;
   gKeyedHistogramAccumulations = nullptr;
+  gChildScalarsActions = nullptr;
+  gChildKeyedScalarsActions = nullptr;
 }
 
 void
 TelemetryIPCAccumulator::DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
   nsCOMPtr<nsIThread> thread;
   nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
--- a/toolkit/components/telemetry/TelemetryIPCAccumulator.h
+++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.h
@@ -5,33 +5,45 @@
 
 #ifndef TelemetryIPCAccumulator_h__
 #define TelemetryIPCAccumulator_h__
 
 #include "mozilla/AlreadyAddRefed.h"
 
 class nsIRunnable;
 class nsITimer;
+class nsAString;
 class nsCString;
+class nsIVariant;
 
 namespace mozilla {
 namespace Telemetry {
 
 enum ID : uint32_t;
+enum class ScalarID : uint32_t;
+enum class ScalarActionType : uint32_t;
 
 } // Telemetry
 } // mozilla
 
 namespace TelemetryIPCAccumulator {
 
 // Histogram accumulation functions.
 void AccumulateChildHistogram(mozilla::Telemetry::ID aId, uint32_t aSample);
 void AccumulateChildKeyedHistogram(mozilla::Telemetry::ID aId, const nsCString& aKey,
                                    uint32_t aSample);
 
+// Scalar accumulation functions.
+void RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind,
+                             mozilla::Telemetry::ScalarActionType aAction, nsIVariant* aValue);
+
+void RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+                                  uint32_t aKind, mozilla::Telemetry::ScalarActionType aAction,
+                                  nsIVariant* aValue);
+
 void IPCTimerFired(nsITimer* aTimer, void* aClosure);
 void DeInitializeGlobalState();
 
 void DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent);
 
 }
 
 #endif // TelemetryIPCAccumulator_h__
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -5,33 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsITelemetry.h"
 #include "nsIVariant.h"
 #include "nsVariant.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsIXPConnect.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/Unused.h"
 
+#include "TelemetryComms.h"
 #include "TelemetryCommon.h"
+#include "TelemetryIPCAccumulator.h"
 #include "TelemetryScalar.h"
 #include "TelemetryScalarData.h"
 
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::IsInDataset;
 using mozilla::Telemetry::Common::LogToBrowserConsole;
+using mozilla::Telemetry::ScalarActionType;
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
 // * Functions named internal_*: these can only be reached via an
 //   interface function (TelemetryScalar::*). They expect the interface
@@ -74,16 +78,17 @@ const uint32_t kMaximumKeyStringLength =
 const uint32_t kMaximumStringValueLength = 50;
 const uint32_t kScalarCount =
   static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
 
 enum class ScalarResult : uint8_t {
   // Nothing went wrong.
   Ok,
   // General Scalar Errors
+  CannotRecordInProcess,
   OperationNotSupported,
   InvalidType,
   InvalidValue,
   // Keyed Scalar Errors
   KeyTooLong,
   TooManyKeys,
   // String Scalar Errors
   StringTooLong,
@@ -102,16 +107,17 @@ typedef AutoHashtable<CharPtrEntryType> 
  * @param aSr The error code used internally in this module.
  * @return {nsresult} A NS_* error code.
  */
 nsresult
 MapToNsResult(ScalarResult aSr)
 {
   switch (aSr) {
     case ScalarResult::Ok:
+    case ScalarResult::CannotRecordInProcess:
       return NS_OK;
     case ScalarResult::OperationNotSupported:
       return NS_ERROR_NOT_AVAILABLE;
     case ScalarResult::StringTooLong:
       // We don't want to throw if we're setting a string that is too long.
       return NS_OK;
     case ScalarResult::InvalidType:
     case ScalarResult::InvalidValue:
@@ -129,16 +135,56 @@ MapToNsResult(ScalarResult aSr)
 }
 
 bool
 IsValidEnumId(mozilla::Telemetry::ScalarID aID)
 {
   return aID < mozilla::Telemetry::ScalarID::ScalarCount;
 }
 
+/**
+ * The following helpers are used to get a nsIVariant from a uint32_t,
+ * nsAString or bool data.
+ */
+nsresult
+GetVariant(uint32_t aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsUint32(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
+nsresult
+GetVariant(const nsAString& aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsAString(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
+nsresult
+GetVariant(bool aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsBool(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
 // Implements the methods for ScalarInfo.
 const char *
 ScalarInfo::name() const
 {
   return &gScalarsStringTable[this->name_offset];
 }
 
 const char *
@@ -283,23 +329,17 @@ ScalarUnsigned::SetMaximum(uint32_t aVal
   if (aValue > mStorage) {
     mStorage = aValue;
   }
 }
 
 nsresult
 ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsUint32(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarUnsigned::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 }
 
@@ -384,23 +424,17 @@ ScalarString::SetValue(const nsAString& 
     return ScalarResult::StringTooLong;
   }
   return ScalarResult::Ok;
 }
 
 nsresult
 ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsAString(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarString::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
   n+= mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   return n;
@@ -458,23 +492,17 @@ void
 ScalarBoolean::SetValue(bool aValue)
 {
   mStorage = aValue;
 }
 
 nsresult
 ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsBool(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 }
 
@@ -701,18 +729,21 @@ KeyedScalar::SizeOfIncludingThis(mozilla
   for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
     n += scalar->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
 
 typedef nsUint32HashKey ScalarIDHashKey;
+typedef nsUint32HashKey ProcessIDHashKey;
 typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
 typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
+typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
+typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE STATE, SHARED BY ALL THREADS
 
@@ -721,23 +752,23 @@ namespace {
 // Set to true once this global state has been initialized.
 bool gInitDone = false;
 
 bool gCanRecordBase;
 bool gCanRecordExtended;
 
 // The Name -> ID cache map.
 ScalarMapType gScalarNameIDMap(kScalarCount);
-// The ID -> Scalar Object map. This is a nsClassHashtable, it owns
-// the scalar instance and takes care of deallocating them when they
-// get removed from the map.
-ScalarStorageMapType gScalarStorageMap;
-// The ID -> Keyed Scalar Object map. As for plain scalars, this is
-// nsClassHashtable. See above.
-KeyedScalarStorageMapType gKeyedScalarStorageMap;
+
+// The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a nsClassHashtable,
+// it owns the scalar instances and takes care of deallocating them when they are
+// removed from the map.
+ProcessesScalarsMapType gScalarStorageMap;
+// As above, for the keyed scalars.
+ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Function that may call JS code.
 
@@ -756,16 +787,17 @@ namespace {
  *
  * @param aSr The error code.
  * @return true if the error should be logged, false otherwise.
  */
 bool
 internal_ShouldLogError(ScalarResult aSr)
 {
   switch (aSr) {
+    case ScalarResult::CannotRecordInProcess: MOZ_FALLTHROUGH;
     case ScalarResult::StringTooLong: MOZ_FALLTHROUGH;
     case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH;
     case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH;
     case ScalarResult::UnsignedNegativeValue: MOZ_FALLTHROUGH;
     case ScalarResult::UnsignedTruncatedValue:
       // Intentional fall-through.
       return true;
 
@@ -786,16 +818,19 @@ internal_ShouldLogError(ScalarResult aSr
  */
 void
 internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
 {
   nsAutoString errorMessage;
   AppendUTF8toUTF16(aScalarName, errorMessage);
 
   switch (aSr) {
+    case ScalarResult::CannotRecordInProcess:
+      errorMessage.Append(NS_LITERAL_STRING(" - Cannot record the scalar in the current process."));
+      break;
     case ScalarResult::StringTooLong:
       errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters."));
       break;
     case ScalarResult::KeyTooLong:
       errorMessage.Append(NS_LITERAL_STRING(" - The key length must be limited to 70 characters."));
       break;
     case ScalarResult::TooManyKeys:
       errorMessage.Append(NS_LITERAL_STRING(" - Keyed scalars cannot have more than 100 keys."));
@@ -848,16 +883,39 @@ internal_InfoForScalarID(mozilla::Teleme
  * @return true if aId refers to a keyed scalar, false otherwise.
  */
 bool
 internal_IsKeyedScalar(mozilla::Telemetry::ScalarID aId)
 {
   return internal_InfoForScalarID(aId).keyed;
 }
 
+/**
+ * Check if we're allowed to record the given scalar in the current
+ * process.
+ *
+ * @param aId The id of the scalar to check.
+ * @return true if the scalar is allowed to be recorded in the current process, false
+ *         otherwise.
+ */
+bool
+internal_CanRecordProcess(mozilla::Telemetry::ScalarID aId)
+{
+  // Get the scalar info from the id.
+  const ScalarInfo &info = internal_InfoForScalarID(aId);
+
+  bool recordAllChild = !!(info.record_in_processes & RecordedProcessType::AllChilds);
+  // We can use (1 << ProcessType) due to the way RecordedProcessType is defined
+  // in ScalarInfo.h
+  bool canRecordProcess =
+    !!(info.record_in_processes & static_cast<RecordedProcessType>(1 << XRE_GetProcessType()));
+
+  return canRecordProcess || (!XRE_IsParentProcess() && recordAllChild);
+}
+
 bool
 internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar info from the id.
   const ScalarInfo &info = internal_InfoForScalarID(aId);
 
   // Can we record at all?
   bool canRecordBase = internal_CanRecordBase();
@@ -900,79 +958,96 @@ internal_GetEnumByScalarName(const nsACS
   return NS_OK;
 }
 
 /**
  * Get a scalar object by its enum id. This implicitly allocates the scalar
  * object in the storage if it wasn't previously allocated.
  *
  * @param aId The scalar id.
+ * @param aProcessStorage This drives the selection of the map to use to store
+ *        the scalar data coming from child processes. This is only meaningful when
+ *        this function is called in parent process. If that's the case, if
+ *        this is not |GeckoProcessType_Default|, the process id is used to
+ *        allocate and store the scalars.
  * @param aRes The output variable that stores scalar object.
  * @return
  *   NS_ERROR_INVALID_ARG if the scalar id is unknown.
  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
  *   NS_OK if the scalar was found. If that's the case, aResult contains a
  *   valid pointer to a scalar type.
  */
 nsresult
-internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
+internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage,
+                         ScalarBase** aRet)
 {
   if (!IsValidEnumId(aId)) {
     MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
     return NS_ERROR_INVALID_ARG;
   }
 
   const uint32_t id = static_cast<uint32_t>(aId);
+  const ScalarInfo &info = gScalars[id];
 
   ScalarBase* scalar = nullptr;
-  if (gScalarStorageMap.Get(id, &scalar)) {
+  ScalarStorageMapType* scalarStorage = nullptr;
+  // Initialize the scalar storage to the parent storage. This will get
+  // set to the child storage if needed.
+  uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
+
+  // Get the process-specific storage or create one if it's not
+  // available.
+  if (!gScalarStorageMap.Get(storageId, &scalarStorage)) {
+    scalarStorage = new ScalarStorageMapType();
+    gScalarStorageMap.Put(storageId, scalarStorage);
+  }
+
+  // Check if the scalar is already allocated in the parent or in the child storage.
+  if (scalarStorage->Get(id, &scalar)) {
     *aRet = scalar;
     return NS_OK;
   }
 
-  const ScalarInfo &info = gScalars[id];
-
   if (IsExpiredVersion(info.expiration())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   scalar = internal_ScalarAllocate(info.kind);
   if (!scalar) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  gScalarStorageMap.Put(id, scalar);
-
+  scalarStorage->Put(id, scalar);
   *aRet = scalar;
   return NS_OK;
 }
 
 /**
  * Get a scalar object by its enum id, if we're allowed to record it.
  *
  * @param aId The scalar id.
  * @return The ScalarBase instance or nullptr if we're not allowed to record
  *         the scalar.
  */
 ScalarBase*
 internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar by the enum (it also internally checks for aId validity).
   ScalarBase* scalar = nullptr;
-  nsresult rv = internal_GetScalarByEnum(aId, &scalar);
+  nsresult rv = internal_GetScalarByEnum(aId, GeckoProcessType_Default, &scalar);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   if (internal_IsKeyedScalar(aId)) {
     return nullptr;
   }
 
   // Are we allowed to record this scalar?
-  if (!internal_CanRecordForScalarID(aId)) {
+  if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) {
     return nullptr;
   }
 
   return scalar;
 }
 
 } // namespace
 
@@ -985,86 +1060,102 @@ internal_GetRecordableScalar(mozilla::Te
 
 namespace {
 
 /**
  * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
  * scalar object in the storage if it wasn't previously allocated.
  *
  * @param aId The scalar id.
- * @param aRes The output variable that stores scalar object.
+ * @param aProcessStorage This drives the selection of the map to use to store
+ *        the scalar data coming from child processes. This is only meaningful when
+ *        this function is called in parent process. If that's the case, if
+ *        this is not |GeckoProcessType_Default|, the process id is used to
+ *        allocate and store the scalars.
+ * @param aRet The output variable that stores scalar object.
  * @return
  *   NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string
  *                        scalar.
  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
  *   NS_OK if the scalar was found. If that's the case, aResult contains a
  *   valid pointer to a scalar type.
  */
 nsresult
-internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aRet)
+internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage,
+                              KeyedScalar** aRet)
 {
   if (!IsValidEnumId(aId)) {
     MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
     return NS_ERROR_INVALID_ARG;
   }
 
   const uint32_t id = static_cast<uint32_t>(aId);
+  const ScalarInfo &info = gScalars[id];
 
   KeyedScalar* scalar = nullptr;
-  if (gKeyedScalarStorageMap.Get(id, &scalar)) {
+  KeyedScalarStorageMapType* scalarStorage = nullptr;
+  // Initialize the scalar storage to the parent storage. This will get
+  // set to the child storage if needed.
+  uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
+
+  // Get the process-specific storage or create one if it's not
+  // available.
+  if (!gKeyedScalarStorageMap.Get(storageId, &scalarStorage)) {
+    scalarStorage = new KeyedScalarStorageMapType();
+    gKeyedScalarStorageMap.Put(storageId, scalarStorage);
+  }
+
+  if (scalarStorage->Get(id, &scalar)) {
     *aRet = scalar;
     return NS_OK;
   }
 
-  const ScalarInfo &info = gScalars[id];
-
   if (IsExpiredVersion(info.expiration())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // We don't currently support keyed string scalars. Disable them.
   if (info.kind == nsITelemetry::SCALAR_STRING) {
     MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
     return NS_ERROR_INVALID_ARG;
   }
 
   scalar = new KeyedScalar(info.kind);
   if (!scalar) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  gKeyedScalarStorageMap.Put(id, scalar);
-
+  scalarStorage->Put(id, scalar);
   *aRet = scalar;
   return NS_OK;
 }
 
 /**
  * Get a keyed scalar object by its enum id, if we're allowed to record it.
  *
  * @param aId The scalar id.
  * @return The KeyedScalar instance or nullptr if we're not allowed to record
  *         the scalar.
  */
 KeyedScalar*
 internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar by the enum (it also internally checks for aId validity).
   KeyedScalar* scalar = nullptr;
-  nsresult rv = internal_GetKeyedScalarByEnum(aId, &scalar);
+  nsresult rv = internal_GetKeyedScalarByEnum(aId, GeckoProcessType_Default, &scalar);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   if (!internal_IsKeyedScalar(aId)) {
     return nullptr;
   }
 
   // Are we allowed to record this scalar?
-  if (!internal_CanRecordForScalarID(aId)) {
+  if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) {
     return nullptr;
   }
 
   return scalar;
 }
 
 } // namespace
 
@@ -1169,28 +1260,40 @@ TelemetryScalar::Add(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eAdd,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->AddValue(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->AddValue(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1233,28 +1336,40 @@ TelemetryScalar::Add(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eAdd, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->AddValue(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->AddValue(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1266,16 +1381,29 @@ TelemetryScalar::Add(const nsACString& a
  * @param aId The scalar enum id.
  * @param aVal The numeric value to add to the scalar.
  */
 void
 TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eAdd,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->AddValue(aValue);
 }
 
@@ -1287,16 +1415,29 @@ TelemetryScalar::Add(mozilla::Telemetry:
  * @param aVal The numeric value to add to the scalar.
  */
 void
 TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eAdd, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->AddValue(aKey, aValue);
 }
 
@@ -1335,28 +1476,40 @@ TelemetryScalar::Set(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSet,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetValue(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetValue(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1399,28 +1552,40 @@ TelemetryScalar::Set(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eSet, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetValue(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetValue(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1432,16 +1597,29 @@ TelemetryScalar::Set(const nsACString& a
  * @param aId The scalar enum id.
  * @param aValue The numeric, unsigned value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1451,16 +1629,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aId The scalar enum id.
  * @param aValue The string value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1470,16 +1661,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aId The scalar enum id.
  * @param aValue The boolean value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1491,16 +1695,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aValue The numeric, unsigned value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSet, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aKey, aValue);
 }
 
@@ -1512,16 +1729,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aValue The boolean value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      bool aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSet, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aKey, aValue);
 }
 
@@ -1560,28 +1790,40 @@ TelemetryScalar::SetMaximum(const nsACSt
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSetMaximum,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetMaximum(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetMaximum(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1624,28 +1866,40 @@ TelemetryScalar::SetMaximum(const nsACSt
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eSetMaximum, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetMaximum(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetMaximum(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1657,16 +1911,29 @@ TelemetryScalar::SetMaximum(const nsACSt
  * @param aId The scalar enum id.
  * @param aValue The numeric value to set the scalar to.
  */
 void
 TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSetMaximum,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetMaximum(aValue);
 }
 
@@ -1678,198 +1945,273 @@ TelemetryScalar::SetMaximum(mozilla::Tel
  * @param aValue The numeric value to set the scalar to.
  */
 void
 TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                             uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSetMaximum, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetMaximum(aKey, aValue);
 }
 
 /**
  * Serializes the scalars from the given dataset to a json-style object and resets them.
- * The returned structure looks like {"group1.probe":1,"group1.other_probe":false,...}.
+ * The returned structure looks like:
+ *    {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }.
  *
  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
  * @param aClear Whether to clear out the scalars after snapshotting.
  */
 nsresult
 TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
                                  uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
 {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Snapshotting scalars should only happen in the parent processes.");
   // If no arguments were passed in, apply the default value.
   if (!optional_argc) {
     aClearScalars = false;
   }
 
   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
   if (!root_obj) {
     return NS_ERROR_FAILURE;
   }
   aResult.setObject(*root_obj);
 
+  // Return `{}` in child processes.
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+
   // Only lock the mutex while accessing our data, without locking any JS related code.
   typedef mozilla::Pair<const char*, nsCOMPtr<nsIVariant>> DataPair;
-  nsTArray<DataPair> scalarsToReflect;
+  typedef nsTArray<DataPair> ScalarArray;
+  nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     // Iterate the scalars in gScalarStorageMap. The storage may contain empty or yet to be
-    // initialized scalars.
+    // initialized scalars from all the supported processes.
     for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-      ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
+      ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
+      ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
 
-      // Get the informations for this scalar.
-      const ScalarInfo& info = gScalars[iter.Key()];
+      // Iterate each available child storage.
+      for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+        ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
+
+        // Get the informations for this scalar.
+        const ScalarInfo& info = gScalars[childIter.Key()];
 
-      // Serialize the scalar if it's in the desired dataset.
-      if (IsInDataset(info.dataset, aDataset)) {
-        // Get the scalar value.
-        nsCOMPtr<nsIVariant> scalarValue;
-        nsresult rv = scalar->GetValue(scalarValue);
-        if (NS_FAILED(rv)) {
-          return rv;
+        // Serialize the scalar if it's in the desired dataset.
+        if (IsInDataset(info.dataset, aDataset)) {
+          // Get the scalar value.
+          nsCOMPtr<nsIVariant> scalarValue;
+          nsresult rv = scalar->GetValue(scalarValue);
+          if (NS_FAILED(rv)) {
+            return rv;
+          }
+          // Append it to our list.
+          processScalars.AppendElement(mozilla::MakePair(info.name(), scalarValue));
         }
-        // Append it to our list.
-        scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarValue));
       }
     }
 
     if (aClearScalars) {
       // The map already takes care of freeing the allocated memory.
       gScalarStorageMap.Clear();
     }
   }
 
   // Reflect it to JS.
-  for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) {
-    const DataPair& scalar = scalarsToReflect[i];
+  for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
+    ScalarArray& processScalars = iter.Data();
+    const char* processName =
+      XRE_ChildProcessTypeToString(static_cast<GeckoProcessType>(iter.Key()));
 
-    // Convert it to a JS Val.
-    JS::Rooted<JS::Value> scalarJsValue(aCx);
-    nsresult rv =
-      nsContentUtils::XPConnect()->VariantToJS(aCx, root_obj, scalar.second(), &scalarJsValue);
-    if (NS_FAILED(rv)) {
-      return rv;
+    // Create the object that will hold the scalars for this process and add it
+    // to the returned root object.
+    JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
+    if (!processObj ||
+        !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
     }
 
-    // Add it to the scalar object.
-    if (!JS_DefineProperty(aCx, root_obj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
+    for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length(); i++) {
+      const DataPair& scalar = processScalars[i];
+
+      // Convert it to a JS Val.
+      JS::Rooted<JS::Value> scalarJsValue(aCx);
+      nsresult rv =
+        nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, scalar.second(), &scalarJsValue);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      // Add it to the scalar object.
+      if (!JS_DefineProperty(aCx, processObj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) {
+        return NS_ERROR_FAILURE;
+      }
     }
   }
 
   return NS_OK;
 }
 
 /**
  * Serializes the scalars from the given dataset to a json-style object and resets them.
  * The returned structure looks like:
- *   { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }
+ *   { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
  *
  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
  * @param aClear Whether to clear out the keyed scalars after snapshotting.
  */
 nsresult
 TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
                                       uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
 {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Snapshotting scalars should only happen in the parent processes.");
   // If no arguments were passed in, apply the default value.
   if (!optional_argc) {
     aClearScalars = false;
   }
 
   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
   if (!root_obj) {
     return NS_ERROR_FAILURE;
   }
   aResult.setObject(*root_obj);
 
+  // Return `{}` in child processes.
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+
   // Only lock the mutex while accessing our data, without locking any JS related code.
   typedef mozilla::Pair<const char*, nsTArray<KeyedScalar::KeyValuePair>> DataPair;
-  nsTArray<DataPair> scalarsToReflect;
+  typedef nsTArray<DataPair> ScalarArray;
+  nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     // Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet
-    // to be initialized scalars.
+    // to be initialized scalars from all the supported processes.
     for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-      KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
+      KeyedScalarStorageMapType* scalarStorage =
+        static_cast<KeyedScalarStorageMapType*>(iter.Data());
+      ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
 
-      // Get the informations for this scalar.
-      const ScalarInfo& info = gScalars[iter.Key()];
+      for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+        KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
+
+        // Get the informations for this scalar.
+        const ScalarInfo& info = gScalars[childIter.Key()];
 
-      // Serialize the scalar if it's in the desired dataset.
-      if (IsInDataset(info.dataset, aDataset)) {
-        // Get the keys for this scalar.
-        nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
-        nsresult rv = scalar->GetValue(scalarKeyedData);
-        if (NS_FAILED(rv)) {
-          return rv;
+        // Serialize the scalar if it's in the desired dataset.
+        if (IsInDataset(info.dataset, aDataset)) {
+          // Get the keys for this scalar.
+          nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
+          nsresult rv = scalar->GetValue(scalarKeyedData);
+          if (NS_FAILED(rv)) {
+            return rv;
+          }
+          // Append it to our list.
+          processScalars.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
         }
-        // Append it to our list.
-        scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
       }
     }
 
     if (aClearScalars) {
       // The map already takes care of freeing the allocated memory.
       gKeyedScalarStorageMap.Clear();
     }
   }
 
   // Reflect it to JS.
-  for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) {
-    const DataPair& keyedScalarData = scalarsToReflect[i];
+  for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
+    ScalarArray& processScalars = iter.Data();
+    const char* processName =
+      XRE_ChildProcessTypeToString(static_cast<GeckoProcessType>(iter.Key()));
 
-    // Go through each keyed scalar and create a keyed scalar object.
-    // This object will hold the values for all the keyed scalar keys.
-    JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
+    // Create the object that will hold the scalars for this process and add it
+    // to the returned root object.
+    JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
+    if (!processObj ||
+        !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length(); i++) {
+      const DataPair& keyedScalarData = processScalars[i];
+
+      // Go through each keyed scalar and create a keyed scalar object.
+      // This object will hold the values for all the keyed scalar keys.
+      JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
 
-    // Define a property for each scalar key, then add it to the keyed scalar
-    // object.
-    const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second();
-    for (uint32_t i = 0; i < keyProps.Length(); i++) {
-      const KeyedScalar::KeyValuePair& keyData = keyProps[i];
+      // Define a property for each scalar key, then add it to the keyed scalar
+      // object.
+      const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second();
+      for (uint32_t i = 0; i < keyProps.Length(); i++) {
+        const KeyedScalar::KeyValuePair& keyData = keyProps[i];
 
-      // Convert the value for the key to a JSValue.
-      JS::Rooted<JS::Value> keyJsValue(aCx);
-      nsresult rv =
-        nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
-      if (NS_FAILED(rv)) {
-        return rv;
+        // Convert the value for the key to a JSValue.
+        JS::Rooted<JS::Value> keyJsValue(aCx);
+        nsresult rv =
+          nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
+        if (NS_FAILED(rv)) {
+          return rv;
+        }
+
+        // Add the key to the scalar representation.
+        const NS_ConvertUTF8toUTF16 key(keyData.first());
+        if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
+          return NS_ERROR_FAILURE;
+        }
       }
 
-      // Add the key to the scalar representation.
-      const NS_ConvertUTF8toUTF16 key(keyData.first());
-      if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
+      // Add the scalar to the root object.
+      if (!JS_DefineProperty(aCx, processObj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) {
         return NS_ERROR_FAILURE;
       }
     }
-
-    // Add the scalar to the root object.
-    if (!JS_DefineProperty(aCx, root_obj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
-    }
   }
 
   return NS_OK;
 }
 
 /**
  * Resets all the stored scalars. This is intended to be only used in tests.
  */
 void
 TelemetryScalar::ClearScalars()
 {
+  MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process.");
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   gScalarStorageMap.Clear();
   gKeyedScalarStorageMap.Clear();
 }
 
 size_t
 TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
@@ -1877,20 +2219,125 @@ TelemetryScalar::GetMapShallowSizesOfExc
   return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
 
 size_t
 TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   size_t n = 0;
-  // For the plain scalars...
+  // Account for scalar data coming from parent and child processes.
   for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-    ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
-    n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
+    for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+      ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
+      n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    }
   }
-  // ...and for the keyed scalars.
+  // Also account for keyed scalar data coming from parent and child processes.
   for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-    KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
-    n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    KeyedScalarStorageMapType* scalarStorage =
+      static_cast<KeyedScalarStorageMapType*>(iter.Data());
+    for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+      KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
+      n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    }
   }
   return n;
 }
+
+void
+TelemetryScalar::UpdateChildData(GeckoProcessType aProcessType,
+                                 const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "The stored child processes scalar data must be updated from the parent process.");
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    if (internal_IsKeyedScalar(upd.mId)) {
+      continue;
+    }
+
+    // Are we allowed to record this scalar? We don't need to check for
+    // allowed processes here, that's taken care of when recording
+    // in child processes.
+    if (!internal_CanRecordForScalarID(upd.mId)) {
+      continue;
+    }
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    ScalarBase* scalar = nullptr;
+    nsresult rv = internal_GetScalarByEnum(upd.mId, aProcessType, &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        scalar->SetValue(upd.mData);
+        break;
+      case ScalarActionType::eAdd:
+        scalar->AddValue(upd.mData);
+        break;
+      case ScalarActionType::eSetMaximum:
+        scalar->SetMaximum(upd.mData);
+        break;
+      default:
+        NS_WARNING("Unsupported action coming from scalar child updates.");
+    }
+  }
+}
+
+void
+TelemetryScalar::UpdateChildKeyedData(GeckoProcessType aProcessType,
+                                      const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "The stored child processes keyed scalar data must be updated from the parent process.");
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    if (!internal_IsKeyedScalar(upd.mId)) {
+      continue;
+    }
+
+    // Are we allowed to record this scalar? We don't need to check for
+    // allowed processes here, that's taken care of when recording
+    // in child processes.
+    if (!internal_CanRecordForScalarID(upd.mId)) {
+      continue;
+    }
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    KeyedScalar* scalar = nullptr;
+    nsresult rv = internal_GetKeyedScalarByEnum(upd.mId, aProcessType, &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      case ScalarActionType::eAdd:
+        scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      case ScalarActionType::eSetMaximum:
+        scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      default:
+        NS_WARNING("Unsupported action coming from keyed scalar child updates.");
+    }
+  }
+}
--- a/toolkit/components/telemetry/TelemetryScalar.h
+++ b/toolkit/components/telemetry/TelemetryScalar.h
@@ -54,11 +54,17 @@ void Set(mozilla::Telemetry::ScalarID aI
 void SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
 
 // Only to be used for testing.
 void ClearScalars();
 
 size_t GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 size_t GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
+void UpdateChildData(GeckoProcessType aProcessType,
+                     const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions);
+
+void UpdateChildKeyedData(GeckoProcessType aProcessType,
+                          const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions);
+
 } // namespace TelemetryScalar
 
 #endif // TelemetryScalar_h__
\ No newline at end of file
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -405,17 +405,17 @@ interface nsITelemetry : nsISupports
    * @param aValue The numeric value to set the scalar to. Only unsigned integers supported.
    */
   [implicit_jscontext]
   void scalarSetMaximum(in ACString aName, in jsval aValue);
 
   /**
    * Serializes the scalars from the given dataset to a JSON-style object and resets them.
    * The returned structure looks like:
-   *   { "group1.probe": 1, "group1.other_probe": false, ... }
+   *   {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }.
    *
    * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    * @param [aClear=false] Whether to clear out the scalars after snapshotting.
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**
@@ -448,17 +448,17 @@ interface nsITelemetry : nsISupports
    */
   [implicit_jscontext]
   void keyedScalarSetMaximum(in ACString aName, in AString aKey, in jsval aValue);
 
   /**
    * Serializes the keyed scalars from the given dataset to a JSON-style object and
    * resets them.
    * The returned structure looks like:
-   *   { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }
+   *   { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
    *
    * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    * @param [aClear=false] Whether to clear out the scalars after snapshotting.
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotKeyedScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**