Bug 1258183 - TSan: data race toolkit/components/telemetry/Telemetry.cpp in CanRecordBase (part 2, derace). r=chutten.
authorJulian Seward <jseward@acm.org>
Wed, 08 Jun 2016 17:46:24 +0200
changeset 301172 1b4582536f10f1b7b8efba8ac1b8a47659dc109f
parent 301171 fc792a05cc490f22c4f247a5470ab976391c731d
child 301173 dcdbaae3b40f20b2a9e13f049c4ed2cf81066e5c
push id30328
push usercbook@mozilla.com
push dateThu, 09 Jun 2016 09:58:24 +0000
treeherdermozilla-central@cb493102af8a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten
bugs1258183
milestone50.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 1258183 - TSan: data race toolkit/components/telemetry/Telemetry.cpp in CanRecordBase (part 2, derace). r=chutten.
ipc/chromium/src/base/histogram.cc
ipc/chromium/src/base/histogram.h
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/Telemetry.h
toolkit/components/telemetry/TelemetryHistogram.cpp
toolkit/components/telemetry/TelemetryHistogram.h
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsEmbedFunctions.cpp
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -24,18 +24,16 @@
 namespace base {
 
 #define DVLOG(x) CHROMIUM_LOG(ERROR)
 #define CHECK_GT DCHECK_GT
 #define CHECK_LT DCHECK_LT
 typedef ::Lock Lock;
 typedef ::AutoLock AutoLock;
 
-using mozilla::OffTheBooksMutexAutoLock;
-
 // Static table of checksums for all possible 8 bit bytes.
 const uint32_t Histogram::kCrcTable[256] = {0x0, 0x77073096L, 0xee0e612cL,
 0x990951baL, 0x76dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L,
 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL,
 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL,
 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L,
 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL,
@@ -172,64 +170,60 @@ void Histogram::WriteHTMLGraph(std::stri
 
 void Histogram::WriteAscii(bool graph_it, const std::string& newline,
                            std::string* output) const {
   // Get local (stack) copies of all effectively volatile class data so that we
   // are consistent across our output activities.
   SampleSet snapshot;
   SnapshotSample(&snapshot);
 
-  // For the rest of the routine, we hold |snapshot|'s lock so as to
-  // be able to examine it atomically.
-  OffTheBooksMutexAutoLock locker(snapshot.mutex());
+  Count sample_count = snapshot.TotalCount();
 
-  Count sample_count = snapshot.TotalCount(locker);
-
-  WriteAsciiHeader(snapshot, locker, sample_count, output);
+  WriteAsciiHeader(snapshot, sample_count, output);
   output->append(newline);
 
   // Prepare to normalize graphical rendering of bucket contents.
   double max_size = 0;
   if (graph_it)
-    max_size = GetPeakBucketSize(snapshot, locker);
+    max_size = GetPeakBucketSize(snapshot);
 
   // Calculate space needed to print bucket range numbers.  Leave room to print
   // nearly the largest bucket range without sliding over the histogram.
   size_t largest_non_empty_bucket = bucket_count() - 1;
-  while (0 == snapshot.counts(locker, largest_non_empty_bucket)) {
+  while (0 == snapshot.counts(largest_non_empty_bucket)) {
     if (0 == largest_non_empty_bucket)
       break;  // All buckets are empty.
     --largest_non_empty_bucket;
   }
 
   // Calculate largest print width needed for any of our bucket range displays.
   size_t print_width = 1;
   for (size_t i = 0; i < bucket_count(); ++i) {
-    if (snapshot.counts(locker, i)) {
+    if (snapshot.counts(i)) {
       size_t width = GetAsciiBucketRange(i).size() + 1;
       if (width > print_width)
         print_width = width;
     }
   }
 
   int64_t remaining = sample_count;
   int64_t past = 0;
   // Output the actual histogram graph.
   for (size_t i = 0; i < bucket_count(); ++i) {
-    Count current = snapshot.counts(locker, i);
+    Count current = snapshot.counts(i);
     if (!current && !PrintEmptyBucket(i))
       continue;
     remaining -= current;
     std::string range = GetAsciiBucketRange(i);
     output->append(range);
     for (size_t j = 0; range.size() + j < print_width + 1; ++j)
       output->push_back(' ');
     if (0 == current &&
-        i < bucket_count() - 1 && 0 == snapshot.counts(locker, i + 1)) {
-      while (i < bucket_count() - 1 && 0 == snapshot.counts(locker, i + 1))
+        i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) {
+      while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1))
         ++i;
       output->append("... ");
       output->append(newline);
       continue;  // No reason to plot emptiness.
     }
     double current_size = GetBucketSize(current, i);
     if (graph_it)
       WriteAsciiBucketGraph(current_size, max_size, output);
@@ -239,34 +233,34 @@ void Histogram::WriteAscii(bool graph_it
   }
   DCHECK_EQ(sample_count, past);
 }
 
 //------------------------------------------------------------------------------
 // Methods for the validating a sample and a related histogram.
 //------------------------------------------------------------------------------
 
-Histogram::Inconsistencies Histogram::FindCorruption(
-    const SampleSet& snapshot,
-    const OffTheBooksMutexAutoLock& snapshotLockEvidence) const {
+Histogram::Inconsistencies
+Histogram::FindCorruption(const SampleSet& snapshot) const
+{
   int inconsistencies = NO_INCONSISTENCIES;
   Sample previous_range = -1;  // Bottom range is always 0.
   int64_t count = 0;
   for (size_t index = 0; index < bucket_count(); ++index) {
-    count += snapshot.counts(snapshotLockEvidence, index);
+    count += snapshot.counts(index);
     int new_range = ranges(index);
     if (previous_range >= new_range)
       inconsistencies |= BUCKET_ORDER_ERROR;
     previous_range = new_range;
   }
 
   if (!HasValidRangeChecksum())
     inconsistencies |= RANGE_CHECKSUM_ERROR;
 
-  int64_t delta64 = snapshot.redundant_count(snapshotLockEvidence) - count;
+  int64_t delta64 = snapshot.redundant_count() - count;
   if (delta64 != 0) {
     int delta = static_cast<int>(delta64);
     if (delta != delta64)
       delta = INT_MAX;  // Flag all giant errors as INT_MAX.
     // Since snapshots of histograms are taken asynchronously relative to
     // sampling (and snapped from different threads), it is pretty likely that
     // we'll catch a redundant count that doesn't match the sample count.  We
     // allow for a certain amount of slop before flagging this as an
@@ -297,17 +291,16 @@ Histogram::Sample Histogram::ranges(size
   return ranges_[i];
 }
 
 size_t Histogram::bucket_count() const {
   return bucket_count_;
 }
 
 void Histogram::SnapshotSample(SampleSet* sample) const {
-  OffTheBooksMutexAutoLock locker(sample_.mutex());
   *sample = sample_;
 }
 
 bool Histogram::HasConstructorArguments(Sample minimum,
                                         Sample maximum,
                                         size_t bucket_count) {
   return ((minimum == declared_min_) && (maximum == declared_max_) &&
           (bucket_count == bucket_count_));
@@ -331,19 +324,19 @@ size_t Histogram::SizeOfIncludingThis(mo
   n += aMallocSizeOf(this);
   // We're not allowed to do deep dives into STL data structures.  This
   // is as close as we can get to measuring this array.
   n += aMallocSizeOf(&ranges_[0]);
   n += sample_.SizeOfExcludingThis(aMallocSizeOf);
   return n;
 }
 
-size_t Histogram::SampleSet::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+size_t
+Histogram::SampleSet::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
-  OffTheBooksMutexAutoLock locker(mutex_);
   // We're not allowed to do deep dives into STL data structures.  This
   // is as close as we can get to measuring this array.
   return aMallocSizeOf(&counts_[0]);
 }
 
 Histogram::Histogram(const std::string& name, Sample minimum,
                      Sample maximum, size_t bucket_count)
   : sample_(),
@@ -551,39 +544,35 @@ uint32_t Histogram::Crc32(uint32_t sum, 
     sum += sum >> 11;
   }
   return sum;
 }
 
 //------------------------------------------------------------------------------
 // Private methods
 
-double Histogram::GetPeakBucketSize(const SampleSet& snapshot,
-                                    const OffTheBooksMutexAutoLock&
-                                          snapshotLockEvidence) const {
+double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const {
   double max = 0;
   for (size_t i = 0; i < bucket_count() ; ++i) {
     double current_size
-        = GetBucketSize(snapshot.counts(snapshotLockEvidence, i), i);
+        = GetBucketSize(snapshot.counts(i), i);
     if (current_size > max)
       max = current_size;
   }
   return max;
 }
 
 void Histogram::WriteAsciiHeader(const SampleSet& snapshot,
-                                 const OffTheBooksMutexAutoLock&
-                                       snapshotLockEvidence,
                                  Count sample_count,
                                  std::string* output) const {
   StringAppendF(output,
                 "Histogram: %s recorded %d samples",
                 histogram_name().c_str(),
                 sample_count);
-  int64_t snapshot_sum = snapshot.sum(snapshotLockEvidence);
+  int64_t snapshot_sum = snapshot.sum();
   if (0 == sample_count) {
     DCHECK_EQ(snapshot_sum, 0);
   } else {
     double average = static_cast<float>(snapshot_sum) / sample_count;
 
     StringAppendF(output, ", average = %.1f", average);
   }
   if (flags_ & ~kHexRangePrintingFlag)
@@ -624,60 +613,48 @@ void Histogram::WriteAsciiBucketGraph(do
 
 //------------------------------------------------------------------------------
 // Methods for the Histogram::SampleSet class
 //------------------------------------------------------------------------------
 
 Histogram::SampleSet::SampleSet()
     : counts_(),
       sum_(0),
-      redundant_count_(0),
-      mutex_("Histogram::SampleSet::SampleSet") {
+      redundant_count_(0) {
 }
 
 Histogram::SampleSet::~SampleSet() {
 }
 
 void Histogram::SampleSet::Resize(const Histogram& histogram) {
-  OffTheBooksMutexAutoLock locker(mutex_);
   counts_.resize(histogram.bucket_count(), 0);
 }
 
-void Histogram::SampleSet::Accumulate(const OffTheBooksMutexAutoLock& ev,
-                                      Sample value, Count count,
+void Histogram::SampleSet::Accumulate(Sample value, Count count,
                                       size_t index) {
   DCHECK(count == 1 || count == -1);
   counts_[index] += count;
   redundant_count_ += count;
   sum_ += static_cast<int64_t>(count) * value;
   DCHECK_GE(counts_[index], 0);
   DCHECK_GE(sum_, 0);
   DCHECK_GE(redundant_count_, 0);
 }
 
-void Histogram::SampleSet::Accumulate(Sample value,
-                                      Count count,
-                                      size_t index) {
-  OffTheBooksMutexAutoLock locker(mutex_);
-  Accumulate(locker, value, count, index);
-}
-
-Count Histogram::SampleSet::TotalCount(const OffTheBooksMutexAutoLock& ev)
-                                                                       const {
+Count Histogram::SampleSet::TotalCount() const {
   Count total = 0;
   for (Counts::const_iterator it = counts_.begin();
        it != counts_.end();
        ++it) {
     total += *it;
   }
   return total;
 }
 
 void Histogram::SampleSet::Add(const SampleSet& other) {
-  OffTheBooksMutexAutoLock locker(mutex_);
   DCHECK_EQ(counts_.size(), other.counts_.size());
   sum_ += other.sum_;
   redundant_count_ += other.redundant_count_;
   for (size_t index = 0; index < counts_.size(); ++index)
     counts_[index] += other.counts_[index];
 }
 
 //------------------------------------------------------------------------------
@@ -869,37 +846,36 @@ FlagHistogram::Accumulate(Sample value, 
   DCHECK_EQ(value, 1);
   LinearHistogram::Accumulate(value, 1, index);
   size_t zero_index = BucketIndex(0);
   LinearHistogram::Accumulate(0, -1, zero_index);
 }
 
 void
 FlagHistogram::AddSampleSet(const SampleSet& sample) {
-  OffTheBooksMutexAutoLock locker(sample.mutex());
-  DCHECK_EQ(bucket_count(), sample.size(locker));
+  DCHECK_EQ(bucket_count(), sample.size());
   // We can't be sure the SampleSet provided came from another FlagHistogram,
   // so we take the following steps:
   //  - If our flag has already been set do nothing.
   //  - Set our flag if the following hold:
   //      - The sum of the counts in the provided SampleSet is 1.
   //      - The bucket index for that single value is the same as the index where we
   //        would place our set flag.
   //  - Otherwise, take no action.
 
   if (mSwitched) {
     return;
   }
 
-  if (sample.sum(locker) != 1) {
+  if (sample.sum() != 1) {
     return;
   }
 
   size_t one_index = BucketIndex(1);
-  if (sample.counts(locker, one_index) == 1) {
+  if (sample.counts(one_index) == 1) {
     Accumulate(1, 1, one_index);
   }
 }
 
 void
 FlagHistogram::Clear() {
   Histogram::Clear();
 
@@ -941,30 +917,28 @@ void
 CountHistogram::Accumulate(Sample value, Count count, size_t index)
 {
   size_t zero_index = BucketIndex(0);
   LinearHistogram::Accumulate(value, 1, zero_index);
 }
 
 void
 CountHistogram::AddSampleSet(const SampleSet& sample) {
-  OffTheBooksMutexAutoLock locker(sample.mutex());
-  DCHECK_EQ(bucket_count(), sample.size(locker));
+  DCHECK_EQ(bucket_count(), sample.size());
   // We can't be sure the SampleSet provided came from another CountHistogram,
   // so we at least check that the unused buckets are empty.
 
   const size_t indices[] = { BucketIndex(0), BucketIndex(1), BucketIndex(2) };
 
-  if (sample.counts(locker, indices[1]) != 0 ||
-      sample.counts(locker, indices[2]) != 0) {
+  if (sample.counts(indices[1]) != 0 || sample.counts(indices[2]) != 0) {
     return;
   }
 
-  if (sample.counts(locker, indices[0]) != 0) {
-    Accumulate(1, sample.counts(locker, indices[0]), indices[0]);
+  if (sample.counts(indices[0]) != 0) {
+    Accumulate(1, sample.counts(indices[0]), indices[0]);
   }
 }
 
 
 //------------------------------------------------------------------------------
 // CustomHistogram:
 //------------------------------------------------------------------------------
 
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -40,30 +40,26 @@
 // pointer is NOT deleted, and we leak the histograms at process termination.
 
 #ifndef BASE_METRICS_HISTOGRAM_H_
 #define BASE_METRICS_HISTOGRAM_H_
 #pragma once
 
 #include "mozilla/Atomics.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Mutex.h"
 
 #include <map>
 #include <string>
 #include <vector>
 
 #include "base/time.h"
 #include "base/lock.h"
 
 namespace base {
 
-using mozilla::OffTheBooksMutex;
-using mozilla::OffTheBooksMutexAutoLock;
-
 //------------------------------------------------------------------------------
 // Provide easy general purpose histogram in a macro, just like stats counters.
 // The first four macros use 50 buckets.
 
 #define HISTOGRAM_TIMES(name, sample) HISTOGRAM_CUSTOM_TIMES( \
     name, sample, base::TimeDelta::FromMilliseconds(1), \
     base::TimeDelta::FromSeconds(10), 50)
 
@@ -322,74 +318,44 @@ class Histogram {
   //----------------------------------------------------------------------------
   // Statistic values, developed over the life of the histogram.
 
   class SampleSet {
    public:
     explicit SampleSet();
     ~SampleSet();
 
-    // This class contains a mozilla::OffTheBooksMutex, |mutex_|.
-    // Most of the methods are thread-safe: they acquire and release
-    // the mutex themselves.  A few are not thread-safe, and require
-    // the caller to provide evidence that the object is locked, by
-    // supplying a const OffTheBooksMutexAutoLock& parameter.  The
-    // parameter is ignored but must be present.  |mutex_| must be an
-    // OffTheBooks variant because some of the containing SampleSet
-    // objects are leaked until shutdown, so a standard Mutex can't be
-    // used, since that does leak checking, and causes test failures.
-
-    //---------------- THREAD SAFE METHODS ----------------//
-    //
-    // The caller must not already hold |this.mutex_|, otherwise we
-    // will end up deadlocking.
+    // None of the methods in this class are thread-safe.  Callers
+    // must deal with locking themselves.
 
     // Adjust size of counts_ for use with given histogram.
     void Resize(const Histogram& histogram);
 
     // Accessor for histogram to make routine additions.
     void Accumulate(Sample value, Count count, size_t index);
 
     // Arithmetic manipulation of corresponding elements of the set.
     void Add(const SampleSet& other);
 
     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
-    //---------------- THREAD UNSAFE METHODS ----------------//
-    //
-    // The caller must hold |this.mutex_|, and must supply evidence by passing
-    // a const reference to the relevant OffTheBooksMutexAutoLock used.
-
-    Count counts(const OffTheBooksMutexAutoLock& ev, size_t i) const {
+    Count counts(size_t i) const {
        return counts_[i];
     }
-    Count TotalCount(const OffTheBooksMutexAutoLock& ev) const;
-    int64_t sum(const OffTheBooksMutexAutoLock& ev) const {
+    Count TotalCount() const;
+    int64_t sum() const {
        return sum_;
     }
-    int64_t redundant_count(const OffTheBooksMutexAutoLock& ev) const {
+    int64_t redundant_count() const {
        return redundant_count_;
     }
-    size_t size(const OffTheBooksMutexAutoLock& ev) const {
+    size_t size() const {
        return counts_.size();
     }
 
-    // An assignment operator.  The presence of mozilla::OffTheBooksMutex
-    // in this class causes the default assignment operator to be deleted.
-    const SampleSet& operator=(const SampleSet& other) {
-       counts_          = other.counts_;
-       sum_             = other.sum_;
-       redundant_count_ = other.redundant_count_;
-       return *this;
-    }
-
-   private:
-    void Accumulate(const OffTheBooksMutexAutoLock& ev,
-                    Sample value, Count count, size_t index);
-
    protected:
     // Actual histogram data is stored in buckets, showing the count of values
     // that fit into each bucket.
     Counts counts_;
 
     // Save simple stats locally.  Note that this MIGHT get done in base class
     // without shared memory at some point.
     int64_t sum_;         // sum of samples.
@@ -397,23 +363,16 @@ class Histogram {
     // To help identify memory corruption, we reduntantly save the number of
     // samples we've accumulated into all of our buckets.  We can compare this
     // count to the sum of the counts in all buckets, and detect problems.  Note
     // that due to races in histogram accumulation (if a histogram is indeed
     // updated on several threads simultaneously), the tallies might mismatch,
     // and also the snapshotting code may asynchronously get a mismatch (though
     // generally either race based mismatch cause is VERY rare).
     int64_t redundant_count_;
-
-   private:
-    // Protects all data fields.
-    mutable OffTheBooksMutex mutex_;
-
-   public:
-    OffTheBooksMutex& mutex() const { return mutex_; }
   };
 
   //----------------------------------------------------------------------------
   // minimum should start from 1. 0 is invalid as a minimum. 0 is an implicit
   // default underflow bucket.
   static Histogram* FactoryGet(const std::string& name,
                                Sample minimum,
                                Sample maximum,
@@ -461,34 +420,32 @@ class Histogram {
   void ClearFlags(Flags flags) { flags_ = static_cast<Flags>(flags_ & ~flags); }
   int flags() const { return flags_; }
 
   // Check to see if bucket ranges, counts and tallies in the snapshot are
   // consistent with the bucket ranges and checksums in our histogram.  This can
   // produce a false-alarm if a race occurred in the reading of the data during
   // a SnapShot process, but should otherwise be false at all times (unless we
   // have memory over-writes, or DRAM failures).
-  virtual Inconsistencies FindCorruption(const SampleSet& snapshot,
-                                         const OffTheBooksMutexAutoLock&
-                                               snapshotLockEvidence) const;
+  virtual Inconsistencies FindCorruption(const SampleSet& snapshot) const;
 
   //----------------------------------------------------------------------------
   // Accessors for factory constuction, serialization and testing.
   //----------------------------------------------------------------------------
   virtual ClassType histogram_type() const;
   const std::string& histogram_name() const { return histogram_name_; }
   Sample declared_min() const { return declared_min_; }
   Sample declared_max() const { return declared_max_; }
   virtual Sample ranges(size_t i) const;
   uint32_t range_checksum() const { return range_checksum_; }
   virtual size_t bucket_count() const;
 
   // Do a safe atomic snapshot of sample data.  The caller is assumed to
   // have exclusive access to the destination, |*sample|, and no locking
-  // of it is done here.  This routine does lock the source sample though.
+  // of it is done here.
   virtual void SnapshotSample(SampleSet* sample) const;
 
   virtual bool HasConstructorArguments(Sample minimum, Sample maximum,
                                        size_t bucket_count);
 
   virtual bool HasConstructorTimeDeltaArguments(TimeDelta minimum,
                                                 TimeDelta maximum,
                                                 size_t bucket_count);
@@ -554,23 +511,20 @@ class Histogram {
 
   // Checksum function for accumulating range values into a checksum.
   static uint32_t Crc32(uint32_t sum, Sample range);
 
   //----------------------------------------------------------------------------
   // Helpers for emitting Ascii graphic.  Each method appends data to output.
 
   // Find out how large the (graphically) the largest bucket will appear to be.
-  double GetPeakBucketSize(const SampleSet& snapshot,
-                           const OffTheBooksMutexAutoLock&
-                                 snapshotLockEvidence) const;
+  double GetPeakBucketSize(const SampleSet& snapshot) const;
 
   // Write a common header message describing this histogram.
   void WriteAsciiHeader(const SampleSet& snapshot,
-                        const OffTheBooksMutexAutoLock& snapshotLockEvidence,
                         Count sample_count, std::string* output) const;
 
   // Write information about previous, current, and next buckets.
   // Information such as cumulative percentage, etc.
   void WriteAsciiBucketContext(const int64_t past, const Count current,
                                const int64_t remaining, const size_t i,
                                std::string* output) const;
 
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2901,10 +2901,20 @@ SetProfileDir(nsIFile* aProfD)
   nsAutoString profDirPath;
   nsresult rv = aProfD->GetPath(profDirPath);
   if (NS_FAILED(rv)) {
     return;
   }
   sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}"));
 }
 
+void CreateStatisticsRecorder()
+{
+  TelemetryHistogram::CreateStatisticsRecorder();
+}
+
+void DestroyStatisticsRecorder()
+{
+  TelemetryHistogram::DestroyStatisticsRecorder();
+}
+
 } // namespace Telemetry
 } // namespace mozilla
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -33,16 +33,23 @@ namespace HangMonitor {
 namespace Telemetry {
 
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
+ * Create and destroy the underlying base::StatisticsRecorder singleton.
+ * Creation has to be done very early in the startup sequence.
+ */
+void CreateStatisticsRecorder();
+void DestroyStatisticsRecorder();
+
+/**
  * Initialize the Telemetry service on the main thread at startup.
  */
 void Init();
 
 /**
  * Adds sample to a histogram defined in TelemetryHistograms.h
  *
  * @param id - histogram id
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -12,28 +12,82 @@
 #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 "mozilla/StaticMutex.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;
+using mozilla::StaticMutex;
+using mozilla::StaticMutexAutoLock;
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// Naming: there are two kinds of functions in this file:
+//
+// * Functions named internal_*: these can only be reached via an
+//   interface function (TelemetryHistogram::*).  They mostly expect
+//   the interface function to have acquired
+//   |gTelemetryHistogramMutex|, so they do not have to be
+//   thread-safe.  However, those internal_* functions that are
+//   reachable from internal_WrapAndReturnHistogram and
+//   internal_WrapAndReturnKeyedHistogram can sometimes be called
+//   without |gTelemetryHistogramMutex|, and so might be racey.
+//
+// * Functions named TelemetryHistogram::*.  This is the external interface.
+//   Entries and exits to these functions are serialised using
+//   |gTelemetryHistogramMutex|, except for GetAddonHistogramSnapshots,
+//   GetKeyedHistogramSnapshots and CreateHistogramSnapshots.
+//
+// Avoiding races and deadlocks:
+//
+// All functions in the external interface (TelemetryHistogram::*) are
+// serialised using the mutex |gTelemetryHistogramMutex|.  This means
+// that the external interface is thread-safe, and many of the
+// internal_* functions can ignore thread safety.  But it also brings
+// a danger of deadlock if any function in the external interface can
+// get back to that interface.  That is, we will deadlock on any call
+// chain like this
+//
+// TelemetryHistogram::* -> .. any functions .. -> TelemetryHistogram::*
+//
+// To reduce the danger of that happening, observe the following rules:
+//
+// * No function in TelemetryHistogram::* may directly call, nor take the
+//   address of, any other function in TelemetryHistogram::*.
+//
+// * No internal function internal_* may call, nor take the address
+//   of, any function in TelemetryHistogram::*.
+//
+// internal_WrapAndReturnHistogram and
+// internal_WrapAndReturnKeyedHistogram are not protected by
+// |gTelemetryHistogramMutex| because they make calls to the JS
+// engine, but that can in turn call back to Telemetry and hence back
+// to a TelemetryHistogram:: function, in order to report GC and other
+// statistics.  This would lead to deadlock due to attempted double
+// acquisition of |gTelemetryHistogramMutex|, if the internal_* functions
+// were required to be protected by |gTelemetryHistogramMutex|.  To
+// break that cycle, we relax that requirement.  Unfortunately this
+// means that this file is not guaranteed race-free.
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
 #define EXPIRED_ID "__expired__"
@@ -115,128 +169,144 @@ KeyedHistogramMapType gKeyedHistograms;
 
 bool gCorruptHistograms[mozilla::Telemetry::HistogramCount];
 
 // This is for gHistograms, gHistogramStringTable
 #include "TelemetryHistogramData.inc"
 
 AddonMapType gAddonMap;
 
+// The singleton StatisticsRecorder object for this process.
+base::StatisticsRecorder* gStatisticsRecorder = nullptr;
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE CONSTANTS
 
+namespace {
+
 // 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
 };
 
+} // namespace
+
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Misc small helpers
 
 namespace {
 
 bool
-IsHistogramEnumId(mozilla::Telemetry::ID aID)
+internal_CanRecordBase() {
+  return gCanRecordBase;
+}
+
+bool
+internal_CanRecordExtended() {
+  return gCanRecordExtended;
+}
+
+bool
+internal_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)
+internal_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)
+internal_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)
+internal_CanRecordDataset(uint32_t dataset)
 {
   // If we are extended telemetry is enabled, we are allowed to record
   // regardless of the dataset.
-  if (TelemetryHistogram::CanRecordExtended()) {
+  if (internal_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)) {
+  if (internal_CanRecordBase() &&
+      internal_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)
+internal_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)
+internal_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;
+  return ss.counts(0) == 0 && ss.sum() == 0;
 }
 
 bool
-IsExpired(const Histogram *histogram)
+internal_IsExpired(const Histogram *histogram)
 {
   return histogram->histogram_name() == EXPIRED_ID;
 }
 
 nsresult
-GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount,
-                          char*** aHistograms)
+internal_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)) {
+    if (internal_IsExpired(h.expiration()) || h.keyed != keyed ||
+        !internal_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)));
   }
 
@@ -267,18 +337,19 @@ HistogramInfo::expiration() const
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram Get, Add, Clone, Clear functions
 
 namespace {
 
 nsresult
-CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max,
-                        uint32_t bucketCount, bool haveOptArgs)
+internal_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;
 
@@ -296,26 +367,28 @@ CheckHistogramArguments(uint32_t histogr
   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)
+internal_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);
+  nsresult rv = internal_CheckHistogramArguments(histogramType, min, max,
+                                                 bucketCount, haveOptArgs);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  if (IsExpired(expiration)) {
+  if (internal_IsExpired(expiration)) {
     name = EXPIRED_ID;
     min = 1;
     max = 2;
     bucketCount = 3;
     histogramType = nsITelemetry::HISTOGRAM_LINEAR;
   }
 
   switch (histogramType) {
@@ -337,55 +410,55 @@ HistogramGet(const char *name, const cha
   default:
     NS_ASSERTION(false, "Invalid histogram type");
     return NS_ERROR_INVALID_ARG;
   }
   return NS_OK;
 }
 
 nsresult
-GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id)
+internal_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)
+internal_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);
+  nsresult rv = internal_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())) {
+  if (!internal_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");
       }
@@ -393,48 +466,51 @@ GetHistogramByEnumId(mozilla::Telemetry:
   }
 #endif
 
   *ret = knownHistograms[id] = h;
   return NS_OK;
 }
 
 nsresult
-GetHistogramByName(const nsACString &name, Histogram **ret)
+internal_GetHistogramByName(const nsACString &name, Histogram **ret)
 {
   mozilla::Telemetry::ID id;
-  nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
+  nsresult rv
+    = internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  rv = GetHistogramByEnumId(id, ret);
+  rv = internal_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)
+internal_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);
+  rv = internal_HistogramGet(PromiseFlatCString(newName).get(),
+                             info.expiration(),
+                             info.histogramType, existing.declared_min(),
+                             existing.declared_max(), existing.bucket_count(),
+                             true, &clone);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   Histogram::SampleSet ss;
   existing.SnapshotSample(&ss);
   clone->AddSampleSet(ss);
 
@@ -442,33 +518,35 @@ CloneHistogram(const nsACString& newName
 }
 
 /**
  * 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)
+internal_CloneHistogram(const nsACString& newName,
+                        mozilla::Telemetry::ID existingId)
 {
   Histogram *existing = nullptr;
-  nsresult rv = GetHistogramByEnumId(existingId, &existing);
+  nsresult rv = internal_GetHistogramByEnumId(existingId, &existing);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
-  return CloneHistogram(newName, existingId, *existing);
+  return internal_CloneHistogram(newName, existingId, *existing);
 }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
 Histogram*
-GetSubsessionHistogram(Histogram& existing)
+internal_GetSubsessionHistogram(Histogram& existing)
 {
   mozilla::Telemetry::ID id;
-  nsresult rv = GetHistogramEnumId(existing.histogram_name().c_str(), &id);
+  nsresult rv
+    = internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
   if (NS_FAILED(rv) || gHistograms[id].keyed) {
     return nullptr;
   }
 
   static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
   if (subsession[id]) {
     return subsession[id];
   }
@@ -477,126 +555,124 @@ GetSubsessionHistogram(Histogram& existi
   nsDependentCString existingName(gHistograms[id].id());
   if (StringBeginsWith(existingName, prefix)) {
     return nullptr;
   }
 
   nsCString subsessionName(prefix);
   subsessionName.Append(existingName);
 
-  subsession[id] = CloneHistogram(subsessionName, id, existing);
+  subsession[id] = internal_CloneHistogram(subsessionName, id, existing);
   return subsession[id];
 }
 #endif
 
 nsresult
-HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
+internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
 {
   // Check if we are allowed to record the data.
-  if (!CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) {
+  if (!internal_CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) {
     return NS_OK;
   }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = GetSubsessionHistogram(histogram)) {
+  if (Histogram* subsession = internal_GetSubsessionHistogram(histogram)) {
     subsession->Add(value);
   }
 #endif
 
   // It is safe to add to the histogram now: the subsession histogram was already
   // cloned from this so we won't add the sample twice.
   histogram.Add(value);
 
   return NS_OK;
 }
 
 nsresult
-HistogramAdd(Histogram& histogram, int32_t value)
+internal_HistogramAdd(Histogram& histogram, int32_t value)
 {
   uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
   // We only really care about the dataset of the histogram if we are not recording
   // extended telemetry. Otherwise, we always record histogram data.
-  if (!TelemetryHistogram::CanRecordExtended()) {
+  if (!internal_CanRecordExtended()) {
     mozilla::Telemetry::ID id;
-    nsresult rv = GetHistogramEnumId(histogram.histogram_name().c_str(), &id);
+    nsresult rv
+      = internal_GetHistogramEnumId(histogram.histogram_name().c_str(), &id);
     if (NS_FAILED(rv)) {
       // If we can't look up the dataset, it might be because the histogram was added
       // at runtime. Since we're not recording extended telemetry, bail out.
       return NS_OK;
     }
     dataset = gHistograms[id].dataset;
   }
 
-  return HistogramAdd(histogram, value, dataset);
+  return internal_HistogramAdd(histogram, value, dataset);
 }
 
 void
-HistogramClear(Histogram& aHistogram, bool onlySubsession)
+internal_HistogramClear(Histogram& aHistogram, bool onlySubsession)
 {
   if (!onlySubsession) {
     aHistogram.Clear();
   }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
-  if (Histogram* subsession = GetSubsessionHistogram(aHistogram)) {
+  if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) {
     subsession->Clear();
   }
 #endif
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram corruption helpers
 
 namespace {
 
+void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
+
 void
-IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
+internal_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);
+    nsresult rv = internal_GetHistogramEnumId(h->histogram_name().c_str(), &id);
     // This histogram isn't a static histogram, just ignore it.
     if (NS_FAILED(rv)) {
       continue;
     }
 
     if (gCorruptHistograms[id]) {
       continue;
     }
 
     Histogram::SampleSet ss;
     h->SnapshotSample(&ss);
 
-    Histogram::Inconsistencies check;
-    {
-      mozilla::OffTheBooksMutexAutoLock locker(ss.mutex());
-      check = h->FindCorruption(ss, locker);
-    }
-
+    Histogram::Inconsistencies check = h->FindCorruption(ss);
     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);
+      internal_Accumulate(corruptID, 1);
     }
 
     gCorruptHistograms[id] = corrupt;
   }
 }
 
 } // namespace
 
@@ -604,90 +680,90 @@ IdentifyCorruptHistograms(StatisticsReco
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram reflection helpers
 
 namespace {
 
 bool
-FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
+internal_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)
+internal_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) {
+  if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
     return REFLECT_CORRUPT;
   }
 
   if (!(JS_DefineProperty(cx, obj, "min",
                           h->declared_min(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "max",
                              h->declared_max(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "histogram_type",
                              h->histogram_type(), JSPROP_ENUMERATE)
         && JS_DefineProperty(cx, obj, "sum",
-                             double(ss.sum(locker)), JSPROP_ENUMERATE))) {
+                             double(ss.sum()), 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)
+  if (!(internal_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)) {
+                          ss.counts(i), JSPROP_ENUMERATE)) {
       return REFLECT_FAILURE;
     }
   }
 
   return REFLECT_OK;
 }
 
 enum reflectStatus
-ReflectHistogramSnapshot(JSContext *cx, JS::Handle<JSObject*> obj, Histogram *h)
+internal_ReflectHistogramSnapshot(JSContext *cx,
+                                  JS::Handle<JSObject*> obj, Histogram *h)
 {
   Histogram::SampleSet ss;
   h->SnapshotSample(&ss);
-  return ReflectHistogramAndSamples(cx, obj, h, ss);
+  return internal_ReflectHistogramAndSamples(cx, obj, h, ss);
 }
 
 bool
-ShouldReflectHistogram(Histogram *h)
+internal_ShouldReflectHistogram(Histogram *h)
 {
   const char *name = h->histogram_name().c_str();
   mozilla::Telemetry::ID id;
-  nsresult rv = ::GetHistogramEnumId(name, &id);
+  nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_FAILED(rv)) {
     // GetHistogramEnumId generally should not fail.  But a lookup
     // failure shouldn't prevent us from reflecting histograms into JS.
     //
     // However, these two histograms are created by Histogram itself for
     // tracking corruption.  We have our own histograms for that, so
     // ignore these two.
     if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
@@ -695,17 +771,17 @@ ShouldReflectHistogram(Histogram *h)
       return false;
     }
     return true;
   } else {
     return !gCorruptHistograms[id];
   }
 }
 
-}
+} // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: class KeyedHistogram
 
 namespace {
@@ -792,19 +868,19 @@ KeyedHistogram::GetHistogram(const nsCSt
     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);
+  nsresult rv = internal_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);
@@ -832,17 +908,17 @@ KeyedHistogram::GetDataset(uint32_t* dat
   MOZ_ASSERT(dataset);
   *dataset = mDataset;
   return NS_OK;
 }
 
 nsresult
 KeyedHistogram::Add(const nsCString& key, uint32_t sample)
 {
-  if (!CanRecordDataset(mDataset)) {
+  if (!internal_CanRecordDataset(mDataset)) {
     return NS_OK;
   }
 
   Histogram* histogram = GetHistogram(key, false);
   MOZ_ASSERT(histogram);
   if (!histogram) {
     return NS_ERROR_FAILURE;
   }
@@ -914,17 +990,18 @@ 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) {
+  if (internal_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;
   }
@@ -960,17 +1037,17 @@ KeyedHistogram::GetJSSnapshot(JSContext*
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: KeyedHistogram helpers
 
 namespace {
 
 KeyedHistogram*
-GetKeyedHistogramById(const nsACString &name)
+internal_GetKeyedHistogramById(const nsACString &name)
 {
   if (!gInitDone) {
     return nullptr;
   }
 
   KeyedHistogram* keyed = nullptr;
   gKeyedHistograms.Get(name, &keyed);
   return keyed;
@@ -979,20 +1056,36 @@ GetKeyedHistogramById(const nsACString &
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: JSHistogram_* functions
 
+// NOTE: the functions in this section:
+//
+//   internal_JSHistogram_Add
+//   internal_JSHistogram_Snapshot
+//   internal_JSHistogram_Clear
+//   internal_JSHistogram_Dataset
+//   internal_WrapAndReturnHistogram
+//
+// all run without protection from |gTelemetryHistogramMutex|.  If they
+// held |gTelemetryHistogramMutex|, there would be the possibility of
+// deadlock because the JS_ calls that they make may call back into the
+// TelemetryHistogram interface, hence trying to re-acquire the mutex.
+//
+// This means that these functions potentially race against threads, but
+// that seems preferable to risking deadlock.
+
 namespace {
 
 bool
-JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_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);
@@ -1014,53 +1107,53 @@ JSHistogram_Add(JSContext *cx, unsigned 
       return false;
     }
 
     if (!JS::ToInt32(cx, args[0], &value)) {
       return false;
     }
   }
 
-  if (TelemetryHistogram::CanRecordBase()) {
-    HistogramAdd(*h, value);
+  if (internal_CanRecordBase()) {
+    internal_HistogramAdd(*h, value);
   }
 
   return true;
 }
 
 bool
-JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj) {
     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)) {
+  switch (internal_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)
+internal_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)
@@ -1074,77 +1167,100 @@ JSHistogram_Clear(JSContext *cx, unsigne
 
     onlySubsession = JS::ToBoolean(args[0]);
   }
 #endif
 
   Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
   MOZ_ASSERT(h);
   if (h) {
-    HistogramClear(*h, onlySubsession);
+    internal_HistogramClear(*h, onlySubsession);
   }
 
   return true;
 }
 
 bool
-JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_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);
+  nsresult rv = internal_GetHistogramEnumId(h->histogram_name().c_str(), &id);
   if (NS_SUCCEEDED(rv)) {
     args.rval().setNumber(gHistograms[id].dataset);
     return true;
   }
 
   return false;
 }
 
+// NOTE: Runs without protection from |gTelemetryHistogramMutex|.
+// See comment at the top of this section.
 nsresult
-WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
+internal_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))) {
+  // The 4 functions that are wrapped up here are eventually called
+  // by the same thread that runs this function.
+  if (!(JS_DefineFunction(cx, obj, "add", internal_JSHistogram_Add, 1, 0)
+        && JS_DefineFunction(cx, obj, "snapshot",
+                             internal_JSHistogram_Snapshot, 0, 0)
+        && JS_DefineFunction(cx, obj, "clear", internal_JSHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset",
+                             internal_JSHistogram_Dataset, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
   JS_SetPrivate(obj, h);
   ret.setObject(*obj);
   return NS_OK;
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: JSKeyedHistogram_* functions
 
+// NOTE: the functions in this section:
+//
+//   internal_KeyedHistogram_SnapshotImpl
+//   internal_JSKeyedHistogram_Add
+//   internal_JSKeyedHistogram_Keys
+//   internal_JSKeyedHistogram_Snapshot
+//   internal_JSKeyedHistogram_SubsessionSnapshot
+//   internal_JSKeyedHistogram_SnapshotSubsessionAndClear
+//   internal_JSKeyedHistogram_Clear
+//   internal_JSKeyedHistogram_Dataset
+//   internal_WrapAndReturnKeyedHistogram
+//
+// Same comments as above, at the JSHistogram_* section, regarding
+// deadlock avoidance, apply.
+
 namespace {
 
 bool
-KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp,
-                            bool subsession, bool clearSubsession)
+internal_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) {
@@ -1182,32 +1298,32 @@ KeyedHistogram_SnapshotImpl(JSContext *c
     return false;
   }
 
   JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
   if (!snapshot) {
     return false;
   }
 
-  switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
+  switch (internal_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)
+internal_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) {
@@ -1247,61 +1363,64 @@ JSKeyedHistogram_Add(JSContext *cx, unsi
     }
   }
 
   keyed->Add(NS_ConvertUTF16toUTF8(key), value);
   return true;
 }
 
 bool
-JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_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)
+internal_JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
-  return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false);
+  return internal_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)
+internal_JSKeyedHistogram_SubsessionSnapshot(JSContext *cx,
+                                             unsigned argc, JS::Value *vp)
 {
-  return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false);
+  return internal_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)
+internal_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);
+  return internal_KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true);
 }
 #endif
 
 bool
-JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_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) {
@@ -1324,17 +1443,17 @@ JSKeyedHistogram_Clear(JSContext *cx, un
   keyed->Clear(onlySubsession);
 #else
   keyed->Clear(false);
 #endif
   return true;
 }
 
 bool
-JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+internal_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));
@@ -1347,36 +1466,47 @@ JSKeyedHistogram_Dataset(JSContext *cx, 
   if (NS_FAILED(rv)) {
     return false;
   }
 
   args.rval().setNumber(dataset);
   return true;
 }
 
+// NOTE: Runs without protection from |gTelemetryHistogramMutex|.
+// See comment at the top of this section.
 nsresult
-WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
+internal_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)
+  // The 7 functions that are wrapped up here are eventually called
+  // by the same thread that runs this function.
+  if (!(JS_DefineFunction(cx, obj, "add", internal_JSKeyedHistogram_Add, 2, 0)
+        && JS_DefineFunction(cx, obj, "snapshot",
+                             internal_JSKeyedHistogram_Snapshot, 1, 0)
 #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)
+        && JS_DefineFunction(cx, obj, "subsessionSnapshot",
+                             internal_JSKeyedHistogram_SubsessionSnapshot, 1, 0)
+        && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear",
+                             internal_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))) {
+        && JS_DefineFunction(cx, obj, "keys",
+                             internal_JSKeyedHistogram_Keys, 0, 0)
+        && JS_DefineFunction(cx, obj, "clear",
+                             internal_JSKeyedHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset",
+                             internal_JSKeyedHistogram_Dataset, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
 
   JS_SetPrivate(obj, h);
   ret.setObject(*obj);
   return NS_OK;
 }
 
@@ -1390,113 +1520,212 @@ WrapAndReturnKeyedHistogram(KeyedHistogr
 
 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)
+internal_AddonHistogramName(const nsACString &id, const nsACString &name,
+                            nsACString &ret)
 {
   ret.Append(id);
   ret.Append(':');
   ret.Append(name);
 }
 
 bool
-CreateHistogramForAddon(const nsACString &name, AddonHistogramInfo &info)
+internal_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);
+  nsresult rv = internal_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)
+internal_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)) {
+    if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) {
       return false;
     }
   }
 
-  if (IsEmpty(info.h)) {
+  if (internal_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)) {
+  switch (internal_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)
+internal_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)
+  if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj)
         && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
                              subobj, JSPROP_ENUMERATE))) {
     return false;
   }
   return true;
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
-// EXTERNALLY VISIBLE FUNCTIONS
+// PRIVATE: thread-unsafe helpers for the external interface
+
+namespace {
+
+void
+internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
+{
+  if (!internal_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 = internal_GetKeyedHistogramById(id);
+    if (keyed) {
+      keyed->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  } else {
+    Histogram *h;
+    nsresult rv = internal_GetHistogramByEnumId(aID, &h);
+    if (NS_SUCCEEDED(rv)) {
+      h->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
+}
+
+void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
+{
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  Histogram *h;
+  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
+  }
+}
 
-namespace TelemetryHistogram {
+void
+internal_Accumulate(mozilla::Telemetry::ID aID,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (!gInitDone || !internal_CanRecordBase()) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aID];
+  KeyedHistogram* keyed
+     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
+
+// This is a StaticMutex rather than a plain Mutex (1) so that
+// it gets initialised in a thread-safe manner the first time
+// it is used, and (2) because it is never de-initialised, and
+// a normal Mutex would show up as a leak in BloatView.  StaticMutex
+// also has the "OffTheBooks" property, so it won't show as a leak
+// in BloatView.
+static StaticMutex gTelemetryHistogramMutex;
 
-void InitializeGlobalState(bool canRecordBase, bool canRecordExtended)
+// All of these functions are actually in namespace TelemetryHistogram::,
+// but the ::TelemetryHistogram prefix is given explicitly.  This is
+// because it is critical to see which calls from these functions are
+// to another function in this interface.  Mis-identifying "inwards
+// calls" from "calls to another function in this interface" will lead
+// to deadlocking and/or races.  See comments at the top of the file
+// for further (important!) details.
+
+// Create and destroy the singleton StatisticsRecorder object.
+void TelemetryHistogram::CreateStatisticsRecorder()
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  MOZ_ASSERT(!gStatisticsRecorder);
+  gStatisticsRecorder = new base::StatisticsRecorder();
+}
+
+void TelemetryHistogram::DestroyStatisticsRecorder()
+{
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  MOZ_ASSERT(gStatisticsRecorder);
+  if (gStatisticsRecorder) {
+    delete gStatisticsRecorder;
+    gStatisticsRecorder = nullptr;
+  }
+}
+
+void TelemetryHistogram::InitializeGlobalState(bool canRecordBase,
+                                               bool canRecordExtended)
+{
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState "
              "may only be called once");
 
   gCanRecordBase = canRecordBase;
   gCanRecordExtended = canRecordExtended;
 
   // gHistogramMap should have been pre-sized correctly at the
   // declaration point further up in this file.
@@ -1542,343 +1771,361 @@ void InitializeGlobalState(bool canRecor
       "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()
+void TelemetryHistogram::DeInitializeGlobalState()
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
   gCanRecordExtended = false;
   gHistogramMap.Clear();
   gKeyedHistograms.Clear();
   gAddonMap.Clear();
   gInitDone = false;
 }
 
 #ifdef DEBUG
-bool GlobalStateHasBeenInitialized() {
+bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gInitDone;
 }
 #endif
 
 bool
-CanRecordBase() {
-  return gCanRecordBase;
+TelemetryHistogram::CanRecordBase() {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  return internal_CanRecordBase();
 }
 
 void
-SetCanRecordBase(bool b) {
+TelemetryHistogram::SetCanRecordBase(bool b) {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = b;
 }
 
 bool
-CanRecordExtended() {
-  return gCanRecordExtended;
+TelemetryHistogram::CanRecordExtended() {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  return internal_CanRecordExtended();
 }
 
 void
-SetCanRecordExtended(bool b) {
+TelemetryHistogram::SetCanRecordExtended(bool b) {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordExtended = b;
 }
 
 
 void
-InitHistogramRecordingEnabled()
+TelemetryHistogram::InitHistogramRecordingEnabled()
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   const size_t length = mozilla::ArrayLength(kRecordingInitiallyDisabledIDs);
   for (size_t i = 0; i < length; i++) {
-    SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i], false);
+    internal_SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i],
+                                          false);
   }
 }
 
 void
-SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
+TelemetryHistogram::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");
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  internal_SetHistogramRecordingEnabled(aID, aEnabled);
 }
 
 
 nsresult
-SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled)
+TelemetryHistogram::SetHistogramRecordingEnabled(const nsACString &id,
+                                                 bool aEnabled)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   Histogram *h;
-  nsresult rv = GetHistogramByName(id, &h);
+  nsresult rv = internal_GetHistogramByName(id, &h);
   if (NS_SUCCEEDED(rv)) {
     h->SetRecordingEnabled(aEnabled);
     return NS_OK;
   }
 
-  KeyedHistogram* keyed = ::GetKeyedHistogramById(id);
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
   if (keyed) {
     keyed->SetRecordingEnabled(aEnabled);
     return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
 
 
 void
-Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
+TelemetryHistogram::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);
-  }
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  internal_Accumulate(aHistogram, aSample);
 }
 
 void
-Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey, uint32_t aSample)
+TelemetryHistogram::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);
-
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  internal_Accumulate(aID, aKey, aSample);
 }
 
 void
-Accumulate(const char* name, uint32_t sample)
+TelemetryHistogram::Accumulate(const char* name, uint32_t sample)
 {
-  if (!CanRecordBase()) {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
     return;
   }
   mozilla::Telemetry::ID id;
-  nsresult rv = ::GetHistogramEnumId(name, &id);
+  nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_FAILED(rv)) {
     return;
   }
 
   Histogram *h;
-  rv = GetHistogramByEnumId(id, &h);
+  rv = internal_GetHistogramByEnumId(id, &h);
   if (NS_SUCCEEDED(rv)) {
-    HistogramAdd(*h, sample, gHistograms[id].dataset);
+    internal_HistogramAdd(*h, sample, gHistograms[id].dataset);
   }
 }
 
 void
-Accumulate(const char* name, const nsCString& key, uint32_t sample)
+TelemetryHistogram::Accumulate(const char* name,
+                               const nsCString& key, uint32_t sample)
 {
-  if (!CanRecordBase()) {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
     return;
   }
   mozilla::Telemetry::ID id;
-  nsresult rv = ::GetHistogramEnumId(name, &id);
+  nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_SUCCEEDED(rv)) {
-    Accumulate(id, key, sample);
+    internal_Accumulate(id, key, sample);
   }
 }
 
 void
-ClearHistogram(mozilla::Telemetry::ID aId)
+TelemetryHistogram::ClearHistogram(mozilla::Telemetry::ID aId)
 {
-  if (!TelemetryHistogram::CanRecordBase()) {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
     return;
   }
 
   Histogram *h;
-  nsresult rv = ::GetHistogramByEnumId(aId, &h);
+  nsresult rv = internal_GetHistogramByEnumId(aId, &h);
   if (NS_SUCCEEDED(rv) && h) {
-    ::HistogramClear(*h, false);
+    internal_HistogramClear(*h, false);
   }
 }
 
 nsresult
-GetHistogramById(const nsACString &name, JSContext *cx,
-                 JS::MutableHandle<JS::Value> ret)
+TelemetryHistogram::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);
+  Histogram *h = nullptr;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    nsresult rv = internal_GetHistogramByName(name, &h);
+    if (NS_FAILED(rv))
+      return rv;
+  }
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_WrapAndReturnHistogram(h, cx, ret);
 }
 
 nsresult
-GetKeyedHistogramById(const nsACString &name, JSContext *cx,
-                      JS::MutableHandle<JS::Value> ret)
+TelemetryHistogram::GetKeyedHistogramById(const nsACString &name,
+                                          JSContext *cx,
+                                          JS::MutableHandle<JS::Value> ret)
 {
   KeyedHistogram* keyed = nullptr;
-  if (!gKeyedHistograms.Get(name, &keyed)) {
-    return NS_ERROR_FAILURE;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    if (!gKeyedHistograms.Get(name, &keyed)) {
+      return NS_ERROR_FAILURE;
+    }
   }
-
-  return WrapAndReturnKeyedHistogram(keyed, cx, ret);
-
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_WrapAndReturnKeyedHistogram(keyed, cx, ret);
 }
 
 const char*
-GetHistogramName(mozilla::Telemetry::ID id)
+TelemetryHistogram::GetHistogramName(mozilla::Telemetry::ID id)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   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)
+TelemetryHistogram::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 = nullptr;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    if (!internal_IsValidHistogramName(name)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(),
+                                        PromiseFlatCString(expiration).get(),
+                                        histogramType, min, max, bucketCount,
+                                        optArgCount == 3, &h);
+    if (NS_FAILED(rv))
+      return rv;
+    h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
   }
 
-  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);
-
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_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)
+TelemetryHistogram::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;
+  KeyedHistogram* keyed = nullptr;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    if (!internal_IsValidHistogramName(name)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsresult rv
+      = internal_CheckHistogramArguments(histogramType, min, max,
+                                         bucketCount, optArgCount == 3);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    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;
+    }
   }
 
-  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);
-
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_WrapAndReturnKeyedHistogram(keyed, cx, ret);
 }
 
 nsresult
-HistogramFrom(const nsACString &name, const nsACString &existing_name,
-              JSContext *cx, JS::MutableHandle<JS::Value> ret)
+TelemetryHistogram::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 = nullptr;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    mozilla::Telemetry::ID id;
+    nsresult rv
+      = internal_GetHistogramEnumId(PromiseFlatCString(existing_name).get(),
+                                    &id);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    clone = internal_CloneHistogram(name, id);
+    if (!clone) {
+      return NS_ERROR_FAILURE;
+    }
   }
 
-  Histogram* clone = CloneHistogram(name, id);
-  if (!clone) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return WrapAndReturnHistogram(clone, cx, ret);
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_WrapAndReturnHistogram(clone, cx, ret);
 }
 
 nsresult
-CreateHistogramSnapshots(JSContext *cx,
-                         JS::MutableHandle<JS::Value> ret,
-                         bool subsession,
-                         bool clearSubsession)
+TelemetryHistogram::CreateHistogramSnapshots(JSContext *cx,
+                                             JS::MutableHandle<JS::Value> ret,
+                                             bool subsession,
+                                             bool clearSubsession)
 {
+  // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
   if (!root_obj)
     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);
+         = internal_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);
+  internal_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)) {
+    if (!internal_ShouldReflectHistogram(h) || internal_IsEmpty(h) ||
+        internal_IsExpired(h)) {
       continue;
     }
 
     Histogram* original = h;
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
     if (subsession) {
-      h = GetSubsessionHistogram(*h);
+      h = internal_GetSubsessionHistogram(*h);
       if (!h) {
         continue;
       }
     }
 #endif
 
     hobj = JS_NewPlainObject(cx);
     if (!hobj) {
       return NS_ERROR_FAILURE;
     }
-    switch (ReflectHistogramSnapshot(cx, hobj, h)) {
+    switch (internal_ReflectHistogramSnapshot(cx, hobj, h)) {
     case REFLECT_CORRUPT:
       // We can still hit this case even if ShouldReflectHistograms
       // returns true.  The histogram lies outside of our control
       // somehow; just skip it.
       continue;
     case REFLECT_FAILURE:
       return NS_ERROR_FAILURE;
     case REFLECT_OK:
@@ -1893,32 +2140,39 @@ CreateHistogramSnapshots(JSContext *cx,
       h->Clear();
     }
 #endif
   }
   return NS_OK;
 }
 
 nsresult
-RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
-                     char*** aHistograms)
+TelemetryHistogram::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
+                                         char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms);
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  return internal_GetRegisteredHistogramIds(false,
+                                            aDataset, aCount, aHistograms);
 }
 
 nsresult
-RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount,
-                          char*** aHistograms)
+TelemetryHistogram::RegisteredKeyedHistograms(uint32_t aDataset,
+                                              uint32_t *aCount,
+                                              char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms);
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  return internal_GetRegisteredHistogramIds(true,
+                                            aDataset, aCount, aHistograms);
 }
 
 nsresult
-GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
+TelemetryHistogram::GetKeyedHistogramSnapshots(JSContext *cx,
+                                               JS::MutableHandle<JS::Value> ret)
 {
+  // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
   if (!obj) {
     return NS_ERROR_FAILURE;
   }
 
   for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
     JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
     if (!snapshot) {
@@ -1935,20 +2189,24 @@ GetKeyedHistogramSnapshots(JSContext *cx
     }
   }
 
   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)
+TelemetryHistogram::RegisterAddonHistogram(const nsACString &id,
+                                           const nsACString &name,
+                                           uint32_t histogramType,
+                                           uint32_t min, uint32_t max,
+                                           uint32_t bucketCount,
+                                           uint8_t optArgCount)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   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)
@@ -1991,87 +2249,100 @@ RegisterAddonHistogram(const nsACString 
   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)
+TelemetryHistogram::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;
+  AddonHistogramInfo* info = nullptr;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    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;
+    }
+
+    info = &histogramEntry->mData;
+    if (!info->h) {
+      nsAutoCString actualName;
+      internal_AddonHistogramName(id, name, actualName);
+      if (!internal_CreateHistogramForAddon(actualName, *info)) {
+        return NS_ERROR_FAILURE;
+      }
+    }
   }
 
-  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);
+  // Runs without protection from |gTelemetryHistogramMutex|
+  return internal_WrapAndReturnHistogram(info->h, cx, ret);
 }
 
 nsresult
-UnregisterAddonHistograms(const nsACString &id)
+TelemetryHistogram::UnregisterAddonHistograms(const nsACString &id)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   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)
+TelemetryHistogram::GetAddonHistogramSnapshots(JSContext *cx,
+                                               JS::MutableHandle<JS::Value> ret)
 {
+  // Runs without protection from |gTelemetryHistogramMutex|
   JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
   if (!obj) {
     return NS_ERROR_FAILURE;
   }
 
-  if (!gAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) {
+  if (!gAddonMap.ReflectIntoJS(internal_AddonReflector, cx, obj)) {
     return NS_ERROR_FAILURE;
   }
   ret.setObject(*obj);
   return NS_OK;
 }
 
 size_t
-GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+TelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf
+                                                      aMallocSizeOf)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gAddonMap.ShallowSizeOfExcludingThis(aMallocSizeOf) +
          gHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
 
 size_t
-GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
+                                                     aMallocSizeOf)
 {
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   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
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -10,16 +10,19 @@
 
 // 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 CreateStatisticsRecorder();
+void DestroyStatisticsRecorder();
+
 void InitializeGlobalState(bool canRecordBase, bool canRecordExtended);
 void DeInitializeGlobalState();
 #ifdef DEBUG
 bool GlobalStateHasBeenInitialized();
 #endif
 
 bool CanRecordBase();
 void SetCanRecordBase(bool b);
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -89,18 +89,16 @@
 #include "nsPIDOMWindow.h"
 #include "nsIBaseWindow.h"
 #include "nsIWidget.h"
 #include "nsIDocShell.h"
 #include "nsAppShellCID.h"
 #include "mozilla/scache/StartupCache.h"
 #include "gfxPrefs.h"
 
-#include "base/histogram.h"
-
 #include "mozilla/unused.h"
 
 #ifdef XP_WIN
 #include "nsIWinAppHelper.h"
 #include <windows.h>
 #include <intrin.h>
 #include <math.h>
 #include "cairo/cairo-features.h"
@@ -4581,22 +4579,19 @@ void
 XRE_StopLateWriteChecks(void) {
   mozilla::StopLateWriteChecks();
 }
 
 // Separate stub function to let us specifically suppress it in Valgrind
 void
 XRE_CreateStatsObject()
 {
-  // A initializer to initialize histogram collection, a chromium
-  // thing used by Telemetry (and effectively a global; it's all static).
-  // Note: purposely leaked
-  base::StatisticsRecorder* statistics_recorder = new base::StatisticsRecorder();
-  MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(statistics_recorder);
-  Unused << statistics_recorder;
+  // Initialize global variables used by histogram collection
+  // machinery that is used by by Telemetry.  Note: is never de-initialised.
+  Telemetry::CreateStatisticsRecorder();
 }
 
 int
 XRE_main(int argc, char* argv[], const nsXREAppData* aAppData, uint32_t aFlags)
 {
   XREMain main;
 
   XRE_CreateStatsObject();
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -71,17 +71,17 @@
 #include "mozilla/ipc/XPCShellEnvironment.h"
 #include "mozilla/WindowsDllBlocklist.h"
 
 #include "GMPProcessChild.h"
 #include "GMPLoader.h"
 
 #include "GeckoProfiler.h"
 
- #include "base/histogram.h"
+#include "mozilla/Telemetry.h"
 
 #if defined(MOZ_SANDBOX) && defined(XP_WIN)
 #include "mozilla/sandboxTarget.h"
 #include "mozilla/sandboxing/loggingCallbacks.h"
 #endif
 
 #ifdef MOZ_IPDL_TESTS
 #include "mozilla/_ipdltest/IPDLUnitTests.h"
@@ -313,18 +313,17 @@ XRE_InitChildProcess(int aArgc,
 #endif
 
 #ifdef MOZ_JPROF
   // Call the code to install our handler
   setupProfilingStuff();
 #endif
 
   // This is needed by Telemetry to initialize histogram collection.
-  UniquePtr<base::StatisticsRecorder> statisticsRecorder =
-    MakeUnique<base::StatisticsRecorder>();
+  Telemetry::CreateStatisticsRecorder();
 
 #if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK)
   // On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
   // and we must forward it through to the GMP code here.
   GMPProcessChild::SetGMPLoader(aChildData->gmpLoader.get());
 #else
   // On Fennec, the GMPLoader's code resides inside XUL (because for the time
   // being GMPLoader relies upon NSPR, which we can't use in plugin-container
@@ -663,17 +662,17 @@ XRE_InitChildProcess(int aArgc,
 
 #if defined(XP_MACOSX)
       // Everybody should be done using shared memory by now.
       mozilla::ipc::SharedMemoryBasic::Shutdown();
 #endif
     }
   }
 
-  statisticsRecorder = nullptr;
+  Telemetry::DestroyStatisticsRecorder();
   profiler_shutdown();
   NS_LogTerm();
   return XRE_DeinitCommandLine();
 }
 
 MessageLoop*
 XRE_GetIOMessageLoop()
 {