Bug 1261052 - Move C++ histogram implementation code into a separate module. r=gfritzsche.
authorJulian Seward <jseward@acm.org>
Tue, 17 May 2016 15:05:19 +0200
changeset 297667 4d421496a9e38cc03460f50836bac9967ec375eb
parent 297666 4932a868d8a75b3282e07d935610c42123841d28
child 297668 7a336216b9c41a3a427064e396b251cd473c9a2c
push id30264
push userkwierso@gmail.com
push dateTue, 17 May 2016 20:52:53 +0000
treeherdermozilla-central@991f249a6ffa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche
bugs1261052
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1261052 - Move C++ histogram implementation code into a separate module. r=gfritzsche.
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/Telemetry.h
toolkit/components/telemetry/TelemetryHistogram.cpp
toolkit/components/telemetry/TelemetryHistogram.h
toolkit/components/telemetry/gen-histogram-data.py
toolkit/components/telemetry/gen-histogram-enum.py
toolkit/components/telemetry/moz.build
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/unused.h"
 
-#include "base/histogram.h"
 #include "base/pickle.h"
 #include "nsIComponentManager.h"
 #include "nsIServiceManager.h"
 #include "nsThreadManager.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsXPCOMPrivate.h"
 #include "nsIXULAppInfo.h"
@@ -38,16 +37,17 @@
 #include "nsString.h"
 #include "nsITelemetry.h"
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "nsIMemoryReporter.h"
 #include "nsISeekableStream.h"
 #include "Telemetry.h"
 #include "TelemetryCommon.h"
+#include "TelemetryHistogram.h"
 #include "WebrtcTelemetry.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsXULAppAPI.h"
 #include "nsReadableUtils.h"
 #include "nsThreadUtils.h"
@@ -70,52 +70,26 @@
 #include "mozilla/IOInterposer.h"
 #include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/HangMonitor.h"
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 #include "shared-libraries.h"
 #endif
 
-#define EXPIRED_ID "__expired__"
-
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 
-using base::BooleanHistogram;
-using base::CountHistogram;
-using base::FlagHistogram;
-using base::Histogram;
-using base::LinearHistogram;
-using base::StatisticsRecorder;
-
 // The maximum number of chrome hangs stacks that we're keeping.
 const size_t kMaxChromeStacksKept = 50;
 // The maximum depth of a single chrome hang stack.
 const size_t kMaxChromeStackDepth = 50;
 
-#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
-#define SUBSESSION_HISTOGRAM_PREFIX "sub#"
-
-enum reflectStatus {
-  REFLECT_OK,
-  REFLECT_CORRUPT,
-  REFLECT_FAILURE
-};
-
-nsresult
-HistogramGet(const char *name, const char *expiration, uint32_t histogramType,
-             uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs,
-             Histogram **result);
-
-enum reflectStatus
-ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h);
-
 // This class is conceptually a list of ProcessedStack objects, but it represents them
 // more efficiently by keeping a single global list of modules.
 class CombinedStacks {
 public:
   CombinedStacks() : mNextIndex(0) {}
   typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
   const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
   size_t GetModuleCount() const;
@@ -685,58 +659,50 @@ ClearIOReporting()
   if (!sTelemetryIOObserver) {
     return;
   }
   IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
                            sTelemetryIOObserver);
   sTelemetryIOObserver = nullptr;
 }
 
-class KeyedHistogram;
-
 class TelemetryImpl final
   : public nsITelemetry
   , public nsIMemoryReporter
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITELEMETRY
   NS_DECL_NSIMEMORYREPORTER
 
 public:
   void InitMemoryReporter();
 
-  static bool IsInitialized();
-  static bool CanRecordBase();
-  static bool CanRecordExtended();
   static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
   static void ShutdownTelemetry();
   static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
                                   uint32_t delay);
 #if defined(MOZ_ENABLE_PROFILER_SPS)
   static void RecordChromeHang(uint32_t aDuration,
                                Telemetry::ProcessedStack &aStack,
                                int32_t aSystemUptime,
                                int32_t aFirefoxUptime,
                                HangAnnotationsPtr aAnnotations);
 #endif
   static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
-  static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
   struct Stat {
     uint32_t hitCount;
     uint32_t totalTime;
   };
   struct StmtStats {
     struct Stat mainThread;
     struct Stat otherThreads;
   };
   typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
 
-  static KeyedHistogram* GetKeyedHistogramById(const nsACString &id);
-
   static void RecordIceCandidates(const uint32_t iceCandidateBitmask,
                                   const bool success,
                                   const bool loop);
 private:
   TelemetryImpl();
   ~TelemetryImpl();
 
   static nsCString SanitizeSQL(const nsACString& sql);
@@ -753,51 +719,18 @@ private:
   static bool ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat,
                          JSContext *cx, JS::Handle<JSObject*> obj);
 
   bool AddSQLInfo(JSContext *cx, JS::Handle<JSObject*> rootObj, bool mainThread,
                   bool privateSQL);
   bool GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret,
                    bool includePrivateSql);
 
-  // Like GetHistogramById, but returns the underlying C++ object, not the JS one.
-  nsresult GetHistogramByName(const nsACString &name, Histogram **ret);
-  bool ShouldReflectHistogram(Histogram *h);
-  void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs);
-  nsresult CreateHistogramSnapshots(JSContext *cx,
-                                    JS::MutableHandle<JS::Value> ret,
-                                    bool subsession,
-                                    bool clearSubsession);
-  typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
-
-  struct AddonHistogramInfo {
-    uint32_t min;
-    uint32_t max;
-    uint32_t bucketCount;
-    uint32_t histogramType;
-    Histogram *h;
-  };
-  typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType;
-  typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType;
-  typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType;
-  typedef AutoHashtable<AddonEntryType> AddonMapType;
-  static bool AddonHistogramReflector(AddonHistogramEntryType *entry,
-                                      JSContext *cx, JS::Handle<JSObject*> obj);
-  static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj);
-  static bool CreateHistogramForAddon(const nsACString &name,
-                                      AddonHistogramInfo &info);
   void ReadLateWritesStacks(nsIFile* aProfileDir);
-  AddonMapType mAddonMap;
-
-  // This is used for speedy string->Telemetry::ID conversions
-  typedef nsBaseHashtableET<nsDepCharHashKey, Telemetry::ID> CharPtrEntryType;
-  typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
-  HistogramMapType mHistogramMap;
-  bool mCanRecordBase;
-  bool mCanRecordExtended;
+
   static TelemetryImpl *sTelemetry;
   AutoHashtable<SlowSQLEntryType> mPrivateSQL;
   AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
   Mutex mHashMutex;
   HangReports mHangReports;
   Mutex mHangReportsMutex;
   // mThreadHangStats stores recorded, inactive thread hang stats
   Vector<Telemetry::ThreadHangStats> mThreadHangStats;
@@ -806,882 +739,36 @@ private:
   CombinedStacks mLateWritesStacks; // This is collected out of the main thread.
   bool mCachedTelemetryData;
   uint32_t mLastShutdownTime;
   uint32_t mFailedLockCount;
   nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks;
   friend class nsFetchTelemetryData;
 
   WebrtcTelemetry mWebrtcTelemetry;
-
-  typedef nsClassHashtable<nsCStringHashKey, KeyedHistogram> KeyedHistogramMapType;
-  KeyedHistogramMapType mKeyedHistograms;
 };
 
 TelemetryImpl*  TelemetryImpl::sTelemetry = nullptr;
 
 MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf)
 
 NS_IMETHODIMP
 TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData, bool aAnonymize)
 {
   return MOZ_COLLECT_REPORT(
     "explicit/telemetry", KIND_HEAP, UNITS_BYTES,
     SizeOfIncludingThis(TelemetryMallocSizeOf),
     "Memory used by the telemetry system.");
 }
 
