Bug 1454606 - Record scalar operations and apply operations after load. r=Dexter
☠☠ backed out by 032953979332 ☠ ☠
authorJan-Erik Rediger <jrediger@mozilla.com>
Fri, 18 May 2018 15:09:16 +0200
changeset 420522 b126c9d96aafd9cf93c49c22d4fb74327a58e4d1
parent 420521 a1bc7ae8aef007288dee4b8d1b023ad2fcaa0117
child 420523 1df93e12f6ecc7c37633a58694c7b8cecc8a035e
push id103828
push useraiakab@mozilla.com
push dateWed, 30 May 2018 22:08:58 +0000
treeherdermozilla-inbound@8d0ee6e73fb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersDexter
bugs1454606
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1454606 - Record scalar operations and apply operations after load. r=Dexter In order to preserve semantics of scalar operations, we defer applying operations on probes until after the persisted measurements are fully loaded. This is only necessary on GeckoView, as it is the only product doing intermediate persistence of measurements. MozReview-Commit-ID: IHA1MGPRHoQ
toolkit/components/telemetry/TelemetryScalar.cpp
toolkit/components/telemetry/TelemetryScalar.h
toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
toolkit/components/telemetry/ipc/TelemetryComms.h
toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -29,29 +29,33 @@
 #include "TelemetryScalarData.h"
 #include "ipc/TelemetryComms.h"
 #include "ipc/TelemetryIPCAccumulator.h"
 
 using mozilla::Preferences;
 using mozilla::StaticAutoPtr;
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
+using mozilla::Some;
+using mozilla::Nothing;
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::CanRecordProduct;
 using mozilla::Telemetry::Common::IsInDataset;
 using mozilla::Telemetry::Common::LogToBrowserConsole;
 using mozilla::Telemetry::Common::GetNameForProcessID;
 using mozilla::Telemetry::Common::GetIDForProcessName;
 using mozilla::Telemetry::Common::RecordedProcessType;
 using mozilla::Telemetry::Common::IsValidIdentifierString;
 using mozilla::Telemetry::Common::GetCurrentProduct;
 using mozilla::Telemetry::Common::SupportedProduct;
 using mozilla::Telemetry::ScalarActionType;
+using mozilla::Telemetry::ScalarAction;
+using mozilla::Telemetry::KeyedScalarAction;
 using mozilla::Telemetry::ScalarID;
 using mozilla::Telemetry::DynamicScalarDefinition;
 using mozilla::Telemetry::ScalarVariant;
 using mozilla::Telemetry::ProcessID;
 
 namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
 
 ////////////////////////////////////////////////////////////////////////
@@ -100,16 +104,21 @@ const uint32_t kMaximumStringValueLength
 // The category and scalar name maximum lengths are used by the dynamic
 // scalar registration function and must match the constants used by
 // the 'parse_scalars.py' script for static scalars.
 const uint32_t kMaximumCategoryNameLength = 40;
 const uint32_t kMaximumScalarNameLength = 40;
 const uint32_t kScalarCount =
   static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
 
