diff --git a/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js --- a/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js +++ b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js @@ -1,169 +1,168 @@ "use strict"; const { ContentTaskUtils } = ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", {}); const { TelemetryController } = ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", {}); const { TelemetryUtils } = ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", {}); const CONTENT_CREATED = "ipc:content-created"; async function waitForProcessesScalars(aProcesses, aKeyed, aAdditionalCondition = (data) => true) { await ContentTaskUtils.waitForCondition(() => { const scalars = aKeyed ? - Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN) : - Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + Services.telemetry.getSnapshotForKeyedScalars("main", false) : + Services.telemetry.getSnapshotForScalars("main", false); return aProcesses.every(p => Object.keys(scalars).includes(p)) && aAdditionalCondition(scalars); }); } add_task(async function test_setup() { // Make sure the newly spawned content processes will have extended Telemetry enabled. await SpecialPowers.pushPrefEnv({ set: [[TelemetryUtils.Preferences.OverridePreRelease, true]], }); // And take care of the already initialized one as well. let canRecordExtended = Services.telemetry.canRecordExtended; Services.telemetry.canRecordExtended = true; registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended); }); add_task(async function test_recording() { let currentPid = gBrowser.selectedBrowser.frameLoader.tabParent.osPid; // Register test scalars before spawning the content process: the scalar // definitions will propagate to it. Services.telemetry.registerScalars("telemetry.test.dynamic", { "pre_content_spawn": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: false, record_on_release: true, }, "pre_content_spawn_expiration": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: false, release_channel_collection: true, }, }); Services.telemetry.scalarSet("telemetry.test.dynamic.pre_content_spawn_expiration", 3); let processCreated = TestUtils.topicObserved(CONTENT_CREATED); await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank", forceNewProcess: true }, async function(browser) { // Make sure our new browser is in its own process. The processCreated // promise should have already resolved by this point. await processCreated; let newPid = browser.frameLoader.tabParent.osPid; ok(currentPid != newPid, "The new tab must spawn its own process"); // Register test scalars after spawning the content process: the scalar // definitions will propagate to it. // Also attempt to register again "pre_content_spawn_expiration" and set // it to expired. Services.telemetry.registerScalars("telemetry.test.dynamic", { "post_content_spawn": { kind: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN, keyed: false, release_channel_collection: false, }, "post_content_spawn_keyed": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: true, release_channel_collection: true, }, "pre_content_spawn_expiration": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: false, release_channel_collection: true, expired: true, }, }); // Accumulate from the content process into both dynamic scalars. await ContentTask.spawn(browser, {}, async function() { Services.telemetry.scalarAdd("telemetry.test.dynamic.pre_content_spawn_expiration", 1); Services.telemetry.scalarSet("telemetry.test.dynamic.pre_content_spawn", 3); Services.telemetry.scalarSet("telemetry.test.dynamic.post_content_spawn", true); Services.telemetry.keyedScalarSet("telemetry.test.dynamic.post_content_spawn_keyed", "testKey", 3); }); }); // Wait for the dynamic scalars to appear non-keyed snapshots. await waitForProcessesScalars(["dynamic"], false, scalars => { // Wait for the scalars set in the content process to be available. return "telemetry.test.dynamic.pre_content_spawn" in scalars.dynamic; }); // Verify the content of the snapshots. const scalars = - Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + Services.telemetry.getSnapshotForScalars("main", false); ok("dynamic" in scalars, "The scalars must contain the 'dynamic' process section"); ok("telemetry.test.dynamic.pre_content_spawn" in scalars.dynamic, "Dynamic scalars registered before a process spawns must be present."); is(scalars.dynamic["telemetry.test.dynamic.pre_content_spawn"], 3, "The dynamic scalar must contain the expected value."); is(scalars.dynamic["telemetry.test.dynamic.pre_content_spawn_expiration"], 3, "The dynamic scalar must not be updated after being expired."); ok("telemetry.test.dynamic.post_content_spawn" in scalars.dynamic, "Dynamic scalars registered after a process spawns must be present."); is(scalars.dynamic["telemetry.test.dynamic.post_content_spawn"], true, "The dynamic scalar must contain the expected value."); // Wait for the dynamic scalars to appear in the keyed snapshots. await waitForProcessesScalars(["dynamic"], true); - const keyedScalars = - Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars("main", false); ok("dynamic" in keyedScalars, "The keyed scalars must contain the 'dynamic' process section"); ok("telemetry.test.dynamic.post_content_spawn_keyed" in keyedScalars.dynamic, "Dynamic keyed scalars registered after a process spawns must be present."); is(keyedScalars.dynamic["telemetry.test.dynamic.post_content_spawn_keyed"].testKey, 3, "The dynamic keyed scalar must contain the expected value."); }); add_task(async function test_aggregation() { Services.telemetry.clearScalars(); // Register test scalars before spawning the content process: the scalar // definitions will propagate to it. Also cheat TelemetrySession to put // the test scalar in the payload by using "cheattest" instead of "test" in // the scalar category name. Services.telemetry.registerScalars("telemetry.cheattest.dynamic", { "test_aggregation": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: false, record_on_release: true, }, }); const SCALAR_FULL_NAME = "telemetry.cheattest.dynamic.test_aggregation"; Services.telemetry.scalarAdd(SCALAR_FULL_NAME, 1); await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank", forceNewProcess: true }, async function(browser) { // Accumulate from the content process into both dynamic scalars. await ContentTask.spawn(browser, SCALAR_FULL_NAME, async function(aName) { Services.telemetry.scalarAdd(aName, 3); }); }); // Wait for the dynamic scalars to appear. Since we're testing that children // and parent data get aggregated, we might need to wait a bit more: // TelemetryIPCAccumulator.cpp sends batches to the parent process every 2 seconds. await waitForProcessesScalars(["dynamic"], false, scalarData => { return "dynamic" in scalarData && SCALAR_FULL_NAME in scalarData.dynamic && scalarData.dynamic[SCALAR_FULL_NAME] == 4; }); // Check that the definitions made it to the ping payload. const pingData = TelemetryController.getCurrentPingData(true); ok("dynamic" in pingData.payload.processes, "The ping payload must contain the 'dynamic' process section"); is(pingData.payload.processes.dynamic.scalars[SCALAR_FULL_NAME], 4, "The dynamic scalar must contain the aggregated parent and children data."); }); diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp --- a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp +++ b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp @@ -1,189 +1,187 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ #include "TelemetryTestHelpers.h" #include "core/TelemetryCommon.h" #include "gtest/gtest.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/Unused.h" using namespace mozilla; // Helper methods provided to simplify writing tests and meant to be used in C++ Gtests. namespace TelemetryTestHelpers { void CheckUintScalar(const char* aName, JSContext* aCx, JS::HandleValue aSnapshot, uint32_t expectedValue) { // Validate the value of the test scalar. JS::RootedValue value(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) << "The test scalar must be reported."; JS_GetProperty(aCx, scalarObj, aName, &value); ASSERT_TRUE(value.isInt32()) << "The scalar value must be of the correct type."; ASSERT_TRUE(value.toInt32() >= 0) << "The uint scalar type must contain a value >= 0."; ASSERT_EQ(static_cast(value.toInt32()), expectedValue) << "The scalar value must match the expected value."; } void CheckBoolScalar(const char* aName, JSContext* aCx, JS::HandleValue aSnapshot, bool expectedValue) { // Validate the value of the test scalar. JS::RootedValue value(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) << "The test scalar must be reported."; ASSERT_TRUE(value.isBoolean()) << "The scalar value must be of the correct type."; ASSERT_EQ(static_cast(value.toBoolean()), expectedValue) << "The scalar value must match the expected value."; } void CheckStringScalar(const char* aName, JSContext* aCx, JS::HandleValue aSnapshot, const char* expectedValue) { // Validate the value of the test scalar. JS::RootedValue value(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) << "The test scalar must be reported."; ASSERT_TRUE(value.isString()) << "The scalar value must be of the correct type."; bool sameString; ASSERT_TRUE(JS_StringEqualsAscii(aCx, value.toString(), expectedValue, &sameString)) << "JS String comparison failed"; ASSERT_TRUE(sameString) << "The scalar value must match the expected string"; } void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx, JS::HandleValue aSnapshot, uint32_t expectedValue) { JS::RootedValue keyedScalar(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); // Get the aName keyed scalar object from the scalars snapshot. ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) << "The keyed scalar must be reported."; CheckUintScalar(aKey, aCx, keyedScalar, expectedValue); } void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx, JS::HandleValue aSnapshot, bool expectedValue) { JS::RootedValue keyedScalar(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); // Get the aName keyed scalar object from the scalars snapshot. ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) << "The keyed scalar must be reported."; CheckBoolScalar(aKey, aCx, keyedScalar, expectedValue); } void CheckNumberOfProperties(const char* aName, JSContext* aCx, JS::HandleValue aSnapshot, uint32_t expectedNumProperties) { JS::RootedValue keyedScalar(aCx); JS::RootedObject scalarObj(aCx, &aSnapshot.toObject()); // Get the aName keyed scalar object from the scalars snapshot. ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) << "The keyed scalar must be reported."; JS::RootedObject keyedScalarObj(aCx, &keyedScalar.toObject()); JS::Rooted ids(aCx, JS::IdVector(aCx)); ASSERT_TRUE(JS_Enumerate(aCx, keyedScalarObj, &ids)) << "We must be able to get keyed scalar members."; ASSERT_EQ(expectedNumProperties, ids.length()) << "The scalar must report the expected number of properties."; } void GetScalarsSnapshot(bool aKeyed, JSContext* aCx, JS::MutableHandle aResult, ProcessID aProcessType) { nsCOMPtr telemetry = do_GetService("@mozilla.org/base/telemetry;1"); // Get a snapshot of the scalars. JS::RootedValue scalarsSnapshot(aCx); nsresult rv; if (aKeyed) { - rv = telemetry->SnapshotKeyedScalars(nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN, - false, aCx, 0, &scalarsSnapshot); + rv = telemetry->GetSnapshotForKeyedScalars(NS_LITERAL_CSTRING("main"), false, aCx, &scalarsSnapshot); } else { - rv = telemetry->SnapshotScalars(nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN, - false, aCx, 0, &scalarsSnapshot); + rv = telemetry->GetSnapshotForScalars(NS_LITERAL_CSTRING("main"), false, aCx, &scalarsSnapshot); } // Validate the snapshot. ASSERT_EQ(rv, NS_OK) << "Creating a snapshot of the data must not fail."; ASSERT_TRUE(scalarsSnapshot.isObject()) << "The snapshot must be an object."; JS::RootedValue processScalars(aCx); JS::RootedObject scalarObj(aCx, &scalarsSnapshot.toObject()); // Don't complain if no scalars for the process can be found. Just // return an empty object. Unused << JS_GetProperty(aCx, scalarObj, Telemetry::Common::GetNameForProcessID(aProcessType), &processScalars); aResult.set(processScalars); } void GetAndClearHistogram(JSContext* cx, nsCOMPtr mTelemetry, const nsACString &name, bool is_keyed) { JS::RootedValue testHistogram(cx); nsresult rv = is_keyed ? mTelemetry->GetKeyedHistogramById(name, cx, &testHistogram) : mTelemetry->GetHistogramById(name, cx, &testHistogram); ASSERT_EQ(rv, NS_OK) << "Cannot fetch histogram"; // Clear the stored value JS::RootedObject testHistogramObj(cx, &testHistogram.toObject()); JS::RootedValue rval(cx); ASSERT_TRUE(JS_CallFunctionName(cx, testHistogramObj, "clear", JS::HandleValueArray::empty(), &rval)) << "Cannot clear histogram"; } void GetProperty(JSContext* cx, const char* name, JS::HandleValue valueIn, JS::MutableHandleValue valueOut) { JS::RootedValue property(cx); JS::RootedObject valueInObj(cx, &valueIn.toObject()); ASSERT_TRUE(JS_GetProperty(cx, valueInObj, name, &property)) << "Cannot get property '" << name << "'"; valueOut.set(property); } void GetElement(JSContext* cx, uint32_t index, JS::HandleValue valueIn, JS::MutableHandleValue valueOut) { JS::RootedValue element(cx); JS::RootedObject valueInObj(cx, &valueIn.toObject()); ASSERT_TRUE(JS_GetElement(cx, valueInObj, index, &element)) << "Cannot get element at index '" << index << "'"; valueOut.set(element); } void GetSnapshots(JSContext* cx, nsCOMPtr mTelemetry, const char* name, JS::MutableHandleValue valueOut, bool is_keyed) { JS::RootedValue snapshots(cx); - nsresult rv = is_keyed ? mTelemetry->SnapshotKeyedHistograms(nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN, false, cx, &snapshots) - : mTelemetry->SnapshotHistograms(nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN, false, cx, &snapshots); + nsresult rv = is_keyed ? mTelemetry->GetSnapshotForKeyedHistograms(NS_LITERAL_CSTRING("main"), false, cx, &snapshots) + : mTelemetry->GetSnapshotForHistograms(NS_LITERAL_CSTRING("main"), false, cx, &snapshots); JS::RootedValue snapshot(cx); GetProperty(cx, "parent", snapshots, &snapshot); ASSERT_EQ(rv, NS_OK) << "Cannot call histogram snapshots"; valueOut.set(snapshot); } } // namespace TelemetryTestHelpers diff --git a/toolkit/components/telemetry/tests/unit/head_GeckoView.js b/toolkit/components/telemetry/tests/unit/head_GeckoView.js --- a/toolkit/components/telemetry/tests/unit/head_GeckoView.js +++ b/toolkit/components/telemetry/tests/unit/head_GeckoView.js @@ -1,84 +1,84 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); const Telemetry = Services.telemetry; const TelemetryGeckoView = Cc["@mozilla.org/telemetry/geckoview-testing;1"] .createInstance(Ci.nsITelemetryGeckoViewTesting); /** * Run a file in the content process. * @param aFileName - The file to execute in the content process. * @return {Promise} A promise resolved after the execution in the other process * finishes. */ async function run_in_child(aFileName) { const PREF_GECKOVIEW_MODE = "toolkit.telemetry.isGeckoViewMode"; // We don't ship GeckoViewTelemetryController.jsm outside of Android. If // |toolkit.telemetry.isGeckoViewMode| is true, this makes Gecko crash on // other platforms because ContentProcessSingleton.js requires it. Work // around this by temporarily setting the pref to false. const currentValue = Services.prefs.getBoolPref(PREF_GECKOVIEW_MODE, false); Services.prefs.setBoolPref(PREF_GECKOVIEW_MODE, false); await run_test_in_child(aFileName); Services.prefs.setBoolPref(PREF_GECKOVIEW_MODE, currentValue); } /** * Builds a promise to wait for the GeckoView data loading to finish. * @return {Promise} A promise resolved when the data loading finishes. */ function waitGeckoViewLoadComplete() { return new Promise(resolve => { Services.obs.addObserver(function observe() { Services.obs.removeObserver(observe, "internal-telemetry-geckoview-load-complete"); resolve(); }, "internal-telemetry-geckoview-load-complete"); }); } /** * This function waits until the desired histogram is reported into the * snapshot of the relevant process. * @param aHistogramName - The name of the histogram to look for. * @param aProcessName - The name of the process to look in. * @param aKeyed - Whether or not to look in keyed snapshots. */ async function waitForHistogramSnapshotData(aHistogramName, aProcessName, aKeyed) { await ContentTaskUtils.waitForCondition(() => { const data = aKeyed - ? Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false) - : Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false); + ? Telemetry.getSnapshotForKeyedHistograms("main", false) + : Telemetry.getSnapshotForHistograms("main", false); return (aProcessName in data) && (aHistogramName in data[aProcessName]); }); } /** * This function waits until the desired scalar is reported into the * snapshot of the relevant process. * @param aScalarName - The name of the scalar to look for. * @param aProcessName - The name of the process to look in. * @param aKeyed - Whether or not to look in keyed snapshots. */ async function waitForScalarSnapshotData(aScalarName, aProcessName, aKeyed) { await ContentTaskUtils.waitForCondition(() => { const data = aKeyed - ? Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false) - : Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false); + ? Telemetry.getSnapshotForKeyedScalars("main", false) + : Telemetry.getSnapshotForScalars("main", false); return (aProcessName in data) && (aScalarName in data[aProcessName]); }); } if (runningInParent) { Services.prefs.setBoolPref(TelemetryUtils.Preferences.OverridePreRelease, true); } diff --git a/toolkit/components/telemetry/tests/unit/test_ChildScalars.js b/toolkit/components/telemetry/tests/unit/test_ChildScalars.js --- a/toolkit/components/telemetry/tests/unit/test_ChildScalars.js +++ b/toolkit/components/telemetry/tests/unit/test_ChildScalars.js @@ -1,174 +1,173 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", this); ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done"; const PLATFORM_VERSION = "1.9.2"; const APP_VERSION = "1"; const APP_ID = "xpcshell@tests.mozilla.org"; const APP_NAME = "XPCShell"; const UINT_SCALAR = "telemetry.test.unsigned_int_kind"; const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int"; const KEYED_BOOL_SCALAR = "telemetry.test.keyed_boolean_kind"; const CONTENT_ONLY_UINT_SCALAR = "telemetry.test.content_only_uint"; const ALL_PROCESSES_UINT_SCALAR = "telemetry.test.all_processes_uint"; const ALL_CHILD_PROCESSES_STRING_SCALAR = "telemetry.test.all_child_processes_string"; function run_child_test() { // Attempt to set some scalar values from the "content" process. // The next scalars are not allowed to be recorded in the content process. Telemetry.scalarSet(UINT_SCALAR, 1); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "should-not-be-recorded", 1); // The next scalars shou be recorded in only the content process. Telemetry.scalarSet(CONTENT_ONLY_UINT_SCALAR, 37); Telemetry.scalarSet(ALL_CHILD_PROCESSES_STRING_SCALAR, "all-child-processes"); // The next scalar will be recorded in the parent and content processes. Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "content-key", true); Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "content-key2", false); Telemetry.scalarSet(ALL_PROCESSES_UINT_SCALAR, 37); } function setParentScalars() { // The following scalars are not allowed to be recorded in the parent process. Telemetry.scalarSet(CONTENT_ONLY_UINT_SCALAR, 15); Telemetry.scalarSet(ALL_CHILD_PROCESSES_STRING_SCALAR, "all-child-processes"); // The next ones will be recorded only in the parent. Telemetry.scalarSet(UINT_SCALAR, 15); // This last batch will be available both in the parent and child processes. Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "parent-key", false); Telemetry.scalarSet(ALL_PROCESSES_UINT_SCALAR, 37); } function checkParentScalars(processData) { const scalars = processData.scalars; const keyedScalars = processData.keyedScalars; // Check the plain scalars, make sure we're only recording what we expect. Assert.ok(!(CONTENT_ONLY_UINT_SCALAR in scalars), "Scalars must not be recorded in other processes unless allowed."); Assert.ok(!(ALL_CHILD_PROCESSES_STRING_SCALAR in scalars), "Scalars must not be recorded in other processes unless allowed."); Assert.ok(UINT_SCALAR in scalars, `${UINT_SCALAR} must be recorded in the parent process.`); Assert.equal(scalars[UINT_SCALAR], 15, `${UINT_SCALAR} must have the correct value (parent process).`); Assert.ok(ALL_PROCESSES_UINT_SCALAR in scalars, `${ALL_PROCESSES_UINT_SCALAR} must be recorded in the parent process.`); Assert.equal(scalars[ALL_PROCESSES_UINT_SCALAR], 37, `${ALL_PROCESSES_UINT_SCALAR} must have the correct value (parent process).`); // Now check the keyed scalars. Assert.ok(KEYED_BOOL_SCALAR in keyedScalars, `${KEYED_BOOL_SCALAR} must be recorded in the parent process.`); Assert.ok("parent-key" in keyedScalars[KEYED_BOOL_SCALAR], `${KEYED_BOOL_SCALAR} must be recorded in the parent process.`); Assert.equal(Object.keys(keyedScalars[KEYED_BOOL_SCALAR]).length, 1, `${KEYED_BOOL_SCALAR} must only contain the expected key in parent process.`); Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]["parent-key"], false, `${KEYED_BOOL_SCALAR} must have the correct value (parent process).`); } function checkContentScalars(processData) { const scalars = processData.scalars; const keyedScalars = processData.keyedScalars; // Check the plain scalars for the content process. Assert.ok(!(UINT_SCALAR in scalars), "Scalars must not be recorded in other processes unless allowed."); Assert.ok(!(KEYED_UINT_SCALAR in keyedScalars), "Keyed scalars must not be recorded in other processes unless allowed."); Assert.ok(CONTENT_ONLY_UINT_SCALAR in scalars, `${CONTENT_ONLY_UINT_SCALAR} must be recorded in the content process.`); Assert.equal(scalars[CONTENT_ONLY_UINT_SCALAR], 37, `${CONTENT_ONLY_UINT_SCALAR} must have the correct value (content process).`); Assert.ok(ALL_CHILD_PROCESSES_STRING_SCALAR in scalars, `${ALL_CHILD_PROCESSES_STRING_SCALAR} must be recorded in the content process.`); Assert.equal(scalars[ALL_CHILD_PROCESSES_STRING_SCALAR], "all-child-processes", `${ALL_CHILD_PROCESSES_STRING_SCALAR} must have the correct value (content process).`); Assert.ok(ALL_PROCESSES_UINT_SCALAR in scalars, `${ALL_PROCESSES_UINT_SCALAR} must be recorded in the content process.`); Assert.equal(scalars[ALL_PROCESSES_UINT_SCALAR], 37, `${ALL_PROCESSES_UINT_SCALAR} must have the correct value (content process).`); // Check the keyed scalars. Assert.ok(KEYED_BOOL_SCALAR in keyedScalars, `${KEYED_BOOL_SCALAR} must be recorded in the content process.`); Assert.ok("content-key" in keyedScalars[KEYED_BOOL_SCALAR], `${KEYED_BOOL_SCALAR} must be recorded in the content process.`); Assert.ok("content-key2" in keyedScalars[KEYED_BOOL_SCALAR], `${KEYED_BOOL_SCALAR} must be recorded in the content process.`); Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]["content-key"], true, `${KEYED_BOOL_SCALAR} must have the correct value (content process).`); Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]["content-key2"], false, `${KEYED_BOOL_SCALAR} must have the correct value (content process).`); Assert.equal(Object.keys(keyedScalars[KEYED_BOOL_SCALAR]).length, 2, `${KEYED_BOOL_SCALAR} must contain the expected keys in content process.`); } /** * This function waits until content scalars are reported into the * scalar snapshot. */ async function waitForContentScalars() { await ContentTaskUtils.waitForCondition(() => { - const scalars = - Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = Telemetry.getSnapshotForScalars("main", false); return Object.keys(scalars).includes("content"); }); } add_task(async function() { if (!runningInParent) { TelemetryController.testSetupContent(); run_child_test(); do_send_remote_message(MESSAGE_CHILD_TEST_DONE); return; } // Setup. do_get_profile(true); loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION); finishAddonManagerStartup(); await TelemetryController.testSetup(); if (runningInParent) { setParentScalars(); // Make sure we don't generate unexpected pings due to pref changes. await setEmptyPrefWatchlist(); } // Run test in child, don't wait for it to finish: just wait for the // MESSAGE_CHILD_TEST_DONE. run_test_in_child("test_ChildScalars.js"); await do_await_remote_message(MESSAGE_CHILD_TEST_DONE); // Once scalars are set by the content process, they don't immediately get // sent to the parent process. Wait for the Telemetry IPC Timer to trigger // and batch send the data back to the parent process. await waitForContentScalars(); // Get an "environment-changed" ping rather than a "test-ping", as // scalar measurements are only supported in subsession pings. const payload = TelemetrySession.getPayload("environment-change"); // Validate the scalar data. Assert.ok("processes" in payload, "Should have processes section"); Assert.ok("content" in payload.processes, "Should have child process section"); Assert.ok("scalars" in payload.processes.content, "Child process section should have scalars."); Assert.ok("keyedScalars" in payload.processes.content, "Child process section should have keyed scalars."); checkParentScalars(payload.processes.parent); checkContentScalars(payload.processes.content); do_test_finished(); }); diff --git a/toolkit/components/telemetry/tests/unit/test_GeckoView.js b/toolkit/components/telemetry/tests/unit/test_GeckoView.js --- a/toolkit/components/telemetry/tests/unit/test_GeckoView.js +++ b/toolkit/components/telemetry/tests/unit/test_GeckoView.js @@ -1,95 +1,93 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); add_task(async function setup() { // Init the profile. let profileDir = do_get_profile(true); // Set geckoview mode. Services.prefs.setBoolPref("toolkit.telemetry.isGeckoViewMode", true); // Set the ANDROID_DATA_DIR to the profile dir. let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); env.set("MOZ_ANDROID_DATA_DIR", profileDir.path); }); add_task(async function test_persistContentHistograms() { // Get and clear the histograms. let plainHist = Telemetry.getHistogramById("TELEMETRY_TEST_MULTIPRODUCT"); plainHist.clear(); let keyedHist = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); keyedHist.clear(); TelemetryGeckoView.initPersistence(); // Set the histograms in parent. plainHist.add(37); keyedHist.add("parent-test-key", 73); // Set content histograms and wait for the execution in the other // process to finish. await run_in_child("test_GeckoView_content_histograms.js"); // Wait for the data to be collected by the parent process. await waitForHistogramSnapshotData("TELEMETRY_TEST_MULTIPRODUCT", "content", false /* aKeyed */); await waitForHistogramSnapshotData("TELEMETRY_TEST_KEYED_COUNT", "content", true /* aKeyed */); // Force persisting the measurements to file. TelemetryGeckoView.forcePersist(); TelemetryGeckoView.deInitPersistence(); // Clear the histograms for all processes. - Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true /* clear */); - Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true /* clear */); + Telemetry.getSnapshotForHistograms("main", true /* clear */); + Telemetry.getSnapshotForKeyedHistograms("main", true /* clear */); // Start the persistence system again, to unpersist the data. let loadPromise = waitGeckoViewLoadComplete(); TelemetryGeckoView.initPersistence(); // Wait for the load to finish. await loadPromise; // Validate the snapshot data. - const snapshot = - Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); + const snapshot = Telemetry.getSnapshotForHistograms("main", false /* clear */); Assert.ok("parent" in snapshot, "The snapshot object must have a 'parent' entry."); Assert.ok("content" in snapshot, "The snapshot object must have a 'content' entry."); Assert.ok("TELEMETRY_TEST_MULTIPRODUCT" in snapshot.parent, "The TELEMETRY_TEST_MULTIPRODUCT histogram must exist in the parent section."); Assert.equal(snapshot.parent.TELEMETRY_TEST_MULTIPRODUCT.sum, 37, "The TELEMETRY_TEST_MULTIPRODUCT must have the expected value in the parent section."); Assert.ok("TELEMETRY_TEST_MULTIPRODUCT" in snapshot.content, "The TELEMETRY_TEST_MULTIPRODUCT histogram must exist in the content section."); Assert.equal(snapshot.content.TELEMETRY_TEST_MULTIPRODUCT.sum, 73, "The TELEMETRY_TEST_MULTIPRODUCT must have the expected value in the content section."); - const keyedSnapshot = - Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); + const keyedSnapshot = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */); Assert.ok("parent" in keyedSnapshot, "The keyed snapshot object must have a 'parent' entry."); Assert.ok("content" in keyedSnapshot, "The keyed snapshot object must have a 'content' entry."); const parentData = keyedSnapshot.parent; Assert.ok("TELEMETRY_TEST_KEYED_COUNT" in parentData, "The TELEMETRY_TEST_KEYED_COUNT histogram must exist in the parent section."); Assert.ok("parent-test-key" in parentData.TELEMETRY_TEST_KEYED_COUNT, "The histogram in the parent process should have the expected key."); Assert.equal(parentData.TELEMETRY_TEST_KEYED_COUNT["parent-test-key"].sum, 73, "The TELEMETRY_TEST_KEYED_COUNT must have the expected value in the parent section."); const contentData = keyedSnapshot.content; Assert.ok("TELEMETRY_TEST_KEYED_COUNT" in contentData, "The TELEMETRY_TEST_KEYED_COUNT histogram must exist in the content section."); Assert.ok("content-test-key" in contentData.TELEMETRY_TEST_KEYED_COUNT, "The histogram in the content process should have the expected key."); Assert.equal(contentData.TELEMETRY_TEST_KEYED_COUNT["content-test-key"].sum, 37, "The TELEMETRY_TEST_KEYED_COUNT must have the expected value in the content section."); TelemetryGeckoView.deInitPersistence(); }); add_task(async function cleanup() { Services.prefs.clearUserPref("toolkit.telemetry.isGeckoViewMode"); }); diff --git a/toolkit/components/telemetry/tests/unit/test_GeckoView_ScalarSemantics.js b/toolkit/components/telemetry/tests/unit/test_GeckoView_ScalarSemantics.js --- a/toolkit/components/telemetry/tests/unit/test_GeckoView_ScalarSemantics.js +++ b/toolkit/components/telemetry/tests/unit/test_GeckoView_ScalarSemantics.js @@ -1,131 +1,129 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); const CONTENT_ONLY_UINT_SCALAR = "telemetry.test.content_only_uint"; const ALL_PROCESSES_UINT_SCALAR = "telemetry.test.all_processes_uint"; const DEFAULT_PRODUCTS_SCALAR = "telemetry.test.default_products"; const CHILD_KEYED_UNSIGNED_INT = "telemetry.test.child_keyed_unsigned_int"; add_task(async function setup() { // Init the profile. let profileDir = do_get_profile(true); // Set geckoview mode. Services.prefs.setBoolPref("toolkit.telemetry.isGeckoViewMode", true); // Set the ANDROID_DATA_DIR to the profile dir. let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); env.set("MOZ_ANDROID_DATA_DIR", profileDir.path); }); add_task(async function test_MultiprocessScalarSemantics() { /** * To mitigate races during deserialization of persisted data * scalar operations will be recorded and applied after the deserialization is finished. * * This test ensures it works acording to the semantics and follows the documentation example: * * * Scalar deserialization is started * * “test” scalar is incremented by “10” by the application -> The operation [test, add, 10] is recorded into the list. * * The state of the “test” scalar is loaded off the persistence file, and the value “14” is set. * * Deserialization is finished and the pending operations are applied. * * The “test” scalar is incremented by “10”, the value is now “24” */ Telemetry.clearScalars(); let loadPromise = waitGeckoViewLoadComplete(); TelemetryGeckoView.initPersistence(); await loadPromise; // Set something in the parent Telemetry.scalarSet(ALL_PROCESSES_UINT_SCALAR, 34); // Set scalars in the child process. // The child will then wait for a signal to continue. let child = run_in_child("test_GeckoView_content_scalars.js"); // Wait for the data to be collected by the parent process. await waitForScalarSnapshotData(ALL_PROCESSES_UINT_SCALAR, "parent", false /* aKeyed */); await waitForScalarSnapshotData(CONTENT_ONLY_UINT_SCALAR, "content", false /* aKeyed */); await waitForScalarSnapshotData(ALL_PROCESSES_UINT_SCALAR, "content", false /* aKeyed */); await waitForScalarSnapshotData(CHILD_KEYED_UNSIGNED_INT, "content", true /* aKeyed */); - let snapshot = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); - let keyedSnapshot = Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); + let snapshot = Telemetry.getSnapshotForScalars("main", false /* clear */); + let keyedSnapshot = Telemetry.getSnapshotForKeyedScalars("main", false /* clear */); Assert.equal(snapshot.content[CONTENT_ONLY_UINT_SCALAR], 14, `The ${CONTENT_ONLY_UINT_SCALAR} scalar must have the expected value in the content section.`); Assert.equal(snapshot.content[ALL_PROCESSES_UINT_SCALAR], 24, `The ${ALL_PROCESSES_UINT_SCALAR} scalar must have the expected value in the content section.`); Assert.equal(snapshot.parent[ALL_PROCESSES_UINT_SCALAR], 34, `The ${ALL_PROCESSES_UINT_SCALAR} scalar must have the expected value in the parent section.`); Assert.equal(keyedSnapshot.content[CHILD_KEYED_UNSIGNED_INT].chewbacca, 44, `The ${CHILD_KEYED_UNSIGNED_INT} keyed scalar must have the expected value in the content section.`); // Force persisting the measurements to file. TelemetryGeckoView.forcePersist(); TelemetryGeckoView.deInitPersistence(); // Clear all data from memory. Telemetry.clearScalars(); // Mark deserialization as in progress, following operations are recorded and not applied. TelemetryGeckoView.deserializationStarted(); // Modify a scalar in the parent process. Telemetry.scalarAdd(ALL_PROCESSES_UINT_SCALAR, 10); // Let child know to progress and wait for it to finish. do_send_remote_message("child-scalar-semantics"); await child; // Start the persistence system again, to unpersist the data. loadPromise = waitGeckoViewLoadComplete(); TelemetryGeckoView.initPersistence(); // Wait for the load to finish. await loadPromise; // Wait for the data to be collected by the parent process. // We only wait for the new data, as the rest will be in there from the persistence load. await waitForScalarSnapshotData(DEFAULT_PRODUCTS_SCALAR, "content", false /* aKeyed */); // Validate the snapshot data. - snapshot = - Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); - keyedSnapshot = - Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false /* clear */); + snapshot = Telemetry.getSnapshotForScalars("main", false /* clear */); + keyedSnapshot = Telemetry.getSnapshotForKeyedScalars("main", false /* clear */); Assert.ok("parent" in snapshot, "The snapshot object must have a 'content' entry."); Assert.ok("content" in snapshot, "The snapshot object must have a 'content' entry."); Assert.ok("content" in keyedSnapshot, "The keyed snapshot object must have a 'content' entry."); Assert.ok(ALL_PROCESSES_UINT_SCALAR in snapshot.parent, `The ${ALL_PROCESSES_UINT_SCALAR} scalar must exist in the parent section.`); Assert.ok(CONTENT_ONLY_UINT_SCALAR in snapshot.content, `The ${CONTENT_ONLY_UINT_SCALAR} scalar must exist in the content section.`); Assert.ok(ALL_PROCESSES_UINT_SCALAR in snapshot.content, `The ${ALL_PROCESSES_UINT_SCALAR} scalar must exist in the content section.`); Assert.equal(snapshot.content[CONTENT_ONLY_UINT_SCALAR], 24, `The ${CONTENT_ONLY_UINT_SCALAR} must have the expected value in the content section.`); Assert.equal(snapshot.content[ALL_PROCESSES_UINT_SCALAR], 34, `The ${ALL_PROCESSES_UINT_SCALAR} must have the expected value in the content section.`); Assert.equal(snapshot.parent[ALL_PROCESSES_UINT_SCALAR], 44, `The ${ALL_PROCESSES_UINT_SCALAR} must have the expected value in the parent section.`); Assert.equal(keyedSnapshot.content[CHILD_KEYED_UNSIGNED_INT].chewbacca, 54, `The ${CHILD_KEYED_UNSIGNED_INT} keyed scalar must have the expected value in the content section.`); TelemetryGeckoView.deInitPersistence(); }); add_task(async function cleanup() { Services.prefs.clearUserPref("toolkit.telemetry.isGeckoViewMode"); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js --- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -1,650 +1,649 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /* This testcase triggers two telemetry pings. * * Telemetry code keeps histograms of past telemetry pings. The first * ping populates these histograms. One of those histograms is then * checked in the second request. */ ChromeUtils.import("resource://services-common/utils.js"); ChromeUtils.import("resource://gre/modules/ClientID.jsm"); ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryArchive.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this); ChromeUtils.import("resource://gre/modules/Preferences.jsm"); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); const PING_FORMAT_VERSION = 4; const OPTOUT_PING_TYPE = "optout"; const TEST_PING_TYPE = "test-ping-type"; const PLATFORM_VERSION = "1.9.2"; const APP_VERSION = "1"; const APP_NAME = "XPCShell"; var gClientID = null; XPCOMUtils.defineLazyGetter(this, "DATAREPORTING_PATH", function() { return OS.Path.join(OS.Constants.Path.profileDir, "datareporting"); }); function sendPing(aSendClientId, aSendEnvironment) { if (PingServer.started) { TelemetrySend.setServer("http://localhost:" + PingServer.port); } else { TelemetrySend.setServer("http://doesnotexist"); } let options = { addClientId: aSendClientId, addEnvironment: aSendEnvironment, }; return TelemetryController.submitExternalPing(TEST_PING_TYPE, {}, options); } function checkPingFormat(aPing, aType, aHasClientId, aHasEnvironment) { const MANDATORY_PING_FIELDS = [ "type", "id", "creationDate", "version", "application", "payload", ]; const APPLICATION_TEST_DATA = { buildId: gAppInfo.appBuildID, name: APP_NAME, version: APP_VERSION, displayVersion: AppConstants.MOZ_APP_VERSION_DISPLAY, vendor: "Mozilla", platformVersion: PLATFORM_VERSION, xpcomAbi: "noarch-spidermonkey", }; // Check that the ping contains all the mandatory fields. for (let f of MANDATORY_PING_FIELDS) { Assert.ok(f in aPing, f + " must be available."); } Assert.equal(aPing.type, aType, "The ping must have the correct type."); Assert.equal(aPing.version, PING_FORMAT_VERSION, "The ping must have the correct version."); // Test the application section. for (let f in APPLICATION_TEST_DATA) { Assert.equal(aPing.application[f], APPLICATION_TEST_DATA[f], f + " must have the correct value."); } // We can't check the values for channel and architecture. Just make // sure they are in. Assert.ok("architecture" in aPing.application, "The application section must have an architecture field."); Assert.ok("channel" in aPing.application, "The application section must have a channel field."); // Check the clientId and environment fields, as needed. Assert.equal("clientId" in aPing, aHasClientId); Assert.equal("environment" in aPing, aHasEnvironment); } add_task(async function test_setup() { // Addon manager needs a profile directory do_get_profile(); loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); finishAddonManagerStartup(); // Make sure we don't generate unexpected pings due to pref changes. await setEmptyPrefWatchlist(); Services.prefs.setBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, true); await new Promise(resolve => Telemetry.asyncFetchTelemetryData(wrapWithExceptionHandler(resolve))); }); add_task(async function asyncSetup() { await TelemetryController.testSetup(); }); // Ensure that not overwriting an existing file fails silently add_task(async function test_overwritePing() { let ping = {id: "foo"}; await TelemetryStorage.savePing(ping, true); await TelemetryStorage.savePing(ping, false); await TelemetryStorage.cleanupPingFile(ping); }); // Checks that a sent ping is correctly received by a dummy http server. add_task(async function test_simplePing() { PingServer.start(); // Update the Telemetry Server preference with the address of the local server. // Otherwise we might end up sending stuff to a non-existing server after // |TelemetryController.testReset| is called. Preferences.set(TelemetryUtils.Preferences.Server, "http://localhost:" + PingServer.port); await sendPing(false, false); let request = await PingServer.promiseNextRequest(); // Check that we have a version query parameter in the URL. Assert.notEqual(request.queryString, ""); // Make sure the version in the query string matches the new ping format version. let params = request.queryString.split("&"); Assert.ok(params.find(p => p == ("v=" + PING_FORMAT_VERSION))); let ping = decodeRequestPayload(request); checkPingFormat(ping, TEST_PING_TYPE, false, false); }); add_task(async function test_disableDataUpload() { const OPTIN_PROBE = "telemetry.data_upload_optin"; const isUnified = Preferences.get(TelemetryUtils.Preferences.Unified, false); if (!isUnified) { // Skipping the test if unified telemetry is off, as no optout ping will // be generated. return; } // Check that the optin probe is not set, there should be other data in the snapshot though - let snapshot = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + let snapshot = Telemetry.getSnapshotForScalars("main", false).parent; Assert.ok(!(OPTIN_PROBE in snapshot), "Data optin scalar should not be set at start"); // Send a first ping to get the current used client id await sendPing(true, false); let ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, false); let firstClientId = ping.clientId; Assert.ok(firstClientId, "Test ping needs a client ID"); Assert.notEqual(TelemetryUtils.knownClientID, firstClientId, "Client ID should be valid and random"); // Disable FHR upload: this should trigger a optout ping. Preferences.set(TelemetryUtils.Preferences.FhrUploadEnabled, false); ping = await PingServer.promiseNextPing(); checkPingFormat(ping, OPTOUT_PING_TYPE, false, false); // Wait on ping activity to settle. await TelemetrySend.testWaitOnOutgoingPings(); - snapshot = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + snapshot = Telemetry.getSnapshotForScalars("main", false).parent; Assert.ok(!(OPTIN_PROBE in snapshot), "Data optin scalar should not be set after optout"); // Restore FHR Upload. Preferences.set(TelemetryUtils.Preferences.FhrUploadEnabled, true); // We need to wait until the scalar is set await ContentTaskUtils.waitForCondition(() => { - const scalarSnapshot = - Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false); + const scalarSnapshot = Telemetry.getSnapshotForScalars("main", false); return Object.keys(scalarSnapshot).includes("parent") && OPTIN_PROBE in scalarSnapshot.parent; }); - snapshot = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + snapshot = Telemetry.getSnapshotForScalars("main", false).parent; Assert.ok(snapshot[OPTIN_PROBE], "Enabling data upload should set optin probe"); // Simulate a failure in sending the optout ping by disabling the HTTP server. await PingServer.stop(); // Try to send a ping. It will be saved as pending and get deleted when disabling upload. TelemetryController.submitExternalPing(TEST_PING_TYPE, {}); // Disable FHR upload to send a optout ping again. Preferences.set(TelemetryUtils.Preferences.FhrUploadEnabled, false); // Wait on sending activity to settle, as |TelemetryController.testReset()| doesn't do that. await TelemetrySend.testWaitOnOutgoingPings(); // Wait for the pending pings to be deleted. Resetting TelemetryController doesn't // trigger the shutdown, so we need to call it ourselves. await TelemetryStorage.shutdown(); // Simulate a restart, and spin the send task. await TelemetryController.testReset(); // Disabling Telemetry upload must clear out all the pending pings. let pendingPings = await TelemetryStorage.loadPendingPingList(); Assert.equal(pendingPings.length, 0, "All the pending pings should have been deleted, including the optout ping"); // Enable the ping server again. PingServer.start(); // We set the new server using the pref, otherwise it would get reset with // |TelemetryController.testReset|. Preferences.set(TelemetryUtils.Preferences.Server, "http://localhost:" + PingServer.port); // Stop the sending task and then start it again. await TelemetrySend.shutdown(); // Reset the controller to spin the ping sending task. await TelemetryController.testReset(); // Re-enable Telemetry Preferences.set(TelemetryUtils.Preferences.FhrUploadEnabled, true); // Send a test ping await sendPing(true, false); // We should have only received the test ping ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, false); // The data in the test ping should be different than before Assert.notEqual(TelemetryUtils.knownClientID, ping.clientId, "Client ID should be reset to a random value"); Assert.notEqual(firstClientId, ping.clientId, "Client ID should be different from the previous value"); // Wait on ping activity to settle before moving on to the next test. If we were // to shut down telemetry, even though the PingServer caught the expected pings, // TelemetrySend could still be processing them (clearing pings would happen in // a couple of ticks). Shutting down would cancel the request and save them as // pending pings. await TelemetrySend.testWaitOnOutgoingPings(); }); add_task(async function test_pingHasClientId() { // Make sure we have no cached client ID for this test: we'll try to send // a ping with it while Telemetry is being initialized. Preferences.reset(TelemetryUtils.Preferences.CachedClientId); await TelemetryController.testShutdown(); await ClientID._reset(); await TelemetryStorage.testClearPendingPings(); // And also clear the counter histogram since we're here. let h = Telemetry.getHistogramById("TELEMETRY_PING_SUBMISSION_WAITING_CLIENTID"); h.clear(); // Init telemetry and try to send a ping with a client ID. let promisePingSetup = TelemetryController.testReset(); await sendPing(true, false); Assert.equal(h.snapshot().sum, 1, "We must have a ping waiting for the clientId early during startup."); // Wait until we are fully initialized. Pings will be assembled but won't get // sent before then. await promisePingSetup; let ping = await PingServer.promiseNextPing(); // Fetch the client ID after initializing and fetching the the ping, so we // don't unintentionally trigger its loading. We'll still need the client ID // to see if the ping looks sane. gClientID = await ClientID.getClientID(); checkPingFormat(ping, TEST_PING_TYPE, true, false); Assert.equal(ping.clientId, gClientID, "The correct clientId must be reported."); // Shutdown Telemetry so we can safely restart it. await TelemetryController.testShutdown(); await TelemetryStorage.testClearPendingPings(); // We should have cached the client ID now. Lets confirm that by checking it before // the async ping setup is finished. h.clear(); promisePingSetup = TelemetryController.testReset(); await sendPing(true, false); await promisePingSetup; // Check that we received the cached client id. Assert.equal(h.snapshot().sum, 0, "We must have used the cached clientId."); ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, false); Assert.equal(ping.clientId, gClientID, "Telemetry should report the correct cached clientId."); // Check that sending a ping without relying on the cache, after the // initialization, still works. Preferences.reset(TelemetryUtils.Preferences.CachedClientId); await TelemetryController.testShutdown(); await TelemetryStorage.testClearPendingPings(); await TelemetryController.testReset(); await sendPing(true, false); ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, false); Assert.equal(ping.clientId, gClientID, "The correct clientId must be reported."); Assert.equal(h.snapshot().sum, 0, "No ping should have been waiting for a clientId."); }); add_task(async function test_pingHasEnvironment() { // Send a ping with the environment data. await sendPing(false, true); let ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, false, true); // Test a field in the environment build section. Assert.equal(ping.application.buildId, ping.environment.build.buildId); }); add_task(async function test_pingHasEnvironmentAndClientId() { // Send a ping with the environment data and client id. await sendPing(true, true); let ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, true); // Test a field in the environment build section. Assert.equal(ping.application.buildId, ping.environment.build.buildId); // Test that we have the correct clientId. Assert.equal(ping.clientId, gClientID, "The correct clientId must be reported."); }); add_task(async function test_archivePings() { let now = new Date(2009, 10, 18, 12, 0, 0); fakeNow(now); // Disable ping upload so that pings don't get sent. // With unified telemetry the FHR upload pref controls this, // with non-unified telemetry the Telemetry enabled pref. const isUnified = Preferences.get(TelemetryUtils.Preferences.Unified, false); const uploadPref = isUnified ? TelemetryUtils.Preferences.FhrUploadEnabled : TelemetryUtils.Preferences.TelemetryEnabled; Preferences.set(uploadPref, false); // If we're using unified telemetry, disabling ping upload will generate a "optout" // ping. Catch it. if (isUnified) { let ping = await PingServer.promiseNextPing(); checkPingFormat(ping, OPTOUT_PING_TYPE, false, false); } // Register a new Ping Handler that asserts if a ping is received, then send a ping. PingServer.registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to.")); let pingId = await sendPing(true, true); // Check that the ping was archived, even with upload disabled. let ping = await TelemetryArchive.promiseArchivedPingById(pingId); Assert.equal(ping.id, pingId, "TelemetryController should still archive pings."); // Check that pings don't get archived if not allowed to. now = new Date(2010, 10, 18, 12, 0, 0); fakeNow(now); Preferences.set(TelemetryUtils.Preferences.ArchiveEnabled, false); pingId = await sendPing(true, true); let promise = TelemetryArchive.promiseArchivedPingById(pingId); Assert.ok((await promiseRejects(promise)), "TelemetryController should not archive pings if the archive pref is disabled."); // Enable archiving and the upload so that pings get sent and archived again. Preferences.set(uploadPref, true); Preferences.set(TelemetryUtils.Preferences.ArchiveEnabled, true); now = new Date(2014, 6, 18, 22, 0, 0); fakeNow(now); // Restore the non asserting ping handler. PingServer.resetPingHandler(); pingId = await sendPing(true, true); // Check that we archive pings when successfully sending them. await PingServer.promiseNextPing(); ping = await TelemetryArchive.promiseArchivedPingById(pingId); Assert.equal(ping.id, pingId, "TelemetryController should still archive pings if ping upload is enabled."); }); // Test that we fuzz the submission time around midnight properly // to avoid overloading the telemetry servers. add_task(async function test_midnightPingSendFuzzing() { const fuzzingDelay = 60 * 60 * 1000; fakeMidnightPingFuzzingDelay(fuzzingDelay); let now = new Date(2030, 5, 1, 11, 0, 0); fakeNow(now); let waitForTimer = () => new Promise(resolve => { fakePingSendTimer((callback, timeout) => { resolve([callback, timeout]); }, () => {}); }); PingServer.clearRequests(); await TelemetryController.testReset(); // A ping after midnight within the fuzzing delay should not get sent. now = new Date(2030, 5, 2, 0, 40, 0); fakeNow(now); PingServer.registerPingHandler((req, res) => { Assert.ok(false, "No ping should be received yet."); }); let timerPromise = waitForTimer(); await sendPing(true, true); let [timerCallback, timerTimeout] = await timerPromise; Assert.ok(!!timerCallback); Assert.deepEqual(futureDate(now, timerTimeout), new Date(2030, 5, 2, 1, 0, 0)); // A ping just before the end of the fuzzing delay should not get sent. now = new Date(2030, 5, 2, 0, 59, 59); fakeNow(now); timerPromise = waitForTimer(); await sendPing(true, true); [timerCallback, timerTimeout] = await timerPromise; Assert.deepEqual(timerTimeout, 1 * 1000); // Restore the previous ping handler. PingServer.resetPingHandler(); // Setting the clock to after the fuzzing delay, we should trigger the two ping sends // with the timer callback. now = futureDate(now, timerTimeout); fakeNow(now); await timerCallback(); const pings = await PingServer.promiseNextPings(2); for (let ping of pings) { checkPingFormat(ping, TEST_PING_TYPE, true, true); } await TelemetrySend.testWaitOnOutgoingPings(); // Moving the clock further we should still send pings immediately. now = futureDate(now, 5 * 60 * 1000); await sendPing(true, true); let ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, true); await TelemetrySend.testWaitOnOutgoingPings(); // Check that pings shortly before midnight are immediately sent. now = fakeNow(2030, 5, 3, 23, 59, 0); await sendPing(true, true); ping = await PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, true); await TelemetrySend.testWaitOnOutgoingPings(); // Clean-up. fakeMidnightPingFuzzingDelay(0); fakePingSendTimer(() => {}, () => {}); }); add_task(async function test_changePingAfterSubmission() { // Submit a ping with a custom payload. let payload = { canary: "test" }; let pingPromise = TelemetryController.submitExternalPing(TEST_PING_TYPE, payload); // Change the payload with a predefined value. payload.canary = "changed"; // Wait for the ping to be archived. const pingId = await pingPromise; // Make sure our changes didn't affect the submitted payload. let archivedCopy = await TelemetryArchive.promiseArchivedPingById(pingId); Assert.equal(archivedCopy.payload.canary, "test", "The payload must not be changed after being submitted."); }); add_task({ skip_if: () => Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false)}, async function test_telemetryEnabledUnexpectedValue() { // Remove the default value for toolkit.telemetry.enabled from the default prefs. // Otherwise, we wouldn't be able to set the pref to a string. let defaultPrefBranch = Services.prefs.getDefaultBranch(null); defaultPrefBranch.deleteBranch(TelemetryUtils.Preferences.TelemetryEnabled); // Set the preferences controlling the Telemetry status to a string. Preferences.set(TelemetryUtils.Preferences.TelemetryEnabled, "false"); // Check that Telemetry is not enabled. await TelemetryController.testReset(); Assert.equal(Telemetry.canRecordExtended, false, "Invalid values must not enable Telemetry recording."); // Delete the pref again. defaultPrefBranch.deleteBranch(TelemetryUtils.Preferences.TelemetryEnabled); // Make sure that flipping it to true works. Preferences.set(TelemetryUtils.Preferences.TelemetryEnabled, true); await TelemetryController.testReset(); Assert.equal(Telemetry.canRecordExtended, true, "True must enable Telemetry recording."); // Also check that the false works as well. Preferences.set(TelemetryUtils.Preferences.TelemetryEnabled, false); await TelemetryController.testReset(); Assert.equal(Telemetry.canRecordExtended, false, "False must disable Telemetry recording."); // Restore the state of the pref. Preferences.set(TelemetryUtils.Preferences.TelemetryEnabled, true); }); add_task(async function test_telemetryCleanFHRDatabase() { const FHR_DBNAME_PREF = "datareporting.healthreport.dbName"; const CUSTOM_DB_NAME = "unlikely.to.be.used.sqlite"; const DEFAULT_DB_NAME = "healthreport.sqlite"; // Check that we're able to remove a FHR DB with a custom name. const CUSTOM_DB_PATHS = [ OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME), OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME + "-wal"), OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME + "-shm"), ]; Preferences.set(FHR_DBNAME_PREF, CUSTOM_DB_NAME); // Write fake DB files to the profile directory. for (let dbFilePath of CUSTOM_DB_PATHS) { await OS.File.writeAtomic(dbFilePath, "some data"); } // Trigger the cleanup and check that the files were removed. await TelemetryStorage.removeFHRDatabase(); for (let dbFilePath of CUSTOM_DB_PATHS) { Assert.ok(!(await OS.File.exists(dbFilePath)), "The DB must not be on the disk anymore: " + dbFilePath); } // We should not break anything if there's no DB file. await TelemetryStorage.removeFHRDatabase(); // Check that we're able to remove a FHR DB with the default name. Preferences.reset(FHR_DBNAME_PREF); const DEFAULT_DB_PATHS = [ OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME), OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME + "-wal"), OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME + "-shm"), ]; // Write fake DB files to the profile directory. for (let dbFilePath of DEFAULT_DB_PATHS) { await OS.File.writeAtomic(dbFilePath, "some data"); } // Trigger the cleanup and check that the files were removed. await TelemetryStorage.removeFHRDatabase(); for (let dbFilePath of DEFAULT_DB_PATHS) { Assert.ok(!(await OS.File.exists(dbFilePath)), "The DB must not be on the disk anymore: " + dbFilePath); } }); add_task(async function test_sendNewProfile() { if (gIsAndroid || (AppConstants.platform == "linux" && OS.Constants.Sys.bits == 32)) { // We don't support the pingsender on Android, yet, see bug 1335917. // We also don't suppor the pingsender testing on Treeherder for // Linux 32 bit (due to missing libraries). So skip it there too. // See bug 1310703 comment 78. return; } const NEWPROFILE_PING_TYPE = "new-profile"; const PREF_NEWPROFILE_ENABLED = "toolkit.telemetry.newProfilePing.enabled"; const PREF_NEWPROFILE_DELAY = "toolkit.telemetry.newProfilePing.delay"; // Make sure Telemetry is shut down before beginning and that we have // no pending pings. let resetTest = async function() { await TelemetryController.testShutdown(); await TelemetryStorage.testClearPendingPings(); PingServer.clearRequests(); }; await resetTest(); // Make sure to reset all the new-profile ping prefs. const stateFilePath = OS.Path.join(DATAREPORTING_PATH, "session-state.json"); await OS.File.remove(stateFilePath, { ignoreAbsent: true }); Preferences.set(PREF_NEWPROFILE_DELAY, 1); Preferences.set(PREF_NEWPROFILE_ENABLED, true); // Check that a new-profile ping is sent on the first session. let nextReq = PingServer.promiseNextRequest(); await TelemetryController.testReset(); let req = await nextReq; let ping = decodeRequestPayload(req); checkPingFormat(ping, NEWPROFILE_PING_TYPE, true, true); Assert.equal(ping.payload.reason, "startup", "The new-profile ping generated after startup must have the correct reason"); // Check that is not sent with the pingsender during startup. Assert.throws(() => req.getHeader("X-PingSender-Version"), /NS_ERROR_NOT_AVAILABLE/, "Should not have used the pingsender."); // Make sure that the new-profile ping is sent at shutdown if it wasn't sent before. await resetTest(); await OS.File.remove(stateFilePath, { ignoreAbsent: true }); Preferences.reset(PREF_NEWPROFILE_DELAY); nextReq = PingServer.promiseNextRequest(); await TelemetryController.testReset(); await TelemetryController.testShutdown(); req = await nextReq; ping = decodeRequestPayload(req); checkPingFormat(ping, NEWPROFILE_PING_TYPE, true, true); Assert.equal(ping.payload.reason, "shutdown", "The new-profile ping generated at shutdown must have the correct reason"); // Check that the new-profile ping is sent at shutdown using the pingsender. Assert.equal(req.getHeader("User-Agent"), "pingsender/1.0", "Should have received the correct user agent string."); Assert.equal(req.getHeader("X-PingSender-Version"), "1.0", "Should have received the correct PingSender version string."); // Check that no new-profile ping is sent on second sessions, not at startup // nor at shutdown. await resetTest(); PingServer.registerPingHandler( () => Assert.ok(false, "The new-profile ping must be sent only on new profiles.")); await TelemetryController.testReset(); await TelemetryController.testShutdown(); // Check that we don't send the new-profile ping if the profile already contains // a state file (but no "newProfilePingSent" property). await resetTest(); await OS.File.remove(stateFilePath, { ignoreAbsent: true }); const sessionState = { sessionId: null, subsessionId: null, profileSubsessionCounter: 3785, }; await CommonUtils.writeJSON(sessionState, stateFilePath); await TelemetryController.testReset(); await TelemetryController.testShutdown(); // Reset the pref and restart Telemetry. Preferences.reset(PREF_NEWPROFILE_ENABLED); PingServer.resetPingHandler(); }); // Testing shutdown and checking that pings sent afterwards are rejected. add_task(async function test_pingRejection() { await TelemetryController.testReset(); await TelemetryController.testShutdown(); await sendPing(false, false) .then(() => Assert.ok(false, "Pings submitted after shutdown must be rejected."), () => Assert.ok(true, "Ping submitted after shutdown correctly rejected.")); }); add_task(async function test_newCanRecordsMatchTheOld() { Assert.equal(Telemetry.canRecordBase, Telemetry.canRecordReleaseData, "Release Data is the new way to say Base Collection"); Assert.equal(Telemetry.canRecordExtended, Telemetry.canRecordPrereleaseData, "Prerelease Data is the new way to say Extended Collection"); }); add_task(async function stopServer() { await PingServer.stop(); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js --- a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js @@ -1,826 +1,826 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ ChromeUtils.defineModuleGetter(this, "TestUtils", "resource://testing-common/TestUtils.jsm"); const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN; const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT; function checkEventFormat(events) { Assert.ok(Array.isArray(events), "Events should be serialized to an array."); for (let e of events) { Assert.ok(Array.isArray(e), "Event should be an array."); Assert.greaterOrEqual(e.length, 4, "Event should have at least 4 elements."); Assert.lessOrEqual(e.length, 6, "Event should have at most 6 elements."); Assert.equal(typeof(e[0]), "number", "Element 0 should be a number."); Assert.equal(typeof(e[1]), "string", "Element 1 should be a string."); Assert.equal(typeof(e[2]), "string", "Element 2 should be a string."); Assert.equal(typeof(e[3]), "string", "Element 3 should be a string."); if (e.length > 4) { Assert.ok(e[4] === null || typeof(e[4]) == "string", "Event element 4 should be null or a string."); } if (e.length > 5) { Assert.ok(e[5] === null || typeof(e[5]) == "object", "Event element 5 should be null or an object."); } let extra = e[5]; if (extra) { Assert.ok(Object.keys(extra).every(k => typeof(k) == "string"), "All extra keys should be strings."); Assert.ok(Object.values(extra).every(v => typeof(v) == "string"), "All extra values should be strings."); } } } /** * @param summaries is of the form * [{process, [event category, event object, event method], count}] * @param clearScalars - true if you want to clear the scalars */ function checkEventSummary(summaries, clearScalars) { - let scalars = Telemetry.snapshotKeyedScalars(OPTOUT, clearScalars); + let scalars = Telemetry.getSnapshotForKeyedScalars("main", clearScalars); for (let [process, [category, eObject, method], count] of summaries) { let uniqueEventName = `${category}#${eObject}#${method}`; let summaryCount; if (process === "dynamic") { summaryCount = scalars.dynamic["telemetry.dynamic_event_counts"][uniqueEventName]; } else { summaryCount = scalars[process]["telemetry.event_counts"][uniqueEventName]; } Assert.equal(summaryCount, count, `${uniqueEventName} had wrong summary count`); } } function checkRegistrationFailure(failureType) { - let snapshot = Telemetry.snapshotHistograms(OPTIN, true); + let snapshot = Telemetry.getSnapshotForHistograms("main", true); Assert.ok("parent" in snapshot, "There should be at least one parent histogram when checking for registration failures."); Assert.ok("TELEMETRY_EVENT_REGISTRATION_ERROR" in snapshot.parent, "TELEMETRY_EVENT_REGISTRATION_ERROR should exist when checking for registration failures."); let values = snapshot.parent.TELEMETRY_EVENT_REGISTRATION_ERROR.values; Assert.ok(!!values, "TELEMETRY_EVENT_REGISTRATION_ERROR's values should exist when checking for registration failures."); Assert.equal(values[failureType], 1, `Event registration ought to have failed due to type ${failureType}`); } function checkRecordingFailure(failureType) { - let snapshot = Telemetry.snapshotHistograms(OPTIN, true); + let snapshot = Telemetry.getSnapshotForHistograms("main", true); Assert.ok("parent" in snapshot, "There should be at least one parent histogram when checking for recording failures."); Assert.ok("TELEMETRY_EVENT_RECORDING_ERROR" in snapshot.parent, "TELEMETRY_EVENT_RECORDING_ERROR should exist when checking for recording failures."); let values = snapshot.parent.TELEMETRY_EVENT_RECORDING_ERROR.values; Assert.ok(!!values, "TELEMETRY_EVENT_RECORDING_ERROR's values should exist when checking for recording failures."); Assert.equal(values[failureType], 1, `Event recording ought to have failed due to type ${failureType}`); } add_task(async function test_event_summary_limit() { if (AppConstants.DEBUG) { // This test will intentionally assert in DEBUG builds return; } Telemetry.clearEvents(); Telemetry.clearScalars(); const limit = 50; Services.prefs.setIntPref("toolkit.telemetry.maxEventSummaryKeys", limit); let objects = []; for (let i = 0; i < limit + 1; i++) { objects.push("object" + i); } // Using "telemetry.test.dynamic" as using "telemetry.test" will enable // the "telemetry.test" category. Telemetry.registerEvents("telemetry.test.dynamic", { test_method: { methods: ["testMethod"], objects, record_on_release: true, }, }); for (let object of objects) { Telemetry.recordEvent("telemetry.test.dynamic", "testMethod", object); } let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, limit + 1, "Should have recorded all events"); - let scalarSnapshot = Telemetry.snapshotKeyedScalars(OPTOUT, true); + let scalarSnapshot = Telemetry.getSnapshotForKeyedScalars("main", true); Assert.equal(Object.keys(scalarSnapshot.dynamic["telemetry.dynamic_event_counts"]).length, limit, "Should not have recorded more than `limit` events"); }); add_task(async function test_recording_state() { Telemetry.clearEvents(); Telemetry.clearScalars(); const events = [ ["telemetry.test", "test1", "object1"], ["telemetry.test.second", "test", "object1"], ]; // Both test categories should be off by default. events.forEach(e => Telemetry.recordEvent(...e)); let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not have recorded any events."); checkEventSummary(events.map(e => (["parent", e, 1])), true); // Enable one test category and see that we record correctly. Telemetry.setEventRecordingEnabled("telemetry.test", true); events.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 1, "Should have recorded one event."); Assert.equal(snapshot.parent[0][1], "telemetry.test", "Should have recorded one event in telemetry.test"); checkEventSummary(events.map(e => (["parent", e, 1])), true); // Also enable the other test category and see that we record correctly. Telemetry.setEventRecordingEnabled("telemetry.test.second", true); events.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 2, "Should have recorded two events."); Assert.equal(snapshot.parent[0][1], "telemetry.test", "Should have recorded one event in telemetry.test"); Assert.equal(snapshot.parent[1][1], "telemetry.test.second", "Should have recorded one event in telemetry.test.second"); checkEventSummary(events.map(e => (["parent", e, 1])), true); // Now turn of one category again and check that this works as expected. Telemetry.setEventRecordingEnabled("telemetry.test", false); events.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 1, "Should have recorded one event."); Assert.equal(snapshot.parent[0][1], "telemetry.test.second", "Should have recorded one event in telemetry.test.second"); checkEventSummary(events.map(e => (["parent", e, 1])), true); }); add_task(async function recording_setup() { // Make sure both test categories are enabled for the remaining tests. // Otherwise their event recording won't work. Telemetry.setEventRecordingEnabled("telemetry.test", true); Telemetry.setEventRecordingEnabled("telemetry.test.second", true); }); add_task(async function test_recording() { Telemetry.clearScalars(); Telemetry.clearEvents(); // Record some events. let expected = [ {optout: false, event: ["telemetry.test", "test1", "object1"]}, {optout: false, event: ["telemetry.test", "test2", "object2"]}, {optout: false, event: ["telemetry.test", "test1", "object1", "value"]}, {optout: false, event: ["telemetry.test", "test1", "object1", "value", null]}, {optout: false, event: ["telemetry.test", "test1", "object1", null, {"key1": "value1"}]}, {optout: false, event: ["telemetry.test", "test1", "object1", "value", {"key1": "value1", "key2": "value2"}]}, {optout: true, event: ["telemetry.test", "optout", "object1"]}, {optout: false, event: ["telemetry.test.second", "test", "object1"]}, {optout: false, event: ["telemetry.test.second", "test", "object1", null, {"key1": "value1"}]}, ]; for (let entry of expected) { entry.tsBefore = Math.floor(Telemetry.msSinceProcessStart()); try { Telemetry.recordEvent(...entry.event); } catch (ex) { Assert.ok(false, `Failed to record event ${JSON.stringify(entry.event)}: ${ex}`); } entry.tsAfter = Math.floor(Telemetry.msSinceProcessStart()); } // Strip off trailing null values to match the serialized events. for (let entry of expected) { let e = entry.event; while ((e.length >= 3) && (e[e.length - 1] === null)) { e.pop(); } } // Check that the events were summarized properly. let summaries = {}; expected.forEach(({optout, event}) => { let [category, eObject, method] = event; let uniqueEventName = `${category}#${eObject}#${method}`; if (!(uniqueEventName in summaries)) { summaries[uniqueEventName] = ["parent", event, 1]; } else { summaries[uniqueEventName][2]++; } }); checkEventSummary(Object.values(summaries), true); // The following should not result in any recorded events. Assert.throws(() => Telemetry.recordEvent("unknown.category", "test1", "object1"), /Error: Unknown event: \["unknown.category", "test1", "object1"\]/, "Should throw on unknown category."); checkRecordingFailure(0 /* UnknownEvent */); Assert.throws(() => Telemetry.recordEvent("telemetry.test", "unknown", "object1"), /Error: Unknown event: \["telemetry.test", "unknown", "object1"\]/, "Should throw on unknown method."); checkRecordingFailure(0 /* UnknownEvent */); Assert.throws(() => Telemetry.recordEvent("telemetry.test", "test1", "unknown"), /Error: Unknown event: \["telemetry.test", "test1", "unknown"\]/, "Should throw on unknown object."); checkRecordingFailure(0 /* UnknownEvent */); let checkEvents = (events, expectedEvents) => { checkEventFormat(events); Assert.equal(events.length, expectedEvents.length, "Snapshot should have the right number of events."); for (let i = 0; i < events.length; ++i) { let {tsBefore, tsAfter} = expectedEvents[i]; let ts = events[i][0]; Assert.greaterOrEqual(ts, tsBefore, "The recorded timestamp should be greater than the one before recording."); Assert.lessOrEqual(ts, tsAfter, "The recorded timestamp should be less than the one after recording."); let recordedData = events[i].slice(1); let expectedData = expectedEvents[i].event.slice(); Assert.deepEqual(recordedData, expectedData, "The recorded event data should match."); } }; // Check that the expected events were recorded. let snapshot = Telemetry.snapshotEvents(OPTIN, false); Assert.ok(("parent" in snapshot), "Should have entry for main process."); checkEvents(snapshot.parent, expected); // Check serializing only opt-out events. snapshot = Telemetry.snapshotEvents(OPTOUT, false); Assert.ok(("parent" in snapshot), "Should have entry for main process."); let filtered = expected.filter(e => !!e.optout); checkEvents(snapshot.parent, filtered); }); add_task(async function test_clear() { Telemetry.clearEvents(); const COUNT = 10; for (let i = 0; i < COUNT; ++i) { Telemetry.recordEvent("telemetry.test", "test1", "object1"); Telemetry.recordEvent("telemetry.test.second", "test", "object1"); } // Check that events were recorded. // The events are cleared by passing the respective flag. let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 2 * COUNT, `Should have recorded ${2 * COUNT} events.`); // Now the events should be cleared. snapshot = Telemetry.snapshotEvents(OPTIN, false); Assert.equal(Object.keys(snapshot).length, 0, `Should have cleared the events.`); for (let i = 0; i < COUNT; ++i) { Telemetry.recordEvent("telemetry.test", "test1", "object1"); Telemetry.recordEvent("telemetry.test.second", "test", "object1"); } snapshot = Telemetry.snapshotEvents(OPTIN, true, 5); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 5, "Should have returned 5 events"); snapshot = Telemetry.snapshotEvents(OPTIN, false); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, (2 * COUNT) - 5, `Should have returned ${(2 * COUNT) - 5} events`); Telemetry.recordEvent("telemetry.test", "test1", "object1"); snapshot = Telemetry.snapshotEvents(OPTIN, false, 5); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 5, "Should have returned 5 events"); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, (2 * COUNT) - 5 + 1, `Should have returned ${(2 * COUNT) - 5 + 1} events`); }); add_task(async function test_expiry() { Telemetry.clearEvents(); // Recording call with event that is expired by version. Telemetry.recordEvent("telemetry.test", "expired_version", "object1"); checkRecordingFailure(1 /* Expired */); let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not record event with expired version."); // Recording call with event that has expiry_version set into the future. Telemetry.recordEvent("telemetry.test", "not_expired_optout", "object1"); snapshot = Telemetry.snapshotEvents(OPTOUT, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); Assert.equal(snapshot.parent.length, 1, "Should record event when version is not expired."); }); add_task(async function test_invalidParams() { Telemetry.clearEvents(); // Recording call with wrong type for value argument. Telemetry.recordEvent("telemetry.test", "test1", "object1", 1); let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when value argument with invalid type is passed."); checkRecordingFailure(3 /* Value */); // Recording call with wrong type for extra argument. Telemetry.recordEvent("telemetry.test", "test1", "object1", null, "invalid"); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid type is passed."); checkRecordingFailure(4 /* Extra */); // Recording call with unknown extra key. Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key3": "x"}); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid key is passed."); checkRecordingFailure(2 /* ExtraKey */); // Recording call with invalid value type. Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key3": 1}); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid value type is passed."); checkRecordingFailure(4 /* Extra */); }); add_task(async function test_storageLimit() { Telemetry.clearEvents(); let limitReached = TestUtils.topicObserved("event-telemetry-storage-limit-reached"); // Record more events than the storage limit allows. let LIMIT = 1000; let COUNT = LIMIT + 10; for (let i = 0; i < COUNT; ++i) { Telemetry.recordEvent("telemetry.test", "test1", "object1", String(i)); } await limitReached; Assert.ok(true, "Topic was notified when event limit was reached"); // Check that the right events were recorded. let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); let events = snapshot.parent; Assert.equal(events.length, COUNT, `Should have only recorded all ${COUNT} events`); Assert.ok(events.every((e, idx) => e[4] === String(idx)), "Should have recorded all events."); }); add_task(async function test_valueLimits() { Telemetry.clearEvents(); // Record values that are at or over the limits for string lengths. let LIMIT = 80; let expected = [ ["telemetry.test", "test1", "object1", "a".repeat(LIMIT - 10), null], ["telemetry.test", "test1", "object1", "a".repeat(LIMIT ), null], ["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 1), null], ["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 10), null], ["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT - 10)}], ["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT )}], ["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT + 1)}], ["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT + 10)}], ]; for (let event of expected) { Telemetry.recordEvent(...event); if (event[3]) { event[3] = event[3].substr(0, LIMIT); } if (event[4]) { event[4].key1 = event[4].key1.substr(0, LIMIT); } } // Strip off trailing null values to match the serialized events. for (let e of expected) { while ((e.length >= 3) && (e[e.length - 1] === null)) { e.pop(); } } // Check that the right events were recorded. let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); let events = snapshot.parent; Assert.equal(events.length, expected.length, "Should have recorded the expected number of events"); for (let i = 0; i < expected.length; ++i) { Assert.deepEqual(events[i].slice(1), expected[i], "Should have recorded the expected event data."); } }); add_task(async function test_unicodeValues() { Telemetry.clearEvents(); // Record string values containing unicode characters. let value = "漢語"; Telemetry.recordEvent("telemetry.test", "test1", "object1", value); Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key1": value}); // Check that the values were correctly recorded. let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(("parent" in snapshot), "Should have entry for main process."); let events = snapshot.parent; Assert.equal(events.length, 2, "Should have recorded 2 events."); Assert.equal(events[0][4], value, "Should have recorded the right value."); Assert.equal(events[1][5].key1, value, "Should have recorded the right extra value."); }); add_task(async function test_dynamicEvents() { Telemetry.clearEvents(); Telemetry.clearScalars(); Telemetry.canRecordExtended = true; // Register some test events. Telemetry.registerEvents("telemetry.test.dynamic", { // Event with only required fields. "test1": { methods: ["test1"], objects: ["object1"], }, // Event with extra_keys. "test2": { methods: ["test2", "test2b"], objects: ["object1"], extra_keys: ["key1", "key2"], }, // Expired event. "test3": { methods: ["test3"], objects: ["object1"], expired: true, }, // A release-channel recording event. "test4": { methods: ["test4"], objects: ["object1"], record_on_release: true, }, }); // Record some valid events. Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1"); Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null, {"key1": "foo", "key2": "bar"}); Telemetry.recordEvent("telemetry.test.dynamic", "test2b", "object1", null, {"key1": "foo", "key2": "bar"}); Telemetry.recordEvent("telemetry.test.dynamic", "test3", "object1", "some value"); Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1", null); // Test recording an unknown event. Assert.throws(() => Telemetry.recordEvent("telemetry.test.dynamic", "unknown", "unknown"), /Error: Unknown event: \["telemetry\.test\.dynamic", "unknown", "unknown"\]/, "Should throw when recording an unknown dynamic event."); checkRecordingFailure(0 /* UnknownEvent */); // Now check that the snapshot contains the expected data. let snapshot = Telemetry.snapshotEvents(OPTIN, false); Assert.ok(("dynamic" in snapshot), "Should have dynamic events in the snapshot."); let expected = [ ["telemetry.test.dynamic", "test1", "object1"], ["telemetry.test.dynamic", "test2", "object1", null, {key1: "foo", key2: "bar"}], ["telemetry.test.dynamic", "test2b", "object1", null, {key1: "foo", key2: "bar"}], // "test3" is epxired, so it should not be recorded. ["telemetry.test.dynamic", "test4", "object1"], ]; let events = snapshot.dynamic; Assert.equal(events.length, expected.length, "Should have recorded the right amount of events."); for (let i = 0; i < expected.length; ++i) { Assert.deepEqual(events[i].slice(1), expected[i], "Should have recorded the expected event data."); } // Check that we've summarized the recorded events checkEventSummary(expected.map(ev => ["dynamic", ev, 1]), true); // Check that the opt-out snapshot contains only the one expected event. snapshot = Telemetry.snapshotEvents(OPTOUT, false); Assert.ok(("dynamic" in snapshot), "Should have dynamic events in the snapshot."); Assert.equal(snapshot.dynamic.length, 1, "Should have one opt-out event in the snapshot."); expected = ["telemetry.test.dynamic", "test4", "object1"]; Assert.deepEqual(snapshot.dynamic[0].slice(1), expected); // Recording with unknown extra keys should be ignored and print an error. Telemetry.clearEvents(); Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1", null, {"key1": "foo"}); Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null, {"key1": "foo", "unknown": "bar"}); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(!("dynamic" in snapshot), "Should have not recorded dynamic events with unknown extra keys."); // Other built-in events should not show up in the "dynamic" bucket of the snapshot. Telemetry.recordEvent("telemetry.test", "test1", "object1"); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.ok(!("dynamic" in snapshot), "Should have not recorded built-in event into dynamic bucket."); // Test that recording opt-in and opt-out events works as expected. Telemetry.clearEvents(); Telemetry.canRecordExtended = false; Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1"); Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1"); expected = [ // Only "test4" should have been recorded. ["telemetry.test.dynamic", "test4", "object1"], ]; snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, 1, "Should have one opt-out event in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); }); add_task(async function test_dynamicEventRegistrationValidation() { Telemetry.canRecordExtended = true; Telemetry.clearEvents(); // Test registration of invalid categories. - Telemetry.snapshotHistograms(OPTIN, true); // Clear histograms before we begin. + Telemetry.getSnapshotForHistograms("main", true); // Clear histograms before we begin. Assert.throws(() => Telemetry.registerEvents("telemetry+test+dynamic", { "test1": { methods: ["test1"], objects: ["object1"], }, }), /Category parameter should match the identifier pattern\./, "Should throw when registering category names with invalid characters."); checkRegistrationFailure(2 /* Category */); Assert.throws(() => Telemetry.registerEvents("telemetry.test.test.test.test.test.test.test.test", { "test1": { methods: ["test1"], objects: ["object1"], }, }), /Category parameter should match the identifier pattern\./, "Should throw when registering overly long category names."); checkRegistrationFailure(2 /* Category */); // Test registration of invalid event names. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic1", { "test?1": { methods: ["test1"], objects: ["object1"], }, }), /Event names should match the identifier pattern\./, "Should throw when registering event names with invalid characters."); checkRegistrationFailure(1 /* Name */); Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic2", { "test1test1test1test1test1test1test1": { methods: ["test1"], objects: ["object1"], }, }), /Event names should match the identifier pattern\./, "Should throw when registering overly long event names."); checkRegistrationFailure(1 /* Name */); // Test registration of invalid method names. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic3", { "test1": { methods: ["test?1"], objects: ["object1"], }, }), /Method names should match the identifier pattern\./, "Should throw when registering method names with invalid characters."); checkRegistrationFailure(3 /* Method */); Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic", { "test1": { methods: ["test1test1test1test1test1test1test1"], objects: ["object1"], }, }), /Method names should match the identifier pattern\./, "Should throw when registering overly long method names."); checkRegistrationFailure(3 /* Method */); // Test registration of invalid object names. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic4", { "test1": { methods: ["test1"], objects: ["object?1"], }, }), /Object names should match the identifier pattern\./, "Should throw when registering object names with invalid characters."); checkRegistrationFailure(4 /* Object */); Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic5", { "test1": { methods: ["test1"], objects: ["object1object1object1object1object1object1"], }, }), /Object names should match the identifier pattern\./, "Should throw when registering overly long object names."); checkRegistrationFailure(4 /* Object */); // Test validation of invalid key names. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic6", { "test1": { methods: ["test1"], objects: ["object1"], extra_keys: ["a?1"], }, }), /Extra key names should match the identifier pattern\./, "Should throw when registering extra key names with invalid characters."); checkRegistrationFailure(5 /* ExtraKeys */); // Test validation of key names that are too long - we allow a maximum of 15 characters. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic7", { "test1": { methods: ["test1"], objects: ["object1"], extra_keys: ["a012345678901234"], }, }), /Extra key names should match the identifier pattern\./, "Should throw when registering extra key names which are too long."); checkRegistrationFailure(5 /* ExtraKeys */); Telemetry.registerEvents("telemetry.test.dynamic8", { "test1": { methods: ["test1"], objects: ["object1"], extra_keys: ["a01234567890123"], }, }); // Test validation of extra key count - we only allow 10. Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic9", { "test1": { methods: ["test1"], objects: ["object1"], extra_keys: ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11"], }, }), /No more than 10 extra keys can be registered\./, "Should throw when registering too many extra keys."); checkRegistrationFailure(5 /* ExtraKeys */); Telemetry.registerEvents("telemetry.test.dynamic10", { "test1": { methods: ["test1"], objects: ["object1"], extra_keys: ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10"], }, }); }); // When add-ons update, they may re-register some of the dynamic events. // Test through some possible scenarios. add_task(async function test_dynamicEventRegisterAgain() { Telemetry.canRecordExtended = true; Telemetry.clearEvents(); const category = "telemetry.test.register.again"; let events = { "test1": { methods: ["test1"], objects: ["object1"], }, }; // First register the initial event and make sure it can be recorded. Telemetry.registerEvents(category, events); let expected = [ [category, "test1", "object1"], ]; expected.forEach(e => Telemetry.recordEvent(...e)); let snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, expected.length, "Should have right number of events in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); // Register the same event again and make sure it can still be recorded. Telemetry.registerEvents(category, events); Telemetry.recordEvent(category, "test1", "object1"); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, expected.length, "Should have right number of events in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); // Now register another event in the same category and make sure both events can be recorded. events.test2 = { methods: ["test2"], objects: ["object2"], }; Telemetry.registerEvents(category, events); expected = [ [category, "test1", "object1"], [category, "test2", "object2"], ]; expected.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, expected.length, "Should have right number of events in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); // Check that adding a new object to an event entry works. events.test1.methods = ["test1a"]; events.test2.objects = ["object2", "object2a"]; Telemetry.registerEvents(category, events); expected = [ [category, "test1", "object1"], [category, "test2", "object2"], [category, "test1a", "object1"], [category, "test2", "object2a"], ]; expected.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, expected.length, "Should have right number of events in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); // Make sure that we can expire events that are already registered. events.test2.expired = true; Telemetry.registerEvents(category, events); expected = [ [category, "test1", "object1"], ]; expected.forEach(e => Telemetry.recordEvent(...e)); snapshot = Telemetry.snapshotEvents(OPTIN, true); Assert.equal(snapshot.dynamic.length, expected.length, "Should have right number of events in the snapshot."); Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected); }); add_task({ skip_if: () => gIsAndroid, }, async function test_productSpecificEvents() { const EVENT_CATEGORY = "telemetry.test"; const DEFAULT_PRODUCTS_EVENT = "default_products"; const DESKTOP_ONLY_EVENT = "desktop_only"; const MULTIPRODUCT_EVENT = "multiproduct"; const MOBILE_ONLY_EVENT = "mobile_only"; Telemetry.clearEvents(); // Try to record the desktop and multiproduct event Telemetry.recordEvent(EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"); Telemetry.recordEvent(EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1"); Telemetry.recordEvent(EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"); // Try to record the mobile-only event Telemetry.recordEvent(EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1"); let events = Telemetry.snapshotEvents(OPTIN, true).parent; let expected = [ [EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"], [EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1"], [EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"], ]; Assert.equal(events.length, expected.length, "Should have recorded the right amount of events."); for (let i = 0; i < expected.length; ++i) { Assert.deepEqual(events[i].slice(1), expected[i], "Should have recorded the expected event data."); } }); add_task({ skip_if: () => !gIsAndroid, }, async function test_mobileSpecificEvents() { const EVENT_CATEGORY = "telemetry.test"; const DEFAULT_PRODUCTS_EVENT = "default_products"; const DESKTOP_ONLY_EVENT = "desktop_only"; const MULTIPRODUCT_EVENT = "multiproduct"; const MOBILE_ONLY_EVENT = "mobile_only"; Telemetry.clearEvents(); // Try to record the mobile-only and multiproduct event Telemetry.recordEvent(EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"); Telemetry.recordEvent(EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1"); Telemetry.recordEvent(EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"); // Try to record the mobile-only event Telemetry.recordEvent(EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1"); let events = Telemetry.snapshotEvents(OPTIN, true).parent; let expected = [ [EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"], [EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1"], [EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"], ]; Assert.equal(events.length, expected.length, "Should have recorded the right amount of events."); for (let i = 0; i < expected.length; ++i) { Assert.deepEqual(events[i].slice(1), expected[i], "Should have recorded the expected event data."); } }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js --- a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js @@ -1,1180 +1,1173 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const INT_MAX = 0x7FFFFFFF; ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this); // Return an array of numbers from lower up to, excluding, upper function numberRange(lower, upper) { let a = []; for (let i = lower; i < upper; ++i) { a.push(i); } return a; } function expect_fail(f) { let failed = false; try { f(); failed = false; } catch (e) { failed = true; } Assert.ok(failed); } function expect_success(f) { let succeeded = false; try { f(); succeeded = true; } catch (e) { succeeded = false; } Assert.ok(succeeded); } function check_histogram(histogram_type, name, min, max, bucket_count) { var h = Telemetry.getHistogramById(name); h.add(0); var s = h.snapshot(); Assert.equal(0, s.sum); - var hgrams = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false).parent; + var hgrams = Telemetry.getSnapshotForHistograms("main", false).parent; let gh = hgrams[name]; Assert.equal(gh.histogram_type, histogram_type); Assert.deepEqual(gh.range, [min, max]); // Check that booleans work with nonboolean histograms h.add(false); h.add(true); s = Object.values(h.snapshot().values); Assert.deepEqual(s, [2, 1, 0]); // Check that clearing works. h.clear(); s = h.snapshot(); Assert.deepEqual(s.values, {}); Assert.equal(s.sum, 0); h.add(0); h.add(1); var c = Object.values(h.snapshot().values); Assert.deepEqual(c, [1, 1, 0]); } // This MUST be the very first test of this file. add_task({ skip_if: () => gIsAndroid, }, function test_instantiate() { const ID = "TELEMETRY_TEST_COUNT"; let h = Telemetry.getHistogramById(ID); // Instantiate the subsession histogram through |add| and make sure they match. // This MUST be the first use of "TELEMETRY_TEST_COUNT" in this file, otherwise // |add| will not instantiate the histogram. h.add(1); let snapshot = h.snapshot(); - let subsession = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */).parent; + let subsession = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(ID in subsession); Assert.equal(snapshot.sum, subsession[ID].sum, "Histogram and subsession histogram sum must match."); // Clear the histogram, so we don't void the assumptions from the other tests. h.clear(); }); add_task(async function test_parameterChecks() { let kinds = [Telemetry.HISTOGRAM_EXPONENTIAL, Telemetry.HISTOGRAM_LINEAR]; let testNames = ["TELEMETRY_TEST_EXPONENTIAL", "TELEMETRY_TEST_LINEAR"]; for (let i = 0; i < kinds.length; i++) { let histogram_type = kinds[i]; let test_type = testNames[i]; let [min, max, bucket_count] = [1, INT_MAX - 1, 10]; check_histogram(histogram_type, test_type, min, max, bucket_count); } }); add_task(async function test_parameterCounts() { let histogramIds = [ "TELEMETRY_TEST_EXPONENTIAL", "TELEMETRY_TEST_LINEAR", "TELEMETRY_TEST_FLAG", "TELEMETRY_TEST_CATEGORICAL", "TELEMETRY_TEST_BOOLEAN", ]; for (let id of histogramIds) { let h = Telemetry.getHistogramById(id); h.clear(); h.add(); Assert.equal(h.snapshot().sum, 0, "Calling add() without a value should only log an error."); h.clear(); } }); add_task(async function test_parameterCountsKeyed() { let histogramIds = [ "TELEMETRY_TEST_KEYED_FLAG", "TELEMETRY_TEST_KEYED_BOOLEAN", "TELEMETRY_TEST_KEYED_EXPONENTIAL", "TELEMETRY_TEST_KEYED_LINEAR", ]; for (let id of histogramIds) { let h = Telemetry.getKeyedHistogramById(id); h.clear(); h.add("key"); Assert.equal(h.snapshot("key").sum, 0, "Calling add('key') without a value should only log an error."); h.clear(); } }); add_task(async function test_noSerialization() { // Instantiate the storage for this histogram and make sure it doesn't // get reflected into JS, as it has no interesting data in it. Telemetry.getHistogramById("NEWTAB_PAGE_PINNED_SITES_COUNT"); - let histograms = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */).parent; + let histograms = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.equal(false, "NEWTAB_PAGE_PINNED_SITES_COUNT" in histograms); }); add_task(async function test_boolean_histogram() { var h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN"); var r = h.snapshot().range; // boolean histograms ignore numeric parameters Assert.deepEqual(r, [1, 2]); h.add(0); h.add(1); h.add(2); h.add(true); h.add(false); var s = h.snapshot(); Assert.equal(s.histogram_type, Telemetry.HISTOGRAM_BOOLEAN); // last bucket should always be 0 since .add parameters are normalized to either 0 or 1 Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0}); Assert.equal(s.sum, 3); }); add_task(async function test_flag_histogram() { var h = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG"); var r = h.snapshot().range; // Flag histograms ignore numeric parameters. Assert.deepEqual(r, [1, 2]); // Should already have a 0 counted. var v = h.snapshot().values; var s = h.snapshot().sum; Assert.deepEqual(v, {0: 1, 1: 0}); Assert.equal(s, 0); // Should switch counts. h.add(1); var v2 = h.snapshot().values; var s2 = h.snapshot().sum; Assert.deepEqual(v2, {0: 0, 1: 1, 2: 0}); Assert.equal(s2, 1); // Should only switch counts once. h.add(1); var v3 = h.snapshot().values; var s3 = h.snapshot().sum; Assert.deepEqual(v3, {0: 0, 1: 1, 2: 0}); Assert.equal(s3, 1); Assert.equal(h.snapshot().histogram_type, Telemetry.HISTOGRAM_FLAG); }); add_task(async function test_count_histogram() { let h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT2"); let s = h.snapshot(); Assert.deepEqual(s.range, [1, 2]); Assert.deepEqual(s.values, {}); Assert.equal(s.sum, 0); h.add(); s = h.snapshot(); Assert.deepEqual(s.values, {0: 1, 1: 0}); Assert.equal(s.sum, 1); h.add(); s = h.snapshot(); Assert.deepEqual(s.values, {0: 2, 1: 0}); Assert.equal(s.sum, 2); }); add_task(async function test_categorical_histogram() { let h1 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL"); for (let v of ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1]) { h1.add(v); } for (let s of ["", "Label4", "1234"]) { // The |add| method should not throw for unexpected values, but rather // print an error message in the console. h1.add(s); } let snapshot = h1.snapshot(); Assert.equal(snapshot.sum, 6); Assert.deepEqual(snapshot.range, [1, 50]); Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 2, 3: 0}); let h2 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL_OPTOUT"); for (let v of ["CommonLabel", "CommonLabel", "Label4", "Label5", "Label6", 0, 1]) { h2.add(v); } for (let s of ["", "Label3", "1234"]) { // The |add| method should not throw for unexpected values, but rather // print an error message in the console. h2.add(s); } snapshot = h2.snapshot(); Assert.equal(snapshot.sum, 7); Assert.deepEqual(snapshot.range, [1, 50]); Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 1, 3: 1, 4: 0}); // This histogram overrides the default of 50 values to 70. let h3 = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL_NVALUES"); for (let v of ["CommonLabel", "Label7", "Label8"]) { h3.add(v); } snapshot = h3.snapshot(); Assert.equal(snapshot.sum, 3); Assert.deepEqual(snapshot.range, [1, 70]); Assert.deepEqual(snapshot.values, {0: 1, 1: 1, 2: 1, 3: 0}); }); add_task(async function test_add_error_behaviour() { const PLAIN_HISTOGRAMS_TO_TEST = [ "TELEMETRY_TEST_FLAG", "TELEMETRY_TEST_EXPONENTIAL", "TELEMETRY_TEST_LINEAR", "TELEMETRY_TEST_BOOLEAN", ]; const KEYED_HISTOGRAMS_TO_TEST = [ "TELEMETRY_TEST_KEYED_FLAG", "TELEMETRY_TEST_KEYED_COUNT", "TELEMETRY_TEST_KEYED_BOOLEAN", ]; // Check that |add| doesn't throw for plain histograms. for (let hist of PLAIN_HISTOGRAMS_TO_TEST) { const returnValue = Telemetry.getHistogramById(hist).add("unexpected-value"); Assert.strictEqual(returnValue, undefined, "Adding to an histogram must return 'undefined'."); } // And for keyed histograms. for (let hist of KEYED_HISTOGRAMS_TO_TEST) { const returnValue = Telemetry.getKeyedHistogramById(hist).add("some-key", "unexpected-value"); Assert.strictEqual(returnValue, undefined, "Adding to a keyed histogram must return 'undefined'."); } }); add_task(async function test_API_return_values() { // Check that the plain scalar functions don't allow to crash the browser. // We expect 'undefined' to be returned so that .add(1).add() can't be called. // See bug 1321349 for context. let hist = Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR"); let keyedHist = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); const RETURN_VALUES = [ hist.clear(), hist.add(1), keyedHist.clear(), keyedHist.add("some-key", 1), ]; for (let returnValue of RETURN_VALUES) { Assert.strictEqual(returnValue, undefined, "The function must return undefined"); } }); add_task(async function test_getHistogramById() { try { Telemetry.getHistogramById("nonexistent"); do_throw("This can't happen"); } catch (e) { } var h = Telemetry.getHistogramById("CYCLE_COLLECTOR"); var s = h.snapshot(); Assert.equal(s.histogram_type, Telemetry.HISTOGRAM_EXPONENTIAL); Assert.deepEqual(s.range, [1, 10000]); }); add_task(async function test_getSlowSQL() { var slow = Telemetry.slowSQL; Assert.ok(("mainThread" in slow) && ("otherThreads" in slow)); }); add_task(async function test_getWebrtc() { var webrtc = Telemetry.webrtcStats; Assert.ok("IceCandidatesStats" in webrtc); var icestats = webrtc.IceCandidatesStats; Assert.ok("webrtc" in icestats); }); // Check that telemetry doesn't record in private mode add_task(async function test_privateMode() { var h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN"); var orig = h.snapshot(); Telemetry.canRecordExtended = false; h.add(1); Assert.deepEqual(orig, h.snapshot()); Telemetry.canRecordExtended = true; h.add(1); Assert.notDeepEqual(orig, h.snapshot()); }); // Check that telemetry records only when it is suppose to. add_task(async function test_histogramRecording() { // Check that no histogram is recorded if both base and extended recording are off. Telemetry.canRecordBase = false; Telemetry.canRecordExtended = false; let h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTOUT"); h.clear(); let orig = h.snapshot(); h.add(1); Assert.equal(orig.sum, h.snapshot().sum); // Check that only base histograms are recorded. Telemetry.canRecordBase = true; h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "Histogram value should have incremented by 1 due to recording."); // Extended histograms should not be recorded. h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTIN"); orig = h.snapshot(); h.add(1); Assert.equal(orig.sum, h.snapshot().sum, "Histograms should be equal after recording."); // Runtime created histograms should not be recorded. h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN"); orig = h.snapshot(); h.add(1); Assert.equal(orig.sum, h.snapshot().sum, "Histograms should be equal after recording."); // Check that extended histograms are recorded when required. Telemetry.canRecordExtended = true; h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "Runtime histogram value should have incremented by 1 due to recording."); h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTIN"); orig = h.snapshot(); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "Histogram value should have incremented by 1 due to recording."); // Check that base histograms are still being recorded. h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTOUT"); h.clear(); orig = h.snapshot(); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "Histogram value should have incremented by 1 due to recording."); }); add_task(async function test_expired_histogram() { var test_expired_id = "TELEMETRY_TEST_EXPIRED"; var dummy = Telemetry.getHistogramById(test_expired_id); dummy.add(1); for (let process of ["main", "content", "gpu", "extension"]) { - let histograms = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let histograms = Telemetry.getSnapshotForHistograms("main", false /* clear */); if (!(process in histograms)) { info("Nothing present for process " + process); continue; } Assert.equal(histograms[process].__expired__, undefined); } - let parentHgrams = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let parentHgrams = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.equal(parentHgrams[test_expired_id], undefined); }); add_task(async function test_keyed_expired_histogram() { var test_expired_id = "TELEMETRY_TEST_EXPIRED_KEYED"; var dummy = Telemetry.getKeyedHistogramById(test_expired_id); dummy.add("someKey", 1); - const histograms = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */); + const histograms = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */); for (let process of ["parent", "content", "gpu", "extension"]) { if (!(process in histograms)) { info("Nothing present for process " + process); continue; } Assert.ok(!(test_expired_id in histograms[process]), "The expired keyed histogram must not be reported"); } }); add_task(async function test_keyed_histogram() { // Check that invalid names get rejected. let threw = false; try { Telemetry.getKeyedHistogramById("test::unknown histogram", "never", Telemetry.HISTOGRAM_BOOLEAN); } catch (e) { // This should throw as it is an unknown ID threw = true; } Assert.ok(threw, "getKeyedHistogramById should have thrown"); }); add_task(async function test_keyed_boolean_histogram() { const KEYED_ID = "TELEMETRY_TEST_KEYED_BOOLEAN"; let KEYS = numberRange(0, 2).map(i => "key" + (i + 1)); KEYS.push("漢語"); let histogramBase = { "range": [1, 2], "bucket_count": 3, "histogram_type": 2, "sum": 1, "values": {0: 0, 1: 1, 2: 0}, }; let testHistograms = numberRange(0, 3).map(i => JSON.parse(JSON.stringify(histogramBase))); let testKeys = []; let testSnapShot = {}; let h = Telemetry.getKeyedHistogramById(KEYED_ID); for (let i = 0; i < 2; ++i) { let key = KEYS[i]; h.add(key, true); testSnapShot[key] = testHistograms[i]; testKeys.push(key); Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(), testSnapShot); } h = Telemetry.getKeyedHistogramById(KEYED_ID); Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(), testSnapShot); let key = KEYS[2]; h.add(key, false); testKeys.push(key); testSnapShot[key] = testHistograms[2]; testSnapShot[key].sum = 0; testSnapShot[key].values = {0: 1, 1: 0}; Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(), testSnapShot); - let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */).parent; + let parentHgrams = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */).parent; Assert.deepEqual(parentHgrams[KEYED_ID], testSnapShot); h.clear(); Assert.deepEqual(h.keys(), []); Assert.deepEqual(h.snapshot(), {}); }); add_task(async function test_keyed_count_histogram() { const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; const KEYS = numberRange(0, 5).map(i => "key" + (i + 1)); let histogramBase = { "range": [1, 2], "bucket_count": 3, "histogram_type": 4, "sum": 0, "values": {0: 1, 1: 0}, }; let testHistograms = numberRange(0, 5).map(i => JSON.parse(JSON.stringify(histogramBase))); let testKeys = []; let testSnapShot = {}; let h = Telemetry.getKeyedHistogramById(KEYED_ID); h.clear(); for (let i = 0; i < 4; ++i) { let key = KEYS[i]; let value = i * 2 + 1; for (let k = 0; k < value; ++k) { h.add(key); } testHistograms[i].values[0] = value; testHistograms[i].sum = value; testSnapShot[key] = testHistograms[i]; testKeys.push(key); Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(key), testHistograms[i]); Assert.deepEqual(h.snapshot(), testSnapShot); } h = Telemetry.getKeyedHistogramById(KEYED_ID); Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(), testSnapShot); let key = KEYS[4]; h.add(key); testKeys.push(key); testHistograms[4].values[0] = 1; testHistograms[4].sum = 1; testSnapShot[key] = testHistograms[4]; Assert.deepEqual(h.keys().sort(), testKeys); Assert.deepEqual(h.snapshot(), testSnapShot); - let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */).parent; + let parentHgrams = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */).parent; Assert.deepEqual(parentHgrams[KEYED_ID], testSnapShot); // Test clearing categorical histogram. h.clear(); Assert.deepEqual(h.keys(), []); Assert.deepEqual(h.snapshot(), {}); // Test leaving out the value argument. That should increment by 1. h.add("key"); Assert.equal(h.snapshot("key").sum, 1); }); add_task(async function test_keyed_categorical_histogram() { const KEYED_ID = "TELEMETRY_TEST_KEYED_CATEGORICAL"; const KEYS = numberRange(0, 5).map(i => "key" + (i + 1)); let h = Telemetry.getKeyedHistogramById(KEYED_ID); for (let k of KEYS) { // Test adding both per label and index. for (let v of ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1]) { h.add(k, v); } // The |add| method should not throw for unexpected values, but rather // print an error message in the console. for (let s of ["", "Label4", "1234"]) { h.add(k, s); } } // Check that the set of keys in the snapshot is what we expect. let snapshot = h.snapshot(); let snapshotKeys = Object.keys(snapshot); Assert.equal(KEYS.length, snapshotKeys.length); Assert.ok(KEYS.every(k => snapshotKeys.includes(k))); // Check the snapshot values. for (let k of KEYS) { Assert.ok(k in snapshot); Assert.equal(snapshot[k].sum, 6); Assert.deepEqual(snapshot[k].range, [1, 50]); Assert.deepEqual(snapshot[k].values, {0: 3, 1: 2, 2: 2, 3: 0}); } }); add_task(async function test_keyed_flag_histogram() { const KEYED_ID = "TELEMETRY_TEST_KEYED_FLAG"; let h = Telemetry.getKeyedHistogramById(KEYED_ID); const KEY = "default"; h.add(KEY, true); let testSnapshot = {}; testSnapshot[KEY] = { "range": [1, 2], "bucket_count": 3, "histogram_type": 3, "sum": 1, "values": {0: 0, 1: 1, 2: 0}, }; Assert.deepEqual(h.keys().sort(), [KEY]); Assert.deepEqual(h.snapshot(), testSnapshot); - let parentHgrams = Telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, - false /* clear */).parent; + let parentHgrams = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */).parent; Assert.deepEqual(parentHgrams[KEYED_ID], testSnapshot); h.clear(); Assert.deepEqual(h.keys(), []); Assert.deepEqual(h.snapshot(), {}); }); add_task(async function test_keyed_histogram_recording() { // Check that no histogram is recorded if both base and extended recording are off. Telemetry.canRecordBase = false; Telemetry.canRecordExtended = false; const TEST_KEY = "record_foo"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 0); // Check that only base histograms are recorded. Telemetry.canRecordBase = true; h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "The keyed histogram should record the correct value."); // Extended set keyed histograms should not be recorded. h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTIN"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 0, "The keyed histograms should not record any data."); // Check that extended histograms are recorded when required. Telemetry.canRecordExtended = true; h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "The runtime keyed histogram should record the correct value."); h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTIN"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "The keyed histogram should record the correct value."); // Check that base histograms are still being recorded. h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1); }); add_task(async function test_histogram_recording_enabled() { Telemetry.canRecordBase = true; Telemetry.canRecordExtended = true; // Check that a "normal" histogram respects recording-enabled on/off var h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT"); var orig = h.snapshot(); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "add should record by default."); // Check that when recording is disabled - add is ignored Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_COUNT", false); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "When recording is disabled add should not record."); // Check that we're back to normal after recording is enabled Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_COUNT", true); h.add(1); Assert.equal(orig.sum + 2, h.snapshot().sum, "When recording is re-enabled add should record."); // Check that we're correctly accumulating values other than 1. h.clear(); h.add(3); Assert.equal(3, h.snapshot().sum, "Recording counts greater than 1 should work."); // Check that a histogram with recording disabled by default behaves correctly h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT_INIT_NO_RECORD"); orig = h.snapshot(); h.add(1); Assert.equal(orig.sum, h.snapshot().sum, "When recording is disabled by default, add should not record by default."); Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_COUNT_INIT_NO_RECORD", true); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "When recording is enabled add should record."); // Restore to disabled Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_COUNT_INIT_NO_RECORD", false); h.add(1); Assert.equal(orig.sum + 1, h.snapshot().sum, "When recording is disabled add should not record."); }); add_task(async function test_keyed_histogram_recording_enabled() { Telemetry.canRecordBase = true; Telemetry.canRecordExtended = true; // Check RecordingEnabled for keyed histograms which are recording by default const TEST_KEY = "record_foo"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "Keyed histogram add should record by default"); Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT", false); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "Keyed histogram add should not record when recording is disabled"); Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT", true); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "Keyed histogram add should record when recording is re-enabled"); // Check that a histogram with recording disabled by default behaves correctly h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD"); h.clear(); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 0, "Keyed histogram add should not record by default for histograms which don't record by default"); Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD", true); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "Keyed histogram add should record when recording is enabled"); // Restore to disabled Telemetry.setHistogramRecordingEnabled("TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD", false); h.add(TEST_KEY, 1); Assert.equal(h.snapshot(TEST_KEY).sum, 1, "Keyed histogram add should not record when recording is disabled"); }); add_task(async function test_histogramSnapshots() { let keyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); keyed.add("a", 1); // Check that keyed histograms are not returned - let parentHgrams = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let parentHgrams = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(!("TELEMETRY_TEST_KEYED_COUNT" in parentHgrams)); }); add_task(async 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; + const currentRecordExtended = Telemetry.canRecordExtended; // Check that registeredHistogram works properly - let registered = Telemetry.snapshotHistograms(RELEASE_CHANNEL_OPTIN, - false /* clear */); + Telemetry.canRecordExtended = true; + let registered = Telemetry.getSnapshotForHistograms("main", false /* clear */); registered = new Set(Object.keys(registered.parent)); 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.snapshotHistograms(RELEASE_CHANNEL_OPTOUT, - false /* clear */); + Telemetry.canRecordExtended = false; + registered = Telemetry.getSnapshotForHistograms("main", false /* clear */); registered = new Set(Object.keys(registered.parent)); 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.snapshotKeyedHistograms(RELEASE_CHANNEL_OPTIN, - false /* clear */); + Telemetry.canRecordExtended = true; + registered = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */); registered = new Set(Object.keys(registered.parent)); Assert.ok(registered.has("TELEMETRY_TEST_KEYED_FLAG")); Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT")); - registered = Telemetry.snapshotKeyedHistograms(RELEASE_CHANNEL_OPTOUT, - false /* clear */); + Telemetry.canRecordExtended = false; + registered = Telemetry.getSnapshotForKeyedHistograms("main", false /* clear */); registered = new Set(Object.keys(registered.parent)); Assert.ok(!registered.has("TELEMETRY_TEST_KEYED_FLAG")); Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT")); + + Telemetry.canRecordExtended = currentRecordExtended; }); add_task(async function test_keyed_keys() { let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_KEYS"); h.clear(); Telemetry.clearScalars(); // The |add| method should not throw for keys that are not allowed. h.add("testkey", true); h.add("thirdKey", false); h.add("not-allowed", true); // Check that we have the expected keys. let snap = h.snapshot(); Assert.equal(Object.keys(snap).length, 2, "Only 2 keys must be recorded."); Assert.ok("testkey" in snap, "'testkey' must be recorded."); Assert.ok("thirdKey" in snap, "'thirdKey' must be recorded."); Assert.deepEqual(snap.testkey.values, {0: 0, 1: 1, 2: 0}, "'testkey' must contain the correct value."); Assert.deepEqual(snap.thirdKey.values, {0: 1, 1: 0}, "'thirdKey' must contain the correct value."); // Keys that are not allowed must not be recorded. Assert.ok(!("not-allowed" in snap), "'not-allowed' must not be recorded."); // Check that these failures were correctly tracked. - const parentScalars = - Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + const parentScalars = Telemetry.getSnapshotForKeyedScalars("main", false).parent; const scalarName = "telemetry.accumulate_unknown_histogram_keys"; Assert.ok(scalarName in parentScalars, "Accumulation to unallowed keys must be reported."); Assert.ok("TELEMETRY_TEST_KEYED_KEYS" in parentScalars[scalarName], "Accumulation to unallowed keys must be recorded with the correct key."); Assert.equal(parentScalars[scalarName].TELEMETRY_TEST_KEYED_KEYS, 1, "Accumulation to unallowed keys must report the correct value."); }); add_task(async function test_count_multiple_samples() { let valid = [1, 1, 3, 0]; let invalid = ["1", "0", "", "random"]; let h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT"); h.clear(); // If the array contains even a single invalid value, no accumulation should take place // Keep the valid values in front of invalid to check if it is simply accumulating as // it's traversing the array and throwing upon first invalid value. That should not happen. h.add(valid.concat(invalid)); let s1 = h.snapshot(); Assert.equal(s1.sum, 0); // Ensure that no accumulations of 0-like values took place. // These accumulations won't increase the sum. Assert.deepEqual({}, s1.values); h.add(valid); let s2 = h.snapshot(); Assert.deepEqual(s2.values, {0: 4, 1: 0}); Assert.equal(s2.sum, 5); }); add_task(async function test_categorical_multiple_samples() { let h = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL"); h.clear(); let valid = ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1]; let invalid = ["", "Label4", "1234", "0", "1", 5000]; // At least one invalid parameter, so no accumulation should happen here // Valid values in front of invalid. h.add(valid.concat(invalid)); let s1 = h.snapshot(); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(valid); let snapshot = h.snapshot(); Assert.equal(snapshot.sum, 6); Assert.deepEqual(snapshot.values, {0: 3, 1: 2, 2: 2, 3: 0}); }); add_task(async function test_boolean_multiple_samples() { let valid = [true, false, 0, 1, 2]; let invalid = ["", "0", "1", ",2", "true", "false", "random"]; let h = Telemetry.getHistogramById("TELEMETRY_TEST_BOOLEAN"); h.clear(); // At least one invalid parameter, so no accumulation should happen here // Valid values in front of invalid. h.add(valid.concat(invalid)); let s1 = h.snapshot(); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(valid); let s = h.snapshot(); Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0}); Assert.equal(s.sum, 3); }); add_task(async function test_linear_multiple_samples() { // According to telemetry.mozilla.org/histogram-simulator, bucket at // index 1 of TELEMETRY_TEST_LINEAR has max value of 268.44M let valid = [0, 1, 5, 10, 268450000, 268450001, Math.pow(2, 31) + 1]; let invalid = ["", "0", "1", "random"]; let h = Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR"); h.clear(); // At least one invalid paramater, so no accumulations. // Valid values in front of invalid. h.add(valid.concat(invalid)); let s1 = h.snapshot(); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(valid); let s2 = h.snapshot(); // Values >= INT32_MAX are accumulated as INT32_MAX - 1 Assert.equal(s2.sum, valid.reduce((acc, cur) => acc + cur) - 3); Assert.deepEqual(Object.values(s2.values), [1, 3, 2, 1]); }); add_task(async function test_keyed_no_arguments() { // Test for no accumulation when add is called with no arguments let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_LINEAR"); h.clear(); h.add(); // No keys should be added due to no accumulation. Assert.equal(h.keys().length, 0); }); add_task(async function test_keyed_categorical_invalid_string() { // Test for no accumulation when add is called on a // keyed categorical histogram with an invalid string label. let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_CATEGORICAL"); h.clear(); h.add("someKey", "#notALabel"); // No keys should be added due to no accumulation. Assert.equal(h.keys().length, 0); }); add_task(async function test_keyed_count_multiple_samples() { let valid = [1, 1, 3, 0]; let invalid = ["1", "0", "", "random"]; let key = "somekeystring"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); h.clear(); // If the array contains even a single invalid value, no accumulation should take place // Keep the valid values in front of invalid to check if it is simply accumulating as // it's traversing the array and throwing upon first invalid value. That should not happen. h.add(key, valid.concat(invalid)); let s1 = h.snapshot(key); Assert.equal(s1.sum, 0); // Ensure that no accumulations of 0-like values took place. // These accumulations won't increase the sum. Assert.deepEqual({}, s1.values); h.add(key, valid); let s2 = h.snapshot(key); Assert.deepEqual(s2.values, {0: 4, 1: 0}); Assert.equal(s2.sum, 5); }); add_task(async function test_keyed_categorical_multiple_samples() { let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_CATEGORICAL"); h.clear(); let valid = ["CommonLabel", "Label2", "Label3", "Label3", 0, 0, 1]; let invalid = ["", "Label4", "1234", "0", "1", 5000]; let key = "somekeystring"; // At least one invalid parameter, so no accumulation should happen here // Valid values in front of invalid. h.add(key, valid.concat(invalid)); let s1 = h.snapshot(key); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(key, valid); let snapshot = h.snapshot(key); Assert.equal(snapshot.sum, 6); Assert.deepEqual(Object.values(snapshot.values), [3, 2, 2, 0]); }); add_task(async function test_keyed_boolean_multiple_samples() { let valid = [true, false, 0, 1, 2]; let invalid = ["", "0", "1", ",2", "true", "false", "random"]; let key = "somekey"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_BOOLEAN"); h.clear(); // At least one invalid parameter, so no accumulation should happen here // Valid values in front of invalid. h.add(key, valid.concat(invalid)); let s1 = h.snapshot(key); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(key, valid); let s = h.snapshot(key); Assert.deepEqual(s.values, {0: 2, 1: 3, 2: 0}); Assert.equal(s.sum, 3); }); add_task(async function test_keyed_linear_multiple_samples() { // According to telemetry.mozilla.org/histogram-simulator, bucket at // index 1 of TELEMETRY_TEST_LINEAR has max value of 3.13K let valid = [0, 1, 5, 10, 268450000, 268450001, Math.pow(2, 31) + 1]; let invalid = ["", "0", "1", "random"]; let key = "somestring"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_LINEAR"); h.clear(); // At least one invalid paramater, so no accumulations. // Valid values in front of invalid. h.add(key, valid.concat(invalid)); let s1 = h.snapshot(key); Assert.equal(s1.sum, 0); Assert.deepEqual({}, s1.values); h.add(key, valid); let s2 = h.snapshot(key); // Values >= INT32_MAX are accumulated as INT32_MAX - 1 Assert.equal(s2.sum, valid.reduce((acc, cur) => acc + cur) - 3); Assert.deepEqual(s2.range, [1, 250000]); Assert.deepEqual(s2.values, {0: 1, 1: 3, 250000: 3}); }); add_task(async function test_non_array_non_string_obj() { let invalid_obj = { "prop1": "someValue", "prop2": "someOtherValue", }; let key = "someString"; let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_LINEAR"); h.clear(); h.add(key, invalid_obj); Assert.equal(h.keys().length, 0); }); add_task({ skip_if: () => gIsAndroid, }, async function test_productSpecificHistograms() { const DEFAULT_PRODUCTS_HISTOGRAM = "TELEMETRY_TEST_DEFAULT_PRODUCTS"; const DESKTOP_ONLY_HISTOGRAM = "TELEMETRY_TEST_DESKTOP_ONLY"; const MULTIPRODUCT_HISTOGRAM = "TELEMETRY_TEST_MULTIPRODUCT"; const MOBILE_ONLY_HISTOGRAM = "TELEMETRY_TEST_MOBILE_ONLY"; var default_histo = Telemetry.getHistogramById(DEFAULT_PRODUCTS_HISTOGRAM); var desktop_histo = Telemetry.getHistogramById(DESKTOP_ONLY_HISTOGRAM); var multiproduct_histo = Telemetry.getHistogramById(MULTIPRODUCT_HISTOGRAM); var mobile_histo = Telemetry.getHistogramById(MOBILE_ONLY_HISTOGRAM); default_histo.clear(); desktop_histo.clear(); multiproduct_histo.clear(); mobile_histo.clear(); default_histo.add(42); desktop_histo.add(42); multiproduct_histo.add(42); mobile_histo.add(42); - let histograms = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let histograms = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(DEFAULT_PRODUCTS_HISTOGRAM in histograms, "Should have recorded default products histogram"); Assert.ok(DESKTOP_ONLY_HISTOGRAM in histograms, "Should have recorded desktop-only histogram"); Assert.ok(MULTIPRODUCT_HISTOGRAM in histograms, "Should have recorded multiproduct histogram"); Assert.ok(!(MOBILE_ONLY_HISTOGRAM in histograms), "Should not have recorded mobile-only histogram"); }); add_task({ skip_if: () => !gIsAndroid, }, async function test_mobileSpecificHistograms() { const DEFAULT_PRODUCTS_HISTOGRAM = "TELEMETRY_TEST_DEFAULT_PRODUCTS"; const DESKTOP_ONLY_HISTOGRAM = "TELEMETRY_TEST_DESKTOP_ONLY"; const MULTIPRODUCT_HISTOGRAM = "TELEMETRY_TEST_MULTIPRODUCT"; const MOBILE_ONLY_HISTOGRAM = "TELEMETRY_TEST_MOBILE_ONLY"; var default_histo = Telemetry.getHistogramById(DEFAULT_PRODUCTS_HISTOGRAM); var desktop_histo = Telemetry.getHistogramById(DESKTOP_ONLY_HISTOGRAM); var multiproduct_histo = Telemetry.getHistogramById(MULTIPRODUCT_HISTOGRAM); var mobile_histo = Telemetry.getHistogramById(MOBILE_ONLY_HISTOGRAM); default_histo.clear(); desktop_histo.clear(); multiproduct_histo.clear(); mobile_histo.clear(); default_histo.add(1); desktop_histo.add(1); multiproduct_histo.add(1); mobile_histo.add(1); - let histograms = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let histograms = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(DEFAULT_PRODUCTS_HISTOGRAM in histograms, "Should have recorded default products histogram"); Assert.ok(MOBILE_ONLY_HISTOGRAM in histograms, "Should have recorded mobile-only histogram"); Assert.ok(MULTIPRODUCT_HISTOGRAM in histograms, "Should have recorded multiproduct histogram"); Assert.ok(!(DESKTOP_ONLY_HISTOGRAM in histograms), "Should not have recorded desktop-only histogram"); }); add_task({ skip_if: () => gIsAndroid, }, async function test_clearHistogramsOnSnapshot() { const COUNT = "TELEMETRY_TEST_COUNT"; let h = Telemetry.getHistogramById(COUNT); h.clear(); let snapshot; // The first snapshot should be empty, nothing recorded. - snapshot = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + snapshot = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(!(COUNT in snapshot)); // After recording into a histogram, the data should be in the snapshot. Don't delete it. h.add(1); Assert.equal(h.snapshot().sum, 1); - snapshot = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + snapshot = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(COUNT in snapshot); Assert.equal(snapshot[COUNT].sum, 1); // After recording into a histogram again, the data should be updated and in the snapshot. // Clean up after. h.add(41); Assert.equal(h.snapshot().sum, 42); - snapshot = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + snapshot = Telemetry.getSnapshotForHistograms("main", true /* clear */).parent; Assert.ok(COUNT in snapshot); Assert.equal(snapshot[COUNT].sum, 42); // Finally, no data should be in the snapshot. Assert.equal(h.snapshot().sum, 0); - snapshot = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + snapshot = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(!(COUNT in snapshot)); }); add_task(async function test_valid_os_smoketest() { let nonExistingProbe; let existingProbe; switch (AppConstants.platform) { case "linux": nonExistingProbe = "TELEMETRY_TEST_OS_ANDROID_ONLY"; existingProbe = "TELEMETRY_TEST_OS_LINUX_ONLY"; break; case "macosx": nonExistingProbe = "TELEMETRY_TEST_OS_ANDROID_ONLY"; existingProbe = "TELEMETRY_TEST_OS_MAC_ONLY"; break; case "win": nonExistingProbe = "TELEMETRY_TEST_OS_ANDROID_ONLY"; existingProbe = "TELEMETRY_TEST_OS_WIN_ONLY"; break; case "android": nonExistingProbe = "TELEMETRY_TEST_OS_LINUX_ONLY"; existingProbe = "TELEMETRY_TEST_OS_ANDROID_ONLY"; break; default: /* Unknown OS. Let's not test OS-specific probes */ return; } Assert.throws(() => Telemetry.getHistogramById(nonExistingProbe), /NS_ERROR_FAILURE/, `Should throw on ${nonExistingProbe} probe that's not available on ${AppConstants.platform}`); let h = Telemetry.getHistogramById(existingProbe); h.clear(); h.add(1); - let snapshot = Telemetry.snapshotHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, + let snapshot = Telemetry.getSnapshotForHistograms("main", false /* clear */).parent; Assert.ok(existingProbe in snapshot, `${existingProbe} should be recorded on ${AppConstants.platform}`); Assert.equal(snapshot[existingProbe].sum, 1); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js --- a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js @@ -1,894 +1,863 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const UINT_SCALAR = "telemetry.test.unsigned_int_kind"; const STRING_SCALAR = "telemetry.test.string_kind"; const BOOLEAN_SCALAR = "telemetry.test.boolean_kind"; const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int"; const KEYED_EXCEED_SCALAR = "telemetry.keyed_scalars_exceed_limit"; -function getProcessScalars(aChannel, aProcessName, aKeyed = false, aClear = false) { +function getProcessScalars(aProcessName, aKeyed = false, aClear = false) { const scalars = aKeyed ? - Telemetry.snapshotKeyedScalars(aChannel, aClear)[aProcessName] : - Telemetry.snapshotScalars(aChannel, aClear)[aProcessName]; + Telemetry.getSnapshotForKeyedScalars("main", aClear)[aProcessName] : + Telemetry.getSnapshotForScalars("main", aClear)[aProcessName]; return scalars || {}; } -function getParentProcessScalars(aChannel, aKeyed = false, aClear = false) { - return getProcessScalars(aChannel, "parent", aKeyed, aClear); +function getParentProcessScalars(aKeyed = false, aClear = false) { + return getProcessScalars("parent", aKeyed, aClear); } add_task(async function test_serializationFormat() { Telemetry.clearScalars(); // Set the scalars to a known value. const expectedUint = 3785; const expectedString = "some value"; Telemetry.scalarSet(UINT_SCALAR, expectedUint); Telemetry.scalarSet(STRING_SCALAR, expectedString); Telemetry.scalarSet(BOOLEAN_SCALAR, true); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "first_key", 1234); // Get a snapshot of the scalars for the main process (internally called "default"). - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); // Check that they are serialized to the correct format. Assert.equal(typeof(scalars[UINT_SCALAR]), "number", UINT_SCALAR + " must be serialized to the correct format."); Assert.ok(Number.isInteger(scalars[UINT_SCALAR]), UINT_SCALAR + " must be a finite integer."); Assert.equal(scalars[UINT_SCALAR], expectedUint, UINT_SCALAR + " must have the correct value."); Assert.equal(typeof(scalars[STRING_SCALAR]), "string", STRING_SCALAR + " must be serialized to the correct format."); Assert.equal(scalars[STRING_SCALAR], expectedString, STRING_SCALAR + " must have the correct value."); Assert.equal(typeof(scalars[BOOLEAN_SCALAR]), "boolean", BOOLEAN_SCALAR + " must be serialized to the correct format."); Assert.equal(scalars[BOOLEAN_SCALAR], true, BOOLEAN_SCALAR + " must have the correct value."); Assert.ok(!(KEYED_UINT_SCALAR in scalars), "Keyed scalars must be reported in a separate section."); }); add_task(async function test_keyedSerializationFormat() { Telemetry.clearScalars(); const expectedKey = "first_key"; const expectedOtherKey = "漢語"; const expectedUint = 3785; const expectedOtherValue = 1107; Telemetry.scalarSet(UINT_SCALAR, expectedUint); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, expectedKey, expectedUint); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, expectedOtherKey, expectedOtherValue); // Get a snapshot of the scalars. - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const keyedScalars = getParentProcessScalars(true); Assert.ok(!(UINT_SCALAR in keyedScalars), UINT_SCALAR + " must not be serialized with the keyed scalars."); Assert.ok(KEYED_UINT_SCALAR in keyedScalars, KEYED_UINT_SCALAR + " must be serialized with the keyed scalars."); Assert.equal(Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length, 2, "The keyed scalar must contain exactly 2 keys."); Assert.ok(expectedKey in keyedScalars[KEYED_UINT_SCALAR], KEYED_UINT_SCALAR + " must contain the expected keys."); Assert.ok(expectedOtherKey in keyedScalars[KEYED_UINT_SCALAR], KEYED_UINT_SCALAR + " must contain the expected keys."); Assert.ok(Number.isInteger(keyedScalars[KEYED_UINT_SCALAR][expectedKey]), KEYED_UINT_SCALAR + "." + expectedKey + " must be a finite integer."); Assert.equal(keyedScalars[KEYED_UINT_SCALAR][expectedKey], expectedUint, KEYED_UINT_SCALAR + "." + expectedKey + " must have the correct value."); Assert.equal(keyedScalars[KEYED_UINT_SCALAR][expectedOtherKey], expectedOtherValue, KEYED_UINT_SCALAR + "." + expectedOtherKey + " must have the correct value."); }); add_task(async function test_nonexistingScalar() { const NON_EXISTING_SCALAR = "telemetry.test.non_existing"; Telemetry.clearScalars(); // The JS API must not throw when used incorrectly but rather print // a message to the console. Telemetry.scalarAdd(NON_EXISTING_SCALAR, 11715); Telemetry.scalarSet(NON_EXISTING_SCALAR, 11715); Telemetry.scalarSetMaximum(NON_EXISTING_SCALAR, 11715); // Make sure we do not throw on any operation for non-existing scalars. Telemetry.keyedScalarAdd(NON_EXISTING_SCALAR, "some_key", 11715); Telemetry.keyedScalarSet(NON_EXISTING_SCALAR, "some_key", 11715); Telemetry.keyedScalarSetMaximum(NON_EXISTING_SCALAR, "some_key", 11715); // Get a snapshot of the scalars. - const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.ok(!(NON_EXISTING_SCALAR in scalars), "The non existing scalar must not be persisted."); - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const keyedScalars = getParentProcessScalars(true); Assert.ok(!(NON_EXISTING_SCALAR in keyedScalars), "The non existing keyed scalar must not be persisted."); }); add_task(async function test_expiredScalar() { const EXPIRED_SCALAR = "telemetry.test.expired"; const EXPIRED_KEYED_SCALAR = "telemetry.test.keyed_expired"; const UNEXPIRED_SCALAR = "telemetry.test.unexpired"; Telemetry.clearScalars(); // Try to set the expired scalar to some value. We will not be recording the value, // but we shouldn't throw. Telemetry.scalarAdd(EXPIRED_SCALAR, 11715); Telemetry.scalarSet(EXPIRED_SCALAR, 11715); Telemetry.scalarSetMaximum(EXPIRED_SCALAR, 11715); Telemetry.keyedScalarAdd(EXPIRED_KEYED_SCALAR, "some_key", 11715); Telemetry.keyedScalarSet(EXPIRED_KEYED_SCALAR, "some_key", 11715); Telemetry.keyedScalarSetMaximum(EXPIRED_KEYED_SCALAR, "some_key", 11715); // The unexpired scalar has an expiration version, but far away in the future. const expectedValue = 11716; Telemetry.scalarSet(UNEXPIRED_SCALAR, expectedValue); // Get a snapshot of the scalars. - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const scalars = getParentProcessScalars(); + const keyedScalars = getParentProcessScalars(); Assert.ok(!(EXPIRED_SCALAR in scalars), "The expired scalar must not be persisted."); Assert.equal(scalars[UNEXPIRED_SCALAR], expectedValue, "The unexpired scalar must be persisted with the correct value."); Assert.ok(!(EXPIRED_KEYED_SCALAR in keyedScalars), "The expired keyed scalar must not be persisted."); }); add_task(async function test_unsignedIntScalar() { let checkScalar = (expectedValue) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.equal(scalars[UINT_SCALAR], expectedValue, UINT_SCALAR + " must contain the expected value."); }; Telemetry.clearScalars(); // Let's start with an accumulation without a prior set. Telemetry.scalarAdd(UINT_SCALAR, 1); Telemetry.scalarAdd(UINT_SCALAR, 2); // Do we get what we expect? checkScalar(3); // Let's test setting the scalar to a value. Telemetry.scalarSet(UINT_SCALAR, 3785); checkScalar(3785); Telemetry.scalarAdd(UINT_SCALAR, 1); checkScalar(3786); // Does setMaximum work? Telemetry.scalarSet(UINT_SCALAR, 2); checkScalar(2); Telemetry.scalarSetMaximum(UINT_SCALAR, 5); checkScalar(5); // The value of the probe should still be 5, as the previous value // is greater than the one we want to set. Telemetry.scalarSetMaximum(UINT_SCALAR, 3); checkScalar(5); // Check that non-integer numbers get truncated and set. Telemetry.scalarSet(UINT_SCALAR, 3.785); checkScalar(3); // Setting or adding a negative number must report an error through // the console and drop the change (shouldn't throw). Telemetry.scalarAdd(UINT_SCALAR, -5); Telemetry.scalarSet(UINT_SCALAR, -5); Telemetry.scalarSetMaximum(UINT_SCALAR, -1); checkScalar(3); // If we try to set a value of a different type, the JS API should not // throw but rather print a console message. Telemetry.scalarSet(UINT_SCALAR, 1); Telemetry.scalarSet(UINT_SCALAR, "unexpected value"); Telemetry.scalarAdd(UINT_SCALAR, "unexpected value"); Telemetry.scalarSetMaximum(UINT_SCALAR, "unexpected value"); // The stored value must not be compromised. checkScalar(1); }); add_task(async function test_stringScalar() { let checkExpectedString = (expectedString) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.equal(scalars[STRING_SCALAR], expectedString, STRING_SCALAR + " must contain the expected string value."); }; Telemetry.clearScalars(); // Let's check simple strings... let expected = "test string"; Telemetry.scalarSet(STRING_SCALAR, expected); checkExpectedString(expected); expected = "漢語"; Telemetry.scalarSet(STRING_SCALAR, expected); checkExpectedString(expected); // We have some unsupported operations for strings. Telemetry.scalarAdd(STRING_SCALAR, 1); Telemetry.scalarAdd(STRING_SCALAR, "string value"); Telemetry.scalarSetMaximum(STRING_SCALAR, 1); Telemetry.scalarSetMaximum(STRING_SCALAR, "string value"); Telemetry.scalarSet(STRING_SCALAR, 1); // Try to set the scalar to a string longer than the maximum length limit. const LONG_STRING = "browser.qaxfiuosnzmhlg.rpvxicawolhtvmbkpnludhedobxvkjwqyeyvmv"; Telemetry.scalarSet(STRING_SCALAR, LONG_STRING); checkExpectedString(LONG_STRING.substr(0, 50)); }); add_task(async function test_booleanScalar() { let checkExpectedBool = (expectedBoolean) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.equal(scalars[BOOLEAN_SCALAR], expectedBoolean, BOOLEAN_SCALAR + " must contain the expected boolean value."); }; Telemetry.clearScalars(); // Set a test boolean value. let expected = false; Telemetry.scalarSet(BOOLEAN_SCALAR, expected); checkExpectedBool(expected); expected = true; Telemetry.scalarSet(BOOLEAN_SCALAR, expected); checkExpectedBool(expected); // Check that setting a numeric value implicitly converts to boolean. Telemetry.scalarSet(BOOLEAN_SCALAR, 1); checkExpectedBool(true); Telemetry.scalarSet(BOOLEAN_SCALAR, 0); checkExpectedBool(false); Telemetry.scalarSet(BOOLEAN_SCALAR, 1.0); checkExpectedBool(true); Telemetry.scalarSet(BOOLEAN_SCALAR, 0.0); checkExpectedBool(false); // Check that unsupported operations for booleans do not throw. Telemetry.scalarAdd(BOOLEAN_SCALAR, 1); Telemetry.scalarAdd(BOOLEAN_SCALAR, "string value"); Telemetry.scalarSetMaximum(BOOLEAN_SCALAR, 1); Telemetry.scalarSetMaximum(BOOLEAN_SCALAR, "string value"); Telemetry.scalarSet(BOOLEAN_SCALAR, "true"); }); add_task(async function test_scalarRecording() { const OPTIN_SCALAR = "telemetry.test.release_optin"; const OPTOUT_SCALAR = "telemetry.test.release_optout"; let checkValue = (scalarName, expectedValue) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.equal(scalars[scalarName], expectedValue, scalarName + " must contain the expected value."); }; let checkNotSerialized = (scalarName) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.ok(!(scalarName in scalars), scalarName + " was not recorded."); }; Telemetry.canRecordBase = false; Telemetry.canRecordExtended = false; Telemetry.clearScalars(); // Check that no scalar is recorded if both base and extended recording are off. Telemetry.scalarSet(OPTOUT_SCALAR, 3); Telemetry.scalarSet(OPTIN_SCALAR, 3); checkNotSerialized(OPTOUT_SCALAR); checkNotSerialized(OPTIN_SCALAR); // Check that opt-out scalars are recorded, while opt-in are not. Telemetry.canRecordBase = true; Telemetry.scalarSet(OPTOUT_SCALAR, 3); Telemetry.scalarSet(OPTIN_SCALAR, 3); checkValue(OPTOUT_SCALAR, 3); checkNotSerialized(OPTIN_SCALAR); // Check that both opt-out and opt-in scalars are recorded. Telemetry.canRecordExtended = true; Telemetry.scalarSet(OPTOUT_SCALAR, 5); Telemetry.scalarSet(OPTIN_SCALAR, 6); checkValue(OPTOUT_SCALAR, 5); checkValue(OPTIN_SCALAR, 6); }); add_task(async function test_keyedScalarRecording() { const OPTIN_SCALAR = "telemetry.test.keyed_release_optin"; const OPTOUT_SCALAR = "telemetry.test.keyed_release_optout"; const testKey = "policy_key"; let checkValue = (scalarName, expectedValue) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const scalars = getParentProcessScalars(true); Assert.equal(scalars[scalarName][testKey], expectedValue, scalarName + " must contain the expected value."); }; let checkNotSerialized = (scalarName) => { - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const scalars = getParentProcessScalars(true); Assert.ok(!(scalarName in scalars), scalarName + " was not recorded."); }; Telemetry.canRecordBase = false; Telemetry.canRecordExtended = false; Telemetry.clearScalars(); // Check that no scalar is recorded if both base and extended recording are off. Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 3); Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 3); checkNotSerialized(OPTOUT_SCALAR); checkNotSerialized(OPTIN_SCALAR); // Check that opt-out scalars are recorded, while opt-in are not. Telemetry.canRecordBase = true; Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 3); Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 3); checkValue(OPTOUT_SCALAR, 3); checkNotSerialized(OPTIN_SCALAR); // Check that both opt-out and opt-in scalars are recorded. Telemetry.canRecordExtended = true; Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 5); Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 6); checkValue(OPTOUT_SCALAR, 5); checkValue(OPTIN_SCALAR, 6); }); add_task(async function test_subsession() { Telemetry.clearScalars(); // Set the scalars to a known value. Telemetry.scalarSet(UINT_SCALAR, 3785); Telemetry.scalarSet(STRING_SCALAR, "some value"); Telemetry.scalarSet(BOOLEAN_SCALAR, false); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "some_random_key", 12); // Get a snapshot and reset the subsession. The value we set must be there. - let scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false, true); - let keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, true); + let scalars = getParentProcessScalars(false, true); + let keyedScalars = getParentProcessScalars(true, true); Assert.equal(scalars[UINT_SCALAR], 3785, UINT_SCALAR + " must contain the expected value."); Assert.equal(scalars[STRING_SCALAR], "some value", STRING_SCALAR + " must contain the expected value."); Assert.equal(scalars[BOOLEAN_SCALAR], false, BOOLEAN_SCALAR + " must contain the expected value."); Assert.equal(keyedScalars[KEYED_UINT_SCALAR].some_random_key, 12, KEYED_UINT_SCALAR + " must contain the expected value."); // Get a new snapshot and reset the subsession again. Since no new value // was set, the scalars should not be reported. - scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false, true); - keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, true); + scalars = getParentProcessScalars(false, true); + keyedScalars = getParentProcessScalars(true, true); Assert.ok(!(UINT_SCALAR in scalars), UINT_SCALAR + " must be empty and not reported."); Assert.ok(!(STRING_SCALAR in scalars), STRING_SCALAR + " must be empty and not reported."); Assert.ok(!(BOOLEAN_SCALAR in scalars), BOOLEAN_SCALAR + " must be empty and not reported."); Assert.ok(!(KEYED_UINT_SCALAR in keyedScalars), KEYED_UINT_SCALAR + " must be empty and not reported."); }); add_task(async function test_keyed_uint() { Telemetry.clearScalars(); const KEYS = [ "a_key", "another_key", "third_key" ]; let expectedValues = [ 1, 1, 1 ]; // Set all the keys to a baseline value. for (let key of KEYS) { Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, key, 1); } // Increment only one key. Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, KEYS[1], 1); expectedValues[1]++; // Use SetMaximum on the third key. Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, KEYS[2], 37); expectedValues[2] = 37; // Get a snapshot of the scalars and make sure the keys contain // the correct values. - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const keyedScalars = getParentProcessScalars(true); for (let k = 0; k < 3; k++) { const keyName = KEYS[k]; Assert.equal(keyedScalars[KEYED_UINT_SCALAR][keyName], expectedValues[k], KEYED_UINT_SCALAR + "." + keyName + " must contain the correct value."); } // Do not throw when doing unsupported things on uint keyed scalars. // Just test one single unsupported operation, the other are covered in the plain // unsigned scalar test. Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "new_key", "unexpected value"); }); add_task(async function test_keyed_boolean() { Telemetry.clearScalars(); const KEYED_BOOLEAN_TYPE = "telemetry.test.keyed_boolean_kind"; const first_key = "first_key"; const second_key = "second_key"; // Set the initial values. Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, true); Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, false); // Get a snapshot of the scalars and make sure the keys contain // the correct values. - let keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + let keyedScalars = getParentProcessScalars(true); Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][first_key], true, "The key must contain the expected value."); Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][second_key], false, "The key must contain the expected value."); // Now flip the values and make sure we get the expected values back. Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, false); Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, true); - keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + keyedScalars = getParentProcessScalars(true); Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][first_key], false, "The key must contain the expected value."); Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][second_key], true, "The key must contain the expected value."); // Do not throw when doing unsupported things on a boolean keyed scalars. // Just test one single unsupported operation, the other are covered in the plain // boolean scalar test. Telemetry.keyedScalarAdd(KEYED_BOOLEAN_TYPE, "somehey", 1); }); add_task(async function test_keyed_keys_length() { Telemetry.clearScalars(); const LONG_KEY_STRING = "browser.qaxfiuosnzmhlg.rpvxicawolhtvmbkpnludhedobxvkjwqyeyvmv.somemoresowereach70chars"; const NORMAL_KEY = "a_key"; // Set the value for a key within the length limits. Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, NORMAL_KEY, 1); // Now try to set and modify the value for a very long key (must not throw). Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, LONG_KEY_STRING, 10); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, LONG_KEY_STRING, 1); Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LONG_KEY_STRING, 10); // Also attempt to set the value for an empty key. Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "", 1); // Make sure the key with the right length contains the expected value. - let keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + let keyedScalars = getParentProcessScalars(true); Assert.equal(Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length, 1, "The keyed scalar must contain exactly 1 key."); Assert.ok(NORMAL_KEY in keyedScalars[KEYED_UINT_SCALAR], "The keyed scalar must contain the expected key."); Assert.equal(keyedScalars[KEYED_UINT_SCALAR][NORMAL_KEY], 1, "The key must contain the expected value."); Assert.ok(!(LONG_KEY_STRING in keyedScalars[KEYED_UINT_SCALAR]), "The data for the long key should not have been recorded."); Assert.ok(!("" in keyedScalars[KEYED_UINT_SCALAR]), "The data for the empty key should not have been recorded."); }); add_task(async function test_keyed_max_keys() { Telemetry.clearScalars(); // Generate the names for the first 100 keys. let keyNamesSet = new Set(); for (let k = 0; k < 100; k++) { keyNamesSet.add("key_" + k); } // Add 100 keys to an histogram and set their initial value. let valueToSet = 0; keyNamesSet.forEach(keyName => { Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, keyName, valueToSet++); }); // Perform some operations on the 101th key. This should throw, as // we're not allowed to have more than 100 keys. const LAST_KEY_NAME = "overflowing_key"; Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, LAST_KEY_NAME, 10); Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, LAST_KEY_NAME, 1); Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LAST_KEY_NAME, 10); // Make sure all the keys except the last one are available and have the correct // values. - let keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + let keyedScalars = getParentProcessScalars(true); // Check that the keyed scalar only contain the first 100 keys. const reportedKeysSet = new Set(Object.keys(keyedScalars[KEYED_UINT_SCALAR])); Assert.ok([...keyNamesSet].filter(x => reportedKeysSet.has(x)) && [...reportedKeysSet].filter(x => keyNamesSet.has(x)), "The keyed scalar must contain all the 100 keys, and drop the others."); // Check that all the keys recorded the expected values. let expectedValue = 0; keyNamesSet.forEach(keyName => { Assert.equal(keyedScalars[KEYED_UINT_SCALAR][keyName], expectedValue++, "The key must contain the expected value."); }); // Check that KEYED_EXCEED_SCALAR is in keyedScalars Assert.ok((KEYED_EXCEED_SCALAR in keyedScalars), "We have exceeded maximum number of Keys."); // Generate the names for the exceeded keys let keyNamesSet2 = new Set(); for (let k = 0; k < 100; k++) { keyNamesSet2.add("key2_" + k); } // Add 100 keys to the keyed exceed scalar and set their initial value. valueToSet = 0; keyNamesSet2.forEach(keyName2 => { Telemetry.keyedScalarSet(KEYED_EXCEED_SCALAR, keyName2, valueToSet++); }); // Check that there are exactly 100 keys in KEYED_EXCEED_SCALAR let snapshot = Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false); Assert.equal(100, Object.keys(snapshot.parent[KEYED_UINT_SCALAR]).length, "The keyed scalar must contain all the 100 keys."); // Check that KEYED_UINT_SCALAR is in keyedScalars and its value equals 3 Assert.ok((KEYED_UINT_SCALAR in keyedScalars[KEYED_EXCEED_SCALAR]), "The keyed Scalar is in the keyed exceeded scalar"); Assert.equal(keyedScalars[KEYED_EXCEED_SCALAR][KEYED_UINT_SCALAR], 3, "We have exactly 3 keys over the limit"); }); add_task(async function test_dynamicScalars_registration() { Telemetry.clearScalars(); const TEST_CASES = [ { "category": "telemetry.test", "data": { "missing_kind": { keyed: false, record_on_release: true, }, }, "evaluation": /missing 'kind'/, "description": "Registration must fail if required fields are missing", }, { "category": "telemetry.test", "data": { "invalid_collection": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: "opt-in", }, }, "evaluation": /Invalid 'record_on_release'/, "description": "Registration must fail if 'record_on_release' is of the wrong type", }, { "category": "telemetry.test", "data": { "invalid_kind": { kind: "12", }, }, "evaluation": /Invalid or missing 'kind'/, "description": "Registration must fail if 'kind' is of the wrong type", }, { "category": "telemetry.test", "data": { "invalid_expired": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, expired: "never", }, }, "evaluation": /Invalid 'expired'/, "description": "Registration must fail if 'expired' is of the wrong type", }, { "category": "telemetry.test", "data": { "valid_scalar": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: false, record_on_release: true, }, "invalid_scalar": { expired: false, }, }, "evaluation": /Invalid or missing 'kind'/, "description": "No scalar must be registered if the batch contains an invalid one", }, ]; for (let testCase of TEST_CASES) { Assert.throws(() => Telemetry.registerScalars(testCase.category, testCase.data), testCase.evaluation, testCase.description); } }); add_task(async function test_dynamicScalars_doubleRegistration() { Telemetry.clearScalars(); // Register a test scalar. Telemetry.registerScalars("telemetry.test.dynamic", { "double_registration_1": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: true, }, }); // Verify that we can record the scalar. Telemetry.scalarSet("telemetry.test.dynamic.double_registration_1", 1); // Register the same scalar again, along with a second scalar. // This must not throw. Telemetry.registerScalars("telemetry.test.dynamic", { "double_registration_1": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: true, }, "double_registration_2": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: true, }, }); // Set the dynamic scalars to some test values. Telemetry.scalarAdd("telemetry.test.dynamic.double_registration_1", 1); Telemetry.scalarSet("telemetry.test.dynamic.double_registration_2", 3); // Get a snapshot of the scalars and check that the dynamic ones were correctly set. - let scalars = - getProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, "dynamic", false, false); + let scalars = getProcessScalars("dynamic", false, false); Assert.equal(scalars["telemetry.test.dynamic.double_registration_1"], 2, "The recorded scalar must contain the right value."); Assert.equal(scalars["telemetry.test.dynamic.double_registration_2"], 3, "The recorded scalar must contain the right value."); // Register an existing scalar again, only change the definition // to make it expire. Telemetry.registerScalars("telemetry.test.dynamic", { "double_registration_2": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: true, expired: true, }, }); // Attempt to record and make sure that no recording happens. Telemetry.scalarAdd("telemetry.test.dynamic.double_registration_2", 1); - scalars = - getProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, "dynamic", false, false); + scalars = getProcessScalars("dynamic", false, false); Assert.equal(scalars["telemetry.test.dynamic.double_registration_2"], 3, "The recorded scalar must contain the right value."); }); add_task(async function test_dynamicScalars_recording() { Telemetry.clearScalars(); // Disable extended recording so that we will just record opt-out. Telemetry.canRecordExtended = false; // Register some test scalars. Telemetry.registerScalars("telemetry.test.dynamic", { "record_optout": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, record_on_release: true, }, "record_keyed": { kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, keyed: true, record_on_release: true, }, "record_optin": { kind: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN, record_on_release: false, }, "record_expired": { kind: Ci.nsITelemetry.SCALAR_TYPE_STRING, expired: true, record_on_release: true, }, }); // Set the dynamic scalars to some test values. Telemetry.scalarSet("telemetry.test.dynamic.record_optout", 1); Telemetry.keyedScalarSet("telemetry.test.dynamic.record_keyed", "someKey", 5); Telemetry.scalarSet("telemetry.test.dynamic.record_optin", false); Telemetry.scalarSet("telemetry.test.dynamic.record_expired", "test"); // Get a snapshot of the scalars and check that the dynamic ones were correctly set. - let scalars = - getProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, "dynamic", false, false); - let keyedScalars = - getProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, "dynamic", true, true); + let scalars = getProcessScalars("dynamic", false, false); + let keyedScalars = getProcessScalars("dynamic", true, true); Assert.ok(!("telemetry.test.dynamic.record_optin" in scalars), "Dynamic opt-in scalars must not be recorded."); Assert.ok("telemetry.test.dynamic.record_keyed" in keyedScalars, "Dynamic opt-out keyed scalars must be recorded."); Assert.ok(!("telemetry.test.dynamic.record_expired" in scalars), "Dynamic expired scalars must not be recorded."); Assert.ok("telemetry.test.dynamic.record_optout" in scalars, "Dynamic opt-out scalars must be recorded."); Assert.equal(scalars["telemetry.test.dynamic.record_optout"], 1, "The recorded scalar must contain the right value."); Assert.equal(keyedScalars["telemetry.test.dynamic.record_keyed"].someKey, 5, "The recorded keyed scalar must contain the right value."); // Enable extended recording. Telemetry.canRecordExtended = true; // Set the dynamic scalars to some test values. Telemetry.scalarSet("telemetry.test.dynamic.record_optin", true); Telemetry.scalarSet("telemetry.test.dynamic.record_expired", "test"); // Get a snapshot of the scalars and check that the dynamic ones were correctly set. - scalars = - getProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, "dynamic", false, true); + scalars = getProcessScalars("dynamic", false, true); Assert.ok(!("telemetry.test.dynamic.record_expired" in scalars), "Dynamic expired scalars must not be recorded."); Assert.ok("telemetry.test.dynamic.record_optin" in scalars, "Dynamic opt-in scalars must be recorded."); Assert.equal(scalars["telemetry.test.dynamic.record_optin"], true, "The recorded scalar must contain the right value."); }); add_task({ skip_if: () => gIsAndroid, }, async function test_productSpecificScalar() { const DEFAULT_PRODUCT_SCALAR = "telemetry.test.default_products"; const DESKTOP_ONLY_SCALAR = "telemetry.test.desktop_only"; const MULTIPRODUCT_SCALAR = "telemetry.test.multiproduct"; const MOBILE_ONLY_SCALAR = "telemetry.test.mobile_only"; const MOBILE_ONLY_KEYED_SCALAR = "telemetry.test.keyed_mobile_only"; Telemetry.clearScalars(); // Try to set the desktop scalars let expectedValue = 11714; Telemetry.scalarAdd(DEFAULT_PRODUCT_SCALAR, expectedValue); Telemetry.scalarAdd(DESKTOP_ONLY_SCALAR, expectedValue); Telemetry.scalarAdd(MULTIPRODUCT_SCALAR, expectedValue); // Try to set the mobile-only scalar to some value. We will not be recording the value, // but we shouldn't throw. let expectedKey = "some_key"; Telemetry.scalarSet(MOBILE_ONLY_SCALAR, 11715); Telemetry.scalarSetMaximum(MOBILE_ONLY_SCALAR, 11715); Telemetry.keyedScalarAdd(MOBILE_ONLY_KEYED_SCALAR, expectedKey, 11715); Telemetry.keyedScalarSet(MOBILE_ONLY_KEYED_SCALAR, expectedKey, 11715); Telemetry.keyedScalarSetMaximum(MOBILE_ONLY_KEYED_SCALAR, expectedKey, 11715); // Get a snapshot of the scalars. - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const scalars = getParentProcessScalars(); + const keyedScalars = getParentProcessScalars(); Assert.equal(scalars[DEFAULT_PRODUCT_SCALAR], expectedValue, "The default platfomrs scalar must contain the right value"); Assert.equal(scalars[DESKTOP_ONLY_SCALAR], expectedValue, "The desktop-only scalar must contain the right value"); Assert.equal(scalars[MULTIPRODUCT_SCALAR], expectedValue, "The multiproduct scalar must contain the right value"); Assert.ok(!(MOBILE_ONLY_SCALAR in scalars), "The mobile-only scalar must not be persisted."); Assert.ok(!(MOBILE_ONLY_KEYED_SCALAR in keyedScalars), "The mobile-only keyed scalar must not be persisted."); }); add_task({ skip_if: () => !gIsAndroid, }, async function test_mobileSpecificScalar() { const DEFAULT_PRODUCT_SCALAR = "telemetry.test.default_products"; const DESKTOP_ONLY_SCALAR = "telemetry.test.desktop_only"; const DESKTOP_ONLY_KEYED_SCALAR = "telemetry.test.keyed_desktop_only"; const MULTIPRODUCT_SCALAR = "telemetry.test.multiproduct"; const MOBILE_ONLY_SCALAR = "telemetry.test.mobile_only"; const MOBILE_ONLY_KEYED_SCALAR = "telemetry.test.keyed_mobile_only"; Telemetry.clearScalars(); // Try to set the mobile and multiproduct scalars let expectedValue = 11714; let expectedKey = "some_key"; Telemetry.scalarAdd(DEFAULT_PRODUCT_SCALAR, expectedValue); Telemetry.scalarAdd(MOBILE_ONLY_SCALAR, expectedValue); Telemetry.keyedScalarSet(MOBILE_ONLY_KEYED_SCALAR, expectedKey, expectedValue); Telemetry.scalarAdd(MULTIPRODUCT_SCALAR, expectedValue); // Try to set the desktop-only scalar to some value. We will not be recording the value, // but we shouldn't throw. Telemetry.scalarSet(DESKTOP_ONLY_SCALAR, 11715); Telemetry.scalarSetMaximum(DESKTOP_ONLY_SCALAR, 11715); Telemetry.keyedScalarAdd(DESKTOP_ONLY_KEYED_SCALAR, expectedKey, 11715); Telemetry.keyedScalarSet(DESKTOP_ONLY_KEYED_SCALAR, expectedKey, 11715); Telemetry.keyedScalarSetMaximum(DESKTOP_ONLY_KEYED_SCALAR, expectedKey, 11715); // Get a snapshot of the scalars. - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); - const keyedScalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true); + const scalars = getParentProcessScalars(); + const keyedScalars = getParentProcessScalars(true); Assert.equal(scalars[DEFAULT_PRODUCT_SCALAR], expectedValue, "The default products scalar must contain the right value"); Assert.equal(scalars[MOBILE_ONLY_SCALAR], expectedValue, "The mobile-only scalar must contain the right value"); Assert.equal(keyedScalars[MOBILE_ONLY_KEYED_SCALAR][expectedKey], expectedValue, "The mobile-only keyed scalar must contain the right value"); Assert.equal(scalars[MULTIPRODUCT_SCALAR], expectedValue, "The multiproduct scalar must contain the right value"); Assert.ok(!(DESKTOP_ONLY_SCALAR in scalars), "The desktop-only scalar must not be persisted."); Assert.ok(!(DESKTOP_ONLY_KEYED_SCALAR in keyedScalars), "The desktop-only keyed scalar must not be persisted."); }); add_task({ skip_if: () => !gIsAndroid, }, async function test_geckoviewSpecificScalar() { const DEFAULT_PRODUCT_SCALAR = "telemetry.test.default_products"; const DESKTOP_ONLY_SCALAR = "telemetry.test.desktop_only"; const MULTIPRODUCT_SCALAR = "telemetry.test.multiproduct"; const MOBILE_ONLY_SCALAR = "telemetry.test.mobile_only"; const GECKOVIEW_ONLY_SCALAR = "telemetry.test.geckoview_only"; Telemetry.clearScalars(); // Fake a geckoview-like environment Services.prefs.setBoolPref("toolkit.telemetry.isGeckoViewMode", true); Telemetry.resetCurrentProduct(); // Try to set the mobile and multiproduct scalars let expectedValue = 11714; Telemetry.scalarAdd(GECKOVIEW_ONLY_SCALAR, expectedValue); Telemetry.scalarAdd(MOBILE_ONLY_SCALAR, expectedValue); Telemetry.scalarAdd(MULTIPRODUCT_SCALAR, expectedValue); Telemetry.scalarSet(DEFAULT_PRODUCT_SCALAR, expectedValue); // Try to set the desktop-only scalar to some value. We will not be recording the value, // but we shouldn't throw. Telemetry.scalarSet(DESKTOP_ONLY_SCALAR, 11715); Telemetry.scalarSetMaximum(DESKTOP_ONLY_SCALAR, 11715); // Get a snapshot of the scalars. - const scalars = - getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + const scalars = getParentProcessScalars(); Assert.equal(scalars[GECKOVIEW_ONLY_SCALAR], expectedValue, "The geckoview-only scalar must contain the right value"); Assert.equal(scalars[MOBILE_ONLY_SCALAR], expectedValue, "The mobile-only scalar must contain the right value"); Assert.equal(scalars[MULTIPRODUCT_SCALAR], expectedValue, "The multiproduct scalar must contain the right value"); Assert.equal(scalars[DEFAULT_PRODUCT_SCALAR], expectedValue, "The default products scalar must contain the right value"); Assert.ok(!(DESKTOP_ONLY_SCALAR in scalars), "The desktop-only scalar must not be persisted."); // Reset to original environment Services.prefs.clearUserPref("toolkit.telemetry.isGeckoViewMode"); Telemetry.resetCurrentProduct(); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js --- a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js @@ -1,137 +1,135 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const UINT_SCALAR = "telemetry.test.unsigned_int_kind"; const STRING_SCALAR = "telemetry.test.string_kind"; const BOOLEAN_SCALAR = "telemetry.test.boolean_kind"; const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int"; ChromeUtils.import("resource://services-common/utils.js"); /** * Return the path to the definitions file for the scalars. */ function getDefinitionsPath() { // Write the scalar definition to the spec file in the binary directory. let definitionFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); definitionFile = Services.dirsvc.get("GreD", Ci.nsIFile); definitionFile.append("ScalarArtifactDefinitions.json"); return definitionFile.path; } add_task(async function test_setup() { do_get_profile(); }); add_task({ // The test needs to write a file, and that fails in tests on Android. // We don't really need the Android coverage, so skip on Android. skip_if: () => AppConstants.platform == "android", }, async function test_invalidJSON() { const INVALID_JSON = "{ invalid,JSON { {1}"; const FILE_PATH = getDefinitionsPath(); // Write a corrupted JSON file. await OS.File.writeAtomic(FILE_PATH, INVALID_JSON, { encoding: "utf-8", noOverwrite: false }); // Simulate Firefox startup. This should not throw! await TelemetryController.testSetup(); await TelemetryController.testPromiseJsProbeRegistration(); // Cleanup. await TelemetryController.testShutdown(); await OS.File.remove(FILE_PATH); }); add_task({ // The test needs to write a file, and that fails in tests on Android. // We don't really need the Android coverage, so skip on Android. skip_if: () => AppConstants.platform == "android", }, async function test_dynamicBuiltin() { const DYNAMIC_SCALAR_SPEC = { "telemetry.test": { "builtin_dynamic": { "kind": "nsITelemetry::SCALAR_TYPE_COUNT", "expired": false, "record_on_release": false, "keyed": false, }, "builtin_dynamic_other": { "kind": "nsITelemetry::SCALAR_TYPE_BOOLEAN", "expired": false, "record_on_release": false, "keyed": false, }, }, }; Telemetry.clearScalars(); // Let's write to the definition file to also cover the file // loading part. const FILE_PATH = getDefinitionsPath(); await CommonUtils.writeJSON(DYNAMIC_SCALAR_SPEC, FILE_PATH); // Start TelemetryController to trigger loading the specs. await TelemetryController.testReset(); await TelemetryController.testPromiseJsProbeRegistration(); // Store to that scalar. const TEST_SCALAR1 = "telemetry.test.builtin_dynamic"; const TEST_SCALAR2 = "telemetry.test.builtin_dynamic_other"; Telemetry.scalarSet(TEST_SCALAR1, 3785); Telemetry.scalarSet(TEST_SCALAR2, true); // Check the values we tried to store. - const scalars = - Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + const scalars = Telemetry.getSnapshotForScalars("main", false).parent; // Check that they are serialized to the correct format. Assert.equal(typeof(scalars[TEST_SCALAR1]), "number", TEST_SCALAR1 + " must be serialized to the correct format."); Assert.ok(Number.isInteger(scalars[TEST_SCALAR1]), TEST_SCALAR1 + " must be a finite integer."); Assert.equal(scalars[TEST_SCALAR1], 3785, TEST_SCALAR1 + " must have the correct value."); Assert.equal(typeof(scalars[TEST_SCALAR2]), "boolean", TEST_SCALAR2 + " must be serialized to the correct format."); Assert.equal(scalars[TEST_SCALAR2], true, TEST_SCALAR2 + " must have the correct value."); // Clean up. await TelemetryController.testShutdown(); await OS.File.remove(FILE_PATH); }); add_task(async function test_keyedDynamicBuiltin() { Telemetry.clearScalars(); // Register the built-in scalars (let's not take the I/O hit). Telemetry.registerBuiltinScalars("telemetry.test", { "builtin_dynamic_keyed": { "kind": Ci.nsITelemetry.SCALAR_TYPE_COUNT, "expired": false, "record_on_release": false, "keyed": true, }, }); // Store to that scalar. const TEST_SCALAR1 = "telemetry.test.builtin_dynamic_keyed"; Telemetry.keyedScalarSet(TEST_SCALAR1, "test-key", 3785); // Check the values we tried to store. - const scalars = - Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent; + const scalars = Telemetry.getSnapshotForKeyedScalars("main", false).parent; // Check that they are serialized to the correct format. Assert.equal(typeof(scalars[TEST_SCALAR1]), "object", TEST_SCALAR1 + " must be a keyed scalar."); Assert.equal(typeof(scalars[TEST_SCALAR1]["test-key"]), "number", TEST_SCALAR1 + " must be serialized to the correct format."); Assert.ok(Number.isInteger(scalars[TEST_SCALAR1]["test-key"]), TEST_SCALAR1 + " must be a finite integer."); Assert.equal(scalars[TEST_SCALAR1]["test-key"], 3785, TEST_SCALAR1 + " must have the correct value."); });