-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 GetDataset(uint32_t* dataset) const;
-  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);
-
-private:
-  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
-  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
-  KeyedHistogramMapType mHistogramMap;
-#if !defined(MOZ_WIDGET_GONK) && !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;
-};
-
-// Hardcoded probes
-struct TelemetryHistogram {
-  uint32_t min;
-  uint32_t max;
-  uint32_t bucketCount;
-  uint32_t histogramType;
-  uint32_t id_offset;
-  uint32_t expiration_offset;
-  uint32_t dataset;
-  bool keyed;
-
-  const char *id() const;
-  const char *expiration() const;
-};
-
-#include "TelemetryHistogramData.inc"
-bool gCorruptHistograms[Telemetry::HistogramCount];
-
-const char *
-TelemetryHistogram::id() const
-{
-  return &gHistogramStringTable[this->id_offset];
-}
-
-const char *
-TelemetryHistogram::expiration() const
-{
-  return &gHistogramStringTable[this->expiration_offset];
-}
-
-bool
-IsHistogramEnumId(Telemetry::ID aID)
-{
-  static_assert(((Telemetry::ID)-1 > 0), "ID should be unsigned.");
-  return aID < Telemetry::HistogramCount;
-}
-
-// List of histogram IDs which should have recording disabled initially.
-const Telemetry::ID kRecordingInitiallyDisabledIDs[] = {
-  Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
-
-  // The array must not be empty. Leave these item here.
-  Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
-  Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
-};
-
 void
 InitHistogramRecordingEnabled()
 {
-  const size_t length = mozilla::ArrayLength(kRecordingInitiallyDisabledIDs);
-  for (size_t i = 0; i < length; i++) {
-    SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i], false);
-  }
-}
-
-bool
-IsExpired(const char *expiration){
-  static Version current_version = Version(MOZ_APP_VERSION);
-  MOZ_ASSERT(expiration);
-  return strcmp(expiration, "never") && strcmp(expiration, "default") &&
-    (mozilla::Version(expiration) <= current_version);
-}
-
-bool
-IsExpired(const Histogram *histogram){
-  return histogram->histogram_name() == EXPIRED_ID;
-}
-
-bool
-IsValidHistogramName(const nsACString& name)
-{
-  return !FindInReadable(NS_LITERAL_CSTRING(KEYED_HISTOGRAM_NAME_SEPARATOR), name);
-}
-
-bool
-IsInDataset(uint32_t dataset, uint32_t containingDataset)
-{
-  if (dataset == containingDataset) {
-    return true;
-  }
-
-  // The "optin on release channel" dataset is a superset of the
-  // "optout on release channel one".
-  if (containingDataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN
-      && dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) {
-    return true;
-  }
-
-  return false;
-}
-
-bool
-CanRecordDataset(uint32_t dataset)
-{
-  // If we are extended telemetry is enabled, we are allowed to record regardless of
-  // the dataset.
-  if (TelemetryImpl::CanRecordExtended()) {
-    return true;
-  }
-
-  // If base telemetry data is enabled and we're trying to record base telemetry, allow it.
-  if (TelemetryImpl::CanRecordBase() &&
-      IsInDataset(dataset, nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT)) {
-      return true;
-  }
-
-  // We're not recording extended telemetry or this is not the base dataset. Bail out.
-  return false;
-}
-
-nsresult
-CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max,
-                        uint32_t bucketCount, bool haveOptArgs)
-{
-  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)
-      return NS_ERROR_ILLEGAL_VALUE;
-
-    // Sanity checks for histogram parameters.
-    if (min >= max)
-      return NS_ERROR_ILLEGAL_VALUE;
-
-    if (bucketCount <= 2)
-      return NS_ERROR_ILLEGAL_VALUE;
-
-    if (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
-HistogramGet(const char *name, const char *expiration, uint32_t histogramType,
-             uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs,
-             Histogram **result)
-{
-  nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, haveOptArgs);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  if (IsExpired(expiration)) {
-    name = EXPIRED_ID;
-    min = 1;
-    max = 2;
-    bucketCount = 3;
-    histogramType = nsITelemetry::HISTOGRAM_LINEAR;
-  }
-
-  switch (histogramType) {
-  case nsITelemetry::HISTOGRAM_EXPONENTIAL:
-    *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
-    break;
-  case nsITelemetry::HISTOGRAM_LINEAR:
-    *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
-    break;
-  case nsITelemetry::HISTOGRAM_BOOLEAN:
-    *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
-    break;
-  case nsITelemetry::HISTOGRAM_FLAG:
-    *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
-    break;
-  case nsITelemetry::HISTOGRAM_COUNT:
-    *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
-    break;
-  default:
-    NS_ASSERTION(false, "Invalid histogram type");
-    return NS_ERROR_INVALID_ARG;
-  }
-  return NS_OK;
-}
-
-// O(1) histogram lookup by numeric id
-nsresult
-GetHistogramByEnumId(Telemetry::ID id, Histogram **ret)
-{
-  static Histogram* knownHistograms[Telemetry::HistogramCount] = {0};
-  Histogram *h = knownHistograms[id];
-  if (h) {
-    *ret = h;
-    return NS_OK;
-  }
-
-  const TelemetryHistogram &p = gHistograms[id];
-  if (p.keyed) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = HistogramGet(p.id(), 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 (!IsExpired(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
-
-  *ret = knownHistograms[id] = h;
-  return NS_OK;
-}
-
-/**
- * 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*
-CloneHistogram(const nsACString& newName, Telemetry::ID existingId,
-               Histogram& existing)
-{
-  const TelemetryHistogram &info = gHistograms[existingId];
-  Histogram *clone = nullptr;
-  nsresult rv;
-
-  rv = 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;
-}
-
-/**
- * This clones a histogram with the id |existingId| to a new histogram
- * with the name |newName|.
- * For simplicity this is limited to registered histograms.
- */
-Histogram*
-CloneHistogram(const nsACString& newName, Telemetry::ID existingId)
-{
-  Histogram *existing = nullptr;
-  nsresult rv = GetHistogramByEnumId(existingId, &existing);
-  if (NS_FAILED(rv)) {
-    return nullptr;
-  }
-
-  return CloneHistogram(newName, existingId, *existing);
-}
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-Histogram*
-GetSubsessionHistogram(Histogram& existing)
-{
-  Telemetry::ID id;
-  nsresult rv = TelemetryImpl::GetHistogramEnumId(existing.histogram_name().c_str(), &id);
-  if (NS_FAILED(rv) || gHistograms[id].keyed) {
-    return nullptr;
-  }
-
-  static Histogram* subsession[Telemetry::HistogramCount] = {};
-  if (subsession[id]) {
-    return subsession[id];
-  }
-
-  NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
-  nsDependentCString existingName(gHistograms[id].id());
-  if (StringBeginsWith(existingName, prefix)) {
-    return nullptr;
-  }
-
-  nsCString subsessionName(prefix);
-  subsessionName.Append(existingName);
-
-  subsession[id] = CloneHistogram(subsessionName, id, existing);
-  return subsession[id];
-}
-#endif
-
-nsresult
-HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
-{
-  // Check if we are allowed to record the data.
-  if (!CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) {
-    return NS_OK;
-  }
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = 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
-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 (!TelemetryImpl::CanRecordExtended()) {
-    Telemetry::ID id;
-    nsresult rv = TelemetryImpl::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 HistogramAdd(histogram, value, dataset);
-}
-
-void
-HistogramClear(Histogram& aHistogram, bool onlySubsession)
-{
-  if (!onlySubsession) {
-    aHistogram.Clear();
-  }
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = GetSubsessionHistogram(aHistogram)) {
-    subsession->Clear();
-  }
-#endif
-}
-
-bool
-FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
-{
-  JS::Rooted<JS::Value> range(cx);
-  for (size_t i = 0; i < h->bucket_count(); i++) {
-    range.setInt32(h->ranges(i));
-    if (!JS_DefineElement(cx, array, i, range, JSPROP_ENUMERATE))
-      return false;
-  }
-  return true;
-}
-
-enum reflectStatus
-ReflectHistogramAndSamples(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h,
-                           const Histogram::SampleSet &ss)
-{
-  mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
-
-  // We don't want to reflect corrupt histograms.
-  if (h->FindCorruption(ss, locker) != 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(locker)), JSPROP_ENUMERATE))) {
-    return REFLECT_FAILURE;
-  }
-
-  const size_t count = h->bucket_count();
-  JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count));
-  if (!rarray) {
-    return REFLECT_FAILURE;
-  }
-  if (!(FillRanges(cx, rarray, h)
-        && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) {
-    return REFLECT_FAILURE;
-  }
-
-  JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count));
-  if (!counts_array) {
-    return REFLECT_FAILURE;
-  }
-  if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) {
-    return REFLECT_FAILURE;
-  }
-  for (size_t i = 0; i < count; i++) {
-    if (!JS_DefineElement(cx, counts_array, i,
-                          ss.counts(locker, i), JSPROP_ENUMERATE)) {
-      return REFLECT_FAILURE;
-    }
-  }
-
-  return REFLECT_OK;
-}
-
-enum reflectStatus
-ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h)
-{
-  Histogram::SampleSet ss;
-  h->SnapshotSample(&ss);
-  return ReflectHistogramAndSamples(cx, obj, h, ss);
-}
-
-bool
-IsEmpty(const Histogram *h)
-{
-  Histogram::SampleSet ss;
-  h->SnapshotSample(&ss);
-
-  mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
-  return ss.counts(locker, 0) == 0 && ss.sum(locker) == 0;
-}
-
-bool
-JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
-  MOZ_ASSERT(h);
-  Histogram::ClassType type = h->histogram_type();
-
-  int32_t value = 1;
-  if (type != base::CountHistogram::COUNT_HISTOGRAM) {
-    JS::CallArgs args = CallArgsFromVp(argc, vp);
-    if (!args.length()) {
-      JS_ReportError(cx, "Expected one argument");
-      return false;
-    }
-
-    if (!(args[0].isNumber() || args[0].isBoolean())) {
-      JS_ReportError(cx, "Not a number");
-      return false;
-    }
-
-    if (!JS::ToInt32(cx, args[0], &value)) {
-      return false;
-    }
-  }
-
-  if (TelemetryImpl::CanRecordBase()) {
-    HistogramAdd(*h, value);
-  }
-
-  return true;
-}
-
-bool
-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) {
-    return false;
-  }
-
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
-  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot)
-    return false;
-
-  switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
-  case REFLECT_FAILURE:
-    return false;
-  case REFLECT_CORRUPT:
-    JS_ReportError(cx, "Histogram is corrupt");
-    return false;
-  case REFLECT_OK:
-    args.rval().setObject(*snapshot);
-    return true;
-  default:
-    MOZ_CRASH("unhandled reflection status");
-  }
-}
-
-bool
-JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  bool onlySubsession = false;
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-  if (args.length() >= 1) {
-    if (!args[0].isBoolean()) {
-      JS_ReportError(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) {
-    HistogramClear(*h, onlySubsession);
-  }
-
-  return true;
-}
-
-bool
-JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
-  Telemetry::ID id;
-  nsresult rv = TelemetryImpl::GetHistogramEnumId(h->histogram_name().c_str(), &id);
-  if (NS_SUCCEEDED(rv)) {
-    args.rval().setNumber(gHistograms[id].dataset);
-    return true;
-  }
-
-  return false;
-}
-
-nsresult
-WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
-{
-  static const JSClass JSHistogram_class = {
-    "JSHistogram",  /* name */
-    JSCLASS_HAS_PRIVATE  /* flags */
-  };
-
-  JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
-  if (!obj)
-    return NS_ERROR_FAILURE;
-  if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0)
-        && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0)
-        && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0)
-        && JS_DefineFunction(cx, obj, "dataset", JSHistogram_Dataset, 0, 0))) {
-    return NS_ERROR_FAILURE;
-  }
-  JS_SetPrivate(obj, h);
-  ret.setObject(*obj);
-  return NS_OK;
-}
-
-bool
-JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
-
-  JS::CallArgs args = CallArgsFromVp(argc, vp);
-  if (args.length() < 1) {
-    JS_ReportError(cx, "Expected one argument");
-    return false;
-  }
-
-  nsAutoJSString key;
-  if (!args[0].isString() || !key.init(cx, args[0])) {
-    JS_ReportError(cx, "Not a string");
-    return false;
-  }
-
-  const uint32_t type = keyed->GetHistogramType();
-  int32_t value = 1;
-
-  if (type != base::CountHistogram::COUNT_HISTOGRAM) {
-    if (args.length() < 2) {
-      JS_ReportError(cx, "Expected two arguments for this histogram type");
-      return false;
-    }
-
-    if (!(args[1].isNumber() || args[1].isBoolean())) {
-      JS_ReportError(cx, "Not a number");
-      return false;
-    }
-
-    if (!JS::ToInt32(cx, args[1], &value)) {
-      return false;
-    }
-  }
-
-  keyed->Add(NS_ConvertUTF16toUTF8(key), value);
-  return true;
-}
-
-bool
-JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
-
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  return NS_SUCCEEDED(keyed->GetJSKeys(cx, args));
-}
-
-bool
-KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp,
-                            bool subsession, bool clearSubsession)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
-
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-  if (args.length() == 0) {
-    JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
-    if (!snapshot) {
-      JS_ReportError(cx, "Failed to create object");
-      return false;
-    }
-
-    if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) {
-      JS_ReportError(cx, "Failed to reflect keyed histograms");
-      return false;
-    }
-
-    args.rval().setObject(*snapshot);
-    return true;
-  }
-
-  nsAutoJSString key;
-  if (!args[0].isString() || !key.init(cx, args[0])) {
-    JS_ReportError(cx, "Not a string");
-    return false;
-  }
-
-  Histogram* h = nullptr;
-  nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession);
-  if (NS_FAILED(rv)) {
-    JS_ReportError(cx, "Failed to get histogram");
-    return false;
-  }
-
-  JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot) {
-    return false;
-  }
-
-  switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
-  case REFLECT_FAILURE:
-    return false;
-  case REFLECT_CORRUPT:
-    JS_ReportError(cx, "Histogram is corrupt");
-    return false;
-  case REFLECT_OK:
-    args.rval().setObject(*snapshot);
-    return true;
-  default:
-    MOZ_CRASH("unhandled reflection status");
-  }
-}
-
-bool
-JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false);
-}
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-bool
-JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false);
-}
-#endif
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-bool
-JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  if (args.length() != 0) {
-    JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear");
-  }
-
-  return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true);
-}
-#endif
-
-bool
-JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  bool onlySubsession = false;
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-  if (args.length() >= 1) {
-    if (!(args[0].isNumber() || args[0].isBoolean())) {
-      JS_ReportError(cx, "Not a boolean");
-      return false;
-    }
-
-    onlySubsession = JS::ToBoolean(args[0]);
-  }
-
-  keyed->Clear(onlySubsession);
-#else
-  keyed->Clear(false);
-#endif
-  return true;
-}
-
-bool
-JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JSObject *obj = JS_THIS_OBJECT(cx, vp);
-  if (!obj) {
-    return false;
-  }
-
-  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
-  if (!keyed) {
-    return false;
-  }
-
-  uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
-  nsresult rv = keyed->GetDataset(&dataset);;
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-
-  args.rval().setNumber(dataset);
-  return true;
-}
-
-nsresult
-WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
-{
-  static const JSClass JSHistogram_class = {
-    "JSKeyedHistogram",  /* name */
-    JSCLASS_HAS_PRIVATE  /* flags */
-  };
-
-  JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
-  if (!obj)
-    return NS_ERROR_FAILURE;
-  if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0)
-        && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0)
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-        && JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0)
-        && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0)
-#endif
-        && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0)
-        && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0)
-        && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) {
-    return NS_ERROR_FAILURE;
-  }
-
-  JS_SetPrivate(obj, h);
-  ret.setObject(*obj);
-  return NS_OK;
+  TelemetryHistogram::InitHistogramRecordingEnabled();
 }
 
 static uint32_t
 ReadLastShutdownDuration(const char *filename) {
   FILE *f = fopen(filename, "r");
   if (!f) {
     return 0;
   }
@@ -1912,124 +999,56 @@ TelemetryImpl::AsyncFetchTelemetryData(n
   nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData(shutdownTimeFilename,
                                                          failedProfileLockFile,
                                                          profileDir);
 
   targetThread->Dispatch(event, NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
-TelemetryImpl::TelemetryImpl():
-mHistogramMap(Telemetry::HistogramCount),
-mCanRecordBase(XRE_IsParentProcess() || XRE_IsContentProcess()),
-mCanRecordExtended(XRE_IsParentProcess() || XRE_IsContentProcess()),
-mHashMutex("Telemetry::mHashMutex"),
-mHangReportsMutex("Telemetry::mHangReportsMutex"),
-mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"),
-mCachedTelemetryData(false),
-mLastShutdownTime(0),
-mFailedLockCount(0)
+TelemetryImpl::TelemetryImpl()
+  : mHashMutex("Telemetry::mHashMutex")
+  , mHangReportsMutex("Telemetry::mHangReportsMutex")
+  , mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex")
+  , mCachedTelemetryData(false)
+  , mLastShutdownTime(0)
+  , mFailedLockCount(0)
 {
-  // Populate the static histogram name->id cache.
-  // Note that the histogram names are statically allocated.
-  for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) {
-    CharPtrEntryType *entry = mHistogramMap.PutEntry(gHistograms[i].id());
-    entry->mData = (Telemetry::ID) i;
-  }
-
-#ifdef DEBUG
-  mHistogramMap.MarkImmutable();
-#endif
-
-  // Create registered keyed histograms
-  for (size_t i = 0; i < ArrayLength(gHistograms); ++i) {
-    const TelemetryHistogram& h = gHistograms[i];
-    if (!h.keyed) {
-      continue;
-    }
-
-    const nsDependentCString id(h.id());
-    const nsDependentCString expiration(h.expiration());
-    mKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
-                                                h.min, h.max, h.bucketCount, h.dataset));
-  }
-
-  // Setup of the initial recording-enabled state (for not-recording-by-default
-  // histograms) using InitHistogramRecordingEnabled() will happen after instantiating
-  // sTelemetry since it depends on the static GetKeyedHistogramById(...) - which
-  // uses the singleton instance at sTelemetry.
-
-  // 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.
-  // TODO: Compare explicitly with gHistograms[<histogram id>].bucketCount here
-  // once we can make gHistograms constexpr (requires VS2015).
-  static_assert((JS::gcreason::NUM_TELEMETRY_REASONS == 100),
-      "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
-      " If this was an intentional change, update this assert with its value "
-      "and 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 == 16),
-      "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json.  If this"
-      " was an intentional change, update this assert with its value and update"
-      " the n_values for the following in Histograms.json:"
-      " STARTUP_MEASUREMENT_ERRORS");
+  // We expect TelemetryHistogram::InitializeGlobalState() to have been
+  // called before we get to this point.
+  MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized());
 }
 
 TelemetryImpl::~TelemetryImpl() {
   UnregisterWeakMemoryReporter(this);
 }
 
 void
 TelemetryImpl::InitMemoryReporter() {
   RegisterWeakMemoryReporter(this);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration, uint32_t histogramType,
                             uint32_t min, uint32_t max, uint32_t bucketCount, JSContext *cx,
                             uint8_t optArgCount, JS::MutableHandle<JS::Value> ret)
 {
-  if (!IsValidHistogramName(name)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  Histogram *h;
-  nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(),
-                             histogramType, min, max, bucketCount, optArgCount == 3, &h);
-  if (NS_FAILED(rv))
-    return rv;
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  return WrapAndReturnHistogram(h, cx, ret);
+  return TelemetryHistogram::NewHistogram(name, expiration, histogramType,
+                                          min, max, bucketCount,
+                                          cx, optArgCount, ret);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::NewKeyedHistogram(const nsACString &name, const nsACString &expiration, uint32_t histogramType,
                             uint32_t min, uint32_t max, uint32_t bucketCount, JSContext *cx,
                             uint8_t optArgCount, JS::MutableHandle<JS::Value> ret)
 {
-  if (!IsValidHistogramName(name)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, optArgCount == 3);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  KeyedHistogram* keyed = new KeyedHistogram(name, expiration, histogramType,
-                                             min, max, bucketCount,
-                                             nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN);
-  if (MOZ_UNLIKELY(!mKeyedHistograms.Put(name, keyed, fallible))) {
-    delete keyed;
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return WrapAndReturnKeyedHistogram(keyed, cx, ret);
+  return TelemetryHistogram::NewKeyedHistogram(name, expiration, histogramType,
+                                               min, max, bucketCount,
+                                               cx, optArgCount, ret);
 }
 
 
 bool
 TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry,
                           const Stat *stat,
                           JSContext *cx,
                           JS::Handle<JSObject*> obj)
@@ -2079,491 +1098,83 @@ TelemetryImpl::AddSQLInfo(JSContext *cx,
     return false;
   }
 
   return JS_DefineProperty(cx, rootObj,
                            mainThread ? "mainThread" : "otherThreads",
                            statsObj, JSPROP_ENUMERATE);
 }
 