+// To stop growing unbounded in memory while waiting for scalar deserialization
+// to finish, we immediately apply pending operations if the array reaches
+// a certain high water mark of elements.
+const size_t kScalarActionsArrayHighWaterMark = 10000;
+
 enum class ScalarResult : uint8_t {
   // Nothing went wrong.
   Ok,
   // General Scalar Errors
   NotInitialized,
   CannotUnpackVariant,
   CannotRecordInProcess,
   CannotRecordDataset,
@@ -923,16 +932,30 @@ ScalarMapType gScalarNameIDMap(kScalarCo
 ProcessesScalarsMapType gScalarStorageMap;
 // As above, for the keyed scalars.
 ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
 // Provide separate storage for "dynamic builtin" plain and keyed scalars,
 // needed to support "build faster" in local developer builds.
 ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
 ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
 
+// Whether or not the deserialization of persisted scalars is still in progress.
+// This is never the case on Desktop or Fennec.
+// Only GeckoView restores persisted scalars.
+bool gIsDeserializing = false;
+// This batches scalar accumulations that should be applied once loading finished.
+StaticAutoPtr<nsTArray<ScalarAction>> gScalarsActions;
+StaticAutoPtr<nsTArray<KeyedScalarAction>> gKeyedScalarsActions;
+
+bool
+internal_IsScalarDeserializing(const StaticMutexAutoLock& lock)
+{
+  return gIsDeserializing;
+}
+
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Function that may call JS code.
 
 // NOTE: the functions in this section all run without protection from
@@ -1239,32 +1262,143 @@ internal_GetScalarByEnum(const StaticMut
     return NS_ERROR_INVALID_ARG;
   }
 
   scalarStorage->Put(aId.id, scalar);
   *aRet = scalar;
   return NS_OK;
 }
 
+void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock);
+
+/**
+ * Record the given action on a scalar into the pending actions list.
+ *
+ * If the pending actions list overflows the high water mark length
+ * all operations are immediately applied, including the passed action.
+ *
+ * @param aScalarAction The action to record.
+ */
+void
+internal_RecordScalarAction(const StaticMutexAutoLock& lock,
+                            const ScalarAction& aScalarAction)
+{
+  // Make sure to have the storage.
+  if (!gScalarsActions) {
+    gScalarsActions = new nsTArray<ScalarAction>();
+  }
+
+  // Store the action.
+  gScalarsActions->AppendElement(aScalarAction);
+
+  // If this action overflows the pending actions array, we immediately apply pending operations
+  // and assume loading is over.
+  // If loading still happens afterwards, some scalar values might be
+  // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
+  if (gScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
+    internal_ApplyPendingOperations(lock);
+    return;
+  }
+}
+
+/**
+ * Record the given action on a scalar on the main process into the pending actions list.
+ *
+ * If the pending actions list overflows the high water mark length
+ * all operations are immediately applied, including the passed action.
+ *
+ * @param aId The scalar's ID this action applies to
+ * @param aDynamic Determines if the scalar is dynamic
+ * @param aAction The action to record
+ * @param aValue The additional data for the recorded action
+ */
+void
+internal_RecordScalarAction(const StaticMutexAutoLock& lock,
+                            uint32_t aId, bool aDynamic,
+                            ScalarActionType aAction, const ScalarVariant& aValue)
+{
+  internal_RecordScalarAction(lock, ScalarAction{
+                                      aId, aDynamic, aAction,
+                                      Some(aValue), ProcessID::Parent
+                                    });
+}
+
+/**
+ * Record the given action on a keyed scalar into the pending actions list.
+ *
+ * If the pending actions list overflows the high water mark length
+ * all operations are immediately applied, including the passed action.
+ *
+ * @param aScalarAction The action to record.
+ */
+void
+internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
+                                 const KeyedScalarAction& aScalarAction)
+{
+  // Make sure to have the storage.
+  if (!gKeyedScalarsActions) {
+    gKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
+  }
+
+  // Store the action.
+  gKeyedScalarsActions->AppendElement(aScalarAction);
+
+  // If this action overflows the pending actions array, we immediately apply pending operations
+  // and assume loading is over.
+  // If loading still happens afterwards, some scalar values might be
+  // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
+  if (gKeyedScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
+    internal_ApplyPendingOperations(lock);
+    return;
+  }
+}
+
+/**
+ * Record the given action on a keyed scalar on the main process into the pending actions list.
+ *
+ * If the pending actions list overflows the high water mark length
+ * all operations are immediately applied, including the passed action.
+ *
+ * @param aId The scalar's ID this action applies to
+ * @param aDynamic Determines if the scalar is dynamic
+ * @param aKey The scalar's key
+ * @param aAction The action to record
+ * @param aValue The additional data for the recorded action
+ */
+void
+internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
+                                 uint32_t aId, bool aDynamic,
+                                 const nsAString& aKey,
+                                 ScalarActionType aAction,
+                                 const ScalarVariant& aValue)
+{
+  internal_RecordKeyedScalarAction(lock, KeyedScalarAction{
+                                           aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey),
+                                           Some(aValue), ProcessID::Parent
+                                         });
+}
+
 /**
  * Update the scalar with the provided value. This is used by the JS API.
  *
  * @param lock Instance of a lock locking gTelemetryHistogramMutex
  * @param aName The scalar name.
  * @param aType The action type for updating the scalar.
  * @param aValue The value to use for updating the scalar.
  * @param aProcessOverride The process for which the scalar must be updated.
  *        This must only be used for GeckoView persistence. It must be
  *        set to the ProcessID::Parent for all the other cases.
+ * @param aForce Whether to force updating even if load is in progress.
  * @return a ScalarResult error value.
  */
 ScalarResult
 internal_UpdateScalar(const StaticMutexAutoLock& lock, const nsACString& aName,
                       ScalarActionType aType, nsIVariant* aValue,
-                      ProcessID aProcessOverride = ProcessID::Parent)
+                      ProcessID aProcessOverride = ProcessID::Parent,
+                      bool aForce = false)
 {
   ScalarKey uniqueId;
   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
   if (NS_FAILED(rv)) {
     return (rv == NS_ERROR_FAILURE) ?
            ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
   }
 
@@ -1286,16 +1420,29 @@ internal_UpdateScalar(const StaticMutexA
       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
       return sr;
     }
     TelemetryIPCAccumulator::RecordChildScalarAction(
       uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
     return ScalarResult::Ok;
   }
 
+  if (!aForce && internal_IsScalarDeserializing(lock)) {
+    const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
+    // Convert the nsIVariant to a Variant.
+    mozilla::Maybe<ScalarVariant> variantValue;
+    sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
+    if (sr != ScalarResult::Ok) {
+      MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
+      return sr;
+    }
+    internal_RecordScalarAction(lock, uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
+    return ScalarResult::Ok;
+  }
+
   // Finally get the scalar.
   ScalarBase* scalar = nullptr;
   rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
   if (NS_FAILED(rv)) {
     // Don't throw on expired scalars.
     if (rv == NS_ERROR_NOT_AVAILABLE) {
       return ScalarResult::Ok;
     }
@@ -1418,17 +1565,18 @@ internal_GetKeyedScalarByEnum(const Stat
  *        This must only be used for GeckoView persistence. It must be
  *        set to the ProcessID::Parent for all the other cases.
  * @return a ScalarResult error value.
  */
 ScalarResult
 internal_UpdateKeyedScalar(const StaticMutexAutoLock& lock,
                            const nsACString& aName, const nsAString& aKey,
                            ScalarActionType aType, nsIVariant* aValue,
-                           ProcessID aProcessOverride = ProcessID::Parent)
+                           ProcessID aProcessOverride = ProcessID::Parent,
+                           bool aForce = false)
 {
   ScalarKey uniqueId;
   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
   if (NS_FAILED(rv)) {
     return (rv == NS_ERROR_FAILURE) ?
            ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
   }
 
@@ -1450,16 +1598,31 @@ internal_UpdateKeyedScalar(const StaticM
       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
       return sr;
     }
     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
       uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref());
     return ScalarResult::Ok;
   }
 
+  if (!aForce && internal_IsScalarDeserializing(lock)) {
+    const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
+    // Convert the nsIVariant to a Variant.
+    mozilla::Maybe<ScalarVariant> variantValue;
+    sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
+    if (sr != ScalarResult::Ok) {
+      MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
+      return sr;
+    }
+    internal_RecordKeyedScalarAction(lock,
+                                     uniqueId.id, uniqueId.dynamic,
+                                     aKey, aType, variantValue.ref());
+    return ScalarResult::Ok;
+  }
+
   // Finally get the scalar.
   KeyedScalar* scalar = nullptr;
   rv = internal_GetKeyedScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
   if (NS_FAILED(rv)) {
     // Don't throw on expired scalars.
     if (rv == NS_ERROR_NOT_AVAILABLE) {
       return ScalarResult::Ok;
     }
@@ -1732,16 +1895,229 @@ internal_GetKeyedScalarSnapshot(const St
     gDynamicBuiltinKeyedScalarStorageMap.Clear();
   }
 
   return NS_OK;
 }
 
 } // namespace
 
+// helpers for recording/applying scalar operations
+namespace {
+
+void
+internal_ApplyScalarActions(const StaticMutexAutoLock& lock,
+                            const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions,
+                            const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
+{
+  if (!internal_CanRecordBase(lock)) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    ScalarKey uniqueId{upd.mId, upd.mDynamic};
+    if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
+      MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
+      continue;
+    }
+
+    if (internal_IsKeyedScalar(lock, uniqueId)) {
+      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(lock, uniqueId)) {
+      continue;
+    }
+
+    // Either we got passed a process type or it was explicitely set on the recorded action.
+    // It should never happen that it is set to an invalid value (such as ProcessID::Count)
+    ProcessID processType = aProcessType.valueOr(upd.mProcessType);
+    MOZ_ASSERT(processType != ProcessID::Count);
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    ScalarBase* scalar = nullptr;
+    nsresult rv = internal_GetScalarByEnum(lock, uniqueId, processType,
+                                           &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    if (upd.mData.isNothing()) {
+      MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
+      continue;
+    }
+
+    // Get the type of this scalar from the scalar ID. We already checked
+    // for its validity a few lines above.
+    const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
+
+    // Extract the data from the mozilla::Variant.
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        {
+          switch (scalarType)
+          {
+            case nsITelemetry::SCALAR_TYPE_COUNT:
+              scalar->SetValue(upd.mData->as<uint32_t>());
+              break;
+            case nsITelemetry::SCALAR_TYPE_BOOLEAN:
+              scalar->SetValue(upd.mData->as<bool>());
+              break;
+            case nsITelemetry::SCALAR_TYPE_STRING:
+              scalar->SetValue(upd.mData->as<nsString>());
+              break;
+          }
+          break;
+        }
+      case ScalarActionType::eAdd:
+        {
+          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
+            NS_WARNING("Attempting to add on a non count scalar.");
+            continue;
+          }
+          // We only support adding uint32_t.
+          scalar->AddValue(upd.mData->as<uint32_t>());
+          break;
+        }
+      case ScalarActionType::eSetMaximum:
+        {
+          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
+            NS_WARNING("Attempting to add on a non count scalar.");
+            continue;
+          }
+          // We only support SetMaximum on uint32_t.
+          scalar->SetMaximum(upd.mData->as<uint32_t>());
+          break;
+        }
+      default:
+        NS_WARNING("Unsupported action coming from scalar child updates.");
+    }
+  }
+}
+
+void
+internal_ApplyKeyedScalarActions(const StaticMutexAutoLock& lock,
+                                 const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions,
+                                 const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
+{
+  if (!internal_CanRecordBase(lock)) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    ScalarKey uniqueId{upd.mId, upd.mDynamic};
+    if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
+      MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
+      continue;
+    }
+
+    if (!internal_IsKeyedScalar(lock, uniqueId)) {
+      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(lock, uniqueId)) {
+      continue;
+    }
+
+    // Either we got passed a process type or it was explicitely set on the recorded action.
+    // It should never happen that it is set to an invalid value (such as ProcessID::Count)
+    ProcessID processType = aProcessType.valueOr(upd.mProcessType);
+    MOZ_ASSERT(processType != ProcessID::Count);
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    KeyedScalar* scalar = nullptr;
+    nsresult rv = internal_GetKeyedScalarByEnum(lock, uniqueId, processType,
+                                                &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    if (upd.mData.isNothing()) {
+      MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
+      continue;
+    }
+
+    // Get the type of this scalar from the scalar ID. We already checked
+    // for its validity a few lines above.
+    const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
+
+    // Extract the data from the mozilla::Variant.
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        {
+          switch (scalarType)
+          {
+            case nsITelemetry::SCALAR_TYPE_COUNT:
+              scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
+              break;
+            case nsITelemetry::SCALAR_TYPE_BOOLEAN:
+              scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<bool>());
+              break;
+            default:
+              NS_WARNING("Unsupported type coming from scalar child updates.");
+          }
+          break;
+        }
+      case ScalarActionType::eAdd:
+        {
+          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
+            NS_WARNING("Attempting to add on a non count scalar.");
+            continue;
+          }
+          // We only support adding on uint32_t.
+          scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
+          break;
+        }
+      case ScalarActionType::eSetMaximum:
+        {
+          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
+            NS_WARNING("Attempting to add on a non count scalar.");
+            continue;
+          }
+          // We only support SetMaximum on uint32_t.
+          scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
+          break;
+        }
+      default:
+        NS_WARNING("Unsupported action coming from keyed scalar child updates.");
+    }
+  }
+}
+
+void
+internal_ApplyPendingOperations(const StaticMutexAutoLock& lock)
+{
+  if (gScalarsActions && gScalarsActions->Length() > 0) {
+    internal_ApplyScalarActions(lock, *gScalarsActions);
+    gScalarsActions->Clear();
+  }
+
+  if (gKeyedScalarsActions && gKeyedScalarsActions->Length() > 0) {
+    internal_ApplyKeyedScalarActions(lock, *gKeyedScalarsActions);
+    gKeyedScalarsActions->Clear();
+  }
+
+  // After all pending operations are applied deserialization is done
+  gIsDeserializing = false;
+}
+
+} // namespace
+
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
 
 // This is a StaticMutex rather than a plain Mutex (1) so that
 // it gets initialised in a thread-safe manner the first time
 // it is used, and (2) because it is never de-initialised, and
