Bug 1468761 - Migrate Telemetry tests to always assume packed histograms r=chutten
☠☠ backed out by a67cc39bf78a ☠ ☠
authorJan-Erik Rediger <jrediger@mozilla.com>
Mon, 22 Oct 2018 15:26:30 +0000
changeset 490756 9ee8406cf1d3585cb4090101ec48aa7fc696bc97
parent 490755 65bfae07e0f4fdcc22bbf909ffbe92f7224f546b
child 490757 8c08dcec61d8a3e5e234aacae1e2a373545bcdeb
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerschutten
bugs1468761
milestone64.0a1
Bug 1468761 - Migrate Telemetry tests to always assume packed histograms r=chutten Depends on D9235 Differential Revision: https://phabricator.services.mozilla.com/D9236
toolkit/components/telemetry/tests/gtest/TestHistograms.cpp
toolkit/components/telemetry/tests/unit/head.js
toolkit/components/telemetry/tests/unit/test_PingAPI.js
toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
toolkit/components/telemetry/tests/unit/test_TelemetryFlagClear.js
toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js
toolkit/components/telemetry/tests/unit/test_TelemetryUtils.js
--- a/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp
+++ b/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp
@@ -161,25 +161,25 @@ TEST_F(TelemetryTestFixture, AccumulateC
   // Get a snapshot for all the histograms
   JS::RootedValue snapshot(cx.GetJSContext());
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", &snapshot, false);
 
   // Get our histogram from the snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, &histogram);
 
-  // Get counts array from histogram. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", histogram,  &counts);
+  // Get values object from histogram. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", histogram,  &values);
 
   // Get the value for the label we care about
   JS::RootedValue value(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
-             counts, &value);
+             values, &value);
 
   // Check that the value stored in the histogram matches with |kExpectedValue|
   uint32_t uValue = 0;
   JS::ToUint32(cx.GetJSContext(), value, &uValue);
   ASSERT_EQ(uValue, kExpectedValue) << "The histogram is not returning expected value";
 }
 
 TEST_F(TelemetryTestFixture, AccumulateKeyedCategoricalHistogram)
@@ -208,40 +208,40 @@ TEST_F(TelemetryTestFixture, AccumulateK
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_CATEGORICAL", &snapshot, true);
   // Get the histogram from the snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot, &histogram);
 
   // Check that the sample histogram contains the values we expect
   JS::RootedValue sample(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "sample", histogram,  &sample);
-  // Get counts array from the sample. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue sampleCounts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", sample,  &sampleCounts);
+  // Get values object from sample. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue sampleValues(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", sample,  &sampleValues);
   // Get the value for the label we care about
   JS::RootedValue sampleValue(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
-             sampleCounts, &sampleValue);
+             sampleValues, &sampleValue);
   // Check that the value stored in the histogram matches with |kSampleExpectedValue|
   uint32_t uSampleValue = 0;
   JS::ToUint32(cx.GetJSContext(), sampleValue, &uSampleValue);
   ASSERT_EQ(uSampleValue, kSampleExpectedValue) << "The sample histogram is not returning expected value";
 
   // Check that the other-sample histogram contains the values we expect
   JS::RootedValue otherSample(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "other-sample", histogram,  &otherSample);
-  // Get counts array from the other-sample. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue otherCounts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", otherSample,  &otherCounts);
+  // Get values object from the other-sample. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue otherValues(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", otherSample,  &otherValues);
   // Get the value for the label we care about
   JS::RootedValue otherValue(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
-             otherCounts, &otherValue);
+             otherValues, &otherValue);
   // Check that the value stored in the histogram matches with |kOtherSampleExpectedValue|
   uint32_t uOtherValue = 0;
   JS::ToUint32(cx.GetJSContext(), otherValue, &uOtherValue);
   ASSERT_EQ(uOtherValue, kOtherSampleExpectedValue) << "The other-sample histogram is not returning expected value";
 }
 
 TEST_F(TelemetryTestFixture, AccumulateCountHistogram_MultipleSamples)
 {
@@ -290,24 +290,24 @@ TEST_F(TelemetryTestFixture, AccumulateL
   // Get a snapshot of all the histograms
   JS::RootedValue snapshot(cx.GetJSContext());
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR", &snapshot, false);
 
   // Get histogram from snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram);
 
-  // Get "counts" array from histogram
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", histogram, &counts);
+  // Get "values" object from histogram
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", histogram, &values);
 
   // Index 0 is only for values less than 'low'. Values within range start at index 1
   JS::RootedValue count(cx.GetJSContext());
   const uint32_t index = 1;
-  GetElement(cx.GetJSContext(), index, counts, &count);
+  GetElement(cx.GetJSContext(), index, values, &count);
 
   // Check that this count matches with nSamples
   uint32_t uCount = 0;
   JS::ToUint32(cx.GetJSContext(), count, &uCount);
   ASSERT_EQ(uCount, kExpectedCount) << "The histogram did not accumulate the correct number of values";
 }
 
 TEST_F(TelemetryTestFixture, AccumulateLinearHistogram_DifferentSamples)
@@ -326,29 +326,30 @@ TEST_F(TelemetryTestFixture, AccumulateL
   // Get a snapshot of all histograms
   JS::RootedValue snapshot(cx.GetJSContext());
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR", &snapshot, false);
 
   // Get histogram from snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram);
 
-  // Get counts array from histogram
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", histogram, &counts);
+  // Get values object from histogram
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", histogram, &values);
 
-  // Get counts in first and last buckets
+  // Get values in first and last buckets
   JS::RootedValue countFirst(cx.GetJSContext());
   JS::RootedValue countLast(cx.GetJSContext());
   const uint32_t firstIndex = 1;
-  const uint32_t lastIndex = 9;
-  GetElement(cx.GetJSContext(), firstIndex, counts, &countFirst);
-  GetElement(cx.GetJSContext(), lastIndex, counts, &countLast);
+  // Buckets are indexed by their start value
+  const uint32_t lastIndex = INT32_MAX - 1;
+  GetElement(cx.GetJSContext(), firstIndex, values, &countFirst);
+  GetElement(cx.GetJSContext(), lastIndex, values, &countLast);
 
-  // Check that the counts match
+  // Check that the values match
   uint32_t uCountFirst = 0;
   uint32_t uCountLast = 0;
   JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst);
   JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast);
 
   const uint32_t kExpectedCountFirst = 2;
   // We expect 2147483646 to be in the last bucket, as well the two samples above 2^31
   // (prior to bug 1438335, values between INT_MAX and UINT32_MAX would end up as 0s)
@@ -425,29 +426,30 @@ TEST_F(TelemetryTestFixture, TestKeyedLi
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_LINEAR", snapshot, &histogram);
 
   // Get "testkey" property from histogram.
   JS::RootedValue expectedKeyData(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "testkey", histogram,  &expectedKeyData);
   ASSERT_TRUE(!expectedKeyData.isUndefined())
     << "Cannot find the expected key in the histogram data";
 
-  // Get counts array from 'testkey' histogram.
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", expectedKeyData, &counts);
+  // Get values object from 'testkey' histogram.
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", expectedKeyData, &values);
 
-  // Get counts in first and last buckets.
+  // Get values in first and last buckets.
   JS::RootedValue countFirst(cx.GetJSContext());
   JS::RootedValue countLast(cx.GetJSContext());
   const uint32_t firstIndex = 1;
-  const uint32_t lastIndex = 9;
-  GetElement(cx.GetJSContext(), firstIndex, counts, &countFirst);
-  GetElement(cx.GetJSContext(), lastIndex, counts, &countLast);
+  // Buckets are indexed by their start value
+  const uint32_t lastIndex = 250000;
+  GetElement(cx.GetJSContext(), firstIndex, values, &countFirst);
+  GetElement(cx.GetJSContext(), lastIndex, values, &countLast);
 
-  // Check that the counts match.
+  // Check that the values match.
   uint32_t uCountFirst = 0;
   uint32_t uCountLast = 0;
   JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst);
   JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast);
 
   const uint32_t kExpectedCountFirst = 2;
   const uint32_t kExpectedCountLast = 2;
   ASSERT_EQ(uCountFirst, kExpectedCountFirst)