-nsresult
-TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
-{
-  if (!sTelemetry) {
-    return NS_ERROR_FAILURE;
-  }
-
-  const TelemetryImpl::HistogramMapType& map = sTelemetry->mHistogramMap;
-  CharPtrEntryType *entry = map.GetEntry(name);
-  if (!entry) {
-    return NS_ERROR_INVALID_ARG;
-  }
-  *id = entry->mData;
-  return NS_OK;
-}
-
-nsresult
-TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret)
-{
-  Telemetry::ID id;
-  nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  rv = GetHistogramByEnumId(id, ret);
-  if (NS_FAILED(rv))
-    return rv;
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name,
                              JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-  Telemetry::ID id;
-  nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  Histogram* clone = CloneHistogram(name, id);
-  if (!clone) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return WrapAndReturnHistogram(clone, cx, ret);
-}
-
-void
-TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
-{
-  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
-    Histogram *h = *it;
-
-    Telemetry::ID id;
-    nsresult rv = 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;
-    {
-      mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
-      check = h->FindCorruption(ss, locker);
-    }
-
-    bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
-
-    if (corrupt) {
-      Telemetry::ID corruptID = Telemetry::HistogramCount;
-      if (check & Histogram::RANGE_CHECKSUM_ERROR) {
-        corruptID = Telemetry::RANGE_CHECKSUM_ERRORS;
-      } else if (check & Histogram::BUCKET_ORDER_ERROR) {
-        corruptID = Telemetry::BUCKET_ORDER_ERRORS;
-      } else if (check & Histogram::COUNT_HIGH_ERROR) {
-        corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS;
-      } else if (check & Histogram::COUNT_LOW_ERROR) {
-        corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS;
-      }
-      Telemetry::Accumulate(corruptID, 1);
-    }
-
-    gCorruptHistograms[id] = corrupt;
-  }
-}
-
-bool
-TelemetryImpl::ShouldReflectHistogram(Histogram *h)
-{
-  const char *name = h->histogram_name().c_str();
-  Telemetry::ID id;
-  nsresult rv = 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;
-  } else {
-    return !gCorruptHistograms[id];
-  }
-}
-
-// Compute the name to pass into Histogram for the addon histogram
-// 'name' from the addon 'id'.  We can't use 'name' directly because it
-// might conflict with other histograms in other addons or even with our
-// own.
-void
-AddonHistogramName(const nsACString &id, const nsACString &name,
-                   nsACString &ret)
-{
-  ret.Append(id);
-  ret.Append(':');
-  ret.Append(name);
+  return TelemetryHistogram::HistogramFrom(name, existing_name, cx, ret);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::RegisterAddonHistogram(const nsACString &id,
                                       const nsACString &name,
                                       uint32_t histogramType,
                                       uint32_t min, uint32_t max,
                                       uint32_t bucketCount,
                                       uint8_t optArgCount)
 {
-  if (histogramType == HISTOGRAM_EXPONENTIAL ||
-      histogramType == HISTOGRAM_LINEAR) {
-    if (optArgCount != 3) {
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    // Sanity checks for histogram parameters.
-    if (min >= max)
-      return NS_ERROR_ILLEGAL_VALUE;
-
-    if (bucketCount <= 2)
-      return NS_ERROR_ILLEGAL_VALUE;
-
-    if (min < 1)
-      return NS_ERROR_ILLEGAL_VALUE;
-  } else {
-    min = 1;
-    max = 2;
-    bucketCount = 3;
-  }
-
-  AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
-  if (!addonEntry) {
-    addonEntry = mAddonMap.PutEntry(id);
-    if (MOZ_UNLIKELY(!addonEntry)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    addonEntry->mData = new AddonHistogramMapType();
-  }
-
-  AddonHistogramMapType *histogramMap = addonEntry->mData;
-  AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
-  // Can't re-register the same histogram.
-  if (histogramEntry) {
-    return NS_ERROR_FAILURE;
-  }
-
-  histogramEntry = histogramMap->PutEntry(name);
-  if (MOZ_UNLIKELY(!histogramEntry)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  AddonHistogramInfo &info = histogramEntry->mData;
-  info.min = min;
-  info.max = max;
-  info.bucketCount = bucketCount;
-  info.histogramType = histogramType;
-
-  return NS_OK;
+  return TelemetryHistogram::RegisterAddonHistogram
+            (id, name, histogramType, min, max, bucketCount, optArgCount);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name,
                                  JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-  AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
-  // The given id has not been registered.
-  if (!addonEntry) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  AddonHistogramMapType *histogramMap = addonEntry->mData;
-  AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
-  // The given histogram name has not been registered.
-  if (!histogramEntry) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  AddonHistogramInfo &info = histogramEntry->mData;
-  if (!info.h) {
-    nsAutoCString actualName;
-    AddonHistogramName(id, name, actualName);
-    if (!CreateHistogramForAddon(actualName, info)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-  return WrapAndReturnHistogram(info.h, cx, ret);
+  return TelemetryHistogram::GetAddonHistogram(id, name, cx, ret);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::UnregisterAddonHistograms(const nsACString &id)
 {
-  AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
-  if (addonEntry) {
-    // Histogram's destructor is private, so this is the best we can do.
-    // The histograms the addon created *will* stick around, but they
-    // will be deleted if and when the addon registers histograms with
-    // the same names.
-    delete addonEntry->mData;
-    mAddonMap.RemoveEntry(addonEntry);
-  }
-
-  return NS_OK;
+  return TelemetryHistogram::UnregisterAddonHistograms(id);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled)
 {
-  Histogram *h;
-  nsresult rv = GetHistogramByName(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    h->SetRecordingEnabled(aEnabled);
-    return NS_OK;
-  }
-
-  KeyedHistogram* keyed = GetKeyedHistogramById(id);
-  if (keyed) {
-    keyed->SetRecordingEnabled(aEnabled);
-    return NS_OK;
-  }
-
-  return NS_ERROR_FAILURE;
-}
-
-nsresult
-TelemetryImpl::CreateHistogramSnapshots(JSContext *cx,
-                                        JS::MutableHandle<JS::Value> ret,
-                                        bool subsession,
-                                        bool clearSubsession)
-{
-  JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
-  if (!root_obj)
-    return NS_ERROR_FAILURE;
-  ret.setObject(*root_obj);
-
-  // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
-  // been created, so that their values are snapshotted.
-  for (size_t i = 0; i < Telemetry::HistogramCount; ++i) {
-    if (gHistograms[i].keyed) {
-      continue;
-    }
-    const uint32_t type = gHistograms[i].histogramType;
-    if (type == nsITelemetry::HISTOGRAM_FLAG ||
-        type == nsITelemetry::HISTOGRAM_COUNT) {
-      Histogram *h;
-      DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-    }
-  }
-
-  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...
-  IdentifyCorruptHistograms(hs);
-
-  // OK, now we can actually reflect things.
-  JS::Rooted<JSObject*> hobj(cx);
-  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
-    Histogram *h = *it;
-    if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) {
-      continue;
-    }
-
-    Histogram* original = h;
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-    if (subsession) {
-      h = GetSubsessionHistogram(*h);
-      if (!h) {
-        continue;
-      }
-    }
-#endif
-
-    hobj = JS_NewPlainObject(cx);
-    if (!hobj) {
-      return NS_ERROR_FAILURE;
-    }
-    switch (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_GONK) && !defined(MOZ_WIDGET_ANDROID)
-    if (subsession && clearSubsession) {
-      h->Clear();
-    }
-#endif
-  }
-  return NS_OK;
+  return TelemetryHistogram::SetHistogramRecordingEnabled(id, aEnabled);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-  return CreateHistogramSnapshots(cx, ret, false, false);
+  return TelemetryHistogram::CreateHistogramSnapshots(cx, ret, false, false);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SnapshotSubsessionHistograms(bool clearSubsession,
                                             JSContext *cx,
                                             JS::MutableHandle<JS::Value> ret)
 {
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  return CreateHistogramSnapshots(cx, ret, true, clearSubsession);
+  return TelemetryHistogram::CreateHistogramSnapshots(cx, ret, true,
+                                                      clearSubsession);
 #else
   return NS_OK;
 #endif
 }
 
-bool
-TelemetryImpl::CreateHistogramForAddon(const nsACString &name,
-                                       AddonHistogramInfo &info)
-{
-  Histogram *h;
-  nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never",
-                             info.histogramType, info.min, info.max,
-                             info.bucketCount, true, &h);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-  // Don't let this histogram be reported via the normal means
-  // (e.g. Telemetry.registeredHistograms); we'll make it available in
-  // other ways.
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  info.h = h;
-  return true;
-}
-
-bool
-TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry,
-                                       JSContext *cx, JS::Handle<JSObject*> obj)
-{
-  AddonHistogramInfo &info = entry->mData;
-
-  // Never even accessed the histogram.
-  if (!info.h) {
-    // Have to force creation of HISTOGRAM_FLAG histograms.
-    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) 
-      return true;
-
-    if (!CreateHistogramForAddon(entry->GetKey(), info)) {
-      return false;
-    }
-  }
-
-  if (IsEmpty(info.h)) {
-    return true;
-  }
-
-  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot) {
-    // Just consider this to be skippable.
-    return true;
-  }
-  switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) {
-  case REFLECT_FAILURE:
-  case REFLECT_CORRUPT:
-    return false;
-  case REFLECT_OK:
-    const nsACString &histogramName = entry->GetKey();
-    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
-                           snapshot, JSPROP_ENUMERATE)) {
-      return false;
-    }
-    break;
-  }
-  return true;
-}
-
-bool
-TelemetryImpl::AddonReflector(AddonEntryType *entry,
-                              JSContext *cx, JS::Handle<JSObject*> obj)
-{
-  const nsACString &addonId = entry->GetKey();
-  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
-  if (!subobj) {
-    return false;
-  }
-
-  AddonHistogramMapType *map = entry->mData;
-  if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj)
-        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
-                             subobj, JSPROP_ENUMERATE))) {
-    return false;
-  }
-  return true;
-}
-
 NS_IMETHODIMP
 TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-  JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