@@ -1799,16 +2175,30 @@ TelemetryScalar::DeInitializeGlobalState
   gKeyedScalarStorageMap.Clear();
   gDynamicBuiltinScalarStorageMap.Clear();
   gDynamicBuiltinKeyedScalarStorageMap.Clear();
   gDynamicScalarInfo = nullptr;
   gInitDone = false;
 }
 
 void
+TelemetryScalar::DeserializationStarted()
+{
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  gIsDeserializing = true;
+}
+
+void
+TelemetryScalar::ApplyPendingOperations()
+{
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  internal_ApplyPendingOperations(locker);
+}
+
+void
 TelemetryScalar::SetCanRecordBase(bool b)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   gCanRecordBase = b;
 }
 
 void
 TelemetryScalar::SetCanRecordExtended(bool b) {
@@ -1916,16 +2306,23 @@ TelemetryScalar::Add(mozilla::Telemetry:
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
                                                      ScalarActionType::eAdd,
                                                      ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                ScalarActionType::eAdd,
+                                ScalarVariant(aValue));
+    return;
+  }
+
   ScalarBase* scalar = nullptr;
   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                          &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->AddValue(aValue);
@@ -1957,16 +2354,24 @@ TelemetryScalar::Add(mozilla::Telemetry:
 
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
       aKey, ScalarActionType::eAdd, ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                     aKey,
+                                     ScalarActionType::eAdd,
+                                     ScalarVariant(aValue));
+    return;
+  }
+
   KeyedScalar* scalar = nullptr;
   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
                                               ProcessID::Parent,
                                               &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