@@ -490,31 +492,31 @@ TEST_F(TelemetryTestFixture, TestKeyedKe
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_KEYS", snapshot, &histogram);
 
   // Get "testkey" property from histogram and check that it stores the correct data.
   JS::RootedValue testKeyData(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "testkey", histogram,  &testKeyData);
   ASSERT_TRUE(!testKeyData.isUndefined())
     << "Cannot find the key 'testkey' in the histogram data";
 
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", testKeyData,  &counts);
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", testKeyData,  &values);
 
-  // Get counts in buckets 0,1,2
+  // Get values in buckets 0,1,2
   const uint32_t falseIndex = 0;
   const uint32_t trueIndex = 1;
   const uint32_t otherIndex = 2;
 
   JS::RootedValue countFalse(cx.GetJSContext());
   JS::RootedValue countTrue(cx.GetJSContext());
   JS::RootedValue countOther(cx.GetJSContext());
 
-  GetElement(cx.GetJSContext(), falseIndex, counts, &countFalse);
-  GetElement(cx.GetJSContext(), trueIndex, counts, &countTrue);
-  GetElement(cx.GetJSContext(), otherIndex, counts, &countOther);
+  GetElement(cx.GetJSContext(), falseIndex, values, &countFalse);
+  GetElement(cx.GetJSContext(), trueIndex, values, &countTrue);
+  GetElement(cx.GetJSContext(), otherIndex, values, &countOther);
 
   uint32_t uCountFalse = 0;
   uint32_t uCountTrue = 0;
   uint32_t uCountOther = 0;
   JS::ToUint32(cx.GetJSContext(), countFalse, &uCountFalse);
   JS::ToUint32(cx.GetJSContext(), countTrue, &uCountTrue);
   JS::ToUint32(cx.GetJSContext(), countOther, &uCountOther);
 
@@ -573,56 +575,56 @@ TEST_F(TelemetryTestFixture, AccumulateC
   // Get a snapshot for all the histograms
   JS::RootedValue snapshot(cx.GetJSContext());
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", &snapshot, false);
 
   // Get our histogram from the snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, &histogram);
 
-  // Get counts array from histogram. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", histogram,  &counts);
+  // Get values object from histogram. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", histogram,  &values);
 
   // Get the value for the label we care about
   JS::RootedValue value(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
-             counts, &value);
+             values, &value);
 
   // Check that the value stored in the histogram matches with |kExpectedValue|
   uint32_t uValue = 0;
   JS::ToUint32(cx.GetJSContext(), value, &uValue);
   ASSERT_EQ(uValue, kExpectedValue) << "The histogram is not returning expected value";
 
   // Now we check for no accumulation when a bad label is present in the array.
   //
-  // The 'counts' property is not initialized unless data is accumulated so keeping another test
+  // The 'values' property is not initialized unless data is accumulated so keeping another test
   // to check for this case alone is wasteful as we will have to accumulate some data anyway.
 
   const nsTArray<nsCString> badLabelArray({
                             NS_LITERAL_CSTRING("CommonLabel"),
                             NS_LITERAL_CSTRING("BadLabel")});
 
   // Try to accumulate the array into the histogram.
   Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL, badLabelArray);
 
   // Get snapshot of all the histograms
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", &snapshot, false);
 
   // Get our histogram from the snapshot
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, &histogram);
 
-  // Get counts array from histogram
-  GetProperty(cx.GetJSContext(), "counts", histogram, &counts);
+  // Get values array from histogram
+  GetProperty(cx.GetJSContext(), "values", histogram, &values);
 
   // Get the value for the label we care about
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
-             counts, &value);
+             values, &value);
 
   // Check that the value stored in the histogram matches with |kExpectedValue|
   uValue = 0;
   JS::ToUint32(cx.GetJSContext(), value, &uValue);
   ASSERT_EQ(uValue, kExpectedValue) << "The histogram accumulated data when it should not have";
 }
 
 TEST_F(TelemetryTestFixture, AccumulateCategoricalHistogram_MultipleEnumValues)
@@ -643,25 +645,25 @@ TEST_F(TelemetryTestFixture, AccumulateC
   // Get a snapshot for all the histograms
   JS::RootedValue snapshot(cx.GetJSContext());
   GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", &snapshot, false);
 
   // Get our histogram from the snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, &histogram);
 
-  // Get counts array from histogram. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue counts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", histogram,  &counts);
+  // Get values object from histogram. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue values(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", histogram,  &values);
 
   // Get the value for the label we care about
   JS::RootedValue value(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
-             counts, &value);
+             values, &value);
 
   // Check that the value stored in the histogram matches with |kExpectedValue|
   uint32_t uValue = 0;
   JS::ToUint32(cx.GetJSContext(), value, &uValue);
   ASSERT_EQ(uValue, kExpectedValue) << "The histogram is not returning expected value";
 }
 
 TEST_F(TelemetryTestFixture, AccumulateKeyedCategoricalHistogram_MultipleEnumValues)
@@ -688,38 +690,38 @@ TEST_F(TelemetryTestFixture, AccumulateK
   // Get the histogram from the snapshot
   JS::RootedValue histogram(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot, &histogram);
 
   // Check that the sampleKey histogram contains correct number of CommonLabel samples
   JS::RootedValue sample(cx.GetJSContext());
   GetProperty(cx.GetJSContext(), "sampleKey", histogram,  &sample);
 
-  // Get counts array from the sample. Each entry in the array maps to a label in the histogram.
-  JS::RootedValue sampleKeyCounts(cx.GetJSContext());
-  GetProperty(cx.GetJSContext(), "counts", sample,  &sampleKeyCounts);
+  // Get values object from the sample. Each entry in the object maps to a label in the histogram.
+  JS::RootedValue sampleKeyValues(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "values", sample,  &sampleKeyValues);
 
   // Get the count of CommonLabel
   JS::RootedValue commonLabelValue(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
-             sampleKeyCounts, &commonLabelValue);
+             sampleKeyValues, &commonLabelValue);
 
   // Check that the value stored in the histogram matches with |kExpectedCommonLabel|
   uint32_t uCommonLabelValue = 0;
   JS::ToUint32(cx.GetJSContext(), commonLabelValue, &uCommonLabelValue);
   ASSERT_EQ(uCommonLabelValue, kExpectedCommonLabel)
         << "The sampleKey histogram did not accumulate the correct number of CommonLabel samples";
 
   // Check that the sampleKey histogram contains the correct number of Label2 values
   // Get the count of Label2
   JS::RootedValue label2Value(cx.GetJSContext());
   GetElement(cx.GetJSContext(),
              static_cast<uint32_t>(Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::Label2),
-             sampleKeyCounts, &label2Value);
+             sampleKeyValues, &label2Value);
 
   // Check that the value stored in the histogram matches with |kExpectedLabel2|
   uint32_t uLabel2Value = 0;
   JS::ToUint32(cx.GetJSContext(), label2Value, &uLabel2Value);
   ASSERT_EQ(uLabel2Value, kExpectedLabel2)
         << "The sampleKey histogram did not accumulate the correct number of Label2 samples";
 }
 
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -339,20 +339,16 @@ function setEmptyPrefWatchlist() {
   let TelemetryEnvironment =
     ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm").TelemetryEnvironment;
   return TelemetryEnvironment.onInitialized().then(() => {
     TelemetryEnvironment.testWatchPreferences(new Map());
 
   });
 }
 
