Bug 1120369 - Allow specifying the desired dataset in Histograms.json. r=vladan
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Thu, 29 Jan 2015 15:17:24 +0100
changeset 240398 72940b27aeaa3c5284252eda375ccfd32ce3b644
parent 240397 231a8c61b49f3fb528fff76b4c4bb18b14cae473
child 240399 3e710889d4e86937c9bb8a6d5c130fa700241d48
push id535
push usercmanchester@mozilla.com
push dateMon, 02 Feb 2015 19:04:18 +0000
reviewersvladan
bugs1120369
milestone38.0a1
Bug 1120369 - Allow specifying the desired dataset in Histograms.json. r=vladan
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetrySession.jsm
toolkit/components/telemetry/gen-histogram-data.py
toolkit/components/telemetry/histogram_tools.py
toolkit/components/telemetry/nsITelemetry.idl
toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4331,16 +4331,42 @@
     "description": "a testing histogram; not meant to be touched"
   },
   "TELEMETRY_TEST_KEYED_COUNT": {
     "expires_in_version": "default",
     "kind": "count",
     "keyed": true,
     "description": "a testing histogram; not meant to be touched"
   },
+  "TELEMETRY_TEST_RELEASE_OPTOUT": {
+    "expires_in_version": "default",
+    "kind": "flag",
+    "releaseChannelCollection": "opt-out",
+    "description": "a testing histogram; not meant to be touched"
+  },
+  "TELEMETRY_TEST_RELEASE_OPTIN": {
+    "expires_in_version": "default",
+    "kind": "flag",
+    "releaseChannelCollection": "opt-in",
+    "description": "a testing histogram; not meant to be touched"
+  },
+  "TELEMETRY_TEST_KEYED_RELEASE_OPTIN": {
+    "expires_in_version": "default",
+    "kind": "flag",
+    "keyed": true,
+    "releaseChannelCollection": "opt-in",
+    "description": "a testing histogram; not meant to be touched"
+  },
+  "TELEMETRY_TEST_KEYED_RELEASE_OPTOUT": {
+    "expires_in_version": "default",
+    "kind": "flag",
+    "keyed": true,
+    "releaseChannelCollection": "opt-out",
+    "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
@@ -621,194 +621,17 @@ ClearIOReporting()
   if (!sTelemetryIOObserver) {
     return;
   }
   IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
                            sTelemetryIOObserver);
   sTelemetryIOObserver = nullptr;
 }
 