@@ -2073,16 +2478,23 @@ TelemetryScalar::Set(mozilla::Telemetry:
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
                                                      ScalarActionType::eSet,
                                                      ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                ScalarActionType::eSet,
+                                ScalarVariant(aValue));
+    return;
+  }
+
   ScalarBase* scalar = nullptr;
   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                          &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->SetValue(aValue);
@@ -2113,16 +2525,23 @@ TelemetryScalar::Set(mozilla::Telemetry:
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
                                                      ScalarActionType::eSet,
                                                      ScalarVariant(nsString(aValue)));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                ScalarActionType::eSet,
+                                ScalarVariant(nsString(aValue)));
+    return;
+  }
+
   ScalarBase* scalar = nullptr;
   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                          &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->SetValue(aValue);
@@ -2153,16 +2572,23 @@ TelemetryScalar::Set(mozilla::Telemetry:
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
                                                      ScalarActionType::eSet,
                                                      ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                ScalarActionType::eSet,
+                                ScalarVariant(aValue));
+    return;
+  }
+
   ScalarBase* scalar = nullptr;
   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                          &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->SetValue(aValue);
@@ -2194,16 +2620,24 @@ TelemetryScalar::Set(mozilla::Telemetry:
 
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
       aKey, ScalarActionType::eSet, ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                     aKey,
+                                     ScalarActionType::eSet,
+                                     ScalarVariant(aValue));
+    return;
+  }
+
   KeyedScalar* scalar = nullptr;
   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
                                               ProcessID::Parent,
                                               &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