-function histogramValueCount(histogramSnapshot) {
-  return histogramSnapshot.counts.reduce((a, b) => a + b);
-}
-
 if (runningInParent) {
   // Set logging preferences for all the tests.
   Services.prefs.setCharPref("toolkit.telemetry.log.level", "Trace");
   // Telemetry archiving should be on.
   Services.prefs.setBoolPref(TelemetryUtils.Preferences.ArchiveEnabled, true);
   // Telemetry xpcshell tests cannot show the infobar.
   Services.prefs.setBoolPref(TelemetryUtils.Preferences.BypassNotification, true);
   // FHR uploads should be enabled.
--- a/toolkit/components/telemetry/tests/unit/test_PingAPI.js
+++ b/toolkit/components/telemetry/tests/unit/test_PingAPI.js
@@ -388,17 +388,17 @@ add_task(async function test_archiveClea
   await TelemetryArchive.promiseArchivedPingList();
   // The following also checks that non oversized pings are not removed.
   await checkArchive();
 
   // Make sure we're correctly updating the related histograms.
   h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").snapshot();
   Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the archive.");
   h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB").snapshot();
-  Assert.equal(h.counts[archivedPingSizeMB], 1,
+  Assert.equal(h.values[archivedPingSizeMB], 1,
                "Telemetry must report the correct size for the oversized ping.");
 });
 
 add_task(async function test_clientId() {
   // Check that a ping submitted after the delayed telemetry initialization completed
   // should get a valid client id.
   await TelemetryController.testReset();
   const clientId = await ClientID.getClientID();
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
@@ -59,32 +59,32 @@ function checkEventSummary(summaries, cl
 }
 
 function checkRegistrationFailure(failureType) {
   let snapshot = Telemetry.snapshotHistograms(OPTIN, true);
   Assert.ok("parent" in snapshot,
             "There should be at least one parent histogram when checking for registration failures.");
   Assert.ok("TELEMETRY_EVENT_REGISTRATION_ERROR" in snapshot.parent,
             "TELEMETRY_EVENT_REGISTRATION_ERROR should exist when checking for registration failures.");
-  let counts = snapshot.parent.TELEMETRY_EVENT_REGISTRATION_ERROR.counts;
-  Assert.ok(!!counts,
-            "TELEMETRY_EVENT_REGISTRATION_ERROR's counts should exist when checking for registration failures.");
-  Assert.equal(counts[failureType], 1, `Event registration ought to have failed due to type ${failureType}`);
+  let values = snapshot.parent.TELEMETRY_EVENT_REGISTRATION_ERROR.values;
+  Assert.ok(!!values,
+            "TELEMETRY_EVENT_REGISTRATION_ERROR's values should exist when checking for registration failures.");
+  Assert.equal(values[failureType], 1, `Event registration ought to have failed due to type ${failureType}`);
 }
 
 function checkRecordingFailure(failureType) {
   let snapshot = Telemetry.snapshotHistograms(OPTIN, true);
   Assert.ok("parent" in snapshot,
             "There should be at least one parent histogram when checking for recording failures.");
   Assert.ok("TELEMETRY_EVENT_RECORDING_ERROR" in snapshot.parent,
             "TELEMETRY_EVENT_RECORDING_ERROR should exist when checking for recording failures.");
-  let counts = snapshot.parent.TELEMETRY_EVENT_RECORDING_ERROR.counts;
-  Assert.ok(!!counts,
-            "TELEMETRY_EVENT_RECORDING_ERROR's counts should exist when checking for recording failures.");
-  Assert.equal(counts[failureType], 1, `Event recording ought to have failed due to type ${failureType}`);
+  let values = snapshot.parent.TELEMETRY_EVENT_RECORDING_ERROR.values;
+  Assert.ok(!!values,
+            "TELEMETRY_EVENT_RECORDING_ERROR's values should exist when checking for recording failures.");
+  Assert.equal(values[failureType], 1, `Event recording ought to have failed due to type ${failureType}`);
 }
 
 add_task(async function test_event_summary_limit() {
   if (AppConstants.DEBUG) {
     // This test will intentionally assert in DEBUG builds
     return;
   }
   Telemetry.clearEvents();
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryFlagClear.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryFlagClear.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   let testFlag = Services.telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
-  equal(JSON.stringify(testFlag.snapshot().counts), "[1,0,0]", "Original value is correct");
+  deepEqual(testFlag.snapshot().values, {0: 1, 1: 0}, "Original value is correct");
   testFlag.add(1);
-  equal(JSON.stringify(testFlag.snapshot().counts), "[0,1,0]", "Value is correct after ping.");
+  deepEqual(testFlag.snapshot().values, {0: 0, 1: 1, 2: 0}, "Value is correct after ping");
   testFlag.clear();
-  equal(JSON.stringify(testFlag.snapshot().counts), "[1,0,0]", "Value is correct after calling clear()");
+  deepEqual(testFlag.snapshot().values, {0: 1, 1: 0}, "Value is correct after calling clear()");
   testFlag.add(1);
-  equal(JSON.stringify(testFlag.snapshot().counts), "[0,1,0]", "Value is correct after ping.");
+  deepEqual(testFlag.snapshot().values, {0: 0, 1: 1, 2: 0}, "Value is correct after ping");
 }
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
@@ -32,79 +32,45 @@ function expect_success(f) {
     f();
     succeeded = true;
   } catch (e) {
     succeeded = false;
   }
   Assert.ok(succeeded);
 }
 
-function compareHistograms(h1, h2) {
-  let s1 = h1.snapshot();
-  let s2 = h2.snapshot();
-
-  Assert.equal(s1.histogram_type, s2.histogram_type);
-  Assert.equal(s1.min, s2.min);
-  Assert.equal(s1.max, s2.max);
-  Assert.equal(s1.sum, s2.sum);
-
-  Assert.equal(s1.counts.length, s2.counts.length);
-  for (let i = 0; i < s1.counts.length; i++)
-    Assert.equal(s1.counts[i], s2.counts[i]);
-
-  Assert.equal(s1.ranges.length, s2.ranges.length);
-  for (let i = 0; i < s1.ranges.length; i++)
-    Assert.equal(s1.ranges[i], s2.ranges[i]);
-}
-
 function check_histogram(histogram_type, name, min, max, bucket_count) {
   var h = Telemetry.getHistogramById(name);
-  var r = h.snapshot().ranges;
-  var sum = 0;
-  for (let i = 0;i < r.length;i++) {
-    var v = r[i];
-    sum += v;
-    h.add(v);
-  }
+  h.add(0);
   var s = h.snapshot();
-  // verify properties
-  Assert.equal(sum, s.sum);
+  Assert.equal(0, s.sum);
 
-  // there should be exactly one element per bucket
-  for (let i of s.counts) {
-    Assert.equal(i, 1);
-  }
   var hgrams = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                             false).parent;
   let gh = hgrams[name];
   Assert.equal(gh.histogram_type, histogram_type);
 
-  Assert.equal(gh.min, min);
-  Assert.equal(gh.max, max);
+  Assert.deepEqual(gh.range, [min, max]);
 
   // Check that booleans work with nonboolean histograms
   h.add(false);
   h.add(true);
-  s = h.snapshot().counts;
-  Assert.equal(s[0], 2);
-  Assert.equal(s[1], 2);
+  s = Object.values(h.snapshot().values);
+  Assert.deepEqual(s, [2, 1, 0]);
 
   // Check that clearing works.
   h.clear();
   s = h.snapshot();
-  for (var i of s.counts) {
-    Assert.equal(i, 0);
-  }
+  Assert.deepEqual(s.values, {});
   Assert.equal(s.sum, 0);
 
   h.add(0);
   h.add(1);
-  var c = h.snapshot().counts;
-  Assert.equal(c[0], 1);
-  Assert.equal(c[1], 1);
+  var c = Object.values(h.snapshot().values);
+  Assert.deepEqual(c, [1, 1, 0]);
 }
 
 // This MUST be the very first test of this file.
 add_task({
   skip_if: () => gIsAndroid,
 },
 function test_instantiate() {
   const ID = "TELEMETRY_TEST_COUNT";
@@ -176,127 +142,114 @@ add_task(async function test_noSerializa
   Telemetry.getHistogramById("NEWTAB_PAGE_PINNED_SITES_COUNT");
   let histograms = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                                 false /* clear */).parent;
   Assert.equal(false, "NEWTAB_PAGE_PINNED_SITES_COUNT" in histograms);
 });
 
 add_task(async function test_boolean_histogram() {
   var h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN");
-  var r = h.snapshot().ranges;
+  var r = h.snapshot().range;
   // boolean histograms ignore numeric parameters
-  Assert.equal(uneval(r), uneval([0, 1, 2]));
-  for (var i = 0;i < r.length;i++) {
-    var v = r[i];
-    h.add(v);
-  }
+  Assert.deepEqual(r, [1, 2]);
+  h.add(0);
+  h.add(1);
+  h.add(2);
+
   h.add(true);
   h.add(false);
   var s = h.snapshot();
   Assert.equal(s.histogram_type, Telemetry.HISTOGRAM_BOOLEAN);
   // last bucket should always be 0 since .add parameters are normalized to either 0 or 1
-  Assert.equal(s.counts[2], 0);
+  Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0});
   Assert.equal(s.sum, 3);
-  Assert.equal(s.counts[0], 2);
 });
 
 add_task(async function test_flag_histogram() {
   var h = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
-  var r = h.snapshot().ranges;
+  var r = h.snapshot().range;
   // Flag histograms ignore numeric parameters.
-  Assert.equal(uneval(r), uneval([0, 1, 2]));
+  Assert.deepEqual(r, [1, 2]);
   // Should already have a 0 counted.
-  var c = h.snapshot().counts;
+  var v = h.snapshot().values;
   var s = h.snapshot().sum;
-  Assert.equal(uneval(c), uneval([1, 0, 0]));
+  Assert.deepEqual(v, {0: 1, 1: 0});
   Assert.equal(s, 0);
   // Should switch counts.
   h.add(1);
-  var c2 = h.snapshot().counts;
+  var v2 = h.snapshot().values;
   var s2 = h.snapshot().sum;
-  Assert.equal(uneval(c2), uneval([0, 1, 0]));
+  Assert.deepEqual(v2, {0: 0, 1: 1, 2: 0});
   Assert.equal(s2, 1);
   // Should only switch counts once.
   h.add(1);
-  var c3 = h.snapshot().counts;
+  var v3 = h.snapshot().values;
   var s3 = h.snapshot().sum;
-  Assert.equal(uneval(c3), uneval([0, 1, 0]));
+  Assert.deepEqual(v3, {0: 0, 1: 1, 2: 0});
   Assert.equal(s3, 1);
   Assert.equal(h.snapshot().histogram_type, Telemetry.HISTOGRAM_FLAG);
 });
 
 add_task(async function test_count_histogram() {
   let h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT2");
   let s = h.snapshot();
-  Assert.equal(uneval(s.ranges), uneval([0, 1, 2]));
-  Assert.equal(uneval(s.counts), uneval([0, 0, 0]));
+  Assert.deepEqual(s.range, [1, 2]);
+  Assert.deepEqual(s.values, {});
   Assert.equal(s.sum, 0);
   h.add();
   s = h.snapshot();
-  Assert.equal(uneval(s.counts), uneval([1, 0, 0]));
+  Assert.deepEqual(s.values, {0: 1, 1: 0});
   Assert.equal(s.sum, 1);
   h.add();
   s = h.snapshot();
-  Assert.equal(uneval(s.counts), uneval([2, 0, 0]));
+  Assert.deepEqual(s.values, {0: 2, 1: 0});
   Assert.equal(s.sum, 2);
 });
 
 add_task(async function test_categorical_histogram() {
   let h1 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL");
   for (let v of ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1]) {
     h1.add(v);
   }
   for (let s of ["", "Label4", "1234"]) {
     // The |add| method should not throw for unexpected values, but rather
     // print an error message in the console.
     h1.add(s);
   }
 
-  // Categorical histograms default to 50 linear buckets.
-  let expectedRanges = [];
-  for (let i = 0; i < 51; ++i) {
-    expectedRanges.push(i);
-  }
-
   let snapshot = h1.snapshot();
   Assert.equal(snapshot.sum, 6);
-  Assert.deepEqual(snapshot.ranges, expectedRanges);
-  Assert.deepEqual(snapshot.counts.slice(0, 4), [3, 2, 2, 0]);
+  Assert.deepEqual(snapshot.range, [1, 50]);
+  Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 2, 3: 0});
 
   let h2 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL_OPTOUT");
   for (let v of ["CommonLabel", "CommonLabel", "Label4", "Label5", "Label6", 0, 1]) {
     h2.add(v);
   }
   for (let s of ["", "Label3", "1234"]) {
     // The |add| method should not throw for unexpected values, but rather
     // print an error message in the console.
     h2.add(s);
   }
 
   snapshot = h2.snapshot();
   Assert.equal(snapshot.sum, 7);
