Bug 1498173 - Allow snapshotting from non-main stores for keyed histograms r=chutten
authorJan-Erik Rediger <jrediger@mozilla.com>
Fri, 23 Nov 2018 11:23:27 +0000
changeset 507021 cf109d30e7efb96d5afbb2f018da189de2198931
parent 507020 5edb94ce0260d72ab9fd67a88842ab63811d5f73
child 507022 5ff9933f5b592e8c94f75f73a21b19c901ccaddb
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten
bugs1498173
milestone65.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 1498173 - Allow snapshotting from non-main stores for keyed histograms r=chutten BREAKING CHANGE: This changes the API of the object returned from `Telemetry.getKeyedHistogramById`. * `add` will continue to record into all registered stores for the histogram. * `snapshot` now takes an object `{store: "storeName"}`, if not given it defaults to "main". Support for getting a snapshot for just one key is dropped. * `keys` now takes an object `{store: "storeName"}`, if not given it defaults to "main" * `clear` now takes an object `{store: "storeName"}`, if not given it defaults to "main" Depends on D12555 Differential Revision: https://phabricator.services.mozilla.com/D12556
toolkit/components/telemetry/core/Telemetry.cpp
toolkit/components/telemetry/core/TelemetryHistogram.cpp
toolkit/components/telemetry/core/TelemetryHistogram.h
toolkit/components/telemetry/core/nsITelemetry.idl
toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
--- a/toolkit/components/telemetry/core/Telemetry.cpp
+++ b/toolkit/components/telemetry/core/Telemetry.cpp
@@ -638,17 +638,17 @@ TelemetryImpl::GetSnapshotForKeyedHistog
                                              bool aClearStore,
                                              bool aFilterTest,
                                              JSContext* aCx,
                                              JS::MutableHandleValue aResult)
 {
   unsigned int dataset = mCanRecordExtended ?
     nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN :
     nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT;
-  return TelemetryHistogram::GetKeyedHistogramSnapshots(aCx, aResult, dataset, aClearStore, aFilterTest);
+  return TelemetryHistogram::GetKeyedHistogramSnapshots(aCx, aResult, aStoreName, dataset, aClearStore, aFilterTest);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetSnapshotForScalars(const nsACString& aStoreName,
                                      bool aClearStore,
                                      bool aFilterTest,
                                      JSContext* aCx,
                                      JS::MutableHandleValue aResult)
@@ -683,17 +683,17 @@ TelemetryImpl::SnapshotHistograms(unsign
                                                       aClearHistograms);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SnapshotKeyedHistograms(unsigned int aDataset,
                                        bool aClearHistograms, JSContext* aCx,
                                        JS::MutableHandleValue aResult)
 {
-  return TelemetryHistogram::GetKeyedHistogramSnapshots(aCx, aResult, aDataset,
+  return TelemetryHistogram::GetKeyedHistogramSnapshots(aCx, aResult, NS_LITERAL_CSTRING("main"), aDataset,
                                                         aClearHistograms);
 }
 
 bool
 TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, bool includePrivateSql)
 {
   JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
   if (!root_obj)
--- a/toolkit/components/telemetry/core/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/core/TelemetryHistogram.cpp
@@ -226,32 +226,34 @@ private:
   // We just store a single flag and all other operations become a no-op.
   bool mIsExpired;
 };
 
 class KeyedHistogram {
 public:
   KeyedHistogram(HistogramID id, const HistogramInfo& info, bool expired);
   ~KeyedHistogram();
-  nsresult GetHistogram(const nsCString& store, const nsCString& key, base::Histogram** histogram);
-  base::Histogram* GetHistogram(const nsCString& store, const nsCString& key);
+  nsresult GetHistogram(const nsCString& aStore, const nsCString& key, base::Histogram** histogram);
+  base::Histogram* GetHistogram(const nsCString& aStore, const nsCString& key);
   uint32_t GetHistogramType() const { return mHistogramInfo.histogramType; }
   nsresult GetKeys(const StaticMutexAutoLock& aLock, const nsCString& store, nsTArray<nsCString>& aKeys);
   // Note: unlike other methods, GetJSSnapshot is thread safe.
   nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
+                         const nsACString& aStore,
                          bool clearSubsession);
   nsresult GetSnapshot(const StaticMutexAutoLock& aLock,
+                       const nsACString& aStore,
                        KeyedHistogramSnapshotData& aSnapshot, bool aClearSubsession);
 
   nsresult Add(const nsCString& key, uint32_t aSample, ProcessID aProcessType);
-  void Clear();
+  void Clear(const nsACString& aStore);
 
   HistogramID GetHistogramID() const { return mId; }
 
-  bool IsEmpty() const;
+  bool IsEmpty(const nsACString& aStore) const;
 
   bool IsExpired() const { return mIsExpired; }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 private:
   typedef nsClassHashtable<nsCStringHashKey, base::Histogram> KeyedHistogramMapType;
   typedef nsClassHashtable<nsCStringHashKey, KeyedHistogramMapType> StoreMapType;
@@ -1175,30 +1177,30 @@ KeyedHistogram::KeyedHistogram(Histogram
 }
 
 KeyedHistogram::~KeyedHistogram()
 {
   delete mSingleStore;
 }
 
 nsresult
-KeyedHistogram::GetHistogram(const nsCString& store, const nsCString& key, base::Histogram** histogram)
+KeyedHistogram::GetHistogram(const nsCString& aStore, const nsCString& key, base::Histogram** histogram)
 {
   if (IsExpired()) {
     MOZ_ASSERT(false, "KeyedHistogram::GetHistogram called on an expired histogram.");
     return NS_ERROR_FAILURE;
   }
 
   KeyedHistogramMapType* histogramMap;
   bool found;
 
   if (mSingleStore != nullptr) {
     histogramMap = mSingleStore;
   } else {
-    found = mStorage.Get(store, &histogramMap);
+    found = mStorage.Get(aStore, &histogramMap);
     if (!found) {
       return NS_ERROR_FAILURE;
     }
   }
 
   found = histogramMap->Get(key, histogram);
   if (found) {
     return NS_OK;
@@ -1216,20 +1218,20 @@ KeyedHistogram::GetHistogram(const nsCSt
   bool inserted = histogramMap->Put(key, h, mozilla::fallible);
   if (MOZ_UNLIKELY(!inserted)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
 base::Histogram*
-KeyedHistogram::GetHistogram(const nsCString& store, const nsCString& key)
+KeyedHistogram::GetHistogram(const nsCString& aStore, const nsCString& key)
 {
   base::Histogram* h = nullptr;
-  if (NS_FAILED(GetHistogram(store, key, &h))) {
+  if (NS_FAILED(GetHistogram(aStore, key, &h))) {
     return nullptr;
   }
   return h;
 }
 
 nsresult
 KeyedHistogram::Add(const nsCString& key, uint32_t sample,
                     ProcessID aProcessType)
@@ -1266,18 +1268,17 @@ KeyedHistogram::Add(const nsCString& key
     TelemetryScalar::Add(
       mozilla::Telemetry::ScalarID::TELEMETRY_ACCUMULATE_CLAMPED_VALUES,
       NS_ConvertASCIItoUTF16(mHistogramInfo.name()), 1);
     sample = INT_MAX;
   }
 
   base::Histogram* histogram;
   if (mSingleStore != nullptr) {
-    // We know there's only a single store, no need to look for a store
-    histogram = GetHistogram(VoidCString(), key);
+    histogram = GetHistogram(NS_LITERAL_CSTRING("main"), key);
     if (!histogram) {
       MOZ_ASSERT(false, "Missing histogram in single store.");
       return NS_ERROR_FAILURE;
     }
 
     histogram->Add(sample);
   } else {
     for (uint32_t i = 0; i < mHistogramInfo.store_count; i++) {
@@ -1291,50 +1292,56 @@ KeyedHistogram::Add(const nsCString& key
       }
     }
   }
 
   return NS_OK;
 }
 
 void
-KeyedHistogram::Clear()
+KeyedHistogram::Clear(const nsACString& aStore)
 {
   MOZ_ASSERT(XRE_IsParentProcess(), "Only clear keyed histograms in the parent process");
   if (!XRE_IsParentProcess()) {
     return;
   }
 
   if (IsExpired()) {
     return;
   }
 
   if (mSingleStore) {
-    mSingleStore->Clear();
+    if (aStore.EqualsASCII("main")) {
+      mSingleStore->Clear();
+    }
     return;
   }
 
   KeyedHistogramMapType* histogramMap;
-  bool found = mStorage.Get(NS_LITERAL_CSTRING("main"), &histogramMap);
+  bool found = mStorage.Get(aStore, &histogramMap);
   if (!found) {
     return;
   }
 
   histogramMap->Clear();
 }
 
 bool
-KeyedHistogram::IsEmpty() const
+KeyedHistogram::IsEmpty(const nsACString& aStore) const
 {
   if (mSingleStore != nullptr) {
-    return mSingleStore->IsEmpty();
+    if (aStore.EqualsASCII("main")) {
+      return mSingleStore->IsEmpty();
+    }
+
+    return true;
   }
 
   KeyedHistogramMapType* histogramMap;
-  bool found = mStorage.Get(NS_LITERAL_CSTRING("main"), &histogramMap);
+  bool found = mStorage.Get(aStore, &histogramMap);
   if (!found) {
     return true;
   }
   return histogramMap->IsEmpty();
 }
 
 size_t
 KeyedHistogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
@@ -1380,52 +1387,57 @@ KeyedHistogram::GetKeys(const StaticMute
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj, bool clearSubsession)
+KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj, const nsACString& aStore, bool clearSubsession)
 {
   // Get a snapshot of the data.
   KeyedHistogramSnapshotData dataSnapshot;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
     MOZ_ASSERT(internal_IsHistogramEnumId(mId));
 
     // Take a snapshot of the data here, protected by the lock, and then,
     // outside of the lock protection, mirror it to a JS structure.
-    nsresult rv = GetSnapshot(locker, dataSnapshot, clearSubsession);
+    nsresult rv = GetSnapshot(locker, aStore, dataSnapshot, clearSubsession);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   // Now that we have a copy of the data, mirror it to JS.
   return internal_ReflectKeyedHistogram(dataSnapshot, gHistogramInfos[mId], cx, obj);
 }
 
 /**
- * Return a histogram snapshot for the main store.
+ * Return a histogram snapshot for the named store.
  *
  * If getting the snapshot succeeds, NS_OK is returned and `aSnapshot` contains the snapshot data.
- * If the histogram is not available in the main store, NS_ERROR_NO_CONTENT is returned.
+ * If the histogram is not available in the named store, NS_ERROR_NO_CONTENT is returned.
  * For other errors, NS_ERROR_FAILURE is returned.
  */
 nsresult
 KeyedHistogram::GetSnapshot(const StaticMutexAutoLock& aLock,
+                            const nsACString& aStore,
                             KeyedHistogramSnapshotData& aSnapshot, bool aClearSubsession)
 {
   KeyedHistogramMapType* histogramMap;
   if (mSingleStore != nullptr) {
+    if (!aStore.EqualsASCII("main")) {
+      return NS_ERROR_NO_CONTENT;
+    }
+
     histogramMap = mSingleStore;
   } else {
-    bool found = mStorage.Get(NS_LITERAL_CSTRING("main"), &histogramMap);
+    bool found = mStorage.Get(aStore, &histogramMap);
     if (!found) {
       // Nothing in the main store is fine, it's just handled as empty
       return NS_ERROR_NO_CONTENT;
     }
   }
 
   // Snapshot every key.
   for (auto iter = histogramMap->ConstIter(); !iter.Done(); iter.Next()) {
@@ -1439,17 +1451,17 @@ KeyedHistogram::GetSnapshot(const Static
       return NS_ERROR_FAILURE;
     }
 
     // Append to the final snapshot.
     aSnapshot.Put(iter.Key(), std::move(keySnapshot));
   }
 
   if (aClearSubsession) {
-    Clear();
+    Clear(aStore);
   }
 
   return NS_OK;
 }
 
 
 /**
  * Helper function to get a snapshot of the keyed histograms.
@@ -1463,16 +1475,17 @@ KeyedHistogram::GetSnapshot(const Static
  * @param {aSkipEmpty} whether or not to skip empty keyed histograms from the
  *        snapshot. Can't always assume "true" for consistency with the other
  *        callers.
  * @return {nsresult} NS_OK if the snapshot was successfully taken or
  *         NS_ERROR_OUT_OF_MEMORY if it failed to allocate memory.
  */
 nsresult
 internal_GetKeyedHistogramsSnapshot(const StaticMutexAutoLock& aLock,
+                                    const nsACString& aStore,
                                     unsigned int aDataset,
                                     bool aClearSubsession,
                                     bool aIncludeGPU,
                                     bool aFilterTest,
                                     KeyedHistogramProcessSnapshotsArray& aOutSnapshot,
                                     bool aSkipEmpty = false)
 {
   if (!aOutSnapshot.resize(static_cast<uint32_t>(ProcessID::Count))) {
@@ -1496,31 +1509,31 @@ internal_GetKeyedHistogramsSnapshot(cons
 
       if (!IsInDataset(info.dataset, aDataset)) {
         continue;
       }
 
       KeyedHistogram* keyed = internal_GetKeyedHistogramById(id,
                                                              ProcessID(process),
                                                              /* instantiate = */ false);
-      if (!keyed || (aSkipEmpty && keyed->IsEmpty()) || keyed->IsExpired()) {
+      if (!keyed || (aSkipEmpty && keyed->IsEmpty(aStore)) || keyed->IsExpired()) {
         continue;
       }
 
       const char* name = info.name();
       if (aFilterTest && strncmp(TEST_HISTOGRAM_PREFIX, name, strlen(TEST_HISTOGRAM_PREFIX)) == 0) {
         if (aClearSubsession) {
-          keyed->Clear();
+          keyed->Clear(aStore);
         }
         continue;
       }
 
       // Take a snapshot of the keyed histogram data!
       KeyedHistogramSnapshotData snapshot;
-      if (!NS_SUCCEEDED(keyed->GetSnapshot(aLock, snapshot, aClearSubsession))) {
+      if (!NS_SUCCEEDED(keyed->GetSnapshot(aLock, aStore, snapshot, aClearSubsession))) {
         return NS_ERROR_FAILURE;
       }
 
       if (!hArray.emplaceBack(KeyedHistogramSnapshotInfo{std::move(snapshot), id})) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
     }
   }
@@ -1632,17 +1645,17 @@ internal_ClearHistogram(const StaticMute
     return;
   }
 
   // Handle keyed histograms.
   if (gHistogramInfos[id].keyed) {
     for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
       KeyedHistogram* kh = internal_GetKeyedHistogramById(id, static_cast<ProcessID>(process), /* instantiate = */ false);
       if (kh) {
-        kh->Clear();
+        kh->Clear(aStore);
       }
     }
   } else {
     // Reset the histograms instances for all processes.
     for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
       Histogram* h = internal_GetHistogramById(aLock, id, static_cast<ProcessID>(process), /* instantiate = */ false);
       if (h) {
         h->Clear(aStore);
@@ -2096,77 +2109,39 @@ internal_KeyedHistogram_SnapshotImpl(JSC
   // covering multiple processes.
   // However, changing this requires some broader changes to callers.
   KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, /* instantiate = */ true);
   if (!keyed) {
     JS_ReportErrorASCII(cx, "Failed to look up keyed histogram");
     return false;
   }
 
-  // No argument was passed, so snapshot all the keys.
-  if (args.length() == 0) {
-    JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
-    if (!snapshot) {
-      JS_ReportErrorASCII(cx, "Failed to create object");
-      return false;
-    }
-
-    nsresult rv = keyed->GetJSSnapshot(cx, snapshot, clearSubsession);
-
-    // If the main store is not available, we return nothing and don't fail
-    if (rv == NS_ERROR_NO_CONTENT) {
-      args.rval().setUndefined();
-      return true;
-    }
-
-    if (!NS_SUCCEEDED(rv)) {
-      JS_ReportErrorASCII(cx, "Failed to reflect keyed histograms");
-      return false;
-    }
-
-    args.rval().setObject(*snapshot);
-    return true;
-  }
-
-  // One argument was passed. If it's a string, use it as a key
-  // and just snapshot the data for that key.
-  nsAutoJSString key;
-  if (!args[0].isString() || !key.init(cx, args[0])) {
-    JS_ReportErrorASCII(cx, "Not a string");
+  nsAutoString storeName;
+  nsresult rv;
+  rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
+  if (NS_FAILED(rv)) {
     return false;
   }
 
-  HistogramSnapshotData dataSnapshot;
-  {
-    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-
-    // Get data for the key we're looking for.
-    base::Histogram* h = nullptr;
-    nsresult rv = keyed->GetHistogram(NS_LITERAL_CSTRING("main"), NS_ConvertUTF16toUTF8(key), &h);
-    if (NS_FAILED(rv)) {
-      return false;
-    }
-
-    // Take a snapshot of the data here, protected by the lock, and then,
-    // outside of the lock protection, mirror it to a JS structure
-    if (NS_FAILED(internal_GetHistogramAndSamples(locker, h, dataSnapshot))) {
-      return false;
-    }
-  }
-
   JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
   if (!snapshot) {
+    JS_ReportErrorASCII(cx, "Failed to create object");
     return false;
   }
 
-  if (NS_FAILED(internal_ReflectHistogramAndSamples(cx,
-                                                    snapshot,
-                                                    gHistogramInfos[id],
-                                                    dataSnapshot))) {
-    JS_ReportErrorASCII(cx, "Failed to reflect histogram");
+  rv = keyed->GetJSSnapshot(cx, snapshot, NS_ConvertUTF16toUTF8(storeName), clearSubsession);
+
+  // If the store is not available, we return nothing and don't fail
+  if (rv == NS_ERROR_NO_CONTENT) {
+    args.rval().setUndefined();
+    return true;
+  }
+
+  if (!NS_SUCCEEDED(rv)) {
+    JS_ReportErrorASCII(cx, "Failed to reflect keyed histograms");
     return false;
   }
 
   args.rval().setObject(*snapshot);
   return true;
 }
 
 bool
@@ -2241,32 +2216,38 @@ internal_JSKeyedHistogram_Keys(JSContext
     return false;
   }
 
   JSObject *obj = &args.thisv().toObject();
   JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
   MOZ_ASSERT(data);
   HistogramID id = data->histogramId;
 
+  nsAutoString storeName;
+  nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
   nsTArray<nsCString> keys;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
     MOZ_ASSERT(internal_IsHistogramEnumId(id));
 
     // This is not good standard behavior given that we have histogram instances
     // covering multiple processes.
     // However, changing this requires some broader changes to callers.
     KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent);
 
     MOZ_ASSERT(keyed);
     if (!keyed) {
       return false;
     }
 
-    if (NS_FAILED(keyed->GetKeys(locker, NS_LITERAL_CSTRING("main"), keys))) {
+    if (NS_FAILED(keyed->GetKeys(locker, NS_ConvertUTF16toUTF8(storeName), keys))) {
       return false;
     }
   }
 
   // Convert keys from nsTArray<nsCString> to JS array.
   JS::AutoValueVector autoKeys(cx);
   if (!autoKeys.reserve(keys.Length())) {
     return false;
@@ -2310,31 +2291,37 @@ internal_JSKeyedHistogram_Clear(JSContex
   JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
   MOZ_ASSERT(data);
   HistogramID id = data->histogramId;
 
   // This function should always return |undefined| and never fail but
   // rather report failures using the console.
   args.rval().setUndefined();
 
+  nsAutoString storeName;
+  nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
   KeyedHistogram* keyed = nullptr;
   {
     MOZ_ASSERT(internal_IsHistogramEnumId(id));
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
 
     // This is not good standard behavior given that we have histogram instances
     // covering multiple processes.
     // However, changing this requires some broader changes to callers.
     keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, /* instantiate = */ false);
 
     if (!keyed) {
       return true;
     }
 
-    keyed->Clear();
+    keyed->Clear(NS_ConvertUTF16toUTF8(storeName));
   }
 
   return true;
 }
 
 // NOTE: Runs without protection from |gTelemetryHistogramMutex|.
 // See comment at the top of this section.
 nsresult
@@ -2345,19 +2332,19 @@ internal_WrapAndReturnKeyedHistogram(His
   if (!obj)
     return NS_ERROR_FAILURE;
   // The 6 functions that are wrapped up here are eventually called
   // by the same thread that runs this function.
   if (!(JS_DefineFunction(cx, obj, "add", internal_JSKeyedHistogram_Add, 2, 0)
         && JS_DefineFunction(cx, obj, "snapshot",
                              internal_JSKeyedHistogram_Snapshot, 1, 0)
         && JS_DefineFunction(cx, obj, "keys",
-                             internal_JSKeyedHistogram_Keys, 0, 0)
+                             internal_JSKeyedHistogram_Keys, 1, 0)
         && JS_DefineFunction(cx, obj, "clear",
-                             internal_JSKeyedHistogram_Clear, 0, 0))) {
+                             internal_JSKeyedHistogram_Clear, 1, 0))) {
     return NS_ERROR_FAILURE;
   }
 
   JSHistogramData* data = new JSHistogramData{id};
   JS_SetPrivate(obj, data);
   ret.setObject(*obj);
 
   return NS_OK;
@@ -2909,16 +2896,17 @@ TelemetryHistogram::CreateHistogramSnaps
     }
   }
   return NS_OK;
 }
 
 nsresult
 TelemetryHistogram::GetKeyedHistogramSnapshots(JSContext* aCx,
                                                JS::MutableHandleValue aResult,
+                                               const nsACString& aStore,
                                                unsigned int aDataset,
                                                bool aClearSubsession,
                                                bool aFilterTest)
 {
   // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
   if (!obj) {
     return NS_ERROR_FAILURE;
@@ -2929,16 +2917,17 @@ TelemetryHistogram::GetKeyedHistogramSna
   // to launch a process for it.
   bool includeGPUProcess = internal_AttemptedGPUProcess();
 
   // Get a snapshot of all the data while holding the mutex.
   KeyedHistogramProcessSnapshotsArray processHistArray;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
     nsresult rv = internal_GetKeyedHistogramsSnapshot(locker,
+                                                      aStore,
                                                       aDataset,
                                                       aClearSubsession,
                                                       includeGPUProcess,
                                                       aFilterTest,
                                                       processHistArray,
                                                       true /* skipEmpty */);
     if (NS_FAILED(rv)) {
       return rv;
@@ -3246,16 +3235,17 @@ TelemetryHistogram::SerializeKeyedHistog
   // Take a snapshot of the keyed histograms.
   KeyedHistogramProcessSnapshotsArray processHistArray;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
     // We always request the "opt-in"/"prerelease" dataset: we internally
     // record the right subset, so this will only return "prerelease" if
     // it was recorded.
     if (NS_FAILED(internal_GetKeyedHistogramsSnapshot(locker,
+                                                      NS_LITERAL_CSTRING("main"),
                                                       nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
                                                       false /* aClearSubsession */,
                                                       includeGPUProcess,
                                                       false /* aFilterTest */,
                                                       processHistArray,
                                                       true /* aSkipEmpty */))) {
       return NS_ERROR_FAILURE;
     }
--- a/toolkit/components/telemetry/core/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/core/TelemetryHistogram.h
@@ -69,17 +69,19 @@ GetHistogramName(mozilla::Telemetry::His
 
 nsresult
 CreateHistogramSnapshots(JSContext* aCx, JS::MutableHandleValue aResult,
                          const nsACString& aStore,
                          unsigned int aDataset,
                          bool aClearSubsession, bool aFilterTest=false);
 
 nsresult
-GetKeyedHistogramSnapshots(JSContext *aCx, JS::MutableHandleValue aResult, unsigned int aDataset,
+GetKeyedHistogramSnapshots(JSContext *aCx, JS::MutableHandleValue aResult,
+                           const nsACString& aStore,
+                           unsigned int aDataset,
                            bool aClearSubsession, bool aFilterTest=false);
 
 size_t
 GetHistogramSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 // These functions are only meant to be used for GeckoView persistence.
 // They are responsible for updating in-memory probes with the data persisted
 // on the disk and vice-versa.
--- a/toolkit/components/telemetry/core/nsITelemetry.idl
+++ b/toolkit/components/telemetry/core/nsITelemetry.idl
@@ -73,17 +73,17 @@ interface nsITelemetry : nsISupports
   [implicit_jscontext]
   jsval getSnapshotForHistograms(in ACString aStoreName, in boolean aClearStore, [optional] in boolean aFilterTest);
 
   /**
    * Serializes the keyed histograms from the given store to a JSON-style object.
    * The returned structure looks like:
    *   { "process": { "name1": { "key_1": histogramData1, "key_2": histogramData2 }, ...}, ... }
    *
-   * @param aStoreName The name of the store to snapshot. Ignored at the moment.
+   * @param aStoreName The name of the store to snapshot. Something like "main".
    * @param aClearStore Whether to clear out the keyed histograms in the named store after snapshotting.
    * @param aFilterTest If true, `TELEMETRY_TEST_` histograms will be filtered out.
                         Filtered histograms are still cleared if `aClearStore` is true.
    *                    Defaults to false.
    */
   [implicit_jscontext]
   jsval getSnapshotForKeyedHistograms(in ACString aStoreName, in boolean aClearStore, [optional] in boolean aFilterTest);
 
@@ -303,21 +303,27 @@ interface nsITelemetry : nsISupports
   [implicit_jscontext]
   jsval snapshotKeyedHistograms(in uint32_t aDataset, in boolean aClear);
 
   /**
    * Create and return a histogram registered in TelemetryHistograms.h.
    *
    * @param id - unique identifier from TelemetryHistograms.h
    * The returned object has the following functions:
-   *   add(string key, [optional] int) - Add an int value to the histogram for that key. If no histogram for that key exists yet, it is created.
-   *   snapshot([optional] string key) - If key is provided, returns a snapshot for the histogram with that key or null. If key is not provided, returns the snapshots of all the registered keys in the form {key1: snapshot1, key2: snapshot2, ...}.
-   *   keys() - Returns an array with the string keys of the currently registered histograms
-   *   clear() - Clears the registered histograms from this.
-   *             This is intended to be only used in tests.
+   *   add(string key, [optional] int) - Add an int value to the histogram for that key.
+                                         If no histogram for that key exists yet, it is created.
+   *   snapshot([optional] {store}) - Returns the snapshots of all the registered keys in the form
+                                      {key1: snapshot1, key2: snapshot2, ...} in the specified store.
+   *                                  Defaults to the "main" store.
+   *   keys([optional] {store}) - Returns an array with the string keys of the currently registered
+                                  histograms in the given store.
+                                  Defaults to "main".
+   *   clear([optional] {store}) - Clears the registered histograms from this.
+   *                               Defaults to the "main" store.
+   *                               Note: This is intended to be only used in tests.
    */
   [implicit_jscontext]
   jsval getKeyedHistogramById(in ACString id);
 
   /**
    * A flag indicating if Telemetry can record base data (FHR data). This is true if the
    * FHR data reporting service or self-support are enabled.
    *
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
@@ -1318,21 +1318,25 @@ add_task(async function test_multistore_
   Assert.ok(!(id in snapshot), `${id} should not be in a main store snapshot`);
 });
 
 add_task(async function test_multistore_argument_handling() {
   Telemetry.canRecordExtended = true;
   // Clear histograms
   Telemetry.getSnapshotForHistograms("main", true);
   Telemetry.getSnapshotForHistograms("sync", true);
+  Telemetry.getSnapshotForKeyedHistograms("main", true);
+  Telemetry.getSnapshotForKeyedHistograms("sync", true);
 
   let id;
   let hist;
   let snapshot;
 
+  // Plain Histograms
+
   id = "TELEMETRY_TEST_MULTIPLE_STORES";
   hist = Telemetry.getHistogramById(id);
   hist.add(37);
 
   // No argument
   snapshot = hist.snapshot();
   Assert.equal(37, snapshot.sum, `${id} should be in a default store snapshot`);
 
@@ -1355,16 +1359,51 @@ add_task(async function test_multistore_
   Assert.throws(() => hist.clear(2, "or", "more", "arguments"),
     /one argument/, "clear should check argument count");
   Assert.throws(() => hist.clear(2),
     /object argument/, "clear should check argument type");
   Assert.throws(() => hist.clear({}),
     /property/, "clear should check for object property");
   Assert.throws(() => hist.clear({store: 1}),
     /string/, "clear should check object property's type");
+
+  // Keyed Histogram
+
+  id = "TELEMETRY_TEST_KEYED_MULTIPLE_STORES";
+  hist = Telemetry.getKeyedHistogramById(id);
+  hist.add("key-1", 37);
+
+  // No argument
+  snapshot = hist.snapshot();
+  Assert.equal(37, snapshot["key-1"].sum, `${id} should be in a default store snapshot`);
+
+  hist.clear();
+  snapshot = hist.snapshot();
+  Assert.ok(!("key-1" in snapshot), `${id} should be cleared in the default store`);
+
+  snapshot = hist.snapshot({store: "sync"});
+  Assert.equal(37, snapshot["key-1"].sum, `${id} should not have been cleared in the sync store`);
+
+  Assert.throws(() => hist.snapshot(2, "or", "more", "arguments"),
+    /one argument/, "snapshot should check argument count");
+  Assert.throws(() => hist.snapshot(2),
+    /object argument/, "snapshot should check argument type");
+  Assert.throws(() => hist.snapshot({}),
+    /property/, "snapshot should check for object property");
+  Assert.throws(() => hist.snapshot({store: 1}),
+    /string/, "snapshot should check object property's type");
+
+  Assert.throws(() => hist.clear(2, "or", "more", "arguments"),
+    /one argument/, "clear should check argument count");
+  Assert.throws(() => hist.clear(2),
+    /object argument/, "clear should check argument type");
+  Assert.throws(() => hist.clear({}),
+    /property/, "clear should check for object property");
+  Assert.throws(() => hist.clear({store: 1}),
+    /string/, "clear should check object property's type");
 });
 
 add_task(async function test_multistore_sync_snapshot() {
   Telemetry.canRecordExtended = true;
   // Clear histograms
   Telemetry.getSnapshotForHistograms("main", true);
   Telemetry.getSnapshotForHistograms("sync", true);
 
@@ -1400,16 +1439,59 @@ add_task(async function test_multistore_
   id = "TELEMETRY_TEST_MAIN_ONLY";
   Assert.ok(!(id in snapshot), `${id} should not be in a sync store snapshot`);
   id = "TELEMETRY_TEST_MULTIPLE_STORES";
   Assert.ok(id in snapshot, `${id} should be in a sync store snapshot`);
   id = "TELEMETRY_TEST_SYNC_ONLY";
   Assert.ok(id in snapshot, `${id} should be in a sync store snapshot`);
 });
 
+add_task(async function test_multistore_keyed_sync_snapshot() {
+  Telemetry.canRecordExtended = true;
+  // Clear histograms
+  Telemetry.getSnapshotForKeyedHistograms("main", true);
+  Telemetry.getSnapshotForKeyedHistograms("sync", true);
+
+  let id;
+  let hist;
+  let snapshot;
+
+  // Plain histograms
+
+  // Fill with data
+  id = "TELEMETRY_TEST_KEYED_LINEAR";
+  hist = Telemetry.getKeyedHistogramById(id);
+  hist.add("key-1", 1);
+
+  id = "TELEMETRY_TEST_KEYED_MULTIPLE_STORES";
+  hist = Telemetry.getKeyedHistogramById(id);
+  hist.add("key-1", 1);
+
+  id = "TELEMETRY_TEST_KEYED_SYNC_ONLY";
+  hist = Telemetry.getKeyedHistogramById(id);
+  hist.add("key-1", 1);
+
+  // Getting snapshot and clearing
+  snapshot = Telemetry.getSnapshotForKeyedHistograms("main", /* clear */ true).parent;
+  id = "TELEMETRY_TEST_KEYED_LINEAR";
+  Assert.ok(id in snapshot, `${id} should be in a main store snapshot`);
+  id = "TELEMETRY_TEST_KEYED_MULTIPLE_STORES";
+  Assert.ok(id in snapshot, `${id} should be in a main store snapshot`);
+  id = "TELEMETRY_TEST_KEYED_SYNC_ONLY";
+  Assert.ok(!(id in snapshot), `${id} should not be in a main store snapshot`);
+
+  snapshot = Telemetry.getSnapshotForKeyedHistograms("sync", /* clear */ true).parent;
+  id = "TELEMETRY_TEST_KEYED_LINEAR";
+  Assert.ok(!(id in snapshot), `${id} should not be in a sync store snapshot`);
+  id = "TELEMETRY_TEST_KEYED_MULTIPLE_STORES";
+  Assert.ok(id in snapshot, `${id} should be in a sync store snapshot`);
+  id = "TELEMETRY_TEST_KEYED_SYNC_ONLY";
+  Assert.ok(id in snapshot, `${id} should be in a sync store snapshot`);
+});
+
 add_task(async function test_multistore_plain_individual_snapshot() {
   Telemetry.canRecordExtended = true;
   // Clear histograms
   Telemetry.getSnapshotForHistograms("main", true);
   Telemetry.getSnapshotForHistograms("sync", true);
 
   let id;
   let hist;
@@ -1458,8 +1540,72 @@ add_task(async function test_multistore_
   hist.add(3);
   Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
   Assert.deepEqual(40, hist.snapshot({store: "sync"}).sum);
 
   hist.clear({store: "sync"});
   Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
   Assert.deepEqual(0, hist.snapshot({store: "sync"}).sum);
 });
+
+add_task(async function test_multistore_keyed_individual_snapshot() {
+  Telemetry.canRecordExtended = true;
+  // Clear histograms
+  Telemetry.getSnapshotForKeyedHistograms("main", true);
+  Telemetry.getSnapshotForKeyedHistograms("sync", true);
+
+  let id;
+  let hist;
+
+  id = "TELEMETRY_TEST_KEYED_LINEAR";
+  hist = Telemetry.getKeyedHistogramById(id);
+
+  hist.add("key-1", 37);
+  Assert.deepEqual(37, hist.snapshot({store: "main"})["key-1"].sum);
+  Assert.deepEqual(undefined, hist.snapshot({store: "sync"}));
+
+  hist.clear({store: "main"});
+  Assert.deepEqual({}, hist.snapshot({store: "main"}));
+  Assert.deepEqual(undefined, hist.snapshot({store: "sync"}));
+
+  hist.add("key-1", 4);
+  hist.clear({store: "sync"});
+  Assert.deepEqual(4, hist.snapshot({store: "main"})["key-1"].sum);
+  Assert.deepEqual(undefined, hist.snapshot({store: "sync"}));
+
+  id = "TELEMETRY_TEST_KEYED_MULTIPLE_STORES";
+  hist = Telemetry.getKeyedHistogramById(id);
+
+  hist.add("key-1", 37);
+  Assert.deepEqual(37, hist.snapshot({store: "main"})["key-1"].sum);
+  Assert.deepEqual(37, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.clear({store: "main"});
+  Assert.deepEqual({}, hist.snapshot({store: "main"}));
+  Assert.deepEqual(37, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.add("key-1", 3);
+  Assert.deepEqual(3, hist.snapshot({store: "main"})["key-1"].sum);
+  Assert.deepEqual(40, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.clear({store: "sync"});
+  Assert.deepEqual(3, hist.snapshot({store: "main"})["key-1"].sum);
+  Assert.deepEqual({}, hist.snapshot({store: "sync"}));
+
+  id = "TELEMETRY_TEST_KEYED_SYNC_ONLY";
+  hist = Telemetry.getKeyedHistogramById(id);
+
+  hist.add("key-1", 37);
+  Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
+  Assert.deepEqual(37, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.clear({store: "main"});
+  Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
+  Assert.deepEqual(37, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.add("key-1", 3);
+  Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
+  Assert.deepEqual(40, hist.snapshot({store: "sync"})["key-1"].sum);
+
+  hist.clear({store: "sync"});
+  Assert.deepEqual(undefined, hist.snapshot({store: "main"}));
+  Assert.deepEqual({}, hist.snapshot({store: "sync"}));
+});