@@ -2236,16 +2670,24 @@ TelemetryScalar::Set(mozilla::Telemetry:
 
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
       aKey, ScalarActionType::eSet, ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                     aKey,
+                                     ScalarActionType::eSet,
+                                     ScalarVariant(aValue));
+    return;
+  }
+
   KeyedScalar* scalar = nullptr;
   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
                                               ProcessID::Parent,
                                               &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
@@ -2352,16 +2794,23 @@ TelemetryScalar::SetMaximum(mozilla::Tel
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
                                                      ScalarActionType::eSetMaximum,
                                                      ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                ScalarActionType::eSetMaximum,
+                                ScalarVariant(aValue));
+    return;
+  }
+
   ScalarBase* scalar = nullptr;
   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                          &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->SetMaximum(aValue);
@@ -2393,16 +2842,24 @@ TelemetryScalar::SetMaximum(mozilla::Tel
 
   // Accumulate in the child process if needed.
   if (!XRE_IsParentProcess()) {
     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
       aKey, ScalarActionType::eSetMaximum, ScalarVariant(aValue));
     return;
   }
 
+  if (internal_IsScalarDeserializing(locker)) {
+    internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
+                                     aKey,
+                                     ScalarActionType::eSetMaximum,
+                                     ScalarVariant(aValue));
+    return;
+  }
+
   KeyedScalar* scalar = nullptr;
   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, ProcessID::Parent,
                                               &scalar);
   if (NS_FAILED(rv)) {
     return;
   }
 
   scalar->SetMaximum(aKey, aValue);
@@ -2756,16 +3213,18 @@ TelemetryScalar::ClearScalars()
     return;
   }
 
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   gScalarStorageMap.Clear();
   gKeyedScalarStorageMap.Clear();
   gDynamicBuiltinScalarStorageMap.Clear();
   gDynamicBuiltinKeyedScalarStorageMap.Clear();
+  gScalarsActions = nullptr;
+  gKeyedScalarsActions = nullptr;
 }
 
 size_t
 TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