-  Assert.deepEqual(snapshot.ranges, expectedRanges);
-  Assert.deepEqual(snapshot.counts.slice(0, 5), [3, 2, 1, 1, 0]);
+  Assert.deepEqual(snapshot.range, [1, 50]);
+  Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 1, 3: 1, 4: 0});
 
   // This histogram overrides the default of 50 values to 70.
   let h3 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL_NVALUES");
   for (let v of ["CommonLabel", "Label7", "Label8"]) {
     h3.add(v);
   }
 
-  expectedRanges = [];
-  for (let i = 0; i < 71; ++i) {
-    expectedRanges.push(i);
-  }
-
   snapshot = h3.snapshot();
   Assert.equal(snapshot.sum, 3);
-  Assert.equal(snapshot.ranges.length, expectedRanges.length);
-  Assert.deepEqual(snapshot.ranges, expectedRanges);
-  Assert.deepEqual(snapshot.counts.slice(0, 4), [1, 1, 1, 0]);
+  Assert.deepEqual(snapshot.range, [1, 70]);
+  Assert.deepEqual(snapshot.values, {0: 1, 1: 1, 2: 1, 3: 0});
 });
 
 add_task(async function test_add_error_behaviour() {
   const PLAIN_HISTOGRAMS_TO_TEST = [
     "TELEMETRY_TEST_FLAG",
     "TELEMETRY_TEST_EXPONENTIAL",
     "TELEMETRY_TEST_LINEAR",
     "TELEMETRY_TEST_BOOLEAN",
@@ -348,18 +301,17 @@ add_task(async function test_getHistogra
     Telemetry.getHistogramById("nonexistent");
     do_throw("This can't happen");
   } catch (e) {
 
   }
   var h = Telemetry.getHistogramById("CYCLE_COLLECTOR");
   var s = h.snapshot();
   Assert.equal(s.histogram_type, Telemetry.HISTOGRAM_EXPONENTIAL);
-  Assert.equal(s.min, 1);
-  Assert.equal(s.max, 10000);
+  Assert.deepEqual(s.range, [1, 10000]);
 });
 
 add_task(async function test_getSlowSQL() {
   var slow = Telemetry.slowSQL;
   Assert.ok(("mainThread" in slow) && ("otherThreads" in slow));
 });
 
 add_task(async function test_getWebrtc() {
@@ -370,20 +322,20 @@ add_task(async function test_getWebrtc()
 });
 
 // Check that telemetry doesn't record in private mode
 add_task(async function test_privateMode() {
   var h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN");
   var orig = h.snapshot();
   Telemetry.canRecordExtended = false;
   h.add(1);
-  Assert.equal(uneval(orig), uneval(h.snapshot()));
+  Assert.deepEqual(orig, h.snapshot());
   Telemetry.canRecordExtended = true;
   h.add(1);
-  Assert.notEqual(uneval(orig), uneval(h.snapshot()));
+  Assert.notDeepEqual(orig, h.snapshot());
 });
 
 // Check that telemetry records only when it is suppose to.
 add_task(async function test_histogramRecording() {
   // Check that no histogram is recorded if both base and extended recording are off.
   Telemetry.canRecordBase = false;
   Telemetry.canRecordExtended = false;
 
@@ -485,22 +437,21 @@ add_task(async function test_keyed_histo
   Assert.ok(threw, "getKeyedHistogramById should have thrown");
 });
 
 add_task(async function test_keyed_boolean_histogram() {
   const KEYED_ID = "TELEMETRY_TEST_KEYED_BOOLEAN";
   let KEYS = numberRange(0, 2).map(i => "key" + (i + 1));
   KEYS.push("漢語");
   let histogramBase = {
-    "min": 1,
-    "max": 2,
+    "range": [1, 2],
+    "bucket_count": 3,
     "histogram_type": 2,
     "sum": 1,
-    "ranges": [0, 1, 2],
-    "counts": [0, 1, 0],
+    "values": {0: 0, 1: 1, 2: 0},
   };
   let testHistograms = numberRange(0, 3).map(i => JSON.parse(JSON.stringify(histogramBase)));
   let testKeys = [];
   let testSnapShot = {};
 
   let h = Telemetry.getKeyedHistogramById(KEYED_ID);
   for (let i = 0; i < 2; ++i) {
     let key = KEYS[i];
@@ -516,71 +467,70 @@ add_task(async function test_keyed_boole
   Assert.deepEqual(h.keys().sort(), testKeys);
   Assert.deepEqual(h.snapshot(), testSnapShot);
 
   let key = KEYS[2];
   h.add(key, false);
   testKeys.push(key);
   testSnapShot[key] = testHistograms[2];
   testSnapShot[key].sum = 0;
-  testSnapShot[key].counts = [1, 0, 0];
+  testSnapShot[key].values = {0: 1, 1: 0};
   Assert.deepEqual(h.keys().sort(), testKeys);
   Assert.deepEqual(h.snapshot(), testSnapShot);
 
   let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                                        false /* clear */).parent;
   Assert.deepEqual(parentHgrams[KEYED_ID], testSnapShot);
 
   h.clear();
   Assert.deepEqual(h.keys(), []);
   Assert.deepEqual(h.snapshot(), {});
 });
 
 add_task(async function test_keyed_count_histogram() {
   const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT";
   const KEYS = numberRange(0, 5).map(i => "key" + (i + 1));
   let histogramBase = {
-    "min": 1,
-    "max": 2,
+    "range": [1, 2],
+    "bucket_count": 3,
     "histogram_type": 4,
     "sum": 0,
-    "ranges": [0, 1, 2],
-    "counts": [1, 0, 0],
+    "values": {0: 1, 1: 0},
   };
   let testHistograms = numberRange(0, 5).map(i => JSON.parse(JSON.stringify(histogramBase)));
   let testKeys = [];
   let testSnapShot = {};
 
   let h = Telemetry.getKeyedHistogramById(KEYED_ID);
   h.clear();
   for (let i = 0; i < 4; ++i) {
     let key = KEYS[i];
     let value = i * 2 + 1;
 
     for (let k = 0; k < value; ++k) {
       h.add(key);
     }
-    testHistograms[i].counts[0] = value;
+    testHistograms[i].values[0] = value;
     testHistograms[i].sum = value;
     testSnapShot[key] = testHistograms[i];
     testKeys.push(key);
 
     Assert.deepEqual(h.keys().sort(), testKeys);
     Assert.deepEqual(h.snapshot(key), testHistograms[i]);
     Assert.deepEqual(h.snapshot(), testSnapShot);
   }
 
   h = Telemetry.getKeyedHistogramById(KEYED_ID);
   Assert.deepEqual(h.keys().sort(), testKeys);
   Assert.deepEqual(h.snapshot(), testSnapShot);
 
   let key = KEYS[4];
   h.add(key);
   testKeys.push(key);
-  testHistograms[4].counts[0] = 1;
+  testHistograms[4].values[0] = 1;
   testHistograms[4].sum = 1;
   testSnapShot[key] = testHistograms[4];
 
   Assert.deepEqual(h.keys().sort(), testKeys);
   Assert.deepEqual(h.snapshot(), testSnapShot);
 
   let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                                        false /* clear */).parent;
@@ -610,52 +560,45 @@ add_task(async function test_keyed_categ
 
     // The |add| method should not throw for unexpected values, but rather
     // print an error message in the console.
     for (let s of ["", "Label4", "1234"]) {
       h.add(k, s);
     }
   }
 
-  // Categorical histograms default to 50 linear buckets.
-  let expectedRanges = [];
-  for (let i = 0; i < 51; ++i) {
-    expectedRanges.push(i);
-  }
-
   // Check that the set of keys in the snapshot is what we expect.
   let snapshot = h.snapshot();
   let snapshotKeys = Object.keys(snapshot);
   Assert.equal(KEYS.length, snapshotKeys.length);
   Assert.ok(KEYS.every(k => snapshotKeys.includes(k)));
 
   // Check the snapshot values.
   for (let k of KEYS) {
     Assert.ok(k in snapshot);
     Assert.equal(snapshot[k].sum, 6);
-    Assert.deepEqual(snapshot[k].ranges, expectedRanges);
-    Assert.deepEqual(snapshot[k].counts.slice(0, 4), [3, 2, 2, 0]);
+    Assert.deepEqual(snapshot[k].range, [1, 50]);
+    Assert.deepEqual(snapshot[k].values, {0: 3, 1: 2, 2: 2, 3: 0});
   }
 });
 
 add_task(async function test_keyed_flag_histogram() {
   const KEYED_ID = "TELEMETRY_TEST_KEYED_FLAG";
   let h = Telemetry.getKeyedHistogramById(KEYED_ID);
 
   const KEY = "default";
   h.add(KEY, true);
 
   let testSnapshot = {};
   testSnapshot[KEY] = {
-    "min": 1,
-    "max": 2,
+    "range": [1, 2],
+    "bucket_count": 3,
     "histogram_type": 3,
     "sum": 1,
-    "ranges": [0, 1, 2],
-    "counts": [0, 1, 0],
+    "values": {0: 0, 1: 1, 2: 0},
   };
 
   Assert.deepEqual(h.keys().sort(), [KEY]);
   Assert.deepEqual(h.snapshot(), testSnapshot);
 
   let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                                        false /* clear */).parent;
   Assert.deepEqual(parentHgrams[KEYED_ID], testSnapshot);
@@ -855,19 +798,19 @@ add_task(async function test_keyed_keys(
   h.add("thirdKey", false);
   h.add("not-allowed", true);
 
   // Check that we have the expected keys.
   let snap = h.snapshot();
   Assert.equal(Object.keys(snap).length, 2, "Only 2 keys must be recorded.");
   Assert.ok("testkey" in snap, "'testkey' must be recorded.");
   Assert.ok("thirdKey" in snap, "'thirdKey' must be recorded.");
-  Assert.deepEqual(snap.testkey.counts, [0, 1, 0],
+  Assert.deepEqual(snap.testkey.values, {0: 0, 1: 1, 2: 0},
                    "'testkey' must contain the correct value.");
-  Assert.deepEqual(snap.thirdKey.counts, [1, 0, 0],
+  Assert.deepEqual(snap.thirdKey.values, {0: 1, 1: 0},
                    "'thirdKey' must contain the correct value.");
 
   // Keys that are not allowed must not be recorded.
   Assert.ok(!("not-allowed" in snap), "'not-allowed' must not be recorded.");
 
   // Check that these failures were correctly tracked.
   const parentScalars =
     Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent;
@@ -889,60 +832,60 @@ add_task(async function test_count_multi
   // If the array contains even a single invalid value, no accumulation should take place
   // Keep the valid values in front of invalid to check if it is simply accumulating as
   // it's traversing the array and throwing upon first invalid value. That should not happen.
   h.add(valid.concat(invalid));
   let s1 = h.snapshot();
   Assert.equal(s1.sum, 0);
   // Ensure that no accumulations of 0-like values took place.
   // These accumulations won't increase the sum.
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(valid);
   let s2 = h.snapshot();
-  Assert.equal(uneval(s2.counts), uneval([4, 0, 0]));
+  Assert.deepEqual(s2.values, {0: 4, 1: 0});
   Assert.equal(s2.sum, 5);
 });
 
 add_task(async function test_categorical_multiple_samples() {
   let h = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL");
   h.clear();
   let valid = ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1];
   let invalid = ["", "Label4", "1234", "0", "1", 5000];
 
   // At least one invalid parameter, so no accumulation should happen here
   // Valid values in front of invalid.
   h.add(valid.concat(invalid));
   let s1 = h.snapshot();
   Assert.equal(s1.sum, 0);
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(valid);
   let snapshot = h.snapshot();
   Assert.equal(snapshot.sum, 6);
-  Assert.deepEqual(snapshot.counts.slice(0, 4), [3, 2, 2, 0]);
+  Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 2, 3: 0});
 });
 
 add_task(async function test_boolean_multiple_samples() {
   let valid = [true, false, 0, 1, 2];
   let invalid = ["", "0", "1", ",2", "true", "false", "random"];
 
   let h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN");
   h.clear();
 
   // At least one invalid parameter, so no accumulation should happen here
   // Valid values in front of invalid.
   h.add(valid.concat(invalid));
   let s1 = h.snapshot();
   Assert.equal(s1.sum, 0);
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(valid);
   let s = h.snapshot();
-  Assert.deepEqual(s.counts, [2, 3, 0]);
+  Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0});
   Assert.equal(s.sum, 3);
 });
 
 add_task(async function test_linear_multiple_samples() {
   // According to telemetry.mozilla.org/histogram-simulator, bucket at
   // index 1 of TELEMETRY_TEST_LINEAR has max value of 268.44M
   let valid = [0, 1, 5, 10, 268450000, 268450001, Math.pow(2, 31) + 1];
   let invalid = ["", "0", "1", "random"];
@@ -950,24 +893,23 @@ add_task(async function test_linear_mult
   let h = Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR");
   h.clear();
 
   // At least one invalid paramater, so no accumulations.
   // Valid values in front of invalid.
    h.add(valid.concat(invalid));
    let s1 = h.snapshot();
    Assert.equal(s1.sum, 0);
-   Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+   Assert.deepEqual({}, s1.values);
 
   h.add(valid);
   let s2 = h.snapshot();
   // Values >= INT32_MAX are accumulated as INT32_MAX - 1
   Assert.equal(s2.sum, valid.reduce((acc, cur) => acc + cur) - 3);
-  Assert.equal(s2.counts[9], 1);
-  Assert.deepEqual(s2.counts.slice(0, 3), [1, 3, 2]);
+  Assert.deepEqual(Object.values(s2.values), [1, 3, 2, 1]);
 });
 
 add_task(async function test_keyed_no_arguments() {
   // Test for no accumulation when add is called with no arguments
   let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_LINEAR");
   h.clear();
 
   h.add();
@@ -999,62 +941,62 @@ add_task(async function test_keyed_count
   // If the array contains even a single invalid value, no accumulation should take place
   // Keep the valid values in front of invalid to check if it is simply accumulating as
   // it's traversing the array and throwing upon first invalid value. That should not happen.
   h.add(key, valid.concat(invalid));
   let s1 = h.snapshot(key);
   Assert.equal(s1.sum, 0);
   // Ensure that no accumulations of 0-like values took place.
   // These accumulations won't increase the sum.
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(key, valid);
   let s2 = h.snapshot(key);
-  Assert.equal(uneval(s2.counts), uneval([4, 0, 0]));
+  Assert.deepEqual(s2.values, {0: 4, 1: 0});
   Assert.equal(s2.sum, 5);
 });
 
 add_task(async function test_keyed_categorical_multiple_samples() {
   let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_CATEGORICAL");
   h.clear();
   let valid = ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1];
   let invalid = ["", "Label4", "1234", "0", "1", 5000];
   let key = "somekeystring";
 
   // At least one invalid parameter, so no accumulation should happen here
   // Valid values in front of invalid.
   h.add(key, valid.concat(invalid));
   let s1 = h.snapshot(key);
   Assert.equal(s1.sum, 0);
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(key, valid);
   let snapshot = h.snapshot(key);
   Assert.equal(snapshot.sum, 6);
-  Assert.deepEqual(snapshot.counts.slice(0, 4), [3, 2, 2, 0]);
+  Assert.deepEqual(Object.values(snapshot.values), [3, 2, 2, 0]);
 });
 
 add_task(async function test_keyed_boolean_multiple_samples() {
   let valid = [true, false, 0, 1, 2];
   let invalid = ["", "0", "1", ",2", "true", "false", "random"];
   let key = "somekey";
 
   let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_BOOLEAN");
   h.clear();
 
   // At least one invalid parameter, so no accumulation should happen here
   // Valid values in front of invalid.
   h.add(key, valid.concat(invalid));
   let s1 = h.snapshot(key);
   Assert.equal(s1.sum, 0);
-  Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+  Assert.deepEqual({}, s1.values);
 
   h.add(key, valid);
   let s = h.snapshot(key);
-  Assert.deepEqual(s.counts, [2, 3, 0]);
+  Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0});
   Assert.equal(s.sum, 3);
 });
 
 add_task(async function test_keyed_linear_multiple_samples() {
   // According to telemetry.mozilla.org/histogram-simulator, bucket at
   // index 1 of TELEMETRY_TEST_LINEAR has max value of 3.13K
   let valid = [0, 1, 5, 10, 268450000, 268450001, Math.pow(2, 31) + 1];
   let invalid = ["", "0", "1", "random"];
@@ -1063,24 +1005,24 @@ add_task(async function test_keyed_linea
   let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_LINEAR");
   h.clear();
 
   // At least one invalid paramater, so no accumulations.
   // Valid values in front of invalid.
    h.add(key, valid.concat(invalid));
    let s1 = h.snapshot(key);
    Assert.equal(s1.sum, 0);
-   Assert.equal(s1.counts.reduce((acc, cur) => acc + cur), 0);
+   Assert.deepEqual({}, s1.values);
 
   h.add(key, valid);
   let s2 = h.snapshot(key);
   // Values >= INT32_MAX are accumulated as INT32_MAX - 1
   Assert.equal(s2.sum, valid.reduce((acc, cur) => acc + cur) - 3);
-  Assert.equal(s2.counts[9], 3);
-  Assert.deepEqual(s2.counts.slice(0, 3), [1, 3, 0]);
+  Assert.deepEqual(s2.range, [1, 250000]);
+  Assert.deepEqual(s2.values, {0: 1, 1: 3, 250000: 3});
 });
 
 add_task(async function test_non_array_non_string_obj() {
   let invalid_obj = {
     "prop1": "someValue",
     "prop2": "someOtherValue",
     };
   let key = "someString";
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
@@ -73,17 +73,17 @@ var checkPingsSaved = async function(pin
       allFound = false;
     }
   }
 
   return allFound;
 };
 
 function histogramValueCount(h) {
-  return h.counts.reduce((a, b) => a + b);
+  return Object.values(h.values).reduce((a, b) => a + b, 0);
 }
 
 add_task(async function test_setup() {
   // Trigger a proper telemetry init.
   do_get_profile(true);
   // Make sure we don't generate unexpected pings due to pref changes.
   await setEmptyPrefWatchlist();
   Services.prefs.setBoolPref(TelemetryUtils.Preferences.HealthPingEnabled, true);
@@ -130,17 +130,17 @@ add_task(async function test_sendPending
     fakePingId("b", i);
     const id = await TelemetryController.submitExternalPing(TEST_TYPE_B, {});
     await setPingLastModified(id, now.getTime() + (i * 1000));
   }
 
   Assert.equal(TelemetrySend.pendingPingCount, TYPE_A_COUNT + TYPE_B_COUNT,
                "Should have correct pending ping count");
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 0, 0],
+  Assert.deepEqual(histSuccess.snapshot().values, {},
                "Should not have recorded any sending in histograms yet.");
   Assert.equal(histSendTimeSuccess.snapshot().sum, 0,
                "Should not have recorded any sending in histograms yet.");
   Assert.equal(histSendTimeFail.snapshot().sum, 0,
                "Should not have recorded any sending in histograms yet.");
 
   // Now enable sending to the ping server.
   now = fakeNow(futureDate(now, MS_IN_A_MINUTE));
@@ -161,17 +161,17 @@ add_task(async function test_sendPending
   PingServer.registerPingHandler(() => Assert.ok(false, "Should not have received any pings now"));
   let countByType = countPingTypes(pings);
 
   Assert.equal(countByType.get(TEST_TYPE_B), TYPE_B_COUNT,
                "Should have received the correct amount of type B pings");
   Assert.equal(countByType.get(TEST_TYPE_A), 10 - TYPE_B_COUNT,
                "Should have received the correct amount of type A pings");
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 10, 0],
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 10, 2: 0},
                "Should have recorded sending success in histograms.");
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 10,
                "Should have recorded successful send times in histograms.");
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0,
                "Should not have recorded any failed sending in histograms yet.");
 
   // As we hit the ping send limit and still have pending pings, a send tick should
   // be scheduled in a minute.
