Bug 1366294 - Part 3 - Refactor TelemetryHistogram storage. r=chutten draft
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Thu, 08 Jun 2017 16:15:38 +0700
changeset 609060 cd8f4f1f85894f5d892bbd3d049eae94b6dd9723
parent 609059 f8d875a8d926dd886604e8056c7ac6396eb0f736
child 609061 2640fb6612eee8fd6ea488f849f1b7399758a62b
push id68499
push userbmo:chutten@mozilla.com
push dateFri, 14 Jul 2017 19:04:19 +0000
reviewerschutten
bugs1366294
milestone56.0a1
Bug 1366294 - Part 3 - Refactor TelemetryHistogram storage. r=chutten Previously we used the base::StatisticsRecorder object for storage by name. This is keyed by histogram name, which doesn't match our storage reality anymore. Instead we use a name to refer to a set of histogram instances that record data from different processes, as well as separating session and subsession data. In this re-write, we instead introduce the following lookup paths (managed in TelemetryHistogram.cpp): - Main storage: - (histogramId, processId, sessionOrSubsession) -> Histogram* - (histogramId, processId) -> KeyedHistogram* (this handles subsessions internally) - Lookup: - (histogramName) -> histogramId - (HistogramID) -> bool (is recording enabled for this histogram?) This is wrapped with a few lookup functions. This also allows us to keep HistogramIDs in the JS histogram instances now, instead of pointers to Histogram instances. That means Histogram instance life-time management is now properly contained inside TelemetryHistogram.cpp. MozReview-Commit-ID: 5yijGv7mc89
ipc/chromium/src/base/histogram.cc
ipc/chromium/src/base/histogram.h
toolkit/components/telemetry/TelemetryHistogram.cpp
toolkit/components/telemetry/gen-histogram-data.py
toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -253,30 +253,28 @@ Histogram::SampleSet::SizeOfExcludingThi
 
 Histogram::Histogram(Sample minimum, Sample maximum, size_t bucket_count)
   : sample_(),
     declared_min_(minimum),
     declared_max_(maximum),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
     ranges_(bucket_count + 1, 0),