@@ -2800,192 +3259,57 @@ TelemetryScalar::GetScalarSizesOfIncludi
 
 void
 TelemetryScalar::UpdateChildData(ProcessID 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(locker)) {
+
+  // If scalars are still being deserialized, we need to record the incoming
+  // operations as well.
+  if (internal_IsScalarDeserializing(locker)) {
+    for (const ScalarAction& action : aScalarActions) {
+      // We're only getting immutable access, so let's copy it
+      ScalarAction copy = action;
+      // Fix up the process type
+      copy.mProcessType = aProcessType;
+      internal_RecordScalarAction(locker, copy);
+    }
+
     return;
   }
 
-  for (auto& upd : aScalarActions) {
-    ScalarKey uniqueId{upd.mId, upd.mDynamic};
-    if (NS_WARN_IF(!internal_IsValidId(locker, uniqueId))) {
-      MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
-      continue;
-    }
-
-    if (internal_IsKeyedScalar(locker, uniqueId)) {
-      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(locker, uniqueId)) {
-      continue;
-    }
-
-    // Refresh the data in the parent process with the data coming from the child
-    // processes.
-    ScalarBase* scalar = nullptr;
-    nsresult rv = internal_GetScalarByEnum(locker, uniqueId, aProcessType,
-                                           &scalar);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
-      continue;
-    }
-
-    if (upd.mData.isNothing()) {
-      MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
-      continue;
-    }
-
-    // Get the type of this scalar from the scalar ID. We already checked
-    // for its validity a few lines above.
-    const uint32_t scalarType = internal_GetScalarInfo(locker, uniqueId).kind;
-
-    // Extract the data from the mozilla::Variant.
-    switch (upd.mActionType)
-    {
-      case ScalarActionType::eSet:
-        {
-          switch (scalarType)
-          {
-            case nsITelemetry::SCALAR_TYPE_COUNT:
-              scalar->SetValue(upd.mData->as<uint32_t>());
-              break;
-            case nsITelemetry::SCALAR_TYPE_BOOLEAN:
-              scalar->SetValue(upd.mData->as<bool>());
-              break;
-            case nsITelemetry::SCALAR_TYPE_STRING:
-              scalar->SetValue(upd.mData->as<nsString>());
-              break;
-          }
-          break;
-        }
-      case ScalarActionType::eAdd:
-        {
-          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
-            NS_WARNING("Attempting to add on a non count scalar.");
-            continue;
-          }
-          // We only support adding uint32_t.
-          scalar->AddValue(upd.mData->as<uint32_t>());
-          break;
-        }
-      case ScalarActionType::eSetMaximum:
-        {
-          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
-            NS_WARNING("Attempting to add on a non count scalar.");
-            continue;
-          }
-          // We only support SetMaximum on uint32_t.
-          scalar->SetMaximum(upd.mData->as<uint32_t>());
-          break;
-        }
-      default:
-        NS_WARNING("Unsupported action coming from scalar child updates.");
-    }
-  }
+  internal_ApplyScalarActions(locker, aScalarActions, Some(aProcessType));
 }
 
 void
 TelemetryScalar::UpdateChildKeyedData(ProcessID 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(locker)) {
+
+  // If scalars are still being deserialized, we need to record the incoming
+  // operations as well.
+  if (internal_IsScalarDeserializing(locker)) {
+    for (const KeyedScalarAction& action : aScalarActions) {
+      // We're only getting immutable access, so let's copy it
+      KeyedScalarAction copy = action;
+      // Fix up the process type
+      copy.mProcessType = aProcessType;
+      internal_RecordKeyedScalarAction(locker, copy);
+    }
+
     return;
   }
 
-  for (auto& upd : aScalarActions) {
-    ScalarKey uniqueId{upd.mId, upd.mDynamic};
-    if (NS_WARN_IF(!internal_IsValidId(locker, uniqueId))) {
-      MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
-      continue;
-    }
-
-    if (!internal_IsKeyedScalar(locker, uniqueId)) {
-      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(locker, uniqueId)) {
-      continue;
-    }
-
-    // Refresh the data in the parent process with the data coming from the child
-    // processes.
-    KeyedScalar* scalar = nullptr;
-    nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, aProcessType,
-                                                &scalar);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
-      continue;
-    }
-
-    if (upd.mData.isNothing()) {
-      MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
-      continue;
-    }
-
-    // Get the type of this scalar from the scalar ID. We already checked
-    // for its validity a few lines above.
-    const uint32_t scalarType = internal_GetScalarInfo(locker, uniqueId).kind;
-
-    // Extract the data from the mozilla::Variant.
-    switch (upd.mActionType)
-    {
-      case ScalarActionType::eSet:
-        {
-          switch (scalarType)
-          {
-            case nsITelemetry::SCALAR_TYPE_COUNT:
-              scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
-              break;
-            case nsITelemetry::SCALAR_TYPE_BOOLEAN:
-              scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<bool>());
-              break;
-            default:
-              NS_WARNING("Unsupported type coming from scalar child updates.");
-          }
-          break;
-        }
-      case ScalarActionType::eAdd:
-        {
-          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
-            NS_WARNING("Attempting to add on a non count scalar.");
-            continue;
-          }
-          // We only support adding on uint32_t.
-          scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
-          break;
-        }
-      case ScalarActionType::eSetMaximum:
-        {
-          if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
-            NS_WARNING("Attempting to add on a non count scalar.");
-            continue;
-          }
-          // We only support SetMaximum on uint32_t.
-          scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
-          break;
-        }
-      default:
-        NS_WARNING("Unsupported action coming from keyed scalar child updates.");
-    }
-  }
+  internal_ApplyKeyedScalarActions(locker, aScalarActions, Some(aProcessType));
 }
 
 void
 TelemetryScalar::RecordDiscardedData(ProcessID aProcessType,
                                      const mozilla::Telemetry::DiscardedData& aDiscardedData)
 {
   MOZ_ASSERT(XRE_IsParentProcess(),
              "Discarded Data must be updated from the parent process.");
@@ -3320,17 +3644,18 @@ TelemetryScalar::DeserializePersistedSca
 
     for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
       PersistedScalarArray& processScalars = iter.Data();
       for (PersistedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
         mozilla::Unused << internal_UpdateScalar(lock,
                                                  processScalars[i].first(),
                                                  ScalarActionType::eSet,
                                                  processScalars[i].second(),
-                                                 ProcessID(iter.Key()));
+                                                 ProcessID(iter.Key()),
+                                                 true /* aForce */);
       }
     }
   }
 
   return NS_OK;
 }
 
 /**
@@ -3492,15 +3817,16 @@ TelemetryScalar::DeserializePersistedKey
     for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
       PersistedKeyedScalarArray& processScalars = iter.Data();
       for (PersistedKeyedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
         mozilla::Unused << internal_UpdateKeyedScalar(lock,
                                                       mozilla::Get<0>(processScalars[i]),
                                                       mozilla::Get<1>(processScalars[i]),
                                                       ScalarActionType::eSet,
                                                       mozilla::Get<2>(processScalars[i]),
-                                                      ProcessID(iter.Key()));
+                                                      ProcessID(iter.Key()),
+                                                      true /* aForce */);
       }
     }
   }
 
   return NS_OK;
 }