@@ -267,17 +267,17 @@ add_task(async function test_backoffTime
   }
 
   timerPromise = waitForTimer();
   await pingSendTimerCallback();
   [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
   Assert.equal(pingSendTimeout, MAX_BACKOFF_TIMEOUT, "Tick timeout should be capped");
   ++sendAttempts;
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [sendAttempts, 0, 0],
+  Assert.deepEqual(histSuccess.snapshot().values, {0: sendAttempts, 1: 0},
                "Should have recorded sending failure in histograms.");
   Assert.equal(histSendTimeSuccess.snapshot().sum, 0,
                "Should not have recorded any sending success in histograms yet.");
   Assert.greaterOrEqual(histSendTimeFail.snapshot().sum, 0,
                         "Should have recorded send failure times in histograms.");
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), sendAttempts,
                "Should have recorded send failure times in histograms.");
 
@@ -304,17 +304,17 @@ add_task(async function test_backoffTime
 
   Assert.equal(countByType.get(TEST_TYPE_C), 1, "Should have received the correct amount of type C pings");
   Assert.equal(countByType.get(TEST_TYPE_D), 1, "Should have received the correct amount of type D pings");
   Assert.equal(countByType.get(TEST_TYPE_E), 1, "Should have received the correct amount of type E pings");
 
   await TelemetrySend.testWaitOnOutgoingPings();
   Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings left");
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [sendAttempts, 3, 0],
+  Assert.deepEqual(histSuccess.snapshot().values, {0: sendAttempts, 1: 3, 2: 0},
                "Should have recorded sending failure in histograms.");
   Assert.greaterOrEqual(histSendTimeSuccess.snapshot().sum, 0,
                         "Should have recorded sending success in histograms.");
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 3,
                "Should have recorded sending success in histograms.");
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), sendAttempts,
                "Should have recorded send failure times in histograms.");
 