-    range_checksum_(0),
-    recording_enabled_(true) {
+    range_checksum_(0) {
   Initialize();
 }
 
 Histogram::Histogram(TimeDelta minimum, TimeDelta maximum, size_t bucket_count)
   : sample_(),
     declared_min_(static_cast<int> (minimum.InMilliseconds())),
     declared_max_(static_cast<int> (maximum.InMilliseconds())),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
     ranges_(bucket_count + 1, 0),
-    range_checksum_(0),
-    recording_enabled_(true) {
+    range_checksum_(0) {
   Initialize();
 }
 
 Histogram::~Histogram() {
   // Just to make sure most derived class did this properly...
   DCHECK(ValidateBucketRanges());
 }
 
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -177,25 +177,21 @@ class Histogram {
                                Sample maximum,
                                size_t bucket_count,
                                Flags flags);
   static Histogram* FactoryTimeGet(base::TimeDelta minimum,
                                    base::TimeDelta maximum,
                                    size_t bucket_count,
                                    Flags flags);
 
+  virtual ~Histogram();
+
   void Add(int value);
   void Subtract(int value);
 
-  // TODO: Currently recording_enabled_ is not used by any Histogram class, but
-  //       rather examined only by the telemetry code (via IsRecordingEnabled).
-  //       Move handling to Histogram's Add() etc after simplifying Histogram.
-  void SetRecordingEnabled(bool aEnabled) { recording_enabled_ = aEnabled; };
-  bool IsRecordingEnabled() const { return recording_enabled_; };
-
   // This method is an interface, used only by BooleanHistogram.
   virtual void AddBoolean(bool value);
 
   // Accept a TimeDelta to increment.
   void AddTime(TimeDelta time) {
     Add(static_cast<int>(time.InMilliseconds()));
   }
 
@@ -243,18 +239,16 @@ class Histogram {
                                                 size_t bucket_count);
   // Return true iff the range_checksum_ matches current ranges_ vector.
   bool HasValidRangeChecksum() const;
 
  protected:
   Histogram(Sample minimum, Sample maximum, size_t bucket_count);
   Histogram(TimeDelta minimum, TimeDelta maximum, size_t bucket_count);
 
-  virtual ~Histogram();
-
   // Initialize ranges_ mapping.
   void InitializeBucketRange();
 
   // Method to override to skip the display of the i'th bucket if it's empty.
   virtual bool PrintEmptyBucket(size_t index) const;
 
   //----------------------------------------------------------------------------
   // Methods to override to create histogram with different bucket widths.
@@ -325,19 +319,16 @@ class Histogram {
   // The dimension of ranges_ is bucket_count + 1.
   Ranges ranges_;
 
   // For redundancy, we store a checksum of all the sample ranges when ranges
   // are generated.  If ever there is ever a difference, then the histogram must
   // have been corrupted.
   uint32_t range_checksum_;
 
-  // When false, new samples are completely ignored.
-  mozilla::Atomic<bool, mozilla::Relaxed> recording_enabled_;
-
   DISALLOW_COPY_AND_ASSIGN(Histogram);
 };
 
 //------------------------------------------------------------------------------
 
 // LinearHistogram is a more traditional histogram, with evenly spaced
 // buckets.
 class LinearHistogram : public Histogram {
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -31,19 +31,25 @@ using base::Histogram;
 using base::BooleanHistogram;
 using base::CountHistogram;
 using base::FlagHistogram;
 using base::LinearHistogram;
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
 using mozilla::Telemetry::Accumulation;
 using mozilla::Telemetry::KeyedAccumulation;
+using mozilla::Telemetry::HistogramID;
 using mozilla::Telemetry::ProcessID;
+using mozilla::Telemetry::HistogramCount;
 using mozilla::Telemetry::Common::LogToBrowserConsole;
 using mozilla::Telemetry::Common::RecordedProcessType;
+using mozilla::Telemetry::Common::AutoHashtable;
+using mozilla::Telemetry::Common::IsExpiredVersion;
+using mozilla::Telemetry::Common::CanRecordDataset;
+using mozilla::Telemetry::Common::IsInDataset;
 
 namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
@@ -93,111 +99,223 @@ namespace TelemetryIPCAccumulator = mozi
 // means that this file is not guaranteed race-free.
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
-#define EXPIRED_ID "__expired__"
-#define SUBSESSION_HISTOGRAM_PREFIX "sub#"
-#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
-#define CONTENT_HISTOGRAM_SUFFIX "#content"
-#define GPU_HISTOGRAM_SUFFIX "#gpu"
-#define EXTENSION_HISTOGRAM_SUFFIX "#extension"
-
 namespace {
 
-using mozilla::Telemetry::Common::AutoHashtable;
-using mozilla::Telemetry::Common::IsExpiredVersion;
-using mozilla::Telemetry::Common::CanRecordDataset;
-using mozilla::Telemetry::Common::IsInDataset;
-
-class KeyedHistogram;
-
-typedef nsBaseHashtableET<nsDepCharHashKey, mozilla::Telemetry::HistogramID>
-          CharPtrEntryType;
-
-typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
-
-typedef nsClassHashtable<nsCStringHashKey, KeyedHistogram>
-          KeyedHistogramMapType;
+typedef nsDataHashtable<nsCStringHashKey, HistogramID> StringToHistogramIdMap;
 
 // Hardcoded probes
 struct HistogramInfo {
   uint32_t min;
   uint32_t max;
   uint32_t bucketCount;
   uint32_t histogramType;
-  uint32_t id_offset;
+  uint32_t name_offset;
   uint32_t expiration_offset;
   uint32_t dataset;
   uint32_t label_index;
   uint32_t label_count;
   RecordedProcessType record_in_processes;
   bool keyed;
 
-  const char *id() const;
+  const char *name() const;
   const char *expiration() const;
   nsresult label_id(const char* label, uint32_t* labelId) const;
 };
 
 enum reflectStatus {
   REFLECT_OK,
-  REFLECT_CORRUPT,
   REFLECT_FAILURE
 };
 
+enum class SessionType {
+  Session = 0,
+  Subsession = 1,
+  Count,
+};
+
+class KeyedHistogram {
+public:
+  KeyedHistogram(HistogramID id, const HistogramInfo& info);
+  nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
+  Histogram* GetHistogram(const nsCString& name, bool subsession);
+  uint32_t GetHistogramType() const { return mHistogramInfo.histogramType; }
+  nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
+  nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
+                         bool subsession, bool clearSubsession);
+
+  nsresult Add(const nsCString& key, uint32_t aSample);
+  void Clear(bool subsession);
+
+  HistogramID GetHistogramID() const { return mId; }
+
+private:
+  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
+  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
+  KeyedHistogramMapType mHistogramMap;
+#if !defined(MOZ_WIDGET_ANDROID)
+  KeyedHistogramMapType mSubsessionMap;
+#endif
+
+  static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry,
+                                    JSContext* cx,
+                                    JS::Handle<JSObject*> obj);
+
+  const HistogramID mId;
+  const HistogramInfo& mHistogramInfo;
+};
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE STATE, SHARED BY ALL THREADS
 
 namespace {
 
 // Set to true once this global state has been initialized
 bool gInitDone = false;
 
+// Whether we are collecting the base, opt-out, Histogram data.
 bool gCanRecordBase = false;
+// Whether we are collecting the extended, opt-in, Histogram data.
 bool gCanRecordExtended = false;
 
-HistogramMapType gHistogramMap(mozilla::Telemetry::HistogramCount);
-
-KeyedHistogramMapType gKeyedHistograms;
+// The storage for actual Histogram instances.
+// We use separate ones for plain and keyed histograms.
+Histogram* gHistogramStorage[HistogramCount][uint32_t(ProcessID::Count)][uint32_t(SessionType::Count)] = {};
+// Keyed histograms internally map string keys to individual Histogram instances.
+// KeyedHistogram keeps track of session & subsession histograms internally.
+KeyedHistogram* gKeyedHistogramStorage[HistogramCount][uint32_t(ProcessID::Count)] = {};
 
-bool gCorruptHistograms[mozilla::Telemetry::HistogramCount];
+// Cache of histogram name to a histogram id.
+StringToHistogramIdMap gNameToHistogramIDMap(HistogramCount);
+
+// To simplify logic below we use a single histogram instance for all expired histograms.
+Histogram* gExpiredHistogram = nullptr;
 
-// This is for gHistograms, gHistogramStringTable
+// This tracks whether recording is enabled for specific histograms.
+// To utilize C++ initialization rules, we invert the meaning to "disabled".
+bool gHistogramRecordingDisabled[HistogramCount] = {};
+
+// This is for gHistogramInfos, gHistogramStringTable
 #include "TelemetryHistogramData.inc"
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE CONSTANTS
 
 namespace {
 
 // List of histogram IDs which should have recording disabled initially.
-const mozilla::Telemetry::HistogramID kRecordingInitiallyDisabledIDs[] = {
+const HistogramID kRecordingInitiallyDisabledIDs[] = {
   mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
 
   // The array must not be empty. Leave these item here.
   mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
   mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
 };
 
 } // namespace
 
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// The core storage access functions.
+// They wrap access to the histogram storage and lookup caches.
+
+namespace {
+
+// Factory function for histogram instances.
+Histogram*
+internal_CreateHistogramInstance(const HistogramInfo& info);
+
+bool
+internal_IsHistogramEnumId(HistogramID aID)
+{
+  static_assert(((HistogramID)-1 > 0), "ID should be unsigned.");
+  return aID < HistogramCount;
+}
+
+// Look up a plain histogram by id.
+Histogram*
+internal_GetHistogramById(HistogramID histogramId, ProcessID processId, SessionType sessionType,
+                          bool instantiate = true)
+{
+  MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
+  MOZ_ASSERT(!gHistogramInfos[histogramId].keyed);
+  MOZ_ASSERT(processId < ProcessID::Count);
+  MOZ_ASSERT(sessionType < SessionType::Count);
+
+  Histogram* h = gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)];
+  if (h || !instantiate) {
+    return h;
+  }
+
+  const HistogramInfo& info = gHistogramInfos[histogramId];
+  h = internal_CreateHistogramInstance(info);
+  MOZ_ASSERT(h);
+  gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)] = h;
+  return h;
+}
+
+// Look up a keyed histogram by id.
+KeyedHistogram*
+internal_GetKeyedHistogramById(HistogramID histogramId, ProcessID processId,
+                               bool instantiate = true)
+{
+  MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
+  MOZ_ASSERT(gHistogramInfos[histogramId].keyed);
+  MOZ_ASSERT(processId < ProcessID::Count);
+
+  KeyedHistogram* kh = gKeyedHistogramStorage[histogramId][uint32_t(processId)];
+  if (kh || !instantiate) {
+    return kh;
+  }
+
+  const HistogramInfo& info = gHistogramInfos[histogramId];
+  kh = new KeyedHistogram(histogramId, info);
+  gKeyedHistogramStorage[histogramId][uint32_t(processId)] = kh;
+
+  return kh;
+}
+
+// Look up a histogram id from a histogram name.
+nsresult
+internal_GetHistogramIdByName(const nsACString& name, HistogramID* id)
+{
+  const bool found = gNameToHistogramIDMap.Get(name, id);
+  if (!found) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  return NS_OK;
+}
+
+// Clear a histogram from storage.
+void
+internal_ClearHistogramById(HistogramID histogramId, ProcessID processId, SessionType sessionType)
+{
+  delete gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)];
+  gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)] = nullptr;
+}
+
+}
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Misc small helpers
 
 namespace {
 
@@ -206,69 +324,76 @@ internal_CanRecordBase() {
   return gCanRecordBase;
 }
 
 bool
 internal_CanRecordExtended() {
   return gCanRecordExtended;
 }
 
-bool
-internal_IsHistogramEnumId(mozilla::Telemetry::HistogramID aID)
-{
-  static_assert(((mozilla::Telemetry::HistogramID)-1 > 0), "ID should be unsigned.");
-  return aID < mozilla::Telemetry::HistogramCount;
-}
-
 // Note: this is completely unrelated to mozilla::IsEmpty.
 bool
 internal_IsEmpty(const Histogram *h)
 {
   Histogram::SampleSet ss;
   h->SnapshotSample(&ss);
   return ss.counts(0) == 0 && ss.sum() == 0;
 }
 
 bool
-internal_IsExpired(const Histogram *histogram)
+internal_IsExpired(Histogram* h)
+{
+  return h == gExpiredHistogram;
+}
+
+void
+internal_SetHistogramRecordingEnabled(HistogramID id, bool aEnabled)
 {
-  return histogram->histogram_name() == EXPIRED_ID;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+  gHistogramRecordingDisabled[id] = !aEnabled;
+}
+
+bool
+internal_IsRecordingEnabled(HistogramID id)
+{
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+  return !gHistogramRecordingDisabled[id];
 }
 
 nsresult
 internal_GetRegisteredHistogramIds(bool keyed, uint32_t dataset,
                                    uint32_t *aCount, char*** aHistograms)
 {
   nsTArray<char*> collection;
 
-  for (const auto & h : gHistograms) {
+  for (const auto & h : gHistogramInfos) {
     if (IsExpiredVersion(h.expiration()) ||
         h.keyed != keyed ||
         !IsInDataset(h.dataset, dataset)) {
       continue;
     }
 
-    const char* id = h.id();
+    const char* id = h.name();
     const size_t len = strlen(id);
     collection.AppendElement(static_cast<char*>(nsMemory::Clone(id, len+1)));
   }
 
   const size_t bytes = collection.Length() * sizeof(char*);
   char** histograms = static_cast<char**>(moz_xmalloc(bytes));
   memcpy(histograms, collection.Elements(), bytes);
   *aHistograms = histograms;
   *aCount = collection.Length();
 
   return NS_OK;
 }
 
 const char *
-HistogramInfo::id() const
+HistogramInfo::name() const
 {
-  return &gHistogramStringTable[this->id_offset];
+  return &gHistogramStringTable[this->name_offset];
 }
 
 const char *
 HistogramInfo::expiration() const
 {
   return &gHistogramStringTable[this->expiration_offset];
 }
 
@@ -303,457 +428,115 @@ HistogramInfo::label_id(const char* labe
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram Get, Add, Clone, Clear functions
 
 namespace {
 
 nsresult
-internal_CheckHistogramArguments(uint32_t histogramType,
-                                 uint32_t min, uint32_t max,
-                                 uint32_t bucketCount, bool haveOptArgs)
+internal_CheckHistogramArguments(const HistogramInfo& info)
 {
-  if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
-      && histogramType != nsITelemetry::HISTOGRAM_FLAG
-      && histogramType != nsITelemetry::HISTOGRAM_COUNT) {
-    // The min, max & bucketCount arguments are not optional for this type.
-    if (!haveOptArgs)
+  if (info.histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
+      && info.histogramType != nsITelemetry::HISTOGRAM_FLAG
+      && info.histogramType != nsITelemetry::HISTOGRAM_COUNT) {
+    // Sanity checks for histogram parameters.
+    if (info.min >= info.max) {
       return NS_ERROR_ILLEGAL_VALUE;
-
-    // Sanity checks for histogram parameters.
-    if (min >= max)
-      return NS_ERROR_ILLEGAL_VALUE;
+    }
 
-    if (bucketCount <= 2)
+    if (info.bucketCount <= 2) {
       return NS_ERROR_ILLEGAL_VALUE;
+    }
 
-    if (min < 1)
+    if (info.min < 1) {
       return NS_ERROR_ILLEGAL_VALUE;
+    }
   }
 
   return NS_OK;
 }
 
-/*
- * min, max & bucketCount are optional for boolean, flag & count histograms.
- * haveOptArgs has to be set if the caller provides them.
- */
-nsresult
-internal_HistogramGet(const char *name, const char *expiration,
-                      uint32_t histogramType, uint32_t min, uint32_t max,
-                      uint32_t bucketCount, bool haveOptArgs,
-                      Histogram **result)
+Histogram*
+internal_CreateHistogramInstance(const HistogramInfo& passedInfo)
 {
-  nsresult rv = internal_CheckHistogramArguments(histogramType, min, max,
-                                                 bucketCount, haveOptArgs);
-  if (NS_FAILED(rv)) {
-    return rv;
+  if (NS_FAILED(internal_CheckHistogramArguments(passedInfo))) {
+    MOZ_ASSERT(false, "Failed histogram argument checks.");
+    return nullptr;
   }
 
-  if (IsExpiredVersion(expiration)) {
-    name = EXPIRED_ID;
-    min = 1;
-    max = 2;
-    bucketCount = 3;
-    histogramType = nsITelemetry::HISTOGRAM_LINEAR;
+  // To keep the code simple we map all the calls to expired histograms to the same histogram instance.
+  // We create that instance lazily when needed.
+  const bool isExpired = IsExpiredVersion(passedInfo.expiration());
+  HistogramInfo info = passedInfo;
+
+  if (isExpired) {
+    if (gExpiredHistogram) {
+      return gExpiredHistogram;
+    }
+
+    info.min = 1;
+    info.max = 2;
+    info.bucketCount = 3;
+    info.histogramType = nsITelemetry::HISTOGRAM_LINEAR;
   }
 
-  switch (histogramType) {
+  Histogram::Flags flags = Histogram::kNoFlags;
+  Histogram* h = nullptr;
+  switch (info.histogramType) {
   case nsITelemetry::HISTOGRAM_EXPONENTIAL:
-    *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
+    h = Histogram::FactoryGet(info.min, info.max, info.bucketCount, flags);
     break;
   case nsITelemetry::HISTOGRAM_LINEAR:
   case nsITelemetry::HISTOGRAM_CATEGORICAL:
-    *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
+    h = LinearHistogram::FactoryGet(info.min, info.max, info.bucketCount, flags);
     break;
   case nsITelemetry::HISTOGRAM_BOOLEAN:
-    *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    h = BooleanHistogram::FactoryGet(flags);
     break;
   case nsITelemetry::HISTOGRAM_FLAG:
-    *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    h = FlagHistogram::FactoryGet(flags);
     break;
   case nsITelemetry::HISTOGRAM_COUNT:
-    *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    h = CountHistogram::FactoryGet(flags);
     break;
   default:
-    NS_ASSERTION(false, "Invalid histogram type");
-    return NS_ERROR_INVALID_ARG;
-  }
-  return NS_OK;
-}
-
-// Read the process type from the given histogram name. The process type, if
-// one exists, is embedded in a suffix.
-mozilla::Telemetry::ProcessID
-GetProcessFromName(const nsACString& aString)
-{
-  if (StringEndsWith(aString, NS_LITERAL_CSTRING(CONTENT_HISTOGRAM_SUFFIX))) {
-    return ProcessID::Content;
-  }
-  if (StringEndsWith(aString, NS_LITERAL_CSTRING(GPU_HISTOGRAM_SUFFIX))) {
-    return ProcessID::Gpu;
-  }
-  if (StringEndsWith(aString, NS_LITERAL_CSTRING(EXTENSION_HISTOGRAM_SUFFIX))) {
-    return ProcessID::Extension;
-  }
-  return ProcessID::Parent;
-}
-
-const char*
-SuffixForProcessType(mozilla::Telemetry::ProcessID aProcessType)
-{
-  switch (aProcessType) {
-    case ProcessID::Parent:
-      return nullptr;
-    case ProcessID::Content:
-      return CONTENT_HISTOGRAM_SUFFIX;
-    case ProcessID::Gpu:
-      return GPU_HISTOGRAM_SUFFIX;
-    case ProcessID::Extension:
-      return EXTENSION_HISTOGRAM_SUFFIX;
-    default:
-      MOZ_ASSERT_UNREACHABLE("unknown process type");
-      return nullptr;
-  }
-}
-
-CharPtrEntryType*
-internal_GetHistogramMapEntry(const char* aName)
-{
-  nsDependentCString name(aName);
-  ProcessID process = GetProcessFromName(name);
-  const char* suffix = SuffixForProcessType(process);
-  if (!suffix) {
-    return gHistogramMap.GetEntry(aName);
+    MOZ_ASSERT(false, "Invalid histogram type");
+    return nullptr;
   }
 
-  auto root = Substring(name, 0, name.Length() - strlen(suffix));
-  return gHistogramMap.GetEntry(PromiseFlatCString(root).get());
+  if (isExpired) {
+    gExpiredHistogram = h;
+  }
+
+  return h;
 }
 
 nsresult
-internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::HistogramID *id)
-{
-  if (!gInitDone) {
-    return NS_ERROR_FAILURE;
-  }
-
-  CharPtrEntryType *entry = internal_GetHistogramMapEntry(name);
-  if (!entry) {
-    return NS_ERROR_INVALID_ARG;
-  }
-  *id = entry->mData;
-  return NS_OK;
-}
-
-// O(1) histogram lookup by numeric id
-nsresult
-internal_GetHistogramByEnumId(mozilla::Telemetry::HistogramID id, Histogram **ret,
-                              ProcessID aProcessType)
+internal_HistogramAdd(Histogram& histogram,
+                      const HistogramID id,
+                      int32_t value)
 {
-  static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  static Histogram* knownContentHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  static Histogram* knownGPUHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  static Histogram* knownExtensionHistograms[mozilla::Telemetry::HistogramCount] = {0};
-
-  Histogram** knownList = nullptr;
-
-  switch (aProcessType) {
-  case ProcessID::Parent:
-    knownList = knownHistograms;
-    break;
-  case ProcessID::Content:
-    knownList = knownContentHistograms;
-    break;
-  case ProcessID::Gpu:
-    knownList = knownGPUHistograms;
-    break;
-  case ProcessID::Extension:
-    knownList = knownExtensionHistograms;
-    break;
-  default:
-    MOZ_ASSERT_UNREACHABLE("unknown process type");
-    return NS_ERROR_FAILURE;
-  }
-
-  Histogram* h = knownList[id];
-  if (h) {
-    *ret = h;
+  // Check if we are allowed to record the data.
+  bool canRecordDataset = CanRecordDataset(gHistogramInfos[id].dataset,
+                                           internal_CanRecordBase(),
+                                           internal_CanRecordExtended());
+  if (!canRecordDataset || !internal_IsRecordingEnabled(id)) {
     return NS_OK;
   }
 
-  const HistogramInfo &p = gHistograms[id];
-  if (p.keyed) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsAutoCString histogramName;
-  histogramName.Append(p.id());
-  if (const char* suffix = SuffixForProcessType(aProcessType)) {
-    histogramName.AppendASCII(suffix);
-  }
-
-  nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
-                                      p.histogramType, p.min, p.max,
-                                      p.bucketCount, true, &h);
-  if (NS_FAILED(rv))
-    return rv;
-
-#ifdef DEBUG
-  // Check that the C++ Histogram code computes the same ranges as the
-  // Python histogram code.
-  if (!IsExpiredVersion(p.expiration())) {
-    const struct bounds &b = gBucketLowerBoundIndex[id];
-    if (b.length != 0) {
-      MOZ_ASSERT(size_t(b.length) == h->bucket_count(),
-                 "C++/Python bucket # mismatch");
-      for (int i = 0; i < b.length; ++i) {
-        MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
-                   "C++/Python bucket mismatch");
-      }
-    }
-  }
-#endif
-
-  knownList[id] = h;
-  *ret = h;
-  return NS_OK;
-}
-
-nsresult
-internal_GetHistogramByName(const nsACString &name, Histogram **ret)
-{
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv
-    = internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  ProcessID process = GetProcessFromName(name);
-  rv = internal_GetHistogramByEnumId(id, ret, process);
-  if (NS_FAILED(rv))
-    return rv;
-
-  return NS_OK;
-}
-
-
-#if !defined(MOZ_WIDGET_ANDROID)
-
-/**
- * This clones a histogram |existing| with the id |existingId| to a
- * new histogram with the name |newName|.
- * For simplicity this is limited to registered histograms.
- */
-Histogram*
-internal_CloneHistogram(const nsACString& newName,
-                        mozilla::Telemetry::HistogramID existingId,
-                        Histogram& existing)
-{
-  const HistogramInfo &info = gHistograms[existingId];
-  Histogram *clone = nullptr;
-  nsresult rv;
-
-  rv = internal_HistogramGet(PromiseFlatCString(newName).get(),
-                             info.expiration(),
-                             info.histogramType, existing.declared_min(),
-                             existing.declared_max(), existing.bucket_count(),
-                             true, &clone);
-  if (NS_FAILED(rv)) {
-    return nullptr;
-  }
-
-  Histogram::SampleSet ss;
-  existing.SnapshotSample(&ss);
-  clone->AddSampleSet(ss);
-
-  return clone;
-}
-
-ProcessID
-GetProcessFromName(const std::string& aString)
-{
-  nsDependentCString string(aString.c_str(), aString.length());
-  return GetProcessFromName(string);
-}
-
-Histogram*
-internal_GetSubsessionHistogram(Histogram& existing)
-{
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv
-    = internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
-  if (NS_FAILED(rv) || gHistograms[id].keyed) {
-    return nullptr;
-  }
-
-  static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
-  static Histogram* subsessionContent[mozilla::Telemetry::HistogramCount] = {};
-  static Histogram* subsessionGPU[mozilla::Telemetry::HistogramCount] = {};
-  static Histogram* subsessionExtension[mozilla::Telemetry::HistogramCount] = {};
-
-  Histogram** cache = nullptr;
-
-  ProcessID process = GetProcessFromName(existing.histogram_name());
-  switch (process) {
-  case ProcessID::Parent:
-    cache = subsession;
-    break;
-  case ProcessID::Content:
-    cache = subsessionContent;
-    break;
-  case ProcessID::Gpu:
-    cache = subsessionGPU;
-    break;
-  case ProcessID::Extension:
-    cache = subsessionExtension;
-    break;
-  default:
-    MOZ_ASSERT_UNREACHABLE("unknown process type");
-    return nullptr;
-  }
-
-  if (Histogram* cached = cache[id]) {
-    return cached;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
-  nsDependentCString existingName(gHistograms[id].id());
-  if (StringBeginsWith(existingName, prefix)) {
-    return nullptr;
-  }
-
-  nsCString subsessionName(prefix);
-  subsessionName.Append(existing.histogram_name().c_str());
-
-  Histogram* clone = internal_CloneHistogram(subsessionName, id, existing);
-  cache[id] = clone;
-  return clone;
-}
-#endif
-
-nsresult
-internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
-{
-  // Check if we are allowed to record the data.
-  bool canRecordDataset = CanRecordDataset(dataset,
-                                           internal_CanRecordBase(),
-                                           internal_CanRecordExtended());
-  if (!canRecordDataset || !histogram.IsRecordingEnabled()) {
-    return NS_OK;
-  }
-
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = internal_GetSubsessionHistogram(histogram)) {
-    subsession->Add(value);
-  }
-#endif
-
   // It is safe to add to the histogram now: the subsession histogram was already
   // cloned from this so we won't add the sample twice.
   histogram.Add(value);
 
   return NS_OK;
 }
 
-nsresult
-internal_HistogramAdd(Histogram& histogram, int32_t value)
-{
-  uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
-  // We only really care about the dataset of the histogram if we are not recording
-  // extended telemetry. Otherwise, we always record histogram data.
-  if (!internal_CanRecordExtended()) {
-    mozilla::Telemetry::HistogramID id;
-    nsresult rv
-      = internal_GetHistogramEnumId(histogram.histogram_name().c_str(), &id);
-    if (NS_FAILED(rv)) {
-      // If we can't look up the dataset, it might be because the histogram was added
-      // at runtime. Since we're not recording extended telemetry, bail out.
-      return NS_OK;
-    }
-    dataset = gHistograms[id].dataset;
-  }
-
-  return internal_HistogramAdd(histogram, value, dataset);
-}
-
-void
-internal_HistogramClear(Histogram& aHistogram, bool onlySubsession)
-{
-  MOZ_ASSERT(XRE_IsParentProcess());
-  if (!XRE_IsParentProcess()) {
-    return;
-  }
-  if (!onlySubsession) {
-    aHistogram.Clear();
-  }
-
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) {
-    subsession->Clear();
-  }
-#endif
-}
-
 } // namespace
 
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
-// PRIVATE: Histogram corruption helpers
-
-namespace {
-
-void internal_Accumulate(mozilla::Telemetry::HistogramID aHistogram, uint32_t aSample);
-
-void
-internal_IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
-{
-  for (auto h : hs) {
-    mozilla::Telemetry::HistogramID id;
-    nsresult rv = internal_GetHistogramEnumId(h->histogram_name().c_str(), &id);
-    // This histogram isn't a static histogram, just ignore it.
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    if (gCorruptHistograms[id]) {
-      continue;
-    }
-
-    Histogram::SampleSet ss;
-    h->SnapshotSample(&ss);
-
-    Histogram::Inconsistencies check = h->FindCorruption(ss);
-    bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
-
-    if (corrupt) {
-      mozilla::Telemetry::HistogramID corruptID = mozilla::Telemetry::HistogramCount;
-      if (check & Histogram::RANGE_CHECKSUM_ERROR) {
-        corruptID = mozilla::Telemetry::RANGE_CHECKSUM_ERRORS;
-      } else if (check & Histogram::BUCKET_ORDER_ERROR) {
-        corruptID = mozilla::Telemetry::BUCKET_ORDER_ERRORS;
-      } else if (check & Histogram::COUNT_HIGH_ERROR) {
-        corruptID = mozilla::Telemetry::TOTAL_COUNT_HIGH_ERRORS;
-      } else if (check & Histogram::COUNT_LOW_ERROR) {
-        corruptID = mozilla::Telemetry::TOTAL_COUNT_LOW_ERRORS;
-      }
-      internal_Accumulate(corruptID, 1);
-    }
-
-    gCorruptHistograms[id] = corrupt;
-  }
-}
-
-} // namespace
-
-
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram reflection helpers
 
 namespace {
 
 bool
@@ -768,21 +551,16 @@ internal_FillRanges(JSContext *cx, JS::H
   return true;
 }
 
 enum reflectStatus
 internal_ReflectHistogramAndSamples(JSContext *cx,
                                     JS::Handle<JSObject*> obj, Histogram *h,
                                     const Histogram::SampleSet &ss)
 {
-  // We don't want to reflect corrupt histograms.
-  if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
-    return REFLECT_CORRUPT;
-  }
-
   if (!(JS_DefineProperty(cx, obj, "min",
                           h->declared_min(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "max",
                              h->declared_max(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "histogram_type",
                              h->histogram_type(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "sum",
                              double(ss.sum()), JSPROP_ENUMERATE))) {
@@ -821,106 +599,47 @@ internal_ReflectHistogramSnapshot(JSCont
                                   JS::Handle<JSObject*> obj, Histogram *h)
 {
   Histogram::SampleSet ss;
   h->SnapshotSample(&ss);
   return internal_ReflectHistogramAndSamples(cx, obj, h, ss);
 }
 
 bool
-internal_ShouldReflectHistogram(Histogram *h)
+internal_ShouldReflectHistogram(Histogram* h, HistogramID id)
 {
-  const char *name = h->histogram_name().c_str();
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv = internal_GetHistogramEnumId(name, &id);
-  if (NS_FAILED(rv)) {
-    // GetHistogramEnumId generally should not fail.  But a lookup
-    // failure shouldn't prevent us from reflecting histograms into JS.
-    //
-    // However, these two histograms are created by Histogram itself for
-    // tracking corruption.  We have our own histograms for that, so
-    // ignore these two.
-    if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
-        || strcmp(name, "Histogram.InconsistentCountLow") == 0) {
-      return false;
-    }
-    return true;
+  // Only count & flag histograms are serialized when they are empty.
+  // This has historical reasons, changing this will require downstream changes.
+  // The cheaper path here is to just deprecate flag histograms in favor
+  // of scalars.
+  uint32_t type = gHistogramInfos[id].histogramType;
+  if (internal_IsEmpty(h) && (type != nsITelemetry::HISTOGRAM_FLAG)) {
+    return false;
   }
-  return !gCorruptHistograms[id];
+
+  return true;
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: class KeyedHistogram
 
 namespace {
 
-class KeyedHistogram {
-public:
-  KeyedHistogram(const nsACString &name, const nsACString &expiration,
-                 uint32_t histogramType, uint32_t min, uint32_t max,
-                 uint32_t bucketCount, uint32_t dataset);
-  nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
-  Histogram* GetHistogram(const nsCString& name, bool subsession);
-  uint32_t GetHistogramType() const { return mHistogramType; }
-  nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
-  nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
-                         bool subsession, bool clearSubsession);
-
-  void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; };
-  bool IsRecordingEnabled() const { return mRecordingEnabled; };
-
-  nsresult Add(const nsCString& key, uint32_t aSample);
-  void Clear(bool subsession);
-
-  nsresult GetEnumId(mozilla::Telemetry::HistogramID& id);
-
-private:
-  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
-  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
-  KeyedHistogramMapType mHistogramMap;
-#if !defined(MOZ_WIDGET_ANDROID)
-  KeyedHistogramMapType mSubsessionMap;
-#endif
-
-  static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry,
-                                    JSContext* cx,
-                                    JS::Handle<JSObject*> obj);
-
-  const nsCString mName;
-  const nsCString mExpiration;
-  const uint32_t mHistogramType;
-  const uint32_t mMin;
-  const uint32_t mMax;
-  const uint32_t mBucketCount;
-  const uint32_t mDataset;
-  mozilla::Atomic<bool, mozilla::Relaxed> mRecordingEnabled;
-};
-
-KeyedHistogram::KeyedHistogram(const nsACString &name,
-                               const nsACString &expiration,
-                               uint32_t histogramType,
-                               uint32_t min, uint32_t max,
-                               uint32_t bucketCount, uint32_t dataset)
+KeyedHistogram::KeyedHistogram(HistogramID id, const HistogramInfo& info)
   : mHistogramMap()
 #if !defined(MOZ_WIDGET_ANDROID)
   , mSubsessionMap()
 #endif
-  , mName(name)
-  , mExpiration(expiration)
-  , mHistogramType(histogramType)
-  , mMin(min)
-  , mMax(max)
-  , mBucketCount(bucketCount)
-  , mDataset(dataset)
-  , mRecordingEnabled(true)
+  , mId(id)
+  , mHistogramInfo(info)
 {
 }
 
 nsresult
 KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram,
                              bool subsession)
 {
 #if !defined(MOZ_WIDGET_ANDROID)
@@ -929,32 +648,19 @@ KeyedHistogram::GetHistogram(const nsCSt
   KeyedHistogramMapType& map = mHistogramMap;
 #endif
   KeyedHistogramEntry* entry = map.GetEntry(key);
   if (entry) {
     *histogram = entry->mData;
     return NS_OK;
   }
 
-  nsCString histogramName;
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (subsession) {
-    histogramName.AppendLiteral(SUBSESSION_HISTOGRAM_PREFIX);
-  }
-#endif
-  histogramName.Append(mName);
-  histogramName.AppendLiteral(KEYED_HISTOGRAM_NAME_SEPARATOR);
-  histogramName.Append(key);
-
-  Histogram* h;
-  nsresult rv = internal_HistogramGet(histogramName.get(), mExpiration.get(),
-                                      mHistogramType, mMin, mMax, mBucketCount,
-                                      true, &h);
-  if (NS_FAILED(rv)) {
-    return rv;
+  Histogram* h = internal_CreateHistogramInstance(mHistogramInfo);
+  if (!h) {
+    return NS_ERROR_FAILURE;
   }
 
   h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
   *histogram = h;
 
   entry = map.PutEntry(key);
   if (MOZ_UNLIKELY(!entry)) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -972,20 +678,20 @@ KeyedHistogram::GetHistogram(const nsCSt
     return nullptr;
   }
   return h;
 }
 
 nsresult
 KeyedHistogram::Add(const nsCString& key, uint32_t sample)
 {
-  bool canRecordDataset = CanRecordDataset(mDataset,
+  bool canRecordDataset = CanRecordDataset(mHistogramInfo.dataset,
                                            internal_CanRecordBase(),
                                            internal_CanRecordExtended());
-  if (!canRecordDataset || !IsRecordingEnabled()) {
+  if (!canRecordDataset || !internal_IsRecordingEnabled(mId)) {
     return NS_OK;
   }
 
   Histogram* histogram = GetHistogram(key, false);
   MOZ_ASSERT(histogram);
   if (!histogram) {
     return NS_ERROR_FAILURE;
   }
@@ -1093,213 +799,172 @@ KeyedHistogram::GetJSSnapshot(JSContext*
   if (subsession && clearSubsession) {
     Clear(true);
   }
 #endif
 
   return NS_OK;
 }
 
-nsresult
-KeyedHistogram::GetEnumId(mozilla::Telemetry::HistogramID& id)
-{
-  return internal_GetHistogramEnumId(mName.get(), &id);
-}
-
 } // namespace
 
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
-// PRIVATE: KeyedHistogram helpers
-
-namespace {
-
-KeyedHistogram*
-internal_GetKeyedHistogramById(const nsACString &name)
-{
-  if (!gInitDone) {
-    return nullptr;
-  }
-
-  KeyedHistogram* keyed = nullptr;
-  gKeyedHistograms.Get(name, &keyed);
-  return keyed;
-}
-
-} // namespace
-
-
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: thread-unsafe helpers for the external interface
 
 // This is a StaticMutex rather than a plain Mutex (1) so that
 // it gets initialised in a thread-safe manner the first time
 // it is used, and (2) because it is never de-initialised, and
 // a normal Mutex would show up as a leak in BloatView.  StaticMutex
 // also has the "OffTheBooks" property, so it won't show as a leak
 // in BloatView.
 static StaticMutex gTelemetryHistogramMutex;
 
 namespace {
 
-void
-internal_SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramID aID, bool aEnabled)
-{
-  if (gHistograms[aID].keyed) {
-    const nsDependentCString id(gHistograms[aID].id());
-    KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
-    if (keyed) {
-      keyed->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  } else {
-    Histogram *h;
-    nsresult rv = internal_GetHistogramByEnumId(aID, &h, ProcessID::Parent);
-    if (NS_SUCCEEDED(rv)) {
-      h->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  }
-
-  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
-}
-
 bool
-internal_RemoteAccumulate(mozilla::Telemetry::HistogramID aId, uint32_t aSample)
+internal_RemoteAccumulate(HistogramID aId, uint32_t aSample)
 {
   if (XRE_IsParentProcess()) {
     return false;
   }
-  Histogram *h;
-  nsresult rv = internal_GetHistogramByEnumId(aId, &h, ProcessID::Parent);
-  if (NS_SUCCEEDED(rv) && !h->IsRecordingEnabled()) {
+
+  if (!internal_IsRecordingEnabled(aId)) {
     return true;
   }
+
   TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample);
   return true;
 }
 
 bool
-internal_RemoteAccumulate(mozilla::Telemetry::HistogramID aId,
-                    const nsCString& aKey, uint32_t aSample)
+internal_RemoteAccumulate(HistogramID aId,
+                          const nsCString& aKey, uint32_t aSample)
 {
   if (XRE_IsParentProcess()) {
     return false;
   }
-  const HistogramInfo& th = gHistograms[aId];
-  KeyedHistogram* keyed
-     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
-  MOZ_ASSERT(keyed);
-  if (!keyed->IsRecordingEnabled()) {
-    return false;
+
+  if (!internal_IsRecordingEnabled(aId)) {
+    return true;
   }
+
   TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample);
   return true;
 }
 
-void internal_Accumulate(mozilla::Telemetry::HistogramID aHistogram, uint32_t aSample)
+void internal_Accumulate(HistogramID aId, uint32_t aSample)
 {
   if (!internal_CanRecordBase() ||
-      internal_RemoteAccumulate(aHistogram, aSample)) {
+      internal_RemoteAccumulate(aId, aSample)) {
     return;
   }
-  Histogram *h;
-  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h, ProcessID::Parent);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
-  }
+
+  Histogram *h = internal_GetHistogramById(aId, ProcessID::Parent, SessionType::Session);
+  MOZ_ASSERT(h);
+  internal_HistogramAdd(*h, aId, aSample);
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  h = internal_GetHistogramById(aId, ProcessID::Parent, SessionType::Subsession);
+  MOZ_ASSERT(h);
+  internal_HistogramAdd(*h, aId, aSample);
+#endif
 }
 
 void
-internal_Accumulate(mozilla::Telemetry::HistogramID aID,
+internal_Accumulate(HistogramID aId,
                     const nsCString& aKey, uint32_t aSample)
 {
   if (!gInitDone || !internal_CanRecordBase() ||
-      internal_RemoteAccumulate(aID, aKey, aSample)) {
+      internal_RemoteAccumulate(aId, aKey, aSample)) {
     return;
   }
-  const HistogramInfo& th = gHistograms[aID];
-  KeyedHistogram* keyed
-     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
+
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, ProcessID::Parent);
   MOZ_ASSERT(keyed);
   keyed->Add(aKey, aSample);
 }
 
 void
-internal_Accumulate(Histogram& aHistogram, uint32_t aSample)
-{
-  if (XRE_IsParentProcess()) {
-    internal_HistogramAdd(aHistogram, aSample);
-    return;
-  }
-
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv = internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(), &id);
-  if (NS_SUCCEEDED(rv)) {
-    internal_RemoteAccumulate(id, aSample);
-  }
-}
-
-void
-internal_Accumulate(KeyedHistogram& aKeyed,
-                    const nsCString& aKey, uint32_t aSample)
-{
-  if (XRE_IsParentProcess()) {
-    aKeyed.Add(aKey, aSample);
-    return;
-  }
-
-  mozilla::Telemetry::HistogramID id;
-  if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) {
-    internal_RemoteAccumulate(id, aKey, aSample);
-  }
-}
-
-void
-internal_AccumulateChild(ProcessID aProcessType, mozilla::Telemetry::HistogramID aId, uint32_t aSample)
+internal_AccumulateChild(ProcessID aProcessType, HistogramID aId, uint32_t aSample)
 {
   if (!internal_CanRecordBase()) {
     return;
   }
-  Histogram* h;
-  nsresult rv = internal_GetHistogramByEnumId(aId, &h, aProcessType);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
+
+  if (Histogram* h = internal_GetHistogramById(aId, aProcessType, SessionType::Session)) {
+    internal_HistogramAdd(*h, aId, aSample);
   } else {
-    NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
+    NS_WARNING("Failed GetHistogramById for CHILD");
   }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  if (Histogram* h = internal_GetHistogramById(aId, aProcessType, SessionType::Subsession)) {
+    internal_HistogramAdd(*h, aId, aSample);
+  } else {
+    NS_WARNING("Failed GetHistogramById for CHILD");
+  }
+#endif
 }
 
 void
-internal_AccumulateChildKeyed(ProcessID aProcessType, mozilla::Telemetry::HistogramID aId,
+internal_AccumulateChildKeyed(ProcessID aProcessType, HistogramID aId,
                               const nsCString& aKey, uint32_t aSample)
 {
   if (!gInitDone || !internal_CanRecordBase()) {
     return;
   }
 
-  const char* suffix = SuffixForProcessType(aProcessType);
-  if (!suffix) {
-    MOZ_ASSERT_UNREACHABLE("suffix should not be null");
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, aProcessType);
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+void
+internal_ClearHistogram(HistogramID id, bool onlySubsession)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
     return;
   }
 
-  const HistogramInfo& th = gHistograms[aId];
+  // 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(onlySubsession);
+      }
+    }
+  }
 
-  nsAutoCString id;
-  id.Append(th.id());
-  id.AppendASCII(suffix);
+  // Handle plain histograms.
+  // Define the session types we want to clear.
+  nsTArray<SessionType> sessionTypes;
+  if (!onlySubsession) {
+    sessionTypes.AppendElement(SessionType::Session);
+  }
+#if !defined(MOZ_WIDGET_ANDROID)
+  sessionTypes.AppendElement(SessionType::Subsession);
+#endif
 
-  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
-  MOZ_ASSERT(keyed);
-  keyed->Add(aKey, aSample);
+  if (sessionTypes.Length() == 0) {
+    // Nothing to do here.
+    return;
+  }
+
+  // Now reset the histograms instances for all processes.
+  for (uint32_t sessionIdx = 0; sessionIdx < sessionTypes.Length(); ++sessionIdx) {
+    for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
+      internal_ClearHistogramById(id,
+                                  static_cast<ProcessID>(process),
+                                  sessionTypes[sessionIdx]);
+    }
+  }
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
@@ -1317,63 +982,82 @@ internal_AccumulateChildKeyed(ProcessID 
 // deadlock because the JS_ calls that they make may call back into the
 // TelemetryHistogram interface, hence trying to re-acquire the mutex.
 //
 // This means that these functions potentially race against threads, but
 // that seems preferable to risking deadlock.
 
 namespace {
 
+void internal_JSHistogram_finalize(JSFreeOp*, JSObject*);
+
+static const JSClassOps sJSHistogramClassOps = {
+  nullptr, /* addProperty */
+  nullptr, /* delProperty */
+  nullptr, /* getProperty */
+  nullptr, /* setProperty */
+  nullptr, /* enumerate */
+  nullptr, /* newEnumerate */
+  nullptr, /* resolve */
+  nullptr, /* mayResolve */
+  internal_JSHistogram_finalize
+};
+
 static const JSClass sJSHistogramClass = {
   "JSHistogram",  /* name */
-  JSCLASS_HAS_PRIVATE  /* flags */
+  JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE,  /* flags */
+  &sJSHistogramClassOps
+};
+
+struct JSHistogramData {
+  HistogramID histogramId;
 };
 
 bool
 internal_JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   MOZ_ASSERT(obj);
   if (!obj ||
       JS_GetClass(obj) != &sJSHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
     return false;
   }
 
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
-  MOZ_ASSERT(h);
-  Histogram::ClassType type = h->histogram_type();
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+  uint32_t type = gHistogramInfos[id].histogramType;
 
   JS::CallArgs args = CallArgsFromVp(argc, vp);
   // This function should always return |undefined| and never fail but
   // rather report failures using the console.
   args.rval().setUndefined();
 
   if (!internal_CanRecordBase()) {
     return true;
   }
 
   uint32_t value = 0;
-  mozilla::Telemetry::HistogramID id;
-  if ((type == base::CountHistogram::COUNT_HISTOGRAM) && (args.length() == 0)) {
+  if ((type == nsITelemetry::HISTOGRAM_COUNT) && (args.length() == 0)) {
     // If we don't have an argument for the count histogram, assume an increment of 1.
     // Otherwise, make sure to run some sanity checks on the argument.
     value = 1;
-  } else if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
-      (args.length() > 0) && args[0].isString() &&
-      NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(), &id)) &&
-      gHistograms[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) {
+  } else if ((args.length() > 0) && args[0].isString() &&
+             gHistogramInfos[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) {
     // For categorical histograms we allow passing a string argument that specifies the label.
     nsAutoJSString label;
     if (!label.init(cx, args[0])) {
       LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Invalid string parameter"));
       return true;
     }
 
     // Get label id value.
-    nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
+    nsresult rv = gHistogramInfos[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
     if (NS_FAILED(rv)) {
       LogToBrowserConsole(nsIScriptError::errorFlag,
                           NS_LITERAL_STRING("Unknown label for categorical histogram"));
       return true;
     }
   } else {
     // All other accumulations expect one numerical argument.
     if (!args.length()) {
@@ -1389,108 +1073,141 @@ internal_JSHistogram_Add(JSContext *cx, 
     if (!JS::ToUint32(cx, args[0], &value)) {
       LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to convert argument"));
       return true;
     }
   }
 
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-    internal_Accumulate(*h, value);
+    internal_Accumulate(id, value);
   }
+
   return true;
 }
 
 bool
 internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
     return false;
   }
 
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+
+  // This is not good standard behavior given that we have histogram instances
+  // covering multiple processes and two session types.
+  // However, changing this requires some broader changes to callers.
+  Histogram* h = internal_GetHistogramById(id, ProcessID::Parent, SessionType::Session);
+  MOZ_ASSERT(h);
+
   JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot)
+  if (!snapshot) {
     return false;
+  }
 
   switch (internal_ReflectHistogramSnapshot(cx, snapshot, h)) {
   case REFLECT_FAILURE:
     return false;
-  case REFLECT_CORRUPT:
-    JS_ReportErrorASCII(cx, "Histogram is corrupt");
-    return false;
   case REFLECT_OK:
     args.rval().setObject(*snapshot);
     return true;
   default:
-    MOZ_CRASH("unhandled reflection status");
+    MOZ_ASSERT_UNREACHABLE("Unhandled reflection status.");
   }
+
+  return true;
 }
 
 bool
 internal_JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
     return false;
   }
 
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+
   bool onlySubsession = false;
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   // This function should always return |undefined| and never fail but
   // rather report failures using the console.
   args.rval().setUndefined();
 
-
 #if !defined(MOZ_WIDGET_ANDROID)
   if (args.length() >= 1) {
     if (!args[0].isBoolean()) {
       JS_ReportErrorASCII(cx, "Not a boolean");
       return false;
     }
 
     onlySubsession = JS::ToBoolean(args[0]);
   }
 #endif
 
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
-  MOZ_ASSERT(h);
-  if (h) {
-    internal_HistogramClear(*h, onlySubsession);
-  }
+  internal_ClearHistogram(id, onlySubsession);
 
   return true;
 }
 
 // NOTE: Runs without protection from |gTelemetryHistogramMutex|.
 // See comment at the top of this section.
 nsresult
-internal_WrapAndReturnHistogram(Histogram *h, JSContext *cx,
+internal_WrapAndReturnHistogram(HistogramID id, JSContext *cx,
                                 JS::MutableHandle<JS::Value> ret)
 {
   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSHistogramClass));
-  if (!obj)
+  if (!obj) {
     return NS_ERROR_FAILURE;
+  }
+
   // The 3 functions that are wrapped up here are eventually called
   // by the same thread that runs this function.
   if (!(JS_DefineFunction(cx, obj, "add", internal_JSHistogram_Add, 1, 0)
         && JS_DefineFunction(cx, obj, "snapshot",
                              internal_JSHistogram_Snapshot, 0, 0)
         && JS_DefineFunction(cx, obj, "clear", internal_JSHistogram_Clear, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
-  JS_SetPrivate(obj, h);
+
+  // TODO: delete in finalizer
+  JSHistogramData* data = new JSHistogramData{id};
+  JS_SetPrivate(obj, data);
   ret.setObject(*obj);
+
   return NS_OK;
 }
 
+void
+internal_JSHistogram_finalize(JSFreeOp*, JSObject* obj)
+{
+  if (!obj ||
+      JS_GetClass(obj) != &sJSHistogramClass) {
+    MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
+    return;
+  }
+
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  delete data;
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: JSKeyedHistogram_* functions
 
@@ -1505,39 +1222,67 @@ internal_WrapAndReturnHistogram(Histogra
 //   internal_JSKeyedHistogram_Clear
 //   internal_WrapAndReturnKeyedHistogram
 //
 // Same comments as above, at the JSHistogram_* section, regarding
 // deadlock avoidance, apply.
 
 namespace {
 
+void internal_JSKeyedHistogram_finalize(JSFreeOp*, JSObject*);
+
+static const JSClassOps sJSKeyedHistogramClassOps = {
+  nullptr, /* addProperty */
+  nullptr, /* delProperty */
+  nullptr, /* getProperty */
+  nullptr, /* setProperty */
+  nullptr, /* enumerate */
+  nullptr, /* newEnumerate */
+  nullptr, /* resolve */
+  nullptr, /* mayResolve */
+  internal_JSKeyedHistogram_finalize
+};
+
 static const JSClass sJSKeyedHistogramClass = {
   "JSKeyedHistogram",  /* name */
-  JSCLASS_HAS_PRIVATE  /* flags */
+  JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE,  /* flags */
+  &sJSKeyedHistogramClassOps
 };
 
 bool
 internal_KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc,
                                      JS::Value *vp,
                                      bool subsession, bool clearSubsession)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSKeyedHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
     return false;
   }
 
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  // This function should always return |undefined| and never fail but
+  // rather report failures using the console.
+  args.rval().setUndefined();
+
+  // This is not good standard behavior given that we have histogram instances
+  // covering multiple processes and two session types.
+  // 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;
   }
 
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
   if (args.length() == 0) {
     JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
     if (!snapshot) {
       JS_ReportErrorASCII(cx, "Failed to create object");
       return false;
     }
 
     if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) {
@@ -1564,86 +1309,82 @@ internal_KeyedHistogram_SnapshotImpl(JSC
 
   JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
   if (!snapshot) {
     return false;
   }
 
   switch (internal_ReflectHistogramSnapshot(cx, snapshot, h)) {
   case REFLECT_FAILURE:
-    return false;
-  case REFLECT_CORRUPT:
-    JS_ReportErrorASCII(cx, "Histogram is corrupt");
+    JS_ReportErrorASCII(cx, "Failed to reflect histogram");
     return false;
   case REFLECT_OK:
     args.rval().setObject(*snapshot);
     return true;
   default:
     MOZ_CRASH("unhandled reflection status");
   }
+
+  return true;
 }
 
 bool
 internal_JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSKeyedHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
     return false;
   }
 
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
 
   JS::CallArgs args = CallArgsFromVp(argc, vp);
   // This function should always return |undefined| and never fail but
   // rather report failures using the console.
   args.rval().setUndefined();
   if (args.length() < 1) {
     LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Expected one argument"));
     return true;
   }
 
   nsAutoJSString key;
   if (!args[0].isString() || !key.init(cx, args[0])) {
     LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Not a string"));
     return true;
   }
 
-  const uint32_t type = keyed->GetHistogramType();
+  const uint32_t type = gHistogramInfos[id].histogramType;
 
   // If we don't have an argument for the count histogram, assume an increment of 1.
   // Otherwise, make sure to run some sanity checks on the argument.
   uint32_t value = 1;
   if ((type != nsITelemetry::HISTOGRAM_COUNT) || (args.length() == 2)) {
     if (args.length() < 2) {
       LogToBrowserConsole(nsIScriptError::errorFlag,
                           NS_LITERAL_STRING("Expected two arguments for this histogram type"));
       return true;
     }
 
     if (type == nsITelemetry::HISTOGRAM_CATEGORICAL && args[1].isString()) {
       // For categorical histograms we allow passing a string argument that specifies the label.
-      mozilla::Telemetry::HistogramID id;
-      if (NS_FAILED(keyed->GetEnumId(id))) {
-        LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to get histogram id."));
-        return true;
-      }
 
       // Get label string.
       nsAutoJSString label;
       if (!label.init(cx, args[1])) {
         LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Invalid string parameter"));
         return true;
       }
 
       // Get label id value.
-      nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
+      nsresult rv = gHistogramInfos[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
       if (NS_FAILED(rv)) {
         LogToBrowserConsole(nsIScriptError::errorFlag,
                             NS_LITERAL_STRING("Unknown label for categorical histogram"));
         return true;
       }
     } else {
       // All other accumulations expect one numerical argument.
       if (!(args[1].isNumber() || args[1].isBoolean())) {
@@ -1653,33 +1394,41 @@ internal_JSKeyedHistogram_Add(JSContext 
 
       if (!JS::ToUint32(cx, args[1], &value)) {
         LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to convert argument"));
         return true;
       }
     }
   }
 
-  {
-    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-    internal_Accumulate(*keyed, NS_ConvertUTF16toUTF8(key), value);
-  }
+  internal_Accumulate(id, NS_ConvertUTF16toUTF8(key), value);
+
   return true;
 }
 
 bool
 internal_JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSKeyedHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
     return false;
   }
 
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
+
+  // This is not good standard behavior given that we have histogram instances
+  // covering multiple processes and two session types.
+  // However, changing this requires some broader changes to callers.
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent);
+  MOZ_ASSERT(keyed);
   if (!keyed) {
     return false;
   }
 
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   return NS_SUCCEEDED(keyed->GetJSKeys(cx, args));
 }
 
@@ -1714,52 +1463,62 @@ internal_JSKeyedHistogram_SnapshotSubses
 #endif
 
 bool
 internal_JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj ||
       JS_GetClass(obj) != &sJSKeyedHistogramClass) {
+    JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
     return false;
   }
 
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  HistogramID id = data->histogramId;
+  MOZ_ASSERT(internal_IsHistogramEnumId(id));
 
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   // This function should always return |undefined| and never fail but
   // rather report failures using the console.
   args.rval().setUndefined();
 
+  // This is not good standard behavior given that we have histogram instances
+  // covering multiple processes and two session types.
+  // However, changing this requires some broader changes to callers.
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, /* instantiate = */ false);
+  if (!keyed) {
+    return true;
+  }
+
 #if !defined(MOZ_WIDGET_ANDROID)
   bool onlySubsession = false;
 
   if (args.length() >= 1) {
     if (!(args[0].isNumber() || args[0].isBoolean())) {
       JS_ReportErrorASCII(cx, "Not a boolean");
       return false;
     }
 
     onlySubsession = JS::ToBoolean(args[0]);
   }
 
   keyed->Clear(onlySubsession);
 #else
   keyed->Clear(false);
 #endif
+
   return true;
 }
 
 // NOTE: Runs without protection from |gTelemetryHistogramMutex|.
 // See comment at the top of this section.
 nsresult
-internal_WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx,
+internal_WrapAndReturnKeyedHistogram(HistogramID id, JSContext *cx,
                                      JS::MutableHandle<JS::Value> ret)
 {
   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSKeyedHistogramClass));
   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)