-  if (!obj) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
-    return NS_ERROR_FAILURE;
-  }
-  ret.setObject(*obj);
-  return NS_OK;
+  return TelemetryHistogram::GetAddonHistogramSnapshots(cx, ret);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-  JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
-  if (!obj) {
-    return NS_ERROR_FAILURE;
-  }
-
-  for (auto iter = mKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
-    JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
-    if (!snapshot) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(),
-                           snapshot, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  ret.setObject(*obj);
-  return NS_OK;
+  return TelemetryHistogram::GetKeyedHistogramSnapshots(cx, ret);
 }
 
 bool
 TelemetryImpl::GetSQLStats(JSContext *cx, JS::MutableHandle<JS::Value> ret, bool includePrivateSql)
 {
   JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
   if (!root_obj)
     return false;
@@ -3223,164 +1834,110 @@ TelemetryImpl::GetLateWrites(JSContext *
   if (report == nullptr) {
     return NS_ERROR_FAILURE;
   }
 
   ret.setObject(*report);
   return NS_OK;
 }
 
-nsresult
-GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount,
-                          char*** aHistograms)
-{
-  nsTArray<char*> collection;
-
-  for (size_t i = 0; i < ArrayLength(gHistograms); ++i) {
-    const TelemetryHistogram& h = gHistograms[i];
-    if (IsExpired(h.expiration()) || h.keyed != keyed ||
-        !IsInDataset(h.dataset, dataset)) {
-      continue;
-    }
-
-    const char* id = h.id();
-    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;
-}
-
 NS_IMETHODIMP
 TelemetryImpl::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
                                     char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms);
+  return
+    TelemetryHistogram::RegisteredHistograms(aDataset, aCount, aHistograms);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount,
                                          char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms);
+  return
+    TelemetryHistogram::RegisteredKeyedHistograms(aDataset, aCount,
+                                                  aHistograms);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx,
                                 JS::MutableHandle<JS::Value> ret)
 {
-  Histogram *h;
-  nsresult rv = GetHistogramByName(name, &h);
-  if (NS_FAILED(rv))
-    return rv;
-
-  return WrapAndReturnHistogram(h, cx, ret);
+  return TelemetryHistogram::GetHistogramById(name, cx, ret);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetKeyedHistogramById(const nsACString &name, JSContext *cx,
                                      JS::MutableHandle<JS::Value> ret)
 {
-  KeyedHistogram* keyed = nullptr;
-  if (!mKeyedHistograms.Get(name, &keyed)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return WrapAndReturnKeyedHistogram(keyed, cx, ret);
-}
-
-/* static */
-KeyedHistogram*
-TelemetryImpl::GetKeyedHistogramById(const nsACString &name)
-{
-  if (!sTelemetry) {
-    return nullptr;
-  }
-
-  KeyedHistogram* keyed = nullptr;
-  sTelemetry->mKeyedHistograms.Get(name, &keyed);
-  return keyed;
-}
-
-NS_IMETHODIMP
-TelemetryImpl::GetCanRecordBase(bool *ret) {
-  *ret = mCanRecordBase;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-TelemetryImpl::SetCanRecordBase(bool canRecord) {
-  mCanRecordBase = canRecord;
-  return NS_OK;
-}
-
-/* static */ bool
-TelemetryImpl::IsInitialized()
-{
-  return sTelemetry;
+  return TelemetryHistogram::GetKeyedHistogramById(name, cx, ret);
 }
 
 /**
  * Indicates if Telemetry can record base data (FHR data). This is true if the
  * FHR data reporting service or self-support are enabled.
  *
  * In the unlikely event that adding a new base probe is needed, please check the data
  * collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection and talk to the
  * Telemetry team.
  */
-bool
-TelemetryImpl::CanRecordBase() {
-  return !sTelemetry || sTelemetry->mCanRecordBase;
-}
-
 NS_IMETHODIMP
-TelemetryImpl::GetCanRecordExtended(bool *ret) {
-  *ret = mCanRecordExtended;
+TelemetryImpl::GetCanRecordBase(bool *ret) {
+  *ret = TelemetryHistogram::CanRecordBase();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TelemetryImpl::SetCanRecordExtended(bool canRecord) {
-  mCanRecordExtended = canRecord;
+TelemetryImpl::SetCanRecordBase(bool canRecord) {
+  TelemetryHistogram::SetCanRecordBase(canRecord);
   return NS_OK;
 }
 
 /**
  * Indicates if Telemetry is allowed to record extended data. Returns false if the user
  * hasn't opted into "extended Telemetry" on the Release channel, when the user has
  * explicitly opted out of Telemetry on Nightly/Aurora/Beta or if manually set to false
  * during tests.
  * If the returned value is false, gathering of extended telemetry statistics is disabled.
  */
-bool
-TelemetryImpl::CanRecordExtended() {
-  return !sTelemetry || sTelemetry->mCanRecordExtended;
+NS_IMETHODIMP
+TelemetryImpl::GetCanRecordExtended(bool *ret) {
+  *ret = TelemetryHistogram::CanRecordExtended();
+  return NS_OK;
 }
 
 NS_IMETHODIMP
+TelemetryImpl::SetCanRecordExtended(bool canRecord) {
+  TelemetryHistogram::SetCanRecordExtended(canRecord);
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
 TelemetryImpl::GetIsOfficialTelemetry(bool *ret) {
 #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && !defined(DEBUG)
   *ret = true;
 #else
   *ret = false;
 #endif
   return NS_OK;
 }
 
 already_AddRefed<nsITelemetry>
 TelemetryImpl::CreateTelemetryInstance()
 {
   MOZ_ASSERT(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()");
+
+  // First, initialize the TelemetryHistogram global state.
+  TelemetryHistogram::InitializeGlobalState(
+                        XRE_IsParentProcess() || XRE_IsContentProcess(),
+                        XRE_IsParentProcess() || XRE_IsContentProcess());
+
+  // Now, create and initialize the Telemetry global state.
   sTelemetry = new TelemetryImpl();
+
   // AddRef for the local reference
   NS_ADDREF(sTelemetry);
   // AddRef for the caller
   nsCOMPtr<nsITelemetry> ret = sTelemetry;
 
   sTelemetry->InitMemoryReporter();
   InitHistogramRecordingEnabled(); // requires sTelemetry to exist
 
@@ -3388,16 +1945,20 @@ TelemetryImpl::CreateTelemetryInstance()
 }
 
 void
 TelemetryImpl::ShutdownTelemetry()
 {
   // No point in collecting IO beyond this point
   ClearIOReporting();
   NS_IF_RELEASE(sTelemetry);
+
+  // Lastly, de-initialise the TelemetryHistogram global state, so as to
+  // release any heap storage that would otherwise be kept alive by it.
+  TelemetryHistogram::DeInitializeGlobalState();
 }
 
 void
 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
                             SanitizedState state)
 {
   AutoHashtable<SlowSQLEntryType> *slowSQLMap = nullptr;
   if (state == Sanitized)
@@ -3606,17 +2167,17 @@ const uint32_t kMaxSlowStatementLength =
 void
 TelemetryImpl::RecordSlowStatement(const nsACString &sql,
                                    const nsACString &dbName,
                                    uint32_t delay)
 {
   MOZ_ASSERT(!sql.IsEmpty());
   MOZ_ASSERT(!dbName.IsEmpty());
 
-  if (!sTelemetry || !sTelemetry->mCanRecordExtended)
+  if (!sTelemetry || !TelemetryHistogram::CanRecordExtended())
     return;
 
   bool recordStatement = false;
 
   for (const TrackedDBEntry& nameEntry : kTrackedDBs) {
     MOZ_ASSERT(nameEntry.mNameLength);
     const nsDependentCString name(nameEntry.mName, nameEntry.mNameLength);
     if (dbName == name) {
@@ -3659,31 +2220,31 @@ TelemetryImpl::RecordSlowStatement(const
                        nsPromiseFlatCString(dbName).get());
   StoreSlowSQL(fullSQL, delay, Unsanitized);
 }
 
 void
 TelemetryImpl::RecordIceCandidates(const uint32_t iceCandidateBitmask,
                                    const bool success, const bool loop)
 {
-  if (!sTelemetry || !sTelemetry->mCanRecordExtended)
+  if (!sTelemetry || !TelemetryHistogram::CanRecordExtended())
     return;
 
   sTelemetry->mWebrtcTelemetry.RecordIceCandidateMask(iceCandidateBitmask, success, loop);
 }
 
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 void
 TelemetryImpl::RecordChromeHang(uint32_t aDuration,
                                 Telemetry::ProcessedStack &aStack,
                                 int32_t aSystemUptime,
                                 int32_t aFirefoxUptime,
                                 HangAnnotationsPtr aAnnotations)
 {
-  if (!sTelemetry || !sTelemetry->mCanRecordExtended)
+  if (!sTelemetry || !TelemetryHistogram::CanRecordExtended())
     return;
 
   HangAnnotationsPtr annotations;
   // We only pass aAnnotations if it is not empty.
   if (aAnnotations && !aAnnotations->IsEmpty()) {
     annotations = Move(aAnnotations);
   }
 
@@ -3693,17 +2254,17 @@ TelemetryImpl::RecordChromeHang(uint32_t
                                    aSystemUptime, aFirefoxUptime,
                                    Move(annotations));
 }
 #endif
 
 void
 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
 {
-  if (!sTelemetry || !sTelemetry->mCanRecordExtended)
+  if (!sTelemetry || !TelemetryHistogram::CanRecordExtended())
     return;
 
   MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex);
 
   // Ignore OOM.
   mozilla::Unused << sTelemetry->mThreadHangStats.append(Move(aStats));
 }
 
@@ -3766,18 +2327,17 @@ TelemetryImpl::MsSinceProcessStart(doubl
 }
 
 size_t
 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   size_t n = aMallocSizeOf(this);
 
   // Ignore the hashtables in mAddonMap; they are not significant.
-  n += mAddonMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  n += mHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
   n += mWebrtcTelemetry.SizeOfExcludingThis(aMallocSizeOf);
   { // Scope for mHashMutex lock
     MutexAutoLock lock(mHashMutex);
     n += mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf);
     n += mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf);
   }
   { // Scope for mHangReportsMutex lock
     MutexAutoLock lock(mHangReportsMutex);
@@ -3788,22 +2348,18 @@ TelemetryImpl::SizeOfIncludingThis(mozil
     n += mThreadHangStats.sizeOfExcludingThis(aMallocSizeOf);
   }
 
   // It's a bit gross that we measure this other stuff that lives outside of
   // TelemetryImpl... oh well.
   if (sTelemetryIOObserver) {
     n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf);
   }
-  StatisticsRecorder::Histograms hs;
-  StatisticsRecorder::GetHistograms(&hs);
-  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
-    Histogram *h = *it;
-    n += h->SizeOfIncludingThis(aMallocSizeOf);
-  }
+
+  n += TelemetryHistogram::GetHistogramSizesofIncludingThis(aMallocSizeOf);
 
   return n;
 }
 
 } // namespace
 
 namespace mozilla {
 void
@@ -3872,135 +2428,72 @@ RecordShutdownEndTimeStamp() {
 }
 
 namespace Telemetry {
 
 // The external API for controlling recording state
 void
 SetHistogramRecordingEnabled(ID aID, bool aEnabled)
 {
-  if (!IsHistogramEnumId(aID)) {
-    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
-    return;
-  }
-
-  if (gHistograms[aID].keyed) {
-    const nsDependentCString id(gHistograms[aID].id());
-    KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(id);
-    if (keyed) {
-      keyed->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  } else {
-    Histogram *h;
-    nsresult rv = GetHistogramByEnumId(aID, &h);
-    if (NS_SUCCEEDED(rv)) {
-      h->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  }
-
-  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
+  TelemetryHistogram::SetHistogramRecordingEnabled(aID, aEnabled);
 }
 
 void
 Accumulate(ID aHistogram, uint32_t aSample)
 {
-  if (!TelemetryImpl::CanRecordBase()) {
-    return;
-  }
-  Histogram *h;
-  nsresult rv = GetHistogramByEnumId(aHistogram, &h);
-  if (NS_SUCCEEDED(rv)) {
-    HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
-  }
+  TelemetryHistogram::Accumulate(aHistogram, aSample);
 }
 
 void
 Accumulate(ID aID, const nsCString& aKey, uint32_t aSample)
 {
-  if (!TelemetryImpl::IsInitialized() || !TelemetryImpl::CanRecordBase()) {
-    return;
-  }
-  const TelemetryHistogram& th = gHistograms[aID];
-  KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(nsDependentCString(th.id()));
-  MOZ_ASSERT(keyed);
-  keyed->Add(aKey, aSample);
+  TelemetryHistogram::Accumulate(aID, aKey, aSample);
 }
 
 void
 Accumulate(const char* name, uint32_t sample)
 {
-  if (!TelemetryImpl::CanRecordBase()) {
-    return;
-  }
-  ID id;
-  nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id);
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  Histogram *h;
-  rv = GetHistogramByEnumId(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    HistogramAdd(*h, sample, gHistograms[id].dataset);
-  }
+  TelemetryHistogram::Accumulate(name, sample);
 }
 
 void
 Accumulate(const char *name, const nsCString& key, uint32_t sample)
 {
-    if (!TelemetryImpl::CanRecordBase()) {
-      return;
-    }
-    ID id;
-    nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id);
-    if (NS_SUCCEEDED(rv)) {
-      Accumulate(id, key, sample);
-    }
+  TelemetryHistogram::Accumulate(name, key, sample);
 }
 
 void
 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
 {
   Accumulate(aHistogram,
              static_cast<uint32_t>((end - start).ToMilliseconds()));
 }
 
 void
 ClearHistogram(ID aId)
 {
-  if (!TelemetryImpl::CanRecordBase()) {
-    return;
-  }
-
-  Histogram *h;
-  nsresult rv = GetHistogramByEnumId(aId, &h);
-  if (NS_SUCCEEDED(rv) && h) {
-    HistogramClear(*h, false);
-  }
+  TelemetryHistogram::ClearHistogram(aId);
+}
+
+const char*
+GetHistogramName(ID id)
+{
+  return TelemetryHistogram::GetHistogramName(id);
 }
 
 bool
 CanRecordBase()
 {
-  return TelemetryImpl::CanRecordBase();
+  return TelemetryHistogram::CanRecordBase();
 }
 
 bool
 CanRecordExtended()
 {
-  return TelemetryImpl::CanRecordExtended();
-}
-
-const char*
-GetHistogramName(Telemetry::ID id)
-{
-  const TelemetryHistogram& h = gHistograms[id];
-  return h.id();
+  return TelemetryHistogram::CanRecordExtended();
 }
 
 void
 RecordSlowSQLStatement(const nsACString &statement,
                        const nsACString &dbName,
                        uint32_t delay)
 {
   TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
@@ -4373,207 +2866,8 @@ NSMODULE_DEFN(nsTelemetryModule) = &kTel
  * The XRE_TelemetryAdd function is to be used by embedding applications
  * that can't use mozilla::Telemetry::Accumulate() directly.
  */
 void
 XRE_TelemetryAccumulate(int aID, uint32_t aSample)
 {
   mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample);
 }
-
-KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expiration,
-                               uint32_t histogramType, uint32_t min, uint32_t max,
-                               uint32_t bucketCount, uint32_t dataset)
-  : mHistogramMap()
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  , mSubsessionMap()
-#endif
-  , mName(name)
-  , mExpiration(expiration)
-  , mHistogramType(histogramType)
-  , mMin(min)
-  , mMax(max)
-  , mBucketCount(bucketCount)
-  , mDataset(dataset)
-  , mRecordingEnabled(true)
-{
-}
-
-nsresult
-KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram,
-                             bool subsession)
-{
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
-#else
-  KeyedHistogramMapType& map = mHistogramMap;
-#endif
-  KeyedHistogramEntry* entry = map.GetEntry(key);
-  if (entry) {
-    *histogram = entry->mData;
-    return NS_OK;
-  }
-
-  nsCString histogramName;
-#if !defined(MOZ_WIDGET_GONK) && !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 = HistogramGet(histogramName.get(), mExpiration.get(),
-                             mHistogramType, mMin, mMax, mBucketCount,
-                             true, &h);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  *histogram = h;
-
-  entry = map.PutEntry(key);
-  if (MOZ_UNLIKELY(!entry)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  entry->mData = h;
-  return NS_OK;
-}
-
-Histogram*
-KeyedHistogram::GetHistogram(const nsCString& key, bool subsession)
-{
-  Histogram* h = nullptr;
-  if (NS_FAILED(GetHistogram(key, &h, subsession))) {
-    return nullptr;
-  }
-  return h;
-}
-
-nsresult
-KeyedHistogram::GetDataset(uint32_t* dataset) const
-{
-  MOZ_ASSERT(dataset);
-  *dataset = mDataset;
-  return NS_OK;
-}
-
-nsresult
-KeyedHistogram::Add(const nsCString& key, uint32_t sample)
-{
-  if (!CanRecordDataset(mDataset)) {
-    return NS_OK;
-  }
-
-  Histogram* histogram = GetHistogram(key, false);
-  MOZ_ASSERT(histogram);
-  if (!histogram) {
-    return NS_ERROR_FAILURE;
-  }
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  Histogram* subsession = GetHistogram(key, true);
-  MOZ_ASSERT(subsession);
-  if (!subsession) {
-    return NS_ERROR_FAILURE;
-  }
-#endif
-
-  if (!IsRecordingEnabled()) {
-    return NS_OK;
-  }
-
-  histogram->Add(sample);
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  subsession->Add(sample);
-#endif
-  return NS_OK;
-}
-
-void
-KeyedHistogram::Clear(bool onlySubsession)
-{
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
-    iter.Get()->mData->Clear();
-  }
-  mSubsessionMap.Clear();
-  if (onlySubsession) {
-    return;
-  }
-#endif
-
-  for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
-    iter.Get()->mData->Clear();
-  }
-  mHistogramMap.Clear();
-}
-
-nsresult
-KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
-{
-  JS::AutoValueVector keys(cx);
-  if (!keys.reserve(mHistogramMap.Count())) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
-    JS::RootedValue jsKey(cx);
-    const NS_ConvertUTF8toUTF16 key(iter.Get()->GetKey());
-    jsKey.setString(JS_NewUCStringCopyN(cx, key.Data(), key.Length()));
-    keys.append(jsKey);
-  }
-
-  JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys));
-  if (!jsKeys) {
-    return NS_ERROR_FAILURE;
-  }
-
-  args.rval().setObject(*jsKeys);
-  return NS_OK;
-}
-
-/* static */
-bool
-KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx, JS::Handle<JSObject*> obj)
-{
-  JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx));
-  if (!histogramSnapshot) {
-    return false;
-  }
-
-  if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) {
-    return false;
-  }
-
-  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
-  if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
-                           histogramSnapshot, JSPROP_ENUMERATE)) {
-    return false;
-  }
-
-  return true;
-}
-
-nsresult
-KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
-                              bool subsession, bool clearSubsession)
-{
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
-#else
-  KeyedHistogramMapType& map = mHistogramMap;
-#endif
-  if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
-    return NS_ERROR_FAILURE;
-  }
-
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  if (subsession && clearSubsession) {
-    Clear(true);
-  }
-#endif
-
-  return NS_OK;
-}
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -7,16 +7,18 @@
 #define Telemetry_h__
 
 #include "mozilla/GuardObjects.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/StartupTimeline.h"
 #include "nsTArray.h"
 #include "nsStringGlue.h"
 