@@ -336,17 +336,17 @@ add_task(async function test_discardBigP
 
   // Submit a ping of a normal size and check that we don't count it in the histogram.
   await TelemetryController.submitExternalPing(TEST_PING_TYPE, { test: "test" });
   await TelemetrySend.testWaitOnOutgoingPings();
   await PingServer.promiseNextPing();
 
   Assert.equal(histSizeExceeded.snapshot().sum, 0, "Telemetry must report no oversized ping submitted.");
   Assert.equal(histDiscardedSize.snapshot().sum, 0, "Telemetry must report no oversized pings.");
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 1, 0], "Should have recorded sending success.");
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 1, 2: 0}, "Should have recorded sending success.");
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 1, "Should have recorded send success time.");
   Assert.greaterOrEqual(histSendTimeSuccess.snapshot().sum, 0, "Should have recorded send success time.");
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0, "Should not have recorded send failure time.");
 
   // Submit an oversized ping and check that it gets discarded.
   TelemetryHealthPing.testReset();
   // Ensure next ping has a 2 MB gzipped payload.
   fakeGzipCompressStringForNextPing(2 * 1024 * 1024);
@@ -357,19 +357,19 @@ add_task(async function test_discardBigP
 
   Assert.equal(ping.type, TelemetryHealthPing.HEALTH_PING_TYPE, "Should have received a health ping.");
   Assert.equal(ping.payload.reason, TelemetryHealthPing.Reason.IMMEDIATE, "Health ping should have the right reason.");
   Assert.deepEqual(ping.payload[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE],
     {[TEST_PING_TYPE]: 1}, "Should have recorded correct type of oversized ping.");
   Assert.deepEqual(ping.payload.os, TelemetryHealthPing.OsInfo, "Should have correct os info.");
 
   Assert.equal(histSizeExceeded.snapshot().sum, 1, "Telemetry must report 1 oversized ping submitted.");
-  Assert.equal(histDiscardedSize.snapshot().counts[2], 1, "Telemetry must report a 2MB, oversized, ping submitted.");
+  Assert.equal(histDiscardedSize.snapshot().values[2], 1, "Telemetry must report a 2MB, oversized, ping submitted.");
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 2, 0], "Should have recorded sending success.");
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 2, 2: 0}, "Should have recorded sending success.");
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 2, "Should have recorded send success time.");
   Assert.greaterOrEqual(histSendTimeSuccess.snapshot().sum, 0, "Should have recorded send success time.");
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0, "Should not have recorded send failure time.");
 });
 
 add_task(async function test_largeButWithinLimit() {
   const TEST_PING_TYPE = "test-ping-type";
 
@@ -379,17 +379,17 @@ add_task(async function test_largeButWit
   // Next ping will have a 900KB gzip payload.
   fakeGzipCompressStringForNextPing(900 * 1024);
   const LARGE_PAYLOAD = {"data": "empty on purpose - policy takes care of size"};
 
   await TelemetryController.submitExternalPing(TEST_PING_TYPE, LARGE_PAYLOAD);
   await TelemetrySend.testWaitOnOutgoingPings();
   await PingServer.promiseNextRequest();
 
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 1, 0], "Should have sent large ping.");
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 1, 2: 0}, "Should have sent large ping.");
 });
 
 add_task(async function test_evictedOnServerErrors() {
   const TEST_TYPE = "test-evicted";
 
   await TelemetrySend.reset();
 
   let histEvicted = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS");
@@ -409,17 +409,17 @@ add_task(async function test_evictedOnSe
   });
 
   // Clear the histogram and submit a ping.
   let pingId = await TelemetryController.submitExternalPing(TEST_TYPE, {});
   await TelemetrySend.testWaitOnOutgoingPings();
 
   Assert.equal(histEvicted.snapshot().sum, 1,
                "Telemetry must report a ping evicted due to server errors");
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 1, 0]);
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 1, 2: 0});
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 1);
   Assert.greaterOrEqual(histSendTimeSuccess.snapshot().sum, 0);
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0);
 
   // The ping should not be persisted.
   await Assert.rejects(TelemetryStorage.loadPendingPing(pingId),
                        /TelemetryStorage.loadPendingPing - no ping with id/,
                        "The ping must not be persisted.");