@@ -1773,21 +1532,38 @@ internal_WrapAndReturnKeyedHistogram(Key
 #endif
         && JS_DefineFunction(cx, obj, "keys",
                              internal_JSKeyedHistogram_Keys, 0, 0)
         && JS_DefineFunction(cx, obj, "clear",
                              internal_JSKeyedHistogram_Clear, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
 
-  JS_SetPrivate(obj, h);
+  // TODO: delete in finalizer
+  JSHistogramData* data = new JSHistogramData{id};
+  JS_SetPrivate(obj, data);
   ret.setObject(*obj);
+
   return NS_OK;
 }
 
+void
+internal_JSKeyedHistogram_finalize(JSFreeOp*, JSObject* obj)
+{
+  if (!obj ||
+      JS_GetClass(obj) != &sJSKeyedHistogramClass) {
+    MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
+    return;
+  }
+
+  JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(data);
+  delete data;
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
 
@@ -1804,98 +1580,59 @@ void TelemetryHistogram::InitializeGloba
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState "
              "may only be called once");
 
   gCanRecordBase = canRecordBase;
   gCanRecordExtended = canRecordExtended;
 
-  // gHistogramMap should have been pre-sized correctly at the
+  // gNameToHistogramIDMap should have been pre-sized correctly at the
   // declaration point further up in this file.
 
   // Populate the static histogram name->id cache.
   // Note that the histogram names are statically allocated.
-  for (uint32_t i = 0; i < mozilla::Telemetry::HistogramCount; i++) {
-    CharPtrEntryType *entry = gHistogramMap.PutEntry(gHistograms[i].id());
-    entry->mData = (mozilla::Telemetry::HistogramID) i;
+  for (uint32_t i = 0; i < HistogramCount; i++) {
+    gNameToHistogramIDMap.Put(nsDependentCString(gHistogramInfos[i].name()), HistogramID(i));
   }
 
 #ifdef DEBUG
-  gHistogramMap.MarkImmutable();
+  gNameToHistogramIDMap.MarkImmutable();
 #endif
 
-  mozilla::PodArrayZero(gCorruptHistograms);
-
-  // Create registered keyed histograms
-  for (const auto & h : gHistograms) {
-    if (!h.keyed) {
-      continue;
-    }
-
-    const nsDependentCString id(h.id());
-    const nsDependentCString expiration(h.expiration());
-    gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
-                                                h.min, h.max, h.bucketCount, h.dataset));
-    if (XRE_IsParentProcess()) {
-      // We must create registered child keyed histograms as well or else the
-      // same code in TelemetrySession.jsm that fails without parent keyed
-      // histograms will fail without child keyed histograms.
-      nsCString contentId(id);
-      contentId.AppendLiteral(CONTENT_HISTOGRAM_SUFFIX);
-      gKeyedHistograms.Put(contentId,
-                           new KeyedHistogram(id, expiration, h.histogramType,
-                                              h.min, h.max, h.bucketCount, h.dataset));
-
-      nsCString gpuId(id);
-      gpuId.AppendLiteral(GPU_HISTOGRAM_SUFFIX);
-      gKeyedHistograms.Put(gpuId,
-                           new KeyedHistogram(id, expiration, h.histogramType,
-                                              h.min, h.max, h.bucketCount, h.dataset));
-
-      nsCString extensionId(id);
-      extensionId.AppendLiteral(EXTENSION_HISTOGRAM_SUFFIX);
-      gKeyedHistograms.Put(extensionId,
-                           new KeyedHistogram(id, expiration, h.histogramType,
-                                              h.min, h.max, h.bucketCount, h.dataset));
-    }
-  }
-
     // Some Telemetry histograms depend on the value of C++ constants and hardcode
     // their values in Histograms.json.
     // We add static asserts here for those values to match so that future changes
     // don't go unnoticed.
     static_assert((JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistograms[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
+                        gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
                   (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistograms[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
+                        gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
                   (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistograms[mozilla::Telemetry::GC_REASON_2].bucketCount,
+                        gHistogramInfos[mozilla::Telemetry::GC_REASON_2].bucketCount,
                   "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
                   " If this was an intentional change, update the n_values for the "
                   "following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, "
                   "GC_REASON_2");
 
     static_assert((mozilla::StartupTimeline::MAX_EVENT_ID + 1) ==
-                        gHistograms[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,
+                        gHistogramInfos[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,
                   "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json.  If this"
                   " was an intentional change, update the n_values for the following in "
                   "Histograms.json: STARTUP_MEASUREMENT_ERRORS");
 
-
   gInitDone = true;
 }
 
 void TelemetryHistogram::DeInitializeGlobalState()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
   gCanRecordExtended = false;
-  gHistogramMap.Clear();
-  gKeyedHistograms.Clear();
+  gNameToHistogramIDMap.Clear();
   gInitDone = false;
 }
 
 #ifdef DEBUG
 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gInitDone;
 }
@@ -1926,98 +1663,83 @@ TelemetryHistogram::SetCanRecordExtended
 }
 
 
 void
 TelemetryHistogram::InitHistogramRecordingEnabled()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   auto processType = XRE_GetProcessType();
-  for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) {
-    const HistogramInfo& h = gHistograms[i];
+  for (size_t i = 0; i < HistogramCount; ++i) {
+    const HistogramInfo& h = gHistogramInfos[i];
     mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
     internal_SetHistogramRecordingEnabled(id,
                                           CanRecordInProcess(h.record_in_processes,
                                                              processType));
   }
 
   for (auto recordingInitiallyDisabledID : kRecordingInitiallyDisabledIDs) {
     internal_SetHistogramRecordingEnabled(recordingInitiallyDisabledID,
                                           false);
   }
 }
 
 void
-TelemetryHistogram::SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramID aID,
+TelemetryHistogram::SetHistogramRecordingEnabled(HistogramID aID,
                                                  bool aEnabled)
 {
   if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
     MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
     return;
   }
 
-  const HistogramInfo& h = gHistograms[aID];
+  const HistogramInfo& h = gHistogramInfos[aID];
   if (!CanRecordInProcess(h.record_in_processes, XRE_GetProcessType())) {
     // Don't permit record_in_process-disabled recording to be re-enabled.
     return;
   }
 
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   internal_SetHistogramRecordingEnabled(aID, aEnabled);
 }
 
 
 nsresult
-TelemetryHistogram::SetHistogramRecordingEnabled(const nsACString &id,
+TelemetryHistogram::SetHistogramRecordingEnabled(const nsACString& name,
                                                  bool aEnabled)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-
-  mozilla::Telemetry::HistogramID hId;
-  nsresult rv = internal_GetHistogramEnumId(PromiseFlatCString(id).get(), &hId);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  const HistogramInfo& hi = gHistograms[hId];
-  if (!CanRecordInProcess(hi.record_in_processes, XRE_GetProcessType())) {
-    return NS_OK;
+  HistogramID id;
+  if (NS_FAILED(internal_GetHistogramIdByName(name, &id))) {
+    return NS_ERROR_FAILURE;
   }
 
-  Histogram *h;
-  rv = internal_GetHistogramByName(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    h->SetRecordingEnabled(aEnabled);
-    return NS_OK;
+  const HistogramInfo& hi = gHistogramInfos[id];
+  if (CanRecordInProcess(hi.record_in_processes, XRE_GetProcessType())) {
+    internal_SetHistogramRecordingEnabled(id, aEnabled);
   }
-
-  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
-  if (keyed) {
-    keyed->SetRecordingEnabled(aEnabled);
-    return NS_OK;
-  }
-
-  return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 
 void
-TelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramID aID,
+TelemetryHistogram::Accumulate(HistogramID aID,
                                uint32_t aSample)
 {
   if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
     MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
     return;
   }
 
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   internal_Accumulate(aID, aSample);
 }
 
 void
-TelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramID aID,
+TelemetryHistogram::Accumulate(HistogramID aID,
                                const nsCString& aKey, uint32_t aSample)
 {
   if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
     MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
     return;
   }
 
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
@@ -2026,54 +1748,54 @@ TelemetryHistogram::Accumulate(mozilla::
 
 void
 TelemetryHistogram::Accumulate(const char* name, uint32_t sample)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
   }
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv = internal_GetHistogramEnumId(name, &id);
+  HistogramID id;
+  nsresult rv = internal_GetHistogramIdByName(nsDependentCString(name), &id);
   if (NS_FAILED(rv)) {
     return;
   }
   internal_Accumulate(id, sample);
 }
 
 void
 TelemetryHistogram::Accumulate(const char* name,
                                const nsCString& key, uint32_t sample)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
   }
-  mozilla::Telemetry::HistogramID id;
-  nsresult rv = internal_GetHistogramEnumId(name, &id);
+  HistogramID id;
+  nsresult rv = internal_GetHistogramIdByName(nsDependentCString(name), &id);
   if (NS_SUCCEEDED(rv)) {
     internal_Accumulate(id, key, sample);
   }
 }
 
 void
-TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::HistogramID aId,
+TelemetryHistogram::AccumulateCategorical(HistogramID aId,
                                           const nsCString& label)
 {
   if (NS_WARN_IF(!internal_IsHistogramEnumId(aId))) {
     MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
     return;
   }
 
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
   }
   uint32_t labelId = 0;
-  if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) {
+  if (NS_FAILED(gHistogramInfos[aId].label_id(label.get(), &labelId))) {
     return;
   }
   internal_Accumulate(aId, labelId);
 }
 
 void
 TelemetryHistogram::AccumulateChild(ProcessID aProcessType,
                                     const nsTArray<Accumulation>& aAccumulations)
@@ -2113,168 +1835,163 @@ TelemetryHistogram::AccumulateChildKeyed
                                   aAccumulations[i].mSample);
   }
 }
 
 nsresult
 TelemetryHistogram::GetHistogramById(const nsACString &name, JSContext *cx,
                                      JS::MutableHandle<JS::Value> ret)
 {
-  Histogram *h = nullptr;
+  HistogramID id;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-    nsresult rv = internal_GetHistogramByName(name, &h);
-    if (NS_FAILED(rv))
-      return rv;
+    nsresult rv = internal_GetHistogramIdByName(name, &id);
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (gHistogramInfos[id].keyed) {
+      return NS_ERROR_FAILURE;
+    }
   }
   // Runs without protection from |gTelemetryHistogramMutex|