-class KeyedHistogram {
-public:
-  KeyedHistogram(const nsACString &name, const nsACString &expiration,
-                 uint32_t histogramType, uint32_t min, uint32_t max,
-                 uint32_t bucketCount);
-  nsresult GetHistogram(const nsCString& name, Histogram** histogram);
-  Histogram* GetHistogram(const nsCString& name);
-  uint32_t GetHistogramType() const { return mHistogramType; }
-  nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
-  nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj);
-  void Clear();
-
-private:
-  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
-  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
-  KeyedHistogramMapType mHistogramMap;
-
-  struct ReflectKeysArgs {
-    JSContext* jsContext;
-    JS::AutoValueVector* vector;
-  };
-  static PLDHashOperator ReflectKeys(KeyedHistogramEntry* entry, void* arg);
-
-  static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry,
-                                    JSContext* cx,
-                                    JS::Handle<JSObject*> obj);
-
-  static PLDHashOperator ClearHistogramEnumerator(KeyedHistogramEntry*, void*);
-
-  const nsCString mName;
-  const nsCString mExpiration;
-  const uint32_t mHistogramType;
-  const uint32_t mMin;
-  const uint32_t mMax;
-  const uint32_t mBucketCount;
-};
-
-KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expiration,
-                               uint32_t histogramType, uint32_t min, uint32_t max,
-                               uint32_t bucketCount)
-  : mHistogramMap()
-  , mName(name)
-  , mExpiration(expiration)
-  , mHistogramType(histogramType)
-  , mMin(min)
-  , mMax(max)
-  , mBucketCount(bucketCount)
-{
-}
-
-nsresult
-KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram)
-{
-  KeyedHistogramEntry* entry = mHistogramMap.GetEntry(key);
-  if (entry) {
-    *histogram = entry->mData;
-    return NS_OK;
-  }
-
-  nsCString histogramName = mName;
-  histogramName.Append(KEYED_HISTOGRAM_NAME_SEPARATOR);
-  histogramName.Append(key);
-
-  Histogram* h;
-  nsresult rv = HistogramGet(histogramName.get(), mExpiration.get(),
-                             mHistogramType, mMin, mMax, mBucketCount,
-                             true, &h);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  h->SetFlags(Histogram::kExtendedStatisticsFlag);
-  *histogram = h;
-
-  entry = mHistogramMap.PutEntry(key);
-  if (MOZ_UNLIKELY(!entry)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  entry->mData = h;
-  return NS_OK;
-}
-
-Histogram*
-KeyedHistogram::GetHistogram(const nsCString& key)
-{
-  Histogram* h = nullptr;
-  if (NS_FAILED(GetHistogram(key, &h))) {
-    return nullptr;
-  }
-  return h;
-}
-
-/* static */
-PLDHashOperator
-KeyedHistogram::ClearHistogramEnumerator(KeyedHistogramEntry* entry, void*)
-{
-  entry->mData->Clear();
-  return PL_DHASH_NEXT;
-}
-
-void
-KeyedHistogram::Clear()
-{
-  mHistogramMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr);
-  mHistogramMap.Clear();
-}
-
-/* static */
-PLDHashOperator
-KeyedHistogram::ReflectKeys(KeyedHistogramEntry* entry, void* arg)
-{
-  ReflectKeysArgs* args = static_cast<ReflectKeysArgs*>(arg);
-
-  JS::RootedValue jsKey(args->jsContext);
-  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
-  jsKey.setString(JS_NewUCStringCopyN(args->jsContext, key.Data(), key.Length()));
-  args->vector->append(jsKey);
-
-  return PL_DHASH_NEXT;
-}
-
-nsresult
-KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
-{
-  JS::AutoValueVector keys(cx);
-  if (!keys.reserve(mHistogramMap.Count())) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  ReflectKeysArgs reflectArgs = { cx, &keys };
-  const uint32_t num = mHistogramMap.EnumerateEntries(&KeyedHistogram::ReflectKeys,
-                                                      static_cast<void*>(&reflectArgs));
-  if (num != mHistogramMap.Count()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys));
-  if (!jsKeys) {
-    return NS_ERROR_FAILURE;
-  }
-
-  args.rval().setObject(*jsKeys);
-  return NS_OK;
-}
-
-/* static */
-bool
-KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx, JS::Handle<JSObject*> obj)
-{
-  JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx));
-  if (!histogramSnapshot) {
-    return false;
-  }
-
-  if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) {
-    return false;
-  }
-
-  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
-  if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
-                           histogramSnapshot, JSPROP_ENUMERATE)) {
-    return false;
-  }
-
-  return true;
-}
-
-nsresult
-KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj)
-{
-  if (!mHistogramMap.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
+class KeyedHistogram;
 
 class TelemetryImpl MOZ_FINAL
   : public nsITelemetry
   , public nsIMemoryReporter
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITELEMETRY
   NS_DECL_NSIMEMORYREPORTER
@@ -935,27 +758,66 @@ TelemetryImpl::CollectReports(nsIHandleR
                               nsISupports* aData, bool aAnonymize)
 {
   return MOZ_COLLECT_REPORT(
     "explicit/telemetry", KIND_HEAP, UNITS_BYTES,
     SizeOfIncludingThis(TelemetryMallocSizeOf),
     "Memory used by the telemetry system.");
 }
 
+class KeyedHistogram {
+public:
+  KeyedHistogram(const nsACString &name, const nsACString &expiration,
+                 uint32_t histogramType, uint32_t min, uint32_t max,
+                 uint32_t bucketCount);
+  nsresult GetHistogram(const nsCString& name, Histogram** histogram);
+  Histogram* GetHistogram(const nsCString& name);
+  uint32_t GetHistogramType() const { return mHistogramType; }
+  nsresult GetDataset(uint32_t* dataset) const;
+  nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
+  nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj);
+  void Clear();
+
+private:
+  typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
+  typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
+  KeyedHistogramMapType mHistogramMap;
+
+  struct ReflectKeysArgs {
+    JSContext* jsContext;
+    JS::AutoValueVector* vector;
+  };
+  static PLDHashOperator ReflectKeys(KeyedHistogramEntry* entry, void* arg);
+
+  static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry,
+                                    JSContext* cx,
+                                    JS::Handle<JSObject*> obj);
+
+  static PLDHashOperator ClearHistogramEnumerator(KeyedHistogramEntry*, void*);
+
+  const nsCString mName;
+  const nsCString mExpiration;
+  const uint32_t mHistogramType;
+  const uint32_t mMin;
+  const uint32_t mMax;
+  const uint32_t mBucketCount;
+};
+
 // A initializer to initialize histogram collection
 StatisticsRecorder gStatisticsRecorder;
 
 // Hardcoded probes
 struct TelemetryHistogram {
   uint32_t min;
   uint32_t max;
   uint32_t bucketCount;
   uint32_t histogramType;
   uint32_t id_offset;
   uint32_t expiration_offset;
+  uint32_t dataset;
   bool extendedStatisticsOK;
   bool keyed;
 
   const char *id() const;
   const char *expiration() const;
 };
 
 #include "TelemetryHistogramData.inc"