--- a/toolkit/components/telemetry/TelemetryScalar.h
+++ b/toolkit/components/telemetry/TelemetryScalar.h
@@ -91,11 +91,16 @@ void GetDynamicScalarDefinitions(nsTArra
 void AddDynamicScalarDefinitions(const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&);
 
 // They are responsible for updating in-memory probes with the data persisted
 // on the disk and vice-versa.
 nsresult SerializeScalars(mozilla::JSONWriter &aWriter);
 nsresult SerializeKeyedScalars(mozilla::JSONWriter &aWriter);
 nsresult DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData);
 nsresult DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData);
+// Mark deserialization as in progress.
+// After this, all scalar operations are recorded into the pending operations list.
+void DeserializationStarted();
+// Apply all operations from the pending operations list and mark deserialization finished afterwards.
+void ApplyPendingOperations();
 } // namespace TelemetryScalar
 
 #endif // TelemetryScalar_h__
--- a/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
+++ b/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
@@ -441,16 +441,19 @@ PersistenceThreadLoadData()
   nsAutoCString fileContent;
   auto scopedArmTimer = MakeScopeExit([&] {
     NS_DispatchToMainThread(
       NS_NewRunnableFunction("MainThreadArmPersistenceTimer", [fileContent]() -> void {
         // Try to parse the probes if the file was not empty.
         if (!fileContent.IsEmpty()) {
           MainThreadParsePersistedProbes(fileContent);
         }
+
+        TelemetryScalar::ApplyPendingOperations();
+
         // Arm the timer.
         MainThreadArmPersistenceTimer();
         // Notify that we're good to take snapshots!
         nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
         if (os) {
           os->NotifyObservers(nullptr, kLoadCompleteTopic, nullptr);
         }
       }));
@@ -508,16 +511,19 @@ TelemetryGeckoViewPersistence::InitPersi
     NS_NewNamedThread("TelemetryGVIO", getter_AddRefs(thread));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     ANDROID_LOG("InitPersistence -  Failed to instantiate the worker thread.");
     return;
   }
 
   gPersistenceThread = thread.forget();
 
+  // From now on all scalar operations should be recorded.
+  TelemetryScalar::DeserializationStarted();
+
   // Trigger the loading of the persistence data. After the function
   // completes it will automatically arm the persistence timer.
   gPersistenceThread->Dispatch(
     NS_NewRunnableFunction("PersistenceThreadLoadData", &PersistenceThreadLoadData));
 }
 
 void
 TelemetryGeckoViewPersistence::DeInitPersistence()