+#include "mozilla/TelemetryHistogramEnums.h"
+
 /******************************************************************************
  * This implements the Telemetry system.
  * It allows recording into histograms as well some more specialized data
  * points and gives access to the data.
  *
  * For documentation on how to add and use new Telemetry probes, see:
  * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe
  *
@@ -25,18 +27,16 @@
  *****************************************************************************/
 
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
-#include "mozilla/TelemetryHistogramEnums.h"
-
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Initialize the Telemetry service on the main thread at startup.
  */
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -0,0 +1,2070 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/GCAPI.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsBaseHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsITelemetry.h"
+#include "nsVersionComparator.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/StartupTimeline.h"
+
+#include "TelemetryCommon.h"
+#include "TelemetryHistogram.h"
+
+#include "base/histogram.h"
+
+using base::Histogram;
+using base::StatisticsRecorder;
+using base::BooleanHistogram;
+using base::CountHistogram;
+using base::FlagHistogram;
+using base::LinearHistogram;
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE TYPES
+
+#define EXPIRED_ID "__expired__"
+#define SUBSESSION_HISTOGRAM_PREFIX "sub#"
+#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
+
+namespace {
+
+class KeyedHistogram;
+
+typedef nsBaseHashtableET<nsDepCharHashKey, mozilla::Telemetry::ID>
+          CharPtrEntryType;
+
+typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
+
+typedef nsClassHashtable<nsCStringHashKey, KeyedHistogram>
+          KeyedHistogramMapType;
+
+// Hardcoded probes
+struct HistogramInfo {
+  uint32_t min;
+  uint32_t max;
+  uint32_t bucketCount;
+  uint32_t histogramType;
+  uint32_t id_offset;
+  uint32_t expiration_offset;
+  uint32_t dataset;
+  bool keyed;
+
+  const char *id() const;
+  const char *expiration() const;
+};
+
+struct AddonHistogramInfo {
+  uint32_t min;
+  uint32_t max;
+  uint32_t bucketCount;
+  uint32_t histogramType;
+  Histogram *h;
+};
+
+enum reflectStatus {
+  REFLECT_OK,
+  REFLECT_CORRUPT,
+  REFLECT_FAILURE
+};
+
+typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
+
+typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo>
+          AddonHistogramEntryType;
+
+typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType;
+
+typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *>
+          AddonEntryType;
+
+typedef AutoHashtable<AddonEntryType> AddonMapType;
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE STATE, SHARED BY ALL THREADS
+
+namespace {
+
+// Set to true once this global state has been initialized
+bool gInitDone = false;
+
+bool gCanRecordBase;
+bool gCanRecordExtended;
+
+HistogramMapType gHistogramMap(mozilla::Telemetry::HistogramCount);
+
+KeyedHistogramMapType gKeyedHistograms;
+
+bool gCorruptHistograms[mozilla::Telemetry::HistogramCount];
+
+// This is for gHistograms, gHistogramStringTable
+#include "TelemetryHistogramData.inc"
+
+AddonMapType gAddonMap;
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE CONSTANTS
+
+// List of histogram IDs which should have recording disabled initially.
+const mozilla::Telemetry::ID 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
+};
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: Misc small helpers
+
+namespace {
+
+bool
+IsHistogramEnumId(mozilla::Telemetry::ID aID)
+{
+  static_assert(((mozilla::Telemetry::ID)-1 > 0), "ID should be unsigned.");
+  return aID < mozilla::Telemetry::HistogramCount;
+}
+
+bool
+IsExpired(const char *expiration)
+{
+  static mozilla::Version current_version = mozilla::Version(MOZ_APP_VERSION);
+  MOZ_ASSERT(expiration);
+  return strcmp(expiration, "never") && strcmp(expiration, "default") &&
+    (mozilla::Version(expiration) <= current_version);
+}
+
+bool
+IsInDataset(uint32_t dataset, uint32_t containingDataset)
+{
+  if (dataset == containingDataset) {
+    return true;
+  }
+
+  // The "optin on release channel" dataset is a superset of the
+  // "optout on release channel one".
+  if (containingDataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN
+      && dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) {
+    return true;
+  }
+
+  return false;
+}
+
+bool
+CanRecordDataset(uint32_t dataset)
+{
+  // If we are extended telemetry is enabled, we are allowed to record
+  // regardless of the dataset.
+  if (TelemetryHistogram::CanRecordExtended()) {
+    return true;
+  }
+
+  // If base telemetry data is enabled and we're trying to record base
+  // telemetry, allow it.
+  if (TelemetryHistogram::CanRecordBase() &&
+      IsInDataset(dataset, nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT)) {
+      return true;
+  }
+
+  // We're not recording extended telemetry or this is not the base
+  // dataset. Bail out.
+  return false;
+}
+
+bool
+IsValidHistogramName(const nsACString& name)
+{
+  return !FindInReadable(NS_LITERAL_CSTRING(KEYED_HISTOGRAM_NAME_SEPARATOR), name);
+}
+
+// Note: this is completely unrelated to mozilla::IsEmpty.
+bool
+IsEmpty(const Histogram *h)
+{
+  Histogram::SampleSet ss;
+  h->SnapshotSample(&ss);
+
+  mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
+  return ss.counts(locker, 0) == 0 && ss.sum(locker) == 0;
+}
+
+bool
+IsExpired(const Histogram *histogram)
+{
+  return histogram->histogram_name() == EXPIRED_ID;
+}
+
+nsresult
+GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount,
+                          char*** aHistograms)
+{
+  nsTArray<char*> collection;
+
+  for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) {
+    const HistogramInfo& h = gHistograms[i];
+    if (IsExpired(h.expiration()) || h.keyed != keyed ||
+        !IsInDataset(h.dataset, dataset)) {
+      continue;
+    }
+
+    const char* id = h.id();
+    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
+{
+  return &gHistogramStringTable[this->id_offset];
+}
+
+const char *
+HistogramInfo::expiration() const
+{
+  return &gHistogramStringTable[this->expiration_offset];
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: Histogram Get, Add, Clone, Clear functions
+
+namespace {
+
+nsresult
+CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max,
+                        uint32_t bucketCount, bool haveOptArgs)
+{
+  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)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    // Sanity checks for histogram parameters.
+    if (min >= max)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (bucketCount <= 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (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
+HistogramGet(const char *name, const char *expiration, uint32_t histogramType,
+             uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs,
+             Histogram **result)
+{
+  nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, haveOptArgs);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (IsExpired(expiration)) {
+    name = EXPIRED_ID;
+    min = 1;
+    max = 2;
+    bucketCount = 3;
+    histogramType = nsITelemetry::HISTOGRAM_LINEAR;
+  }
+
+  switch (histogramType) {
+  case nsITelemetry::HISTOGRAM_EXPONENTIAL:
+    *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
+    break;
+  case nsITelemetry::HISTOGRAM_LINEAR:
+    *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
+    break;
+  case nsITelemetry::HISTOGRAM_BOOLEAN:
+    *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    break;
+  case nsITelemetry::HISTOGRAM_FLAG:
+    *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    break;
+  case nsITelemetry::HISTOGRAM_COUNT:
+    *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    break;
+  default:
+    NS_ASSERTION(false, "Invalid histogram type");
+    return NS_ERROR_INVALID_ARG;
+  }
+  return NS_OK;
+}
+
+nsresult
+GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id)
+{
+  if (!gInitDone) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CharPtrEntryType *entry = gHistogramMap.GetEntry(name);
+  if (!entry) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  *id = entry->mData;
+  return NS_OK;
+}
+
+// O(1) histogram lookup by numeric id
+nsresult
+GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret)
+{
+  static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
+  Histogram *h = knownHistograms[id];
+  if (h) {
+    *ret = h;
+    return NS_OK;
+  }
+
+  const HistogramInfo &p = gHistograms[id];
+  if (p.keyed) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = HistogramGet(p.id(), 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 (!IsExpired(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
+
+  *ret = knownHistograms[id] = h;
+  return NS_OK;
+}
+
+nsresult
+GetHistogramByName(const nsACString &name, Histogram **ret)
+{
+  mozilla::Telemetry::ID id;
+  nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = GetHistogramByEnumId(id, ret);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return NS_OK;
+}
+
+/**
+ * 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*
+CloneHistogram(const nsACString& newName, mozilla::Telemetry::ID existingId,
+               Histogram& existing)
+{
+  const HistogramInfo &info = gHistograms[existingId];
+  Histogram *clone = nullptr;
+  nsresult rv;
+
+  rv = 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;
+}
+
+/**
+ * This clones a histogram with the id |existingId| to a new histogram
+ * with the name |newName|.
+ * For simplicity this is limited to registered histograms.
+ */
+Histogram*
+CloneHistogram(const nsACString& newName, mozilla::Telemetry::ID existingId)
+{
+  Histogram *existing = nullptr;
+  nsresult rv = GetHistogramByEnumId(existingId, &existing);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  return CloneHistogram(newName, existingId, *existing);
+}
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+Histogram*
+GetSubsessionHistogram(Histogram& existing)
+{
+  mozilla::Telemetry::ID id;
+  nsresult rv = GetHistogramEnumId(existing.histogram_name().c_str(), &id);
+  if (NS_FAILED(rv) || gHistograms[id].keyed) {
+    return nullptr;
+  }
+
+  static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
+  if (subsession[id]) {
+    return subsession[id];
+  }
+
+  NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
+  nsDependentCString existingName(gHistograms[id].id());
+  if (StringBeginsWith(existingName, prefix)) {
+    return nullptr;
+  }
+
+  nsCString subsessionName(prefix);
+  subsessionName.Append(existingName);
+
+  subsession[id] = CloneHistogram(subsessionName, id, existing);
+  return subsession[id];
+}
+#endif
+
+nsresult
+HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
+{
+  // Check if we are allowed to record the data.
+  if (!CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) {
+    return NS_OK;
+  }
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  if (Histogram* subsession = 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
+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 (!TelemetryHistogram::CanRecordExtended()) {
+    mozilla::Telemetry::ID id;
+    nsresult rv = 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 HistogramAdd(histogram, value, dataset);
+}
+
+void
+HistogramClear(Histogram& aHistogram, bool onlySubsession)
+{
+  if (!onlySubsession) {
+    aHistogram.Clear();
+  }
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  if (Histogram* subsession = GetSubsessionHistogram(aHistogram)) {
+    subsession->Clear();
+  }
+#endif
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: Histogram corruption helpers
+
+namespace {
+
+void
+IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
+{
+  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
+    Histogram *h = *it;
+
+    mozilla::Telemetry::ID id;
+    nsresult rv = ::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;
+    {
+      mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
+      check = h->FindCorruption(ss, locker);
+    }
+
+    bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
+
+    if (corrupt) {
+      mozilla::Telemetry::ID 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;
+      }
+      TelemetryHistogram::Accumulate(corruptID, 1);
+    }
+
+    gCorruptHistograms[id] = corrupt;
+  }
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: Histogram reflection helpers
+
+namespace {
+
+bool
+FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
+{
+  JS::Rooted<JS::Value> range(cx);
+  for (size_t i = 0; i < h->bucket_count(); i++) {
+    range.setInt32(h->ranges(i));
+    if (!JS_DefineElement(cx, array, i, range, JSPROP_ENUMERATE))
+      return false;
+  }
+  return true;
+}
+
+enum reflectStatus
+ReflectHistogramAndSamples(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h,
+                           const Histogram::SampleSet &ss)
+{
+  mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
+
+  // We don't want to reflect corrupt histograms.
+  if (h->FindCorruption(ss, locker) != 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(locker)), JSPROP_ENUMERATE))) {
+    return REFLECT_FAILURE;
+  }
+
+  const size_t count = h->bucket_count();
+  JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count));
+  if (!rarray) {
+    return REFLECT_FAILURE;
+  }
+  if (!(FillRanges(cx, rarray, h)
+        && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) {
+    return REFLECT_FAILURE;
+  }
+
+  JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count));
+  if (!counts_array) {
+    return REFLECT_FAILURE;
+  }
+  if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) {
+    return REFLECT_FAILURE;
+  }
+  for (size_t i = 0; i < count; i++) {
+    if (!JS_DefineElement(cx, counts_array, i,
+                          ss.counts(locker, i), JSPROP_ENUMERATE)) {
+      return REFLECT_FAILURE;
+    }
+  }
+
+  return REFLECT_OK;
+}
+
+enum reflectStatus
+ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h)
+{
+  Histogram::SampleSet ss;
+  h->SnapshotSample(&ss);
+  return ReflectHistogramAndSamples(cx, obj, h, ss);
+}
+
+bool
+ShouldReflectHistogram(Histogram *h)
+{
+  const char *name = h->histogram_name().c_str();
+  mozilla::Telemetry::ID id;
+  nsresult rv = ::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;
+  } else {
+    return !gCorruptHistograms[id];
+  }
+}
+
+}
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// 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 GetDataset(uint32_t* dataset) const;
+  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);
+
+private:
+  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
+  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
+  KeyedHistogramMapType mHistogramMap;
+#if !defined(MOZ_WIDGET_GONK) && !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)
+  : mHistogramMap()
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  , mSubsessionMap()
+#endif
+  , mName(name)
+  , mExpiration(expiration)
+  , mHistogramType(histogramType)
+  , mMin(min)
+  , mMax(max)
+  , mBucketCount(bucketCount)
+  , mDataset(dataset)
+  , mRecordingEnabled(true)
+{
+}
+
+nsresult
+KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram,
+                             bool subsession)
+{
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
+#else
+  KeyedHistogramMapType& map = mHistogramMap;
+#endif
+  KeyedHistogramEntry* entry = map.GetEntry(key);
+  if (entry) {
+    *histogram = entry->mData;
+    return NS_OK;
+  }
+
+  nsCString histogramName;
+#if !defined(MOZ_WIDGET_GONK) && !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 = HistogramGet(histogramName.get(), mExpiration.get(),
+                             mHistogramType, mMin, mMax, mBucketCount,
+                             true, &h);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  *histogram = h;
+
+  entry = map.PutEntry(key);
+  if (MOZ_UNLIKELY(!entry)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  entry->mData = h;
+  return NS_OK;
+}
+
+Histogram*
+KeyedHistogram::GetHistogram(const nsCString& key, bool subsession)
+{
+  Histogram* h = nullptr;
+  if (NS_FAILED(GetHistogram(key, &h, subsession))) {
+    return nullptr;
+  }
+  return h;
+}
+
+nsresult
+KeyedHistogram::GetDataset(uint32_t* dataset) const
+{
+  MOZ_ASSERT(dataset);
+  *dataset = mDataset;
+  return NS_OK;
+}
+
+nsresult
+KeyedHistogram::Add(const nsCString& key, uint32_t sample)
+{
+  if (!CanRecordDataset(mDataset)) {
+    return NS_OK;
+  }
+
+  Histogram* histogram = GetHistogram(key, false);
+  MOZ_ASSERT(histogram);
+  if (!histogram) {
+    return NS_ERROR_FAILURE;
+  }
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  Histogram* subsession = GetHistogram(key, true);
+  MOZ_ASSERT(subsession);
+  if (!subsession) {
+    return NS_ERROR_FAILURE;
+  }
+#endif
+
+  if (!IsRecordingEnabled()) {
+    return NS_OK;
+  }
+
+  histogram->Add(sample);
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  subsession->Add(sample);
+#endif
+  return NS_OK;
+}
+
+void
+KeyedHistogram::Clear(bool onlySubsession)
+{
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
+    iter.Get()->mData->Clear();
+  }
+  mSubsessionMap.Clear();
+  if (onlySubsession) {
+    return;
+  }
+#endif
+
+  for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
+    iter.Get()->mData->Clear();
+  }
+  mHistogramMap.Clear();
+}
+
+nsresult
+KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
+{
+  JS::AutoValueVector keys(cx);
+  if (!keys.reserve(mHistogramMap.Count())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
+    JS::RootedValue jsKey(cx);
+    const NS_ConvertUTF8toUTF16 key(iter.Get()->GetKey());
+    jsKey.setString(JS_NewUCStringCopyN(cx, key.Data(), key.Length()));
+    keys.append(jsKey);
+  }
+
+  JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys));
+  if (!jsKeys) {
+    return NS_ERROR_FAILURE;
+  }
+
+  args.rval().setObject(*jsKeys);
+  return NS_OK;
+}
+
+bool
+KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry,
+                                      JSContext* cx, JS::Handle<JSObject*> obj)
+{
+  JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx));
+  if (!histogramSnapshot) {
+    return false;
+  }
+
+  if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) {
+    return false;
+  }
+
+  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
+  if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
+                           histogramSnapshot, JSPROP_ENUMERATE)) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
+                              bool subsession, bool clearSubsession)
+{
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
+#else
+  KeyedHistogramMapType& map = mHistogramMap;
+#endif
+  if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
+    return NS_ERROR_FAILURE;
+  }
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  if (subsession && clearSubsession) {
+    Clear(true);
+  }
+#endif
+
+  return NS_OK;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: KeyedHistogram helpers
+
+namespace {
+
+KeyedHistogram*
+GetKeyedHistogramById(const nsACString &name)
+{
+  if (!gInitDone) {
+    return nullptr;
+  }
+
+  KeyedHistogram* keyed = nullptr;
+  gKeyedHistograms.Get(name, &keyed);
+  return keyed;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: JSHistogram_* functions
+
+namespace {
+
+bool
+JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  MOZ_ASSERT(h);
+  Histogram::ClassType type = h->histogram_type();
+
+  int32_t value = 1;
+  if (type != base::CountHistogram::COUNT_HISTOGRAM) {
+    JS::CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length()) {
+      JS_ReportError(cx, "Expected one argument");
+      return false;
+    }
+
+    if (!(args[0].isNumber() || args[0].isBoolean())) {
+      JS_ReportError(cx, "Not a number");
+      return false;
+    }
+
+    if (!JS::ToInt32(cx, args[0], &value)) {
+      return false;
+    }
+  }
+
+  if (TelemetryHistogram::CanRecordBase()) {
+    HistogramAdd(*h, value);
+  }
+
+  return true;
+}
+
+bool
+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) {
+    return false;
+  }
+
+  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
+  if (!snapshot)
+    return false;
+
+  switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
+  case REFLECT_FAILURE:
+    return false;
+  case REFLECT_CORRUPT:
+    JS_ReportError(cx, "Histogram is corrupt");
+    return false;
+  case REFLECT_OK:
+    args.rval().setObject(*snapshot);
+    return true;
+  default:
+    MOZ_CRASH("unhandled reflection status");
+  }
+}
+
+bool
+JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  bool onlySubsession = false;
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+  if (args.length() >= 1) {
+    if (!args[0].isBoolean()) {
+      JS_ReportError(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) {
+    HistogramClear(*h, onlySubsession);
+  }
+
+  return true;
+}
+
+bool
+JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  mozilla::Telemetry::ID id;
+  nsresult rv = ::GetHistogramEnumId(h->histogram_name().c_str(), &id);
+  if (NS_SUCCEEDED(rv)) {
+    args.rval().setNumber(gHistograms[id].dataset);
+    return true;
+  }
+
+  return false;
+}
+
+nsresult
+WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+  static const JSClass JSHistogram_class = {
+    "JSHistogram",  /* name */
+    JSCLASS_HAS_PRIVATE  /* flags */
+  };
+
+  JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
+  if (!obj)
+    return NS_ERROR_FAILURE;
+  if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0)
+        && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0)
+        && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset", JSHistogram_Dataset, 0, 0))) {
+    return NS_ERROR_FAILURE;
+  }
+  JS_SetPrivate(obj, h);
+  ret.setObject(*obj);
+  return NS_OK;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: JSKeyedHistogram_* functions
+
+namespace {
+
+bool
+KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp,
+                            bool subsession, bool clearSubsession)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+  if (args.length() == 0) {
+    JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
+    if (!snapshot) {
+      JS_ReportError(cx, "Failed to create object");
+      return false;
+    }
+
+    if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) {
+      JS_ReportError(cx, "Failed to reflect keyed histograms");
+      return false;
+    }
+
+    args.rval().setObject(*snapshot);
+    return true;
+  }
+
+  nsAutoJSString key;
+  if (!args[0].isString() || !key.init(cx, args[0])) {
+    JS_ReportError(cx, "Not a string");
+    return false;
+  }
+
+  Histogram* h = nullptr;
+  nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession);
+  if (NS_FAILED(rv)) {
+    JS_ReportError(cx, "Failed to get histogram");
+    return false;
+  }
+
+  JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
+  if (!snapshot) {
+    return false;
+  }
+
+  switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
+  case REFLECT_FAILURE:
+    return false;
+  case REFLECT_CORRUPT:
+    JS_ReportError(cx, "Histogram is corrupt");
+    return false;
+  case REFLECT_OK:
+    args.rval().setObject(*snapshot);
+    return true;
+  default:
+    MOZ_CRASH("unhandled reflection status");
+  }
+}
+
+bool
+JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+  JS::CallArgs args = CallArgsFromVp(argc, vp);
+  if (args.length() < 1) {
+    JS_ReportError(cx, "Expected one argument");
+    return false;
+  }
+
+  nsAutoJSString key;
+  if (!args[0].isString() || !key.init(cx, args[0])) {
+    JS_ReportError(cx, "Not a string");
+    return false;
+  }
+
+  const uint32_t type = keyed->GetHistogramType();
+  int32_t value = 1;
+
+  if (type != base::CountHistogram::COUNT_HISTOGRAM) {
+    if (args.length() < 2) {
+      JS_ReportError(cx, "Expected two arguments for this histogram type");
+      return false;
+    }
+
+    if (!(args[1].isNumber() || args[1].isBoolean())) {
+      JS_ReportError(cx, "Not a number");
+      return false;
+    }
+
+    if (!JS::ToInt32(cx, args[1], &value)) {
+      return false;
+    }
+  }
+
+  keyed->Add(NS_ConvertUTF16toUTF8(key), value);
+  return true;
+}
+
+bool
+JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  return NS_SUCCEEDED(keyed->GetJSKeys(cx, args));
+}
+
+bool
+JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false);
+}
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+bool
+JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false);
+}
+#endif
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+bool
+JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  if (args.length() != 0) {
+    JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear");
+  }
+
+  return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true);
+}
+#endif
+
+bool
+JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+  bool onlySubsession = false;
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+  if (args.length() >= 1) {
+    if (!(args[0].isNumber() || args[0].isBoolean())) {
+      JS_ReportError(cx, "Not a boolean");
+      return false;
+    }
+
+    onlySubsession = JS::ToBoolean(args[0]);
+  }
+
+  keyed->Clear(onlySubsession);
+#else
+  keyed->Clear(false);
+#endif
+  return true;
+}
+
+bool
+JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+  uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
+  nsresult rv = keyed->GetDataset(&dataset);;
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  args.rval().setNumber(dataset);
+  return true;
+}
+
+nsresult
+WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+  static const JSClass JSHistogram_class = {
+    "JSKeyedHistogram",  /* name */
+    JSCLASS_HAS_PRIVATE  /* flags */
+  };
+
+  JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
+  if (!obj)
+    return NS_ERROR_FAILURE;
+  if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0)
+        && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0)
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+        && JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0)
+        && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0)
+#endif
+        && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0)
+        && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JS_SetPrivate(obj, h);
+  ret.setObject(*obj);
+  return NS_OK;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: functions related to addon histograms
+
+namespace {
+
+// Compute the name to pass into Histogram for the addon histogram
+// 'name' from the addon 'id'.  We can't use 'name' directly because it
+// might conflict with other histograms in other addons or even with our
+// own.
+void
+AddonHistogramName(const nsACString &id, const nsACString &name,
+                   nsACString &ret)
+{
+  ret.Append(id);
+  ret.Append(':');
+  ret.Append(name);
+}
+
+bool
+CreateHistogramForAddon(const nsACString &name, AddonHistogramInfo &info)
+{
+  Histogram *h;
+  nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never",
+                             info.histogramType, info.min, info.max,
+                             info.bucketCount, true, &h);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  // Don't let this histogram be reported via the normal means
+  // (e.g. Telemetry.registeredHistograms); we'll make it available in
+  // other ways.
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  info.h = h;
+  return true;
+}
+
+bool
+AddonHistogramReflector(AddonHistogramEntryType *entry,
+                        JSContext *cx, JS::Handle<JSObject*> obj)
+{
+  AddonHistogramInfo &info = entry->mData;
+
+  // Never even accessed the histogram.
+  if (!info.h) {
+    // Have to force creation of HISTOGRAM_FLAG histograms.
+    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
+      return true;
+
+    if (!CreateHistogramForAddon(entry->GetKey(), info)) {
+      return false;
+    }
+  }
+
+  if (IsEmpty(info.h)) {
+    return true;
+  }
+
+  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
+  if (!snapshot) {
+    // Just consider this to be skippable.
+    return true;
+  }
+  switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) {
+  case REFLECT_FAILURE:
+  case REFLECT_CORRUPT:
+    return false;
+  case REFLECT_OK:
+    const nsACString &histogramName = entry->GetKey();
+    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
+                           snapshot, JSPROP_ENUMERATE)) {
+      return false;
+    }
+    break;
+  }
+  return true;
+}
+
+bool
+AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle<JSObject*> obj)
+{
+  const nsACString &addonId = entry->GetKey();
+  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
+  if (!subobj) {
+    return false;
+  }
+
+  AddonHistogramMapType *map = entry->mData;
+  if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj)
+        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
+                             subobj, JSPROP_ENUMERATE))) {
+    return false;
+  }
+  return true;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS
+
+namespace TelemetryHistogram {
+
+void InitializeGlobalState(bool canRecordBase, bool canRecordExtended)
+{
+  MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState "
+             "may only be called once");
+
+  gCanRecordBase = canRecordBase;
+  gCanRecordExtended = canRecordExtended;
+
+  // gHistogramMap 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::ID) i;
+  }
+
+#ifdef DEBUG
+  gHistogramMap.MarkImmutable();
+#endif
+
+  mozilla::PodArrayZero(gCorruptHistograms);
+
+  // Create registered keyed histograms
+  for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) {
+    const HistogramInfo& h = gHistograms[i];
+    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));
+  }
+
+  // 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.
+  // TODO: Compare explicitly with gHistograms[<histogram id>].bucketCount here
+  // once we can make gHistograms constexpr (requires VS2015).
+  static_assert((JS::gcreason::NUM_TELEMETRY_REASONS == 100),
+      "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
+      " If this was an intentional change, update this assert with its value "
+      "and 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 == 16),
+      "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json.  If this"
+      " was an intentional change, update this assert with its value and update"
+      " the n_values for the following in Histograms.json:"
+      " STARTUP_MEASUREMENT_ERRORS");
+
+  gInitDone = true;
+}
+
+void DeInitializeGlobalState()
+{
+  gCanRecordBase = false;
+  gCanRecordExtended = false;
+  gHistogramMap.Clear();
+  gKeyedHistograms.Clear();
+  gAddonMap.Clear();
+  gInitDone = false;
+}
+
+#ifdef DEBUG
+bool GlobalStateHasBeenInitialized() {
+  return gInitDone;
+}
+#endif
+
+bool
+CanRecordBase() {
+  return gCanRecordBase;
+}
+
+void
+SetCanRecordBase(bool b) {
+  gCanRecordBase = b;
+}
+
+bool
+CanRecordExtended() {
+  return gCanRecordExtended;
+}
+
+void
+SetCanRecordExtended(bool b) {
+  gCanRecordExtended = b;
+}
+
+
+void
+InitHistogramRecordingEnabled()
+{
+  const size_t length = mozilla::ArrayLength(kRecordingInitiallyDisabledIDs);
+  for (size_t i = 0; i < length; i++) {
+    SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i], false);
+  }
+}
+
+void
+SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
+{
+  if (!IsHistogramEnumId(aID)) {
+    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
+    return;
+  }
+
+  if (gHistograms[aID].keyed) {
+    const nsDependentCString id(gHistograms[aID].id());
+    KeyedHistogram* keyed = ::GetKeyedHistogramById(id);
+    if (keyed) {
+      keyed->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  } else {
+    Histogram *h;
+    nsresult rv = GetHistogramByEnumId(aID, &h);
+    if (NS_SUCCEEDED(rv)) {
+      h->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
+}
+
+
+nsresult
+SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled)
+{
+  Histogram *h;
+  nsresult rv = GetHistogramByName(id, &h);
+  if (NS_SUCCEEDED(rv)) {
+    h->SetRecordingEnabled(aEnabled);
+    return NS_OK;
+  }
+
+  KeyedHistogram* keyed = ::GetKeyedHistogramById(id);
+  if (keyed) {
+    keyed->SetRecordingEnabled(aEnabled);
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+
+void
+Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
+{
+  if (!CanRecordBase()) {
+    return;
+  }
+  Histogram *h;
+  nsresult rv = GetHistogramByEnumId(aHistogram, &h);
+  if (NS_SUCCEEDED(rv)) {
+    HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
+  }
+}
+
+void
+Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey, uint32_t aSample)
+{
+  if (!gInitDone || !CanRecordBase()) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aID];
+  KeyedHistogram* keyed
+     = ::GetKeyedHistogramById(nsDependentCString(th.id()));
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+
+}
+
+void
+Accumulate(const char* name, uint32_t sample)
+{
+  if (!CanRecordBase()) {
+    return;
+  }
+  mozilla::Telemetry::ID id;
+  nsresult rv = ::GetHistogramEnumId(name, &id);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  Histogram *h;
+  rv = GetHistogramByEnumId(id, &h);
+  if (NS_SUCCEEDED(rv)) {
+    HistogramAdd(*h, sample, gHistograms[id].dataset);
+  }
+}
+
+void
+Accumulate(const char* name, const nsCString& key, uint32_t sample)
+{
+  if (!CanRecordBase()) {
+    return;
+  }
+  mozilla::Telemetry::ID id;
+  nsresult rv = ::GetHistogramEnumId(name, &id);
+  if (NS_SUCCEEDED(rv)) {
+    Accumulate(id, key, sample);
+  }
+}
+
+void
+ClearHistogram(mozilla::Telemetry::ID aId)
+{
+  if (!TelemetryHistogram::CanRecordBase()) {
+    return;
+  }
+
+  Histogram *h;
+  nsresult rv = ::GetHistogramByEnumId(aId, &h);
+  if (NS_SUCCEEDED(rv) && h) {
+    ::HistogramClear(*h, false);
+  }
+}
+
+nsresult
+GetHistogramById(const nsACString &name, JSContext *cx,
+                 JS::MutableHandle<JS::Value> ret)
+{
+  Histogram *h;
+  nsresult rv = GetHistogramByName(name, &h);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return WrapAndReturnHistogram(h, cx, ret);
+}
+
+nsresult
+GetKeyedHistogramById(const nsACString &name, JSContext *cx,
+                      JS::MutableHandle<JS::Value> ret)
+{
+  KeyedHistogram* keyed = nullptr;
+  if (!gKeyedHistograms.Get(name, &keyed)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return WrapAndReturnKeyedHistogram(keyed, cx, ret);
+
+}
+
+const char*
+GetHistogramName(mozilla::Telemetry::ID id)
+{
+  const HistogramInfo& h = gHistograms[id];
+  return h.id();
+}
+
+nsresult
+NewHistogram(const nsACString &name, const nsACString &expiration,
+             uint32_t histogramType, uint32_t min, uint32_t max,
+             uint32_t bucketCount, JSContext *cx,
+             uint8_t optArgCount, JS::MutableHandle<JS::Value> ret)
+{
+  if (!IsValidHistogramName(name)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  Histogram *h;
+  nsresult rv = HistogramGet(PromiseFlatCString(name).get(),
+                             PromiseFlatCString(expiration).get(),
+                             histogramType, min, max, bucketCount,
+                             optArgCount == 3, &h);
+  if (NS_FAILED(rv))
+    return rv;
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  return WrapAndReturnHistogram(h, cx, ret);
+
+}
+
+nsresult
+NewKeyedHistogram(const nsACString &name, const nsACString &expiration,
+                  uint32_t histogramType, uint32_t min, uint32_t max,
+                  uint32_t bucketCount, JSContext *cx,
+                  uint8_t optArgCount, JS::MutableHandle<JS::Value> ret)
+{
+  if (!IsValidHistogramName(name)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, optArgCount == 3);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  KeyedHistogram* keyed = new KeyedHistogram(name, expiration, histogramType,
+                                             min, max, bucketCount,
+                                             nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN);
+  if (MOZ_UNLIKELY(!gKeyedHistograms.Put(name, keyed, mozilla::fallible))) {
+    delete keyed;
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return WrapAndReturnKeyedHistogram(keyed, cx, ret);
+
+}
+
+nsresult
+HistogramFrom(const nsACString &name, const nsACString &existing_name,
+              JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+   mozilla::Telemetry::ID id;
+  nsresult rv = ::GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  Histogram* clone = CloneHistogram(name, id);
+  if (!clone) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return WrapAndReturnHistogram(clone, cx, ret);
+}
+
+nsresult
+CreateHistogramSnapshots(JSContext *cx,
+                         JS::MutableHandle<JS::Value> ret,
+                         bool subsession,
+                         bool clearSubsession)
+{
+  JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
+  if (!root_obj)
+    return NS_ERROR_FAILURE;
+  ret.setObject(*root_obj);
+
+  // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
+  // been created, so that their values are snapshotted.
+  for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
+    if (gHistograms[i].keyed) {
+      continue;
+    }
+    const uint32_t type = gHistograms[i].histogramType;
+    if (type == nsITelemetry::HISTOGRAM_FLAG ||
+        type == nsITelemetry::HISTOGRAM_COUNT) {
+      Histogram *h;
+      mozilla::DebugOnly<nsresult> rv
+         = GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+
+  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...
+  IdentifyCorruptHistograms(hs);
+
+  // OK, now we can actually reflect things.
+  JS::Rooted<JSObject*> hobj(cx);
+  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
+    Histogram *h = *it;
+    if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) {
+      continue;
+    }
+
+    Histogram* original = h;
+#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+    if (subsession) {
+      h = GetSubsessionHistogram(*h);
+      if (!h) {
+        continue;
+      }
+    }
+#endif
+
+    hobj = JS_NewPlainObject(cx);
+    if (!hobj) {
+      return NS_ERROR_FAILURE;
+    }
+    switch (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_GONK) && !defined(MOZ_WIDGET_ANDROID)
+    if (subsession && clearSubsession) {
+      h->Clear();
+    }
+#endif
+  }
+  return NS_OK;
+}
+
+nsresult
+RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
+                     char*** aHistograms)
+{
+  return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms);
+}
+
+nsresult
+RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount,
+                          char*** aHistograms)
+{
+  return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms);
+}
+
+nsresult
+GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+  JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
+  if (!obj) {
+    return NS_ERROR_FAILURE;
+  }
+
+  for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
+    JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
+    if (!snapshot) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(),
+                           snapshot, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  ret.setObject(*obj);
+  return NS_OK;
+}
+
+nsresult
+RegisterAddonHistogram(const nsACString &id, const nsACString &name,
+                       uint32_t histogramType, uint32_t min, uint32_t max,
+                       uint32_t bucketCount, uint8_t optArgCount)
+{
+  if (histogramType == nsITelemetry::HISTOGRAM_EXPONENTIAL ||
+      histogramType == nsITelemetry::HISTOGRAM_LINEAR) {
+    if (optArgCount != 3) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    // Sanity checks for histogram parameters.
+    if (min >= max)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (bucketCount <= 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (min < 1)
+      return NS_ERROR_ILLEGAL_VALUE;
+  } else {
+    min = 1;
+    max = 2;
+    bucketCount = 3;
+  }
+
+  AddonEntryType *addonEntry = gAddonMap.GetEntry(id);
+  if (!addonEntry) {
+    addonEntry = gAddonMap.PutEntry(id);
+    if (MOZ_UNLIKELY(!addonEntry)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    addonEntry->mData = new AddonHistogramMapType();
+  }
+
+  AddonHistogramMapType *histogramMap = addonEntry->mData;
+  AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
+  // Can't re-register the same histogram.
+  if (histogramEntry) {
+    return NS_ERROR_FAILURE;
+  }
+
+  histogramEntry = histogramMap->PutEntry(name);
+  if (MOZ_UNLIKELY(!histogramEntry)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  AddonHistogramInfo &info = histogramEntry->mData;
+  info.min = min;
+  info.max = max;
+  info.bucketCount = bucketCount;
+  info.histogramType = histogramType;
+
+  return NS_OK;
+}
+
+nsresult
+GetAddonHistogram(const nsACString &id, const nsACString &name,
+                  JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+  AddonEntryType *addonEntry = gAddonMap.GetEntry(id);
+  // The given id has not been registered.
+  if (!addonEntry) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AddonHistogramMapType *histogramMap = addonEntry->mData;
+  AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
+  // The given histogram name has not been registered.
+  if (!histogramEntry) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AddonHistogramInfo &info = histogramEntry->mData;
+  if (!info.h) {
+    nsAutoCString actualName;
+    AddonHistogramName(id, name, actualName);
+    if (!::CreateHistogramForAddon(actualName, info)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return WrapAndReturnHistogram(info.h, cx, ret);
+}
+
+nsresult
+UnregisterAddonHistograms(const nsACString &id)
+{
+  AddonEntryType *addonEntry = gAddonMap.GetEntry(id);
+  if (addonEntry) {
+    // Histogram's destructor is private, so this is the best we can do.
+    // The histograms the addon created *will* stick around, but they
+    // will be deleted if and when the addon registers histograms with
+    // the same names.
+    delete addonEntry->mData;
+    gAddonMap.RemoveEntry(addonEntry);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
+{
+  JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
+  if (!obj) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!gAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
+    return NS_ERROR_FAILURE;
+  }
+  ret.setObject(*obj);
+  return NS_OK;
+}
+
+size_t
+GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+  return gAddonMap.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+         gHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t
+GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+  StatisticsRecorder::Histograms hs;
+  StatisticsRecorder::GetHistograms(&hs);
+  size_t n = 0;
+  for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
+    Histogram *h = *it;
+    n += h->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  return n;
+}
+
+} // namespace TelemetryHistogram
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -0,0 +1,108 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TelemetryHistogram_h__
+#define TelemetryHistogram_h__
+
+#include "mozilla/TelemetryHistogramEnums.h"
+
+// This module is internal to Telemetry.  It encapsulates Telemetry's
+// histogram accumulation and storage logic.  It should only be used by
+// Telemetry.cpp.  These functions should not be used anywhere else.
+// For the public interface to Telemetry functionality, see Telemetry.h.
+
+namespace TelemetryHistogram {
+
+void InitializeGlobalState(bool canRecordBase, bool canRecordExtended);
+void DeInitializeGlobalState();
+#ifdef DEBUG
+bool GlobalStateHasBeenInitialized();
+#endif
+
+bool CanRecordBase();
+void SetCanRecordBase(bool b);
+bool CanRecordExtended();
+void SetCanRecordExtended(bool b);
+
+void InitHistogramRecordingEnabled();
+void SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled);
+
+nsresult SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled);
+
+void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
+void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey,
+                                            uint32_t aSample);
+void Accumulate(const char* name, uint32_t sample);
+void Accumulate(const char* name, const nsCString& key, uint32_t sample);
+
+void
+ClearHistogram(mozilla::Telemetry::ID aId);
+
+nsresult
+GetHistogramById(const nsACString &name, JSContext *cx,
+                 JS::MutableHandle<JS::Value> ret);
+
+nsresult
+GetKeyedHistogramById(const nsACString &name, JSContext *cx,
+                      JS::MutableHandle<JS::Value> ret);
+
+const char*
+GetHistogramName(mozilla::Telemetry::ID id);
+
+nsresult
+NewHistogram(const nsACString &name, const nsACString &expiration,
+             uint32_t histogramType, uint32_t min, uint32_t max,
+             uint32_t bucketCount, JSContext *cx,
+             uint8_t optArgCount, JS::MutableHandle<JS::Value> ret);
+
+nsresult
+NewKeyedHistogram(const nsACString &name, const nsACString &expiration,
+                  uint32_t histogramType, uint32_t min, uint32_t max,
+                  uint32_t bucketCount, JSContext *cx,
+                  uint8_t optArgCount, JS::MutableHandle<JS::Value> ret);
+
+nsresult
+HistogramFrom(const nsACString &name, const nsACString &existing_name,
+              JSContext *cx, JS::MutableHandle<JS::Value> ret);
+
+nsresult
+CreateHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret,
+                         bool subsession, bool clearSubsession);
+
+nsresult
+RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
+                     char*** aHistograms);
+
+nsresult
+RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount,
+                          char*** aHistograms);
+
+nsresult
+GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret);
+
+nsresult
+RegisterAddonHistogram(const nsACString &id, const nsACString &name,
+                       uint32_t histogramType, uint32_t min, uint32_t max,
+                       uint32_t bucketCount, uint8_t optArgCount);
+
+nsresult
+GetAddonHistogram(const nsACString &id, const nsACString &name,
+                  JSContext *cx, JS::MutableHandle<JS::Value> ret);
+
+nsresult
+UnregisterAddonHistograms(const nsACString &id);
+
+nsresult
+GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret);
+
+size_t
+GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+size_t
+GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+} // namespace TelemetryHistogram
+
+#endif // TelemetryHistogram_h__
--- a/toolkit/components/telemetry/gen-histogram-data.py
+++ b/toolkit/components/telemetry/gen-histogram-data.py
@@ -66,17 +66,17 @@ def print_array_entry(output, histogram,
            name_index, exp_index, histogram.dataset(),
            "true" if histogram.keyed() else "false"), file=output)
     if cpp_guard:
         print("#endif", file=output)
 
 def write_histogram_table(output, histograms):
     table = StringTable()
 