@@ -987,16 +849,33 @@ IsExpired(const Histogram *histogram){
 }
 
 bool
 IsValidHistogramName(const nsACString& name)
 {
   return !FindInReadable(nsCString(KEYED_HISTOGRAM_NAME_SEPARATOR), name);
 }
 
+bool
+IsInDataset(const TelemetryHistogram& h, uint32_t dataset)
+{
+  if (h.dataset == dataset) {
+    return true;
+  }
+
+  // The "optin on release channel" dataset is a superset of the
+  // "optout on release channel one".
+  if (dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN
+      && h.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) {
+    return true;
+  }
+
+  return false;
+}
+
 nsresult
 CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max,
                         uint32_t bucketCount, bool haveOptArgs)
 {
   if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
       && histogramType != nsITelemetry::HISTOGRAM_FLAG
       && histogramType != nsITelemetry::HISTOGRAM_COUNT) {
     // The min, max & bucketCount arguments are not optional for this type.
@@ -1267,30 +1146,51 @@ JSHistogram_Clear(JSContext *cx, unsigne
     return false;
   }
 
   Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
   h->Clear();
   return true;
 }
 
+bool
+JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
+  Telemetry::ID id;
+  nsresult rv = TelemetryImpl::GetHistogramEnumId(h->histogram_name().c_str(), &id);
+  if (NS_SUCCEEDED(rv)) {
+    args.rval().setNumber(gHistograms[id].dataset);
+    return true;
+  }
+
+  return false;
+}
+
 nsresult
 WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
   static const JSClass JSHistogram_class = {
     "JSHistogram",  /* name */
     JSCLASS_HAS_PRIVATE  /* flags */
   };
 
   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
   if (!obj)
     return NS_ERROR_FAILURE;
   if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0)
         && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0)
-        && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0))) {
+        && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset", JSHistogram_Dataset, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
   JS_SetPrivate(obj, h);
   ret.setObject(*obj);
   return NS_OK;
 }
 
 bool
@@ -1443,31 +1343,56 @@ JSKeyedHistogram_Clear(JSContext *cx, un
   if (!keyed) {
     return false;
   }
 
   keyed->Clear();
   return true;
 }
 