@@ -429,17 +429,17 @@ add_task(async function test_evictedOnSe
   pingId = await TelemetryController.submitExternalPing(TEST_TYPE, {});
 
   let ping = await PingServer.promiseNextPings(1);
   Assert.equal(ping[0].id, pingId, "The correct ping must be received");
 
   // We should not have updated the error histogram.
   await TelemetrySend.testWaitOnOutgoingPings();
   Assert.equal(histEvicted.snapshot().sum, 1, "Telemetry must report only one ping evicted due to server errors");
-  Assert.deepEqual(histSuccess.snapshot().counts, [0, 2, 0]);
+  Assert.deepEqual(histSuccess.snapshot().values, {0: 0, 1: 2, 2: 0});
   Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 2);
   Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0);
 });
 
 add_task(async function test_tooLateToSend() {
   Assert.ok(true, "TEST BEGIN");
   const TEST_TYPE = "test-too-late-to-send";
 
@@ -454,17 +454,17 @@ add_task(async function test_tooLateToSe
   const id = await TelemetryController.submitExternalPing(TEST_TYPE, {});
 
   // Triggering a shutdown should persist the pings
   await TelemetrySend.shutdown();
   const pendingPings = TelemetryStorage.getPendingPingList();
   Assert.equal(pendingPings.length, 1, "Should have a pending ping in storage");
   Assert.equal(pendingPings[0].id, id, "Should have pended our test's ping");
 
-  Assert.equal(Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").snapshot().counts[7], 1,
+  Assert.equal(Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").snapshot().values[7], 1,
     "Should have registered the failed attempt to send");
 
   await TelemetryStorage.reset();
   Assert.equal(TelemetrySend.pendingPingCount, 0, "Should clean up after yourself");
 });
 
 // Test that the current, non-persisted pending pings are properly saved on shutdown.
 add_task(async function test_persistCurrentPingsOnShutdown() {
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
@@ -474,31 +474,31 @@ add_task(async function test_pendingPing
                        "The oversized ping should have been pruned.");
   Assert.ok(!(await OS.File.exists(getSavePathForPingId(OVERSIZED_PING_ID))),
             "The ping should not be on the disk anymore.");
 
   // Make sure we're correctly updating the related histograms.
   h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot();
   Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the pending pings directory.");
   h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot();
-  Assert.equal(h.counts[2], 1, "Telemetry must report a 2MB, oversized, ping.");
+  Assert.equal(h.values[2], 1, "Telemetry must report a 2MB, oversized, ping.");
 
   // Save the ping again to check if it gets pruned when scanning the pings directory.
   await TelemetryStorage.savePendingPing(OVERSIZED_PING);
   expectedPrunedPings.push(OVERSIZED_PING_ID);
 
   // Scan the pending pings directory.
   await TelemetryController.testReset();
   await TelemetryStorage.testPendingQuotaTaskPromise();
   await checkPendingPings();
 
   // Make sure we're correctly updating the related histograms.
   h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot();
   Assert.equal(h.sum, 2, "Telemetry must report 1 oversized ping in the pending pings directory.");
   h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot();
-  Assert.equal(h.counts[2], 2, "Telemetry must report two 2MB, oversized, pings.");
+  Assert.equal(h.values[2], 2, "Telemetry must report two 2MB, oversized, pings.");
 
   Services.prefs.setBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, true);
 });
 
 add_task(async function teardown() {
   await PingServer.stop();
 });
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -325,40 +325,40 @@ function checkPayload(payload, reason, s
   const expected_flag = {
     range: [1, 2],
     bucket_count: 3,
     histogram_type: 3,
     values: {0: 1, 1: 0},
     sum: 0,
   };
   let flag = payload.histograms[TELEMETRY_TEST_FLAG];
-  Assert.equal(uneval(flag), uneval(expected_flag));
+  Assert.deepEqual(flag, expected_flag);
 
   // We should have a test count.
   const expected_count = {
     range: [1, 2],
     bucket_count: 3,
     histogram_type: 4,
     values: {0: 1, 1: 0},
     sum: 1,
   };
   let count = payload.histograms[TELEMETRY_TEST_COUNT];
-  Assert.equal(uneval(count), uneval(expected_count));
+  Assert.deepEqual(count, expected_count);
 
   // There should be one successful report from the previous telemetry ping.
   if (successfulPings > 0) {
     const expected_tc = {
       range: [1, 2],
       bucket_count: 3,
       histogram_type: 2,
       values: {0: 2, 1: successfulPings, 2: 0},
       sum: successfulPings,
     };
     let tc = payload.histograms[TELEMETRY_SUCCESS];
-    Assert.equal(uneval(tc), uneval(expected_tc));
+    Assert.deepEqual(tc, expected_tc);
   }
 
   // The ping should include data from memory reporters.  We can't check that
   // this data is correct, because we can't control the values returned by the
   // memory reporters.  But we can at least check that the data is there.
   //
   // It's important to check for the presence of reporters with a mix of units,
   // because MemoryTelemetry has separate logic for each one.  But we can't
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js
@@ -11,25 +11,25 @@ const KEYED_HIST = { id: "TELEMETRY_INVA
 
 var refObj = {}, refObj2 = {};
 
 var originalCount1, originalCount2, originalCount3;
 
 function run_test() {
   let histogram = Telemetry.getHistogramById(HIST_NAME);
   let snapshot = histogram.snapshot();
-  originalCount1 = snapshot.counts.reduce((a, b) => a += b);
+  originalCount1 = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   histogram = Telemetry.getHistogramById(HIST_NAME2);
   snapshot = histogram.snapshot();
-  originalCount2 = snapshot.counts.reduce((a, b) => a += b);
+  originalCount2 = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id);
   snapshot = histogram.snapshot(KEYED_HIST.key);
-  originalCount3 = snapshot.counts.reduce((a, b) => a += b);
+  originalCount3 = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   Assert.ok(!TelemetryStopwatch.start(3));
   Assert.ok(!TelemetryStopwatch.start({}));
   Assert.ok(!TelemetryStopwatch.start("", 3));
   Assert.ok(!TelemetryStopwatch.start("", ""));
   Assert.ok(!TelemetryStopwatch.start({}, {}));
 
   Assert.ok(TelemetryStopwatch.start("mark1"));
@@ -172,24 +172,24 @@ function run_test() {
   Assert.ok(!TelemetryStopwatch.cancelKeyed(KEYED_HIST.id, KEYED_HIST.key));
 
   finishTest();
 }
 
 function finishTest() {
   let histogram = Telemetry.getHistogramById(HIST_NAME);
   let snapshot = histogram.snapshot();
-  let newCount = snapshot.counts.reduce((a, b) => a += b);
+  let newCount = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   Assert.equal(newCount - originalCount1, 5, "The correct number of histograms were added for histogram 1.");
 
   histogram = Telemetry.getHistogramById(HIST_NAME2);
   snapshot = histogram.snapshot();
-  newCount = snapshot.counts.reduce((a, b) => a += b);
+  newCount = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   Assert.equal(newCount - originalCount2, 3, "The correct number of histograms were added for histogram 2.");
 
   histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id);
   snapshot = histogram.snapshot(KEYED_HIST.key);
-  newCount = snapshot.counts.reduce((a, b) => a += b);
+  newCount = Object.values(snapshot.values).reduce((a, b) => a += b, 0);
 
   Assert.equal(newCount - originalCount3, 2, "The correct number of histograms were added for histogram 3.");
 }
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryUtils.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryUtils.js
@@ -5,38 +5,34 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/UpdateUtils.jsm", this);
 
 add_task(async function testHistogramPacking() {
   const HISTOGRAM_SNAPSHOT = {
     sample_process: {
       HISTOGRAM_1_DATA: {
-        counts: [
-          1, 0, 0,
-        ],
-        ranges: [
-          0, 1, 2,
-        ],
-        max: 2,
-        min: 1,
+        range: [1, 2],
+        bucket_count: 3,
+        histogram_type: 4,
+        values: {
+          "0": 1,
+          "1": 0,
+        },
         sum: 1,
-        histogram_type: 4,
       },
       TELEMETRY_TEST_HISTOGRAM_2_DATA: {
-        counts: [
-          1, 0, 0,
-        ],
-        ranges: [
-          0, 1, 2,
-        ],
-        max: 2,
-        min: 1,
+        range: [1, 2],
+        bucket_count: 3,
+        histogram_type: 4,
+        values: {
+          "0": 1,
+          "1": 0,
+        },
         sum: 1,
-        histogram_type: 4,
       },
     },
   };
 
   const HISTOGRAM_1_DATA = {
     range: [1, 2],
     bucket_count: 3,
     histogram_type: 4,
@@ -78,52 +74,46 @@ add_task(async function testHistogramPac
             "Packed data must be correct.");
 });
 
 add_task(async function testKeyedHistogramPacking() {
   const KEYED_HISTOGRAM_SNAPSHOT = {
     sample_process: {
       HISTOGRAM_1_DATA: {
         someKey: {
-          counts: [
-            1, 0, 0,
-          ],
-          ranges: [
-            0, 1, 2,
-          ],
-          max: 2,
-          min: 1,
+          range: [1, 2],
+          bucket_count: 3,
+          histogram_type: 4,
+          values: {
+            "0": 1,
+            "1": 0,
+          },
           sum: 1,
-          histogram_type: 4,
         },
         otherKey: {
-          counts: [
-            1, 0, 0,
-          ],
-          ranges: [
-            0, 1, 2,
-          ],
-          max: 2,
-          min: 1,
+          range: [1, 2],
+          bucket_count: 3,
+          histogram_type: 4,
+          values: {
+            "0": 1,
+            "1": 0,
+          },
           sum: 1,
-          histogram_type: 4,
         },
       },
       TELEMETRY_TEST_HISTOGRAM_2_DATA: {
         someKey: {
-          counts: [
-            1, 0, 0,
-          ],
-          ranges: [
-            0, 1, 2,
-          ],
-          max: 2,
-          min: 1,
+          range: [1, 2],
+          bucket_count: 3,
+          histogram_type: 4,
+          values: {
+            "0": 1,
+            "1": 0,
+          },
           sum: 1,
-          histogram_type: 4,
         },
       },
     },
   };
 
   const someKey = {
     range: [1, 2],
     bucket_count: 3,