Bug 1069873 - Add counter histogram type. r=froydnj, ba=lmandel
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Fri, 26 Sep 2014 17:45:33 +0200
changeset 225905 a4db8f39f372
parent 225904 bda711062d08
child 225906 56b3e37832b9
push id4063
push usergeorg.fritzsche@googlemail.com
push date2014-11-02 23:54 +0000
treeherdermozilla-beta@1ca39da5df9d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1069873
milestone34.0
Bug 1069873 - Add counter histogram type. r=froydnj, ba=lmandel
ipc/chromium/src/base/histogram.cc
ipc/chromium/src/base/histogram.h
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/gen-histogram-bucket-ranges.py
toolkit/components/telemetry/gen-histogram-data.py
toolkit/components/telemetry/histogram_tools.py
toolkit/components/telemetry/nsITelemetry.idl
toolkit/components/telemetry/tests/unit/head.js
toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -1043,16 +1043,71 @@ FlagHistogram::AddSampleSet(const Sample
     return;
   }
 
   size_t one_index = BucketIndex(1);
   if (sample.counts(one_index) == 1) {
     Accumulate(1, 1, one_index);
   }
 }
+
+//------------------------------------------------------------------------------
+// CountHistogram:
+//------------------------------------------------------------------------------
+
+Histogram *
+CountHistogram::FactoryGet(const std::string &name, Flags flags)
+{
+  Histogram *h(nullptr);
+
+  if (!StatisticsRecorder::FindHistogram(name, &h)) {
+    CountHistogram *fh = new CountHistogram(name);
+    fh->InitializeBucketRange();
+    fh->SetFlags(flags);
+    h = StatisticsRecorder::RegisterOrDeleteDuplicate(fh);
+  }
+
+  return h;
+}
+
+CountHistogram::CountHistogram(const std::string &name)
+  : LinearHistogram(name, 1, 2, 3) {
+}
+
+Histogram::ClassType
+CountHistogram::histogram_type() const
+{
+  return COUNT_HISTOGRAM;
+}
+
+void
+CountHistogram::Accumulate(Sample value, Count count, size_t index)
+{
+  size_t zero_index = BucketIndex(0);
+  LinearHistogram::Accumulate(1, 1, zero_index);
+}
+
+void
+CountHistogram::AddSampleSet(const SampleSet& sample) {
+  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(indices[1]) != 0 || sample.counts(indices[2]) != 0) {
+    return;
+  }
+
+  if (sample.counts(indices[0]) != 0) {
+    Accumulate(1, sample.counts(indices[0]), indices[0]);
+  }
+}
+
+
 //------------------------------------------------------------------------------
 // CustomHistogram:
 //------------------------------------------------------------------------------
 
 Histogram* CustomHistogram::FactoryGet(const std::string& name,
                                        const std::vector<Sample>& custom_ranges,
                                        Flags flags) {
   Histogram* histogram(NULL);
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -273,16 +273,17 @@ class Histogram {
 
   // These enums are used to facilitate deserialization of renderer histograms
   // into the browser.
   enum ClassType {
     HISTOGRAM,
     LINEAR_HISTOGRAM,
     BOOLEAN_HISTOGRAM,
     FLAG_HISTOGRAM,
+    COUNT_HISTOGRAM,
     CUSTOM_HISTOGRAM,
     NOT_VALID_IN_RENDERER
   };
 
   enum BucketLayout {
     EXPONENTIAL,
     LINEAR,
     CUSTOM
@@ -685,16 +686,34 @@ public:
 
 private:
   explicit FlagHistogram(const std::string &name);
   bool mSwitched;
 
   DISALLOW_COPY_AND_ASSIGN(FlagHistogram);
 };
 
+// CountHistogram only allows a single monotic counter value.
+class CountHistogram : public LinearHistogram
+{
+public:
+  static Histogram *FactoryGet(const std::string &name, Flags flags);
+
+  virtual ClassType histogram_type() const;
+
+  virtual void Accumulate(Sample value, Count count, size_t index);
+
+  virtual void AddSampleSet(const SampleSet& sample);
+
+private:
+  explicit CountHistogram(const std::string &name);
+
+  DISALLOW_COPY_AND_ASSIGN(CountHistogram);
+};
+
 //------------------------------------------------------------------------------
 
 // CustomHistogram is a histogram for a set of custom integers.
 class CustomHistogram : public Histogram {
  public:
 
   static Histogram* FactoryGet(const std::string& name,
                                const std::vector<Sample>& custom_ranges,
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4203,16 +4203,21 @@
     "n_values": 30,
     "description": "Number of telemetry pings evicted at startup"
   },
   "TELEMETRY_TEST_FLAG": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "a testing histogram; not meant to be touched"
   },
+  "TELEMETRY_TEST_COUNT": {
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "a testing histogram; not meant to be touched"
+  },
   "STARTUP_CRASH_DETECTED": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Whether there was a crash during the last startup"
   },
   "SAFE_MODE_USAGE": {
     "expires_in_version": "never",
     "kind": "enumerated",
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -722,17 +722,18 @@ IsExpired(const Histogram *histogram){
   return histogram->histogram_name() == EXPIRED_ID;
 }
 
 nsresult
 HistogramGet(const char *name, const char *expiration, uint32_t min, uint32_t max,
              uint32_t bucketCount, uint32_t histogramType, Histogram **result)
 {
   if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
-      && histogramType != nsITelemetry::HISTOGRAM_FLAG) {
+      && histogramType != nsITelemetry::HISTOGRAM_FLAG
+      && histogramType != nsITelemetry::HISTOGRAM_COUNT) {
     // Sanity checks for histogram parameters.
     if (min >= max)
       return NS_ERROR_ILLEGAL_VALUE;
 
     if (bucketCount <= 2)
       return NS_ERROR_ILLEGAL_VALUE;
 
     if (min < 1)
@@ -755,16 +756,19 @@ HistogramGet(const char *name, const cha
     *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
     break;
   case nsITelemetry::HISTOGRAM_BOOLEAN:
     *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
     break;
   case nsITelemetry::HISTOGRAM_FLAG:
     *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
     break;
+  case nsITelemetry::HISTOGRAM_COUNT:
+    *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
+    break;
   default:
     return NS_ERROR_INVALID_ARG;
   }
   return NS_OK;
 }
 
 // O(1) histogram lookup by numeric id
 nsresult
@@ -898,43 +902,47 @@ IsEmpty(const Histogram *h)
   h->SnapshotSample(&ss);
 
   return ss.counts(0) == 0 && ss.sum() == 0;
 }
 
 bool
 JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
 {
-  JS::CallArgs args = CallArgsFromVp(argc, vp);
-  if (!args.length()) {
-    JS_ReportError(cx, "Expected one argument");
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
     return false;
   }
 
-  if (!(args[0].isNumber() || args[0].isBoolean())) {
-    JS_ReportError(cx, "Not a number");
-    return false;
-  }
-
-  int32_t value;
-  if (!JS::ToInt32(cx, args[0], &value)) {
-    return false;
+  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  Histogram::ClassType type = h->histogram_type();
+
+  int32_t value = 1;
+  if (type != base::CountHistogram::COUNT_HISTOGRAM) {
+    JS::CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length()) {
+      JS_ReportError(cx, "Expected one argument");
+      return false;
+    }
+
+    if (!(args[0].isNumber() || args[0].isBoolean())) {
+      JS_ReportError(cx, "Not a number");
+      return false;
+    }
+
+    if (!JS::ToInt32(cx, args[0], &value)) {
+      return false;
+    }
   }
 
   if (TelemetryImpl::CanRecord()) {
-    JSObject *obj = JS_THIS_OBJECT(cx, vp);
-    if (!obj) {
-      return false;
-    }
-
-    Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
     h->Add(value);
   }
+
   return true;
-
 }
 
 bool
 JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj) {
@@ -1577,20 +1585,22 @@ TelemetryImpl::UnregisterAddonHistograms
 NS_IMETHODIMP
 TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
   JS::Rooted<JSObject*> root_obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
   if (!root_obj)
     return NS_ERROR_FAILURE;
   ret.setObject(*root_obj);
 
-  // Ensure that all the HISTOGRAM_FLAG histograms have been created, so
-  // that their values are snapshotted.
+  // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
+  // been created, so that their values are snapshotted.
   for (size_t i = 0; i < Telemetry::HistogramCount; ++i) {
-    if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) {
+    const uint32_t type = gHistograms[i].histogramType;
+    if (type == nsITelemetry::HISTOGRAM_FLAG ||
+        type == nsITelemetry::HISTOGRAM_COUNT) {
       Histogram *h;
       DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   };
 
   StatisticsRecorder::Histograms hs;
   StatisticsRecorder::GetHistograms(&hs);
--- a/toolkit/components/telemetry/gen-histogram-bucket-ranges.py
+++ b/toolkit/components/telemetry/gen-histogram-bucket-ranges.py
@@ -23,17 +23,18 @@ def main(argv):
     for histogram in histogram_tools.from_file(filename):
         name = histogram.name()
         parameters = OrderedDict()
         table = {
             'boolean': '2',
             'flag': '3',
             'enumerated': '1',
             'linear': '1',
-            'exponential': '0'
+            'exponential': '0',
+            'count': '4',
             }
         # Use __setitem__ because Python lambdas are so limited.
         histogram_tools.table_dispatch(histogram.kind(), table,
                                        lambda k: parameters.__setitem__('kind', k))
         if histogram.low() == 0:
             parameters['min'] = 1
         else:
             parameters['min'] = histogram.low()
--- a/toolkit/components/telemetry/gen-histogram-data.py
+++ b/toolkit/components/telemetry/gen-histogram-data.py
@@ -92,16 +92,19 @@ def static_assert(expression, message):
     print "static_assert(%s, \"%s\");" % (expression, message)
 
 def static_asserts_for_boolean(histogram):
     pass
 
 def static_asserts_for_flag(histogram):
     pass
 
+def static_asserts_for_count(histogram):
+    pass
+
 def static_asserts_for_enumerated(histogram):
     n_values = histogram.high()
     static_assert("%s > 2" % n_values,
                   "Not enough values for %s" % histogram.name())
 
 def shared_static_asserts(histogram):
     name = histogram.name()
     low = histogram.low()
@@ -123,16 +126,17 @@ def write_histogram_static_asserts(histo
     print """
 // Perform the checks at the beginning of HistogramGet at
 // compile time, so that incorrect histogram definitions
 // give compile-time errors, not runtime errors."""
 
     table = {
         'boolean' : static_asserts_for_boolean,
         'flag' : static_asserts_for_flag,
+        'count': static_asserts_for_count,
         'enumerated' : static_asserts_for_enumerated,
         'linear' : static_asserts_for_linear,
         'exponential' : static_asserts_for_exponential,
         }
 
     for histogram in histograms:
         histogram_tools.table_dispatch(histogram.kind(), table,
                                        lambda f: f(histogram))
--- a/toolkit/components/telemetry/histogram_tools.py
+++ b/toolkit/components/telemetry/histogram_tools.py
@@ -60,48 +60,49 @@ always_allowed_keys = ['kind', 'descript
 class Histogram:
     """A class for representing a histogram definition."""
 
     def __init__(self, name, definition):
         """Initialize a histogram named name with the given definition.
 definition is a dict-like object that must contain at least the keys:
 
  - 'kind': The kind of histogram.  Must be one of 'boolean', 'flag',
-   'enumerated', 'linear', or 'exponential'.
+   'count', 'enumerated', 'linear', or 'exponential'.
  - 'description': A textual description of the histogram.
 
 The key 'cpp_guard' is optional; if present, it denotes a preprocessor
 symbol that should guard C/C++ definitions associated with the histogram."""
         self.verify_attributes(name, definition)
         self._name = name
         self._description = definition['description']
         self._kind = definition['kind']
         self._cpp_guard = definition.get('cpp_guard')
         self._extended_statistics_ok = definition.get('extended_statistics_ok', False)
         self._expiration = definition.get('expires_in_version')
         self.compute_bucket_parameters(definition)
         table = { 'boolean': 'BOOLEAN',
                   'flag': 'FLAG',
+                  'count': 'COUNT',
                   'enumerated': 'LINEAR',
                   'linear': 'LINEAR',
                   'exponential': 'EXPONENTIAL' }
         table_dispatch(self.kind(), table,
                        lambda k: self._set_nsITelemetry_kind(k))
 
     def name(self):
         """Return the name of the histogram."""
         return self._name
 
     def description(self):
         """Return the description of the histogram."""
         return self._description
 
     def kind(self):
         """Return the kind of the histogram.
-Will be one of 'boolean', 'flag', 'enumerated', 'linear', or 'exponential'."""
+Will be one of 'boolean', 'flag', 'count', 'enumerated', 'linear', or 'exponential'."""
         return self._kind
 
     def expiration(self):
         """Return the expiration version of the histogram."""
         return self._expiration
 
     def nsITelemetry_kind(self):
         """Return the nsITelemetry constant corresponding to the kind of
@@ -132,40 +133,43 @@ associated with the histogram.  Returns 
         """Return True if gathering extended statistics for this histogram
 is enabled."""
         return self._extended_statistics_ok
 
     def ranges(self):
         """Return an array of lower bounds for each bucket in the histogram."""
         table = { 'boolean': linear_buckets,
                   'flag': linear_buckets,
+                  'count': linear_buckets,
                   'enumerated': linear_buckets,
                   'linear': linear_buckets,
                   'exponential': exponential_buckets }
         return table_dispatch(self.kind(), table,
                               lambda p: p(self.low(), self.high(), self.n_buckets()))
 
     def compute_bucket_parameters(self, definition):
         table = {
             'boolean': Histogram.boolean_flag_bucket_parameters,
             'flag': Histogram.boolean_flag_bucket_parameters,
+            'count': Histogram.boolean_flag_bucket_parameters,
             'enumerated': Histogram.enumerated_bucket_parameters,
             'linear': Histogram.linear_bucket_parameters,
             'exponential': Histogram.exponential_bucket_parameters
             }
         table_dispatch(self.kind(), table,
                        lambda p: self.set_bucket_parameters(*p(definition)))
 
     def verify_attributes(self, name, definition):
         global always_allowed_keys
         general_keys = always_allowed_keys + ['low', 'high', 'n_buckets']
 
         table = {
             'boolean': always_allowed_keys,
             'flag': always_allowed_keys,
+            'count': always_allowed_keys,
             'enumerated': always_allowed_keys + ['n_values'],
             'linear': general_keys,
             'exponential': general_keys + ['extended_statistics_ok']
             }
         table_dispatch(definition['kind'], table,
                        lambda allowed_keys: Histogram.check_keys(name, definition, allowed_keys))
 
         Histogram.check_expiration(name, definition)
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -7,38 +7,41 @@
 #include "nsIFile.idl"
 
 [scriptable,function, uuid(3d3b9075-5549-4244-9c08-b64fefa1dd60)]
 interface nsIFetchTelemetryDataCallback : nsISupports
 {
   void complete();
 };
 
-[scriptable, uuid(4e4bfc35-dac6-4b28-ade4-7e45760051d5)]
+[scriptable, uuid(df355bbf-437d-4332-80e6-a8a54db959f3)]
 interface nsITelemetry : nsISupports
 {
   /**
    * Histogram types:
    * HISTOGRAM_EXPONENTIAL - buckets increase exponentially
    * HISTOGRAM_LINEAR - buckets increase linearly
    * HISTOGRAM_BOOLEAN - For storing 0/1 values
    * HISTOGRAM_FLAG - For storing a single value; its count is always == 1.
+   * HISTOGRAM_COUNT - For storing counter values without bucketing.
    */
   const unsigned long HISTOGRAM_EXPONENTIAL = 0;
   const unsigned long HISTOGRAM_LINEAR = 1;
   const unsigned long HISTOGRAM_BOOLEAN = 2;
   const unsigned long HISTOGRAM_FLAG = 3;
+  const unsigned long HISTOGRAM_COUNT = 4;
 
   /*
    * An object containing a snapshot from all of the currently registered histograms.
    * { name1: {data1}, name2:{data2}...}
    * where data is consists of the following properties:
    *   min - Minimal bucket size
    *   max - Maximum bucket size
-   *   histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, or HISTOGRAM_BOOLEAN
+   *   histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, HISTOGRAM_BOOLEAN
+   *                    or HISTOGRAM_COUNT
    *   counts - array representing contents of the buckets in the histogram
    *   ranges -  an array with calculated bucket sizes
    *   sum - sum of the bucket contents
    *   static - true for histograms defined in TelemetryHistograms.h, false for ones defined with newHistogram
    */
   [implicit_jscontext]
   readonly attribute jsval histogramSnapshots;
 
@@ -138,17 +141,17 @@ interface nsITelemetry : nsISupports
   /** 
    * Create and return a histogram.  Parameters:
    *
    * @param name Unique histogram name
    * @param expiration Expiration version
    * @param min - Minimal bucket size
    * @param max - Maximum bucket size
    * @param bucket_count - number of buckets in the histogram.
-   * @param type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR or HISTOGRAM_BOOLEAN
+   * @param type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, HISTOGRAM_BOOLEAN or HISTOGRAM_COUNT
    * The returned object has the following functions:
    *   add(int) - Adds an int value to the appropriate bucket
    *   snapshot() - Returns a snapshot of the histogram with the same data fields as in histogramSnapshots()
    *   clear() - Zeros out the histogram's buckets and sum
    */
   [implicit_jscontext]
   jsval newHistogram(in ACString name, in ACString expiration, in uint32_t min, in uint32_t max, in uint32_t bucket_count, in unsigned long histogram_type);
 
@@ -187,18 +190,18 @@ interface nsITelemetry : nsISupports
    * Register a histogram for an addon.  Throws an error if the
    * histogram name has been registered previously.
    *
    * @param addon_id - Unique ID of the addon
    * @param name - Unique histogram name
    * @param min - Minimal bucket size
    * @param max - Maximum bucket size
    * @param bucket_count - number of buckets in the histogram
-   * @param histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, or
-   *        HISTOGRAM_BOOLEAN
+   * @param histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR,
+   *        HISTOGRAM_BOOLEAN or HISTOGRAM_COUNT
    */
   void registerAddonHistogram(in ACString addon_id, in ACString name,
                               in uint32_t min, in uint32_t max,
                               in uint32_t bucket_count,
                               in unsigned long histogram_type);
 
   /**
    * Return a histogram previously registered via
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 // copied from toolkit/mozapps/extensions/test/xpcshell/head_addons.js
 const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
 const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
+let gAppInfo;
 
 function createAppInfo(id, name, version, platformVersion) {
   gAppInfo = {
     // nsIXULAppInfo
     vendor: "Mozilla",
     name: name,
     ID: id,
     version: version,
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
@@ -85,18 +85,20 @@ function registerPingHandler(handler) {
 }
 
 function setupTestData() {
   Telemetry.newHistogram(IGNORE_HISTOGRAM, "never", 1, 2, 3, Telemetry.HISTOGRAM_BOOLEAN);
   Telemetry.histogramFrom(IGNORE_CLONED_HISTOGRAM, IGNORE_HISTOGRAM_TO_CLONE);
   Services.startup.interrupted = true;
   Telemetry.registerAddonHistogram(ADDON_NAME, ADDON_HISTOGRAM, 1, 5, 6,
                                    Telemetry.HISTOGRAM_LINEAR);
-  h1 = Telemetry.getAddonHistogram(ADDON_NAME, ADDON_HISTOGRAM);
+  let h1 = Telemetry.getAddonHistogram(ADDON_NAME, ADDON_HISTOGRAM);
   h1.add(1);
+  let h2 = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT");
+  h2.add();
 }
 
 function getSavedHistogramsFile(basename) {
   let tmpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
   let histogramsFile = tmpDir.clone();
   histogramsFile.append(basename);
   if (histogramsFile.exists()) {
     histogramsFile.remove(true);
@@ -221,19 +223,24 @@ function checkPayload(request, reason, s
   if (isWindows) {
     do_check_true(payload.simpleMeasurements.startupSessionRestoreReadBytes > 0);
     do_check_true(payload.simpleMeasurements.startupSessionRestoreWriteBytes > 0);
   }
 
   const TELEMETRY_PING = "TELEMETRY_PING";
   const TELEMETRY_SUCCESS = "TELEMETRY_SUCCESS";
   const TELEMETRY_TEST_FLAG = "TELEMETRY_TEST_FLAG";
+  const TELEMETRY_TEST_COUNT = "TELEMETRY_TEST_COUNT";
   const READ_SAVED_PING_SUCCESS = "READ_SAVED_PING_SUCCESS";
+
   do_check_true(TELEMETRY_PING in payload.histograms);
   do_check_true(READ_SAVED_PING_SUCCESS in payload.histograms);
+  do_check_true(TELEMETRY_TEST_FLAG in payload.histograms);
+  do_check_true(TELEMETRY_TEST_COUNT in payload.histograms);
+
   let rh = Telemetry.registeredHistograms([]);
   for (let name of rh) {
     if (/SQLITE/.test(name) && name in payload.histograms) {
       do_check_true(("STARTUP_" + name) in payload.histograms); 
     }
   }
   do_check_false(IGNORE_HISTOGRAM in payload.histograms);
   do_check_false(IGNORE_CLONED_HISTOGRAM in payload.histograms);
@@ -246,16 +253,29 @@ function checkPayload(request, reason, s
     values: {0:1, 1:0},
     sum: 0,
     sum_squares_lo: 0,
     sum_squares_hi: 0
   };
   let flag = payload.histograms[TELEMETRY_TEST_FLAG];
   do_check_eq(uneval(flag), uneval(expected_flag));
 
+  // We should have a test count.
+  const expected_count = {
+    range: [1, 2],
+    bucket_count: 3,
+    histogram_type: 4,
+    values: {0:1, 1:0},
+    sum: 1,
+    sum_squares_lo: 1,
+    sum_squares_hi: 0,
+  };
+  let count = payload.histograms[TELEMETRY_TEST_COUNT];
+  do_check_eq(uneval(count), uneval(expected_count));
+
   // There should be one successful report from the previous telemetry ping.
   const expected_tc = {
     range: [1, 2],
     bucket_count: 3,
     histogram_type: 2,
     values: {0:1, 1:successfulPings, 2:0},
     sum: successfulPings,
     sum_squares_lo: successfulPings,
--- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
+++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
@@ -167,16 +167,33 @@ function test_flag_histogram()
   h.add(3);
   var c3 = h.snapshot().counts;
   var s3 = h.snapshot().sum;
   do_check_eq(uneval(c3), uneval([0, 1, 0]));
   do_check_eq(s3, 1);
   do_check_eq(h.snapshot().histogram_type, Telemetry.FLAG_HISTOGRAM);
 }
 
+function test_count_histogram()
+{
+  let h = Telemetry.newHistogram("test::count histogram", "never", 1, 2, 3, Telemetry.HISTOGRAM_COUNT);
+  let s = h.snapshot();
+  do_check_eq(uneval(s.ranges), uneval([0, 1, 2]));
+  do_check_eq(uneval(s.counts), uneval([0, 0, 0]));
+  do_check_eq(s.sum, 0);
+  h.add();
+  s = h.snapshot();
+  do_check_eq(uneval(s.counts), uneval([1, 0, 0]));
+  do_check_eq(s.sum, 1);
+  h.add();
+  s = h.snapshot();
+  do_check_eq(uneval(s.counts), uneval([2, 0, 0]));
+  do_check_eq(s.sum, 2);
+}
+
 function test_getHistogramById() {
   try {
     Telemetry.getHistogramById("nonexistent");
     do_throw("This can't happen");
   } catch (e) {
     
   }
   var h = Telemetry.getHistogramById("CYCLE_COLLECTOR");
@@ -212,31 +229,37 @@ function compareHistograms(h1, h2) {
 }
 
 function test_histogramFrom() {
   // Test one histogram of each type.
   let names = [
       "CYCLE_COLLECTOR",      // EXPONENTIAL
       "GC_REASON_2",          // LINEAR
       "GC_RESET",             // BOOLEAN
-      "TELEMETRY_TEST_FLAG"   // FLAG
+      "TELEMETRY_TEST_FLAG",  // FLAG
+      "TELEMETRY_TEST_COUNT", // COUNT
   ];
 
   for each (let name in names) {
     let [min, max, bucket_count] = [1, INT_MAX - 1, 10]
     let original = Telemetry.getHistogramById(name);
     let clone = Telemetry.histogramFrom("clone" + name, name);
     compareHistograms(original, clone);
   }
 
-  // Additionally, set the flag on TELEMETRY_TEST_FLAG, and check it gets set on the clone.
+  // Additionally, set TELEMETRY_TEST_FLAG and TELEMETRY_TEST_COUNT
+  // and check they get set on the clone.
   let testFlag = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
   testFlag.add(1);
+  let testCount = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT");
+  testCount.add();
   let clone = Telemetry.histogramFrom("FlagClone", "TELEMETRY_TEST_FLAG");
   compareHistograms(testFlag, clone);
+  clone = Telemetry.histogramFrom("CountClone", "TELEMETRY_TEST_COUNT");
+  compareHistograms(testCount, clone);
 }
 
 function test_getSlowSQL() {
   var slow = Telemetry.slowSQL;
   do_check_true(("mainThread" in slow) && ("otherThreads" in slow));
 }
 
 function test_addons() {
@@ -373,16 +396,17 @@ function run_test()
   }
 
   // Instantiate the storage for this histogram and make sure it doesn't
   // get reflected into JS, as it has no interesting data in it.
   let h = Telemetry.getHistogramById("NEWTAB_PAGE_PINNED_SITES_COUNT");
   do_check_false("NEWTAB_PAGE_PINNED_SITES_COUNT" in Telemetry.histogramSnapshots);
 
   test_boolean_histogram();
+  test_count_histogram();
   test_getHistogramById();
   test_histogramFrom();
   test_getSlowSQL();
   test_privateMode();
   test_addons();
   test_extended_stats();
   test_expired_histogram();
 }