+bool
+JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JSObject *obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    return false;
+  }
+
+  KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
+  if (!keyed) {
+    return false;
+  }
+
+  uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
+  nsresult rv = keyed->GetDataset(&dataset);;
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  args.rval().setNumber(dataset);
+  return true;
+}
+
 nsresult
 WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
   static const JSClass JSHistogram_class = {
     "JSKeyedHistogram",  /* name */
     JSCLASS_HAS_PRIVATE  /* flags */
   };
 
   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &JSHistogram_class));
   if (!obj)
     return NS_ERROR_FAILURE;
   if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0)
         && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0)
         && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0)
-        && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0))) {
+        && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0)
+        && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) {
     return NS_ERROR_FAILURE;
   }
 
   JS_SetPrivate(obj, h);
   ret.setObject(*obj);
   return NS_OK;
 }
 
@@ -2882,23 +2807,25 @@ TelemetryImpl::GetLateWrites(JSContext *
     return NS_ERROR_FAILURE;
   }
 
   ret.setObject(*report);
   return NS_OK;
 }
 
 nsresult
-GetRegisteredHistogramIds(bool keyed, uint32_t *aCount, char*** aHistograms)
+GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount,
+                          char*** aHistograms)
 {
   nsTArray<char*> collection;
 
   for (size_t i = 0; i < ArrayLength(gHistograms); ++i) {
     const TelemetryHistogram& h = gHistograms[i];
-    if (IsExpired(h.expiration()) || h.keyed != keyed) {
+    if (IsExpired(h.expiration()) || h.keyed != keyed ||
+        !IsInDataset(h, dataset)) {
       continue;
     }
 
     const char* id = h.id();
     const size_t len = strlen(id);
     collection.AppendElement(static_cast<char*>(nsMemory::Clone(id, len+1)));
   }
 
@@ -2907,25 +2834,27 @@ GetRegisteredHistogramIds(bool keyed, ui
   memcpy(histograms, collection.Elements(), bytes);
   *aHistograms = histograms;
   *aCount = collection.Length();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TelemetryImpl::RegisteredHistograms(uint32_t *aCount, char*** aHistograms)
+TelemetryImpl::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
+                                    char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(false, aCount, aHistograms);
+  return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms);
 }
 
 NS_IMETHODIMP
-TelemetryImpl::RegisteredKeyedHistograms(uint32_t *aCount, char*** aHistograms)
+TelemetryImpl::RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount,
+                                         char*** aHistograms)
 {
-  return GetRegisteredHistogramIds(true, aCount, aHistograms);
+  return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx,
                                 JS::MutableHandle<JS::Value> ret)
 {
   Histogram *h;
   nsresult rv = GetHistogramByName(name, &h);
@@ -3831,8 +3760,165 @@ NSMODULE_DEFN(nsTelemetryModule) = &kTel
  * The XRE_TelemetryAdd function is to be used by embedding applications
  * that can't use mozilla::Telemetry::Accumulate() directly.
  */
 void
 XRE_TelemetryAccumulate(int aID, uint32_t aSample)
 {
   mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample);
 }