--- a/toolkit/components/telemetry/ipc/TelemetryComms.h
+++ b/toolkit/components/telemetry/ipc/TelemetryComms.h
@@ -5,16 +5,17 @@
 
 #ifndef Telemetry_Comms_h__
 #define Telemetry_Comms_h__
 
 #include "ipc/IPCMessageUtils.h"
 #include "nsITelemetry.h"
 #include "nsVariant.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/TelemetryProcessEnums.h"
 
 namespace mozilla {
 namespace Telemetry {
 
 // Histogram accumulation types.
 enum HistogramID : uint32_t;
 
 struct HistogramAccumulation
@@ -44,27 +45,33 @@ typedef mozilla::Variant<uint32_t, bool,
 struct ScalarAction
 {
   uint32_t mId;
   bool mDynamic;
   ScalarActionType mActionType;
   // We need to wrap mData in a Maybe otherwise the IPC system
   // is unable to instantiate a ScalarAction.
   Maybe<ScalarVariant> mData;
+  // The process type this scalar should be recorded for.
+  // The IPC system will determine the process this action was coming from later.
+  mozilla::Telemetry::ProcessID mProcessType;
 };
 
 struct KeyedScalarAction
 {
   uint32_t mId;
   bool mDynamic;
   ScalarActionType mActionType;
   nsCString mKey;
   // We need to wrap mData in a Maybe otherwise the IPC system
   // is unable to instantiate a ScalarAction.
   Maybe<ScalarVariant> mData;
+  // The process type this scalar should be recorded for.
+  // The IPC system will determine the process this action was coming from later.
+  mozilla::Telemetry::ProcessID mProcessType;
 };
 
 // Dynamic scalars support.
 struct DynamicScalarDefinition
 {
   uint32_t type;
   uint32_t dataset;
   bool expired;
--- a/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
+++ b/toolkit/components/telemetry/ipc/TelemetryIPCAccumulator.cpp
@@ -186,18 +186,18 @@ TelemetryIPCAccumulator::RecordChildScal
   if (gChildScalarsActions->Length() >=
       kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) {
     gDiscardedData.mDiscardedScalarActions++;
     return;
   }
   if (gChildScalarsActions->Length() == kScalarActionsArrayHighWaterMark) {
     DispatchIPCTimerFired();
   }
-  // Store the action.
-  gChildScalarsActions->AppendElement(ScalarAction{aId, aDynamic, aAction, Some(aValue)});
+  // Store the action. The ProcessID will be determined by the receiver.
+  gChildScalarsActions->AppendElement(ScalarAction{aId, aDynamic, aAction, Some(aValue), Telemetry::ProcessID::Count});
   ArmIPCTimer(locker);
 }
 
 void
 TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uint32_t aId, bool aDynamic,
                                                       const nsAString& aKey,
                                                       ScalarActionType aAction,
                                                       const ScalarVariant& aValue)
@@ -210,19 +210,19 @@ TelemetryIPCAccumulator::RecordChildKeye
   if (gChildKeyedScalarsActions->Length() >=
       kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) {
     gDiscardedData.mDiscardedKeyedScalarActions++;
     return;
   }
   if (gChildKeyedScalarsActions->Length() == kScalarActionsArrayHighWaterMark) {
     DispatchIPCTimerFired();
   }
-  // Store the action.
+  // Store the action. The ProcessID will be determined by the receiver.
   gChildKeyedScalarsActions->AppendElement(
-    KeyedScalarAction{aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey), Some(aValue)});
+    KeyedScalarAction{aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey), Some(aValue), Telemetry::ProcessID::Count});
   ArmIPCTimer(locker);
 }
 
 void
 TelemetryIPCAccumulator::RecordChildEvent(const mozilla::TimeStamp& timestamp,
                                           const nsACString& category,
                                           const nsACString& method,
                                           const nsACString& object,