-    print("const TelemetryHistogram gHistograms[] = {", file=output)
+    print("const HistogramInfo gHistograms[] = {", file=output)
     for histogram in histograms:
         name_index = table.stringIndex(histogram.name())
         exp_index = table.stringIndex(histogram.expiration())
         print_array_entry(output, histogram, name_index, exp_index)
     print("};", file=output)
 
     strtab_name = "gHistogramStringTable"
     table.writeDefinition(output, strtab_name)
--- a/toolkit/components/telemetry/gen-histogram-enum.py
+++ b/toolkit/components/telemetry/gen-histogram-enum.py
@@ -18,16 +18,20 @@ import histogram_tools
 import itertools
 import sys
 
 banner = """/* This file is auto-generated, see gen-histogram-enum.py.  */
 """
 
 def main(output, *filenames):
     print(banner, file=output)
+    print("#ifndef mozilla_TelemetryHistogramEnums_h", file=output);
+    print("#define mozilla_TelemetryHistogramEnums_h", file=output);
+    print("namespace mozilla {", file=output)
+    print("namespace Telemetry {", file=output)
     print("enum ID : uint32_t {", file=output)
 
     groups = itertools.groupby(histogram_tools.from_files(filenames),
                                lambda h: h.name().startswith("USE_COUNTER2_"))
     seen_use_counters = False
 
     # Note that histogram_tools.py guarantees that all of the USE_COUNTER2_*
     # histograms are defined in a contiguous block.  We therefore assume
@@ -58,11 +62,14 @@ def main(output, *filenames):
     print("  HistogramCount,", file=output)
     if seen_use_counters:
         print("  HistogramUseCounterCount = HistogramLastUseCounter - HistogramFirstUseCounter + 1", file=output)
     else:
         print("  HistogramFirstUseCounter = 0,", file=output)
         print("  HistogramLastUseCounter = 0,", file=output)
         print("  HistogramUseCounterCount = 0", file=output)
     print("};", file=output)
+    print("} // namespace mozilla", file=output)
+    print("} // namespace Telemetry", file=output)
+    print("#endif // mozilla_TelemetryHistogramEnums_h", file=output);
 
 if __name__ == '__main__':
     main(sys.stdout, *sys.argv[1:])
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -18,16 +18,17 @@ EXPORTS.mozilla += [
     '!TelemetryHistogramEnums.h',
     'ProcessedStack.h',
     'Telemetry.h',
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
+    'TelemetryHistogram.cpp',
     'WebrtcTelemetry.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'TelemetryStartup.js',
     'TelemetryStartup.manifest'
 ]