+
+KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expiration,
+                               uint32_t histogramType, uint32_t min, uint32_t max,
+                               uint32_t bucketCount)
+  : mHistogramMap()
+  , mName(name)
+  , mExpiration(expiration)
+  , mHistogramType(histogramType)
+  , mMin(min)
+  , mMax(max)
+  , mBucketCount(bucketCount)
+{
+}
+
+nsresult
+KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram)
+{
+  KeyedHistogramEntry* entry = mHistogramMap.GetEntry(key);
+  if (entry) {
+    *histogram = entry->mData;
+    return NS_OK;
+  }
+
+  nsCString histogramName = mName;
+  histogramName.Append(KEYED_HISTOGRAM_NAME_SEPARATOR);
+  histogramName.Append(key);
+
+  Histogram* h;
+  nsresult rv = HistogramGet(histogramName.get(), mExpiration.get(),
+                             mHistogramType, mMin, mMax, mBucketCount,
+                             true, &h);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  h->SetFlags(Histogram::kExtendedStatisticsFlag);
+  *histogram = h;
+
+  entry = mHistogramMap.PutEntry(key);
+  if (MOZ_UNLIKELY(!entry)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  entry->mData = h;
+  return NS_OK;
+}
+
+Histogram*
+KeyedHistogram::GetHistogram(const nsCString& key)
+{
+  Histogram* h = nullptr;
+  if (NS_FAILED(GetHistogram(key, &h))) {
+    return nullptr;
+  }
+  return h;
+}
+
+nsresult
+KeyedHistogram::GetDataset(uint32_t* dataset) const
+{
+  MOZ_ASSERT(dataset);
+
+  Telemetry::ID id;
+  nsresult rv = TelemetryImpl::GetHistogramEnumId(mName.get(), &id);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  *dataset = gHistograms[id].dataset;
+  return NS_OK;
+}
+
+/* static */
+PLDHashOperator
+KeyedHistogram::ClearHistogramEnumerator(KeyedHistogramEntry* entry, void*)
+{
+  entry->mData->Clear();
+  return PL_DHASH_NEXT;
+}
+
+void
+KeyedHistogram::Clear()
+{
+  mHistogramMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr);
+  mHistogramMap.Clear();
+}
+
+/* static */
+PLDHashOperator
+KeyedHistogram::ReflectKeys(KeyedHistogramEntry* entry, void* arg)
+{
+  ReflectKeysArgs* args = static_cast<ReflectKeysArgs*>(arg);
+
+  JS::RootedValue jsKey(args->jsContext);
+  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
+  jsKey.setString(JS_NewUCStringCopyN(args->jsContext, key.Data(), key.Length()));
+  args->vector->append(jsKey);
+
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
+{
+  JS::AutoValueVector keys(cx);
+  if (!keys.reserve(mHistogramMap.Count())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  ReflectKeysArgs reflectArgs = { cx, &keys };
+  const uint32_t num = mHistogramMap.EnumerateEntries(&KeyedHistogram::ReflectKeys,
+                                                      static_cast<void*>(&reflectArgs));
+  if (num != mHistogramMap.Count()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys));
+  if (!jsKeys) {
+    return NS_ERROR_FAILURE;
+  }
+
+  args.rval().setObject(*jsKeys);
+  return NS_OK;
+}
+
+/* static */
+bool
+KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx, JS::Handle<JSObject*> obj)
+{
+  JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx));
+  if (!histogramSnapshot) {
+    return false;
+  }
+
+  if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) {
+    return false;
+  }
+
+  const NS_ConvertUTF8toUTF16 key(entry->GetKey());
+  if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
+                           histogramSnapshot, JSPROP_ENUMERATE)) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj)
+{
+  if (!mHistogramMap.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -455,17 +455,17 @@ let Impl = {
     if (last && last < c.length)
       retgram.values[r[last]] = 0;
     return retgram;
   },
 
   getHistograms: function getHistograms(hls) {
     this._log.trace("getHistograms");
 
-    let registered = Telemetry.registeredHistograms([]);
+    let registered = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_EXTENDED, []);
     let ret = {};
 
     for (let name of registered) {
       for (let n of [name, "STARTUP_" + name]) {
         if (n in hls) {
           ret[n] = this.packHistogram(hls[n]);
         }
       }
@@ -491,17 +491,17 @@ let Impl = {
     }
 
     return ret;
   },
 
   getKeyedHistograms: function() {
     this._log.trace("getKeyedHistograms");
 
-    let registered = Telemetry.registeredKeyedHistograms([]);
+    let registered = Telemetry.registeredKeyedHistograms(Ci.nsITelemetry.DATASET_EXTENDED, []);
     let ret = {};
 
     for (let id of registered) {
       ret[id] = {};
       let keyed = Telemetry.getKeyedHistogramById(id);
       let snapshot = keyed.snapshot();
       for (let key of Object.keys(snapshot)) {
         ret[id][key] = this.packHistogram(snapshot[key]);
@@ -766,17 +766,17 @@ let Impl = {
   },
 
   /**
    * Make a copy of interesting histograms at startup.
    */
   gatherStartupHistograms: function gatherStartupHistograms() {
     this._log.trace("gatherStartupHistograms");
 
-    let info = Telemetry.registeredHistograms([]);
+    let info = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_EXTENDED, []);
     let snapshots = Telemetry.histogramSnapshots;
     for (let name of info) {
       // Only duplicate histograms with actual data.
       if (this.isInterestingStartupHistogram(name) && name in snapshots) {
         Telemetry.histogramFrom("STARTUP_" + name, name);
       }
     }
   },
--- a/toolkit/components/telemetry/gen-histogram-data.py
+++ b/toolkit/components/telemetry/gen-histogram-data.py
@@ -55,20 +55,20 @@ class StringTable:
                 f.write("  /* %5d */ '\\0',\n" % offset)
         f.write("  /* %5d */ %s, '\\0' };\n\n"
                 % (entries[-1][1], explodeToCharArray(entries[-1][0])))
 
 def print_array_entry(histogram, name_index, exp_index):
     cpp_guard = histogram.cpp_guard()
     if cpp_guard:
         print "#if defined(%s)" % cpp_guard
-    print "  { %s, %s, %s, %s, %d, %d, %s, %s }," \
+    print "  { %s, %s, %s, %s, %d, %d, %s, %s, %s }," \
         % (histogram.low(), histogram.high(),
            histogram.n_buckets(), histogram.nsITelemetry_kind(),
-           name_index, exp_index,
+           name_index, exp_index, histogram.dataset(),
            "true" if histogram.extended_statistics_ok() else "false",
            "true" if histogram.keyed() else "false")
     if cpp_guard:
         print "#endif"
 
 def write_histogram_table(histograms):
     table = StringTable()
 
--- a/toolkit/components/telemetry/histogram_tools.py
+++ b/toolkit/components/telemetry/histogram_tools.py
@@ -50,17 +50,18 @@ def exponential_buckets(dmin, dmax, n_bu
         next_value = int(math.floor(math.exp(log_next) + 0.5))
         if next_value > current:
             current = next_value
         else:
             current = current + 1
         ret_array[bucket_index] = current
     return ret_array
 
-always_allowed_keys = ['kind', 'description', 'cpp_guard', 'expires_in_version', "alert_emails", 'keyed']
+always_allowed_keys = ['kind', 'description', 'cpp_guard', 'expires_in_version',
+                       'alert_emails', 'keyed', 'releaseChannelCollection']
 
 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:
 
@@ -83,16 +84,22 @@ symbol that should guard C/C++ definitio
         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))
+        datasets = { 'opt-in': 'DATASET_RELEASE_CHANNEL_OPTIN',
+                     'opt-out': 'DATASET_RELEASE_CHANNEL_OPTOUT' }
+        value = definition.get('releaseChannelCollection', 'opt-in')
+        if not value in datasets:
+            raise DefinitionException, "unknown release channel collection policy for " + name
+        self._dataset = "nsITelemetry::" + datasets[value]
 
     def name(self):
         """Return the name of the histogram."""
         return self._name
 
     def description(self):
         """Return the description of the histogram."""
         return self._description
@@ -130,16 +137,20 @@ the histogram."""
         """Return the preprocessor symbol that should guard C/C++ definitions
 associated with the histogram.  Returns None if no guarding is necessary."""
         return self._cpp_guard
 
     def keyed(self):
         """Returns True if this a keyed histogram, false otherwise."""
         return self._keyed
 
+    def dataset(self):
+        """Returns the dataset this histogram belongs into."""
+        return self._dataset
+
     def extended_statistics_ok(self):
         """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,
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -24,16 +24,26 @@ interface nsITelemetry : nsISupports
    * 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;
 
+  /**
+   * Dataset types:
+   * DATASET_RELEASE_CHANNEL_OPTOUT - the basic dataset that is on-by-default on all channels
+   * DATASET_RELEASE_CHANNEL_OPTIN - the extended dataset that is opt-in on release,
+   *                                 opt-out on pre-release channels.
+   */
+  const unsigned long DATASET_RELEASE_CHANNEL_OPTOUT = 0;
+  const unsigned long DATASET_RELEASE_CHANNEL_OPTIN = 1;
+
+
   /*
    * 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, HISTOGRAM_BOOLEAN
    *                    or HISTOGRAM_COUNT
@@ -129,33 +139,37 @@ interface nsITelemetry : nsISupports
    * symbolication server.
    */
   [implicit_jscontext]
   readonly attribute jsval lateWrites;
 
   /**
    * Returns an array whose values are the names of histograms defined
    * in Histograms.json.
+   *
+   * @param dataset - DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN
    */
-  void registeredHistograms(out uint32_t count,
+  void registeredHistograms(in uint32_t dataset,
+                            out uint32_t count,
                             [retval, array, size_is(count)] out string histograms);
 
   /**
    * Create and return a histogram.  Parameters:
    *
    * @param name Unique histogram name
    * @param expiration Expiration version
    * @param type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR or HISTOGRAM_BOOLEAN
    * @param min - Minimal bucket size
    * @param max - Maximum bucket size
    * @param bucket_count - number of buckets in the histogram.
    * 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
+   *   dataset() - identifies what dataset this is in: DATASET_RELEASE_CHANNEL_OPTOUT or ...OPTIN
    */
   [implicit_jscontext, optional_argc]
   jsval newHistogram(in ACString name, in ACString expiration,
                      in unsigned long histogram_type,
                      [optional] in uint32_t min,
                      [optional] in uint32_t max,
                      [optional] in uint32_t bucket_count);
 
@@ -195,30 +209,33 @@ interface nsITelemetry : nsISupports
    * @param min - Minimal bucket size
    * @param max - Maximum bucket size
    * @param bucket_count - number of buckets in the histogram.
    * The returned object has the following functions:
    *   add(string key, [optional] int) - Add an int value to the histogram for that key. If no histogram for that key exists yet, it is created.
    *   snapshot([optional] string key) - If key is provided, returns a snapshot for the histogram with that key or null. If key is not provided, returns the snapshots of all the registered keys in the form {key1: snapshot1, key2: snapshot2, ...}.
    *   keys() - Returns an array with the string keys of the currently registered histograms
    *   clear() - Clears the registered histograms from this.
+   *   dataset() - identifies what dataset this is in: DATASET_RELEASE_CHANNEL_OPTOUT or ...OPTIN
    */
   [implicit_jscontext, optional_argc]
   jsval newKeyedHistogram(in ACString name,
                           in ACString expiration,
                           in unsigned long histogram_type,
                           [optional] in uint32_t min,
                           [optional] in uint32_t max,
                           [optional] in uint32_t bucket_count);
 
   /**
    * Returns an array whose values are the names of histograms defined
    * in Histograms.json.
+   *
+   * @param dataset - DATASET_RELEASE_CHANNEL_OPTOUT or ...OPTIN
    */
-  void registeredKeyedHistograms(out uint32_t count,
+  void registeredKeyedHistograms(in uint32_t dataset, out uint32_t count,
                                  [retval, array, size_is(count)] out string histograms);
 
   /**
    * Same as newKeyedHistogram above, but for histograms registered in TelemetryHistograms.h.
    *
    * @param id - unique identifier from TelemetryHistograms.h
    * The returned object has the same functions as a histogram returned from newKeyedHistogram.
    */
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
@@ -254,17 +254,17 @@ function checkPayload(request, reason, s
   const TELEMETRY_TEST_KEYED_COUNT = "TELEMETRY_TEST_KEYED_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([]);
+  let rh = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []);
   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);
 
--- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
+++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js
@@ -10,17 +10,18 @@ const Telemetry = Cc["@mozilla.org/base/
 Cu.import("resource://gre/modules/Services.jsm", this);
 
 function test_expired_histogram() {
   var histogram_id = "FOOBAR";
   var test_expired_id = "TELEMETRY_TEST_EXPIRED";
   var clone_id = "ExpiredClone";
   var dummy = Telemetry.newHistogram(histogram_id, "28.0a1", Telemetry.HISTOGRAM_EXPONENTIAL, 1, 2, 3);
   var dummy_clone = Telemetry.histogramFrom(clone_id, test_expired_id);
-  var rh = Telemetry.registeredHistograms([]);
+  var rh = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []);
+  Assert.ok(!!rh);
 
   dummy.add(1);
   dummy_clone.add(1);
 
   do_check_eq(Telemetry.histogramSnapshots["__expired__"], undefined);
   do_check_eq(Telemetry.histogramSnapshots[histogram_id], undefined);
   do_check_eq(Telemetry.histogramSnapshots[test_expired_id], undefined);
   do_check_eq(Telemetry.histogramSnapshots[clone_id], undefined);
@@ -553,16 +554,62 @@ function test_keyed_histogram() {
 
   // Check specific keyed histogram types working properly.
 
   test_keyed_boolean_histogram();
   test_keyed_count_histogram();
   test_keyed_flag_histogram();
 }
 
+function test_datasets()
+{
+  // Check that datasets work as expected.
+
+  const RELEASE_CHANNEL_OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
+  const RELEASE_CHANNEL_OPTIN  = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+
+  // Histograms should default to the extended dataset
+  let h = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTIN);
+  h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_FLAG");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTIN);
+
+  // Check test histograms with explicit dataset definitions
+  h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTIN");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTIN);
+  h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTOUT");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTOUT);
+  h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTIN");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTIN);
+  h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT");
+  Assert.equal(h.dataset(), RELEASE_CHANNEL_OPTOUT);
+
+  // Check that registeredHistogram works properly
+  let registered = Telemetry.registeredHistograms(RELEASE_CHANNEL_OPTIN, []);
+  registered = new Set(registered);
+  Assert.ok(registered.has("TELEMETRY_TEST_FLAG"));
+  Assert.ok(registered.has("TELEMETRY_TEST_RELEASE_OPTIN"));
+  Assert.ok(registered.has("TELEMETRY_TEST_RELEASE_OPTOUT"));
+  registered = Telemetry.registeredHistograms(RELEASE_CHANNEL_OPTOUT, []);
+  registered = new Set(registered);
+  Assert.ok(!registered.has("TELEMETRY_TEST_FLAG"));
+  Assert.ok(!registered.has("TELEMETRY_TEST_RELEASE_OPTIN"));
+  Assert.ok(registered.has("TELEMETRY_TEST_RELEASE_OPTOUT"));
+
+  // Check that registeredKeyedHistograms works properly
+  registered = Telemetry.registeredKeyedHistograms(RELEASE_CHANNEL_OPTIN, []);
+  registered = new Set(registered);
+  Assert.ok(registered.has("TELEMETRY_TEST_KEYED_FLAG"));
+  Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"));
+  registered = Telemetry.registeredKeyedHistograms(RELEASE_CHANNEL_OPTOUT, []);
+  registered = new Set(registered);
+  Assert.ok(!registered.has("TELEMETRY_TEST_KEYED_FLAG"));
+  Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"));
+}
+
 function generateUUID() {
   let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
   // strip {}
   return str.substring(1, str.length - 1);
 }
 
 function run_test()
 {
@@ -587,9 +634,10 @@ function run_test()
   test_getHistogramById();
   test_histogramFrom();
   test_getSlowSQL();
   test_privateMode();
   test_addons();
   test_extended_stats();
   test_expired_histogram();
   test_keyed_histogram();
+  test_datasets();
 }