-  return internal_WrapAndReturnHistogram(h, cx, ret);
+  return internal_WrapAndReturnHistogram(id, cx, ret);
 }
 
 nsresult
 TelemetryHistogram::GetKeyedHistogramById(const nsACString &name,
                                           JSContext *cx,
                                           JS::MutableHandle<JS::Value> ret)
 {
-  KeyedHistogram* keyed = nullptr;
+  HistogramID id;
   {
     StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-    if (!gKeyedHistograms.Get(name, &keyed)) {
+    nsresult rv = internal_GetHistogramIdByName(name, &id);
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!gHistogramInfos[id].keyed) {
       return NS_ERROR_FAILURE;
     }
   }
   // Runs without protection from |gTelemetryHistogramMutex|
-  return internal_WrapAndReturnKeyedHistogram(keyed, cx, ret);
+  return internal_WrapAndReturnKeyedHistogram(id, cx, ret);
 }
 
 const char*
-TelemetryHistogram::GetHistogramName(mozilla::Telemetry::HistogramID id)
+TelemetryHistogram::GetHistogramName(HistogramID id)
 {
   if (NS_WARN_IF(!internal_IsHistogramEnumId(id))) {
     MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
     return nullptr;
   }
 
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-  const HistogramInfo& h = gHistograms[id];
-  return h.id();
+  const HistogramInfo& h = gHistogramInfos[id];
+  return h.name();
 }
 
 nsresult
 TelemetryHistogram::CreateHistogramSnapshots(JSContext *cx,
                                              JS::MutableHandle<JS::Value> ret,
                                              bool subsession,
                                              bool clearSubsession)
 {
   // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
-  if (!root_obj)
+  if (!root_obj) {
     return NS_ERROR_FAILURE;
+  }
   ret.setObject(*root_obj);
 
   // Include the GPU process in histogram snapshots only if we actually tried
   // to launch a process for it.
   bool includeGPUProcess = false;
   if (auto gpm = mozilla::gfx::GPUProcessManager::Get()) {
     includeGPUProcess = gpm->AttemptedGPUProcess();
   }
 
-  // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
+#if !defined(MOZ_WIDGET_ANDROID)
+  SessionType sessionType = SessionType(subsession);
+#else
+  SessionType sessionType = SessionType::Session;
+#endif
+
+  // Ensure that all the HISTOGRAM_FLAG histograms have
   // been created, so that their values are snapshotted.
   auto processType = XRE_GetProcessType();
-  for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
-    const HistogramInfo& hi = gHistograms[i];
-    if (hi.keyed || !CanRecordInProcess(hi.record_in_processes, processType)) {
+  for (uint32_t histogramId = 0; histogramId < HistogramCount; ++histogramId) {
+    const HistogramInfo& info = gHistogramInfos[histogramId];
+    if (info.keyed ||
+      info.histogramType != nsITelemetry::HISTOGRAM_FLAG ||
+      !CanRecordInProcess(info.record_in_processes, processType)) {
       continue;
     }
-    const uint32_t type = hi.histogramType;
-    if (type == nsITelemetry::HISTOGRAM_FLAG ||
-        type == nsITelemetry::HISTOGRAM_COUNT) {
-      Histogram *h;
-      mozilla::DebugOnly<nsresult> rv;
-      mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
 
-      for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
-        if ((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess) {
-          continue;
-        }
-        rv = internal_GetHistogramByEnumId(id, &h, ProcessID(process));
-        MOZ_ASSERT(NS_SUCCEEDED(rv));
+    for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
+      if ((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess) {
+        continue;
       }
+
+      mozilla::DebugOnly<Histogram*> h = nullptr;
+      h = internal_GetHistogramById(HistogramID(histogramId), ProcessID(process), sessionType);
+      MOZ_ASSERT(h);
     }
   }
 
-  StatisticsRecorder::Histograms hs;
-  StatisticsRecorder::GetHistograms(&hs);
-
-  // We identify corrupt histograms first, rather than interspersing it
-  // in the loop below, to ensure that our corruption statistics don't
-  // depend on histogram enumeration order.
-  //
-  // Of course, we hope that all of these corruption-statistics
-  // histograms are not themselves corrupt...
-  internal_IdentifyCorruptHistograms(hs);
+  // TODO: This won't get us data from different processes.
+  // We'll have to refactor this function to return {"parent": {...}, "content": {...}}.
 
   // OK, now we can actually reflect things.
   JS::Rooted<JSObject*> hobj(cx);
-  for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
-    const HistogramInfo& hi = gHistograms[i];
-    if (hi.keyed) {
+  for (size_t i = 0; i < HistogramCount; ++i) {
+    const HistogramInfo& info = gHistogramInfos[i];
+    if (info.keyed) {
       continue;
     }
 
-    Histogram* h = nullptr;
-    mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
+    HistogramID id = HistogramID(i);
+
+    // TODO: support multiple processes.
+    //for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
+
+    uint32_t process = uint32_t(ProcessID::Parent);
+    if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) ||
+      ((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess)) {
+      continue;
+    }
 
-    for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
-      if (!CanRecordInProcess(hi.record_in_processes, ProcessID(process)) ||
-        ((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess)) {
-        continue;
+    Histogram* h = internal_GetHistogramById(id, ProcessID(process), sessionType, /* instantiate = */ false);
+    if (!h || internal_IsExpired(h) || !internal_ShouldReflectHistogram(h, id)) {
+      continue;
+    }
+
+    hobj = JS_NewPlainObject(cx);
+    if (!hobj) {
+      return NS_ERROR_FAILURE;
+    }
+    switch (internal_ReflectHistogramSnapshot(cx, hobj, h)) {
+    case REFLECT_FAILURE:
+      return NS_ERROR_FAILURE;
+    case REFLECT_OK:
+      if (!JS_DefineProperty(cx, root_obj, gHistogramInfos[id].name(),
+                             hobj, JSPROP_ENUMERATE)) {
+        return NS_ERROR_FAILURE;
       }
-      nsresult rv = internal_GetHistogramByEnumId(id, &h, ProcessID(process));
-      if (NS_WARN_IF(NS_FAILED(rv)) || !internal_ShouldReflectHistogram(h) ||
-        internal_IsEmpty(h) || internal_IsExpired(h)) {
-        continue;
-      }
+    }
 
-      Histogram* original = h;
 #if !defined(MOZ_WIDGET_ANDROID)
-      if (subsession) {
-        h = internal_GetSubsessionHistogram(*h);
-        if (!h) {
-          continue;
-        }
-      }
+    if ((sessionType == SessionType::Subsession) && clearSubsession) {
+      h->Clear();
+    }
 #endif
 
-      hobj = JS_NewPlainObject(cx);
-      if (!hobj) {
-        return NS_ERROR_FAILURE;
-      }
-      switch (internal_ReflectHistogramSnapshot(cx, hobj, h)) {
-      case REFLECT_CORRUPT:
-        // We can still hit this case even if ShouldReflectHistograms
-        // returns true.  The histogram lies outside of our control
-        // somehow; just skip it.
-        continue;
-      case REFLECT_FAILURE:
-        return NS_ERROR_FAILURE;
-      case REFLECT_OK:
-        if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(),
-                               hobj, JSPROP_ENUMERATE)) {
-          return NS_ERROR_FAILURE;
-        }
-      }
-
-#if !defined(MOZ_WIDGET_ANDROID)
-      if (subsession && clearSubsession) {
-        h->Clear();
-      }
-#endif
-    }
+    // TODO: support multiple processes.
+    // }
   }
   return NS_OK;
 }
 
 nsresult
 TelemetryHistogram::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
                                          char*** aHistograms)
 {
@@ -2298,49 +2015,60 @@ TelemetryHistogram::GetKeyedHistogramSna
                                                JS::MutableHandle<JS::Value> ret)
 {
   // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
   if (!obj) {
     return NS_ERROR_FAILURE;
   }
 
-  for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
+  // for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
+  for (uint32_t id = 0; id < HistogramCount; ++id) {
+    if (!gHistogramInfos[id].keyed) {
+      continue;
+    }
+
+    // TODO: This won't get us data from different processes.
+    // We'll have to refactor this function to return {"parent": {...}, "content": {...}}.
+
+    // We don't want to trigger instantiation of empty keyed histograms.
+    KeyedHistogram* keyed = internal_GetKeyedHistogramById(HistogramID(id), ProcessID::Parent,
+                                                           /* instantiate = */ false);
+    if (!keyed) {
+      continue;
+    }
+
     JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
     if (!snapshot) {
       return NS_ERROR_FAILURE;
     }
 
-    if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) {
+    if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, false, false))) {
       return NS_ERROR_FAILURE;
     }
 
-    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(),
+    if (!JS_DefineProperty(cx, obj, gHistogramInfos[id].name(),
                            snapshot, JSPROP_ENUMERATE)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   ret.setObject(*obj);
   return NS_OK;
 }
 
 size_t
 TelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf
                                                       aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-  return gHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  // TODO
+  return 0;
 }
 
 size_t
 TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
                                                      aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-  StatisticsRecorder::Histograms hs;
-  StatisticsRecorder::GetHistograms(&hs);
-  size_t n = 0;
-  for (auto h : hs) {
-    n += h->SizeOfIncludingThis(aMallocSizeOf);
-  }
-  return n;
+  // TODO
+  return 0;
 }
--- a/toolkit/components/telemetry/gen-histogram-data.py
+++ b/toolkit/components/telemetry/gen-histogram-data.py
@@ -36,17 +36,17 @@ def print_array_entry(output, histogram,
         print("#endif", file=output)
 
 
 def write_histogram_table(output, histograms):
     string_table = StringTable()
     label_table = []
     label_count = 0
 
-    print("constexpr HistogramInfo gHistograms[] = {", file=output)
+    print("constexpr HistogramInfo gHistogramInfos[] = {", file=output)
     for histogram in histograms:
         name_index = string_table.stringIndex(histogram.name())
         exp_index = string_table.stringIndex(histogram.expiration())
 
         labels = histogram.labels()
         label_index = 0
         if len(labels) > 0:
             label_index = label_count
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
@@ -110,16 +110,17 @@ function test_instantiate() {
   let h = Telemetry.getHistogramById(ID);
 
   // Instantiate the subsession histogram through |add| and make sure they match.
   // This MUST be the first use of "TELEMETRY_TEST_COUNT" in this file, otherwise
   // |add| will not instantiate the histogram.
   h.add(1);
   let snapshot = h.snapshot();
   let subsession = Telemetry.snapshotSubsessionHistograms();
+  Assert.ok(ID in subsession);
   Assert.equal(snapshot.sum, subsession[ID].sum,
                "Histogram and subsession histogram sum must match.");
   // Clear the histogram, so we don't void the assumptions from the other tests.
   h.clear();
 });
 
 add_task(async function test_parameterChecks() {
   let kinds = [Telemetry.HISTOGRAM_EXPONENTIAL, Telemetry.HISTOGRAM_LINEAR]
@@ -329,17 +330,17 @@ add_task(async function test_API_return_
     hist.clear(),
     hist.add(1),
     keyedHist.clear(),
     keyedHist.add("some-key", 1),
   ];
 
   for (let returnValue of RETURN_VALUES) {
     Assert.strictEqual(returnValue, undefined,
-                       "The function must return undefined.");
+                       "The function must return undefined");
   }
 });
 
 add_task(async function test_getHistogramById() {
   try {
     Telemetry.getHistogramById("nonexistent");
     do_throw("This can't happen");
   } catch (e) {
@@ -806,94 +807,94 @@ add_task(async function test_datasets() 
   Assert.ok(!registered.has("TELEMETRY_TEST_KEYED_FLAG"));
   Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"));
 });
 
 add_task({
   skip_if: () => gIsAndroid
 },
 function test_subsession() {
-  const ID = "TELEMETRY_TEST_COUNT";
+  const COUNT = "TELEMETRY_TEST_COUNT";
   const FLAG = "TELEMETRY_TEST_FLAG";
-  let h = Telemetry.getHistogramById(ID);
+  let h = Telemetry.getHistogramById(COUNT);
   let flag = Telemetry.getHistogramById(FLAG);
 
   // Both original and duplicate should start out the same.
   h.clear();
   let snapshot = Telemetry.histogramSnapshots;
   let subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.ok(!(ID in snapshot));
-  Assert.ok(!(ID in subsession));
+  Assert.ok(!(COUNT in snapshot));
+  Assert.ok(!(COUNT in subsession));
 
   // They should instantiate and pick-up the count.
   h.add(1);
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.ok(ID in snapshot);
-  Assert.ok(ID in subsession);
-  Assert.equal(snapshot[ID].sum, 1);
-  Assert.equal(subsession[ID].sum, 1);
+  Assert.ok(COUNT in snapshot);
+  Assert.ok(COUNT in subsession);
+  Assert.equal(snapshot[COUNT].sum, 1);
+  Assert.equal(subsession[COUNT].sum, 1);
 
   // They should still reset properly.
   h.clear();
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.ok(!(ID in snapshot));
-  Assert.ok(!(ID in subsession));
+  Assert.ok(!(COUNT in snapshot));
+  Assert.ok(!(COUNT in subsession));
 
   // Both should instantiate and pick-up the count.
   h.add(1);
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.equal(snapshot[ID].sum, 1);
-  Assert.equal(subsession[ID].sum, 1);
+  Assert.ok(COUNT in snapshot);
+  Assert.ok(COUNT in subsession);
+  Assert.equal(snapshot[COUNT].sum, 1);
+  Assert.equal(subsession[COUNT].sum, 1);
 
   // Check that we are able to only reset the duplicate histogram.
   h.clear(true);
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.ok(ID in snapshot);
-  Assert.ok(ID in subsession);
-  Assert.equal(snapshot[ID].sum, 1);
-  Assert.equal(subsession[ID].sum, 0);
+  Assert.ok(COUNT in snapshot);
+  Assert.ok(!(COUNT in subsession));
+  Assert.equal(snapshot[COUNT].sum, 1);
 
   // Both should register the next count.
   h.add(1);
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.equal(snapshot[ID].sum, 2);
-  Assert.equal(subsession[ID].sum, 1);
+  Assert.equal(snapshot[COUNT].sum, 2);
+  Assert.equal(subsession[COUNT].sum, 1);
 
   // Retrieve a subsession snapshot and pass the flag to
   // clear subsession histograms too.
   h.clear();
   flag.clear();
   h.add(1);
   flag.add(1);
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms(true);
-  Assert.ok(ID in snapshot);
-  Assert.ok(ID in subsession);
+  Assert.ok(COUNT in snapshot);
+  Assert.ok(COUNT in subsession);
   Assert.ok(FLAG in snapshot);
   Assert.ok(FLAG in subsession);
-  Assert.equal(snapshot[ID].sum, 1);
-  Assert.equal(subsession[ID].sum, 1);
+  Assert.equal(snapshot[COUNT].sum, 1);
+  Assert.equal(subsession[COUNT].sum, 1);
   Assert.equal(snapshot[FLAG].sum, 1);
   Assert.equal(subsession[FLAG].sum, 1);
 
   // The next subsesssion snapshot should show the histograms
   // got reset.
   snapshot = Telemetry.histogramSnapshots;
   subsession = Telemetry.snapshotSubsessionHistograms();
-  Assert.ok(ID in snapshot);
-  Assert.ok(ID in subsession);
+  Assert.ok(COUNT in snapshot);
+  Assert.ok(!(COUNT in subsession));
   Assert.ok(FLAG in snapshot);
   Assert.ok(FLAG in subsession);
-  Assert.equal(snapshot[ID].sum, 1);
-  Assert.equal(subsession[ID].sum, 0);
+  Assert.equal(snapshot[COUNT].sum, 1);
   Assert.equal(snapshot[FLAG].sum, 1);
   Assert.equal(subsession[FLAG].sum, 0);
 });
 
 add_task({
   skip_if: () => gIsAndroid
 },
 function test_keyed_subsession() {