/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); const { ContentTaskUtils } = ChromeUtils.importESModule( "resource://testing-common/ContentTaskUtils.sys.mjs" ); const { setTimeout } = ChromeUtils.importESModule( "resource://gre/modules/Timer.sys.mjs" ); const Telemetry = Services.telemetry; const { TelemetryTestUtils } = ChromeUtils.importESModule( "resource://testing-common/TelemetryTestUtils.sys.mjs" ); function sleep(ms) { /* eslint-disable mozilla/no-arbitrary-setTimeout */ return new Promise(resolve => setTimeout(resolve, ms)); } function scalarValue(aScalarName, aProcessName) { let snapshot = Telemetry.getSnapshotForScalars(); return aProcessName in snapshot ? snapshot[aProcessName][aScalarName] : undefined; } function keyedScalarValue(aScalarName, aProcessName) { let snapshot = Telemetry.getSnapshotForKeyedScalars(); return aProcessName in snapshot ? snapshot[aProcessName][aScalarName] : undefined; } add_setup({ skip_if: () => !runningInParent }, function test_setup() { // Give FOG a temp profile to init within. do_get_profile(); // Allows these tests to properly run on e.g. Thunderbird Services.prefs.setBoolPref( "toolkit.telemetry.testing.overrideProductsCheck", true ); // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. // on Android FOG is set up through head.js if (AppConstants.platform != "android") { Services.fog.initializeFOG(); } }); const COUNT = 42; const CHEESY_STRING = "a very cheesy string!"; const CHEESIER_STRING = "a much cheesier string!"; const CUSTOM_SAMPLES = [3, 4]; const EVENT_EXTRA = { extra1: "so very extra" }; const MEMORIES = [13, 31]; const MEMORY_BUCKETS = ["13193", "31378"]; // buckets are strings : | const A_LABEL_COUNT = 3; const ANOTHER_LABEL_COUNT = 5; const INVALID_COUNTERS = 7; const IRATE_NUMERATOR = 44; const IRATE_DENOMINATOR = 14; add_task({ skip_if: () => runningInParent }, async function run_child_stuff() { let oldCanRecordBase = Telemetry.canRecordBase; Telemetry.canRecordBase = true; // Ensure we're able to record things. Glean.testOnlyIpc.aCounter.add(COUNT); Glean.testOnlyIpc.aStringList.add(CHEESY_STRING); Glean.testOnlyIpc.aStringList.add(CHEESIER_STRING); Glean.testOnlyIpc.noExtraEvent.record(); Glean.testOnlyIpc.anEvent.record(EVENT_EXTRA); for (let memory of MEMORIES) { Glean.testOnlyIpc.aMemoryDist.accumulate(memory); } let t1 = Glean.testOnlyIpc.aTimingDist.start(); let t2 = Glean.testOnlyIpc.aTimingDist.start(); await sleep(5); let t3 = Glean.testOnlyIpc.aTimingDist.start(); Glean.testOnlyIpc.aTimingDist.cancel(t1); await sleep(5); Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t2); // 10ms Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t3); // 5ms Glean.testOnlyIpc.aCustomDist.accumulateSamples(CUSTOM_SAMPLES); Glean.testOnlyIpc.aLabeledCounter.a_label.add(A_LABEL_COUNT); Glean.testOnlyIpc.aLabeledCounter.another_label.add(ANOTHER_LABEL_COUNT); // Has to be different from aLabeledCounter so the error we record doesn't // get in the way. Glean.testOnlyIpc.anotherLabeledCounter["1".repeat(72)].add(INVALID_COUNTERS); Glean.testOnlyIpc.irate.addToNumerator(IRATE_NUMERATOR); Glean.testOnlyIpc.irate.addToDenominator(IRATE_DENOMINATOR); Telemetry.canRecordBase = oldCanRecordBase; }); add_task( { skip_if: () => !runningInParent }, async function test_child_metrics() { Telemetry.setEventRecordingEnabled("telemetry.test", true); // Clear any stray Telemetry data Telemetry.clearScalars(); Telemetry.getSnapshotForHistograms("main", true); Telemetry.clearEvents(); await run_test_in_child("test_GIFFTIPC.js"); // Wait for both IPC mechanisms to flush. await Services.fog.testFlushAllChildren(); await ContentTaskUtils.waitForCondition(() => { let snapshot = Telemetry.getSnapshotForKeyedScalars(); return ( "content" in snapshot && "telemetry.test.mirror_for_rate" in snapshot.content ); }, "failed to find content telemetry in parent"); // boolean // Doesn't work over IPC // counter Assert.equal(Glean.testOnlyIpc.aCounter.testGetValue(), COUNT); Assert.equal( scalarValue("telemetry.test.mirror_for_counter", "content"), COUNT, "content-process Scalar has expected count" ); // custom_distribution const customSampleSum = CUSTOM_SAMPLES.reduce((acc, a) => acc + a, 0); const customData = Glean.testOnlyIpc.aCustomDist.testGetValue("store1"); Assert.equal(customSampleSum, customData.sum, "Sum's correct"); for (let [bucket, count] of Object.entries(customData.values)) { Assert.ok( count == 0 || (count == CUSTOM_SAMPLES.length && bucket == 1), // both values in the low bucket `Only two buckets have a sample ${bucket} ${count}` ); } const histSnapshot = Telemetry.getSnapshotForHistograms( "main", false, false ); const histData = histSnapshot.content.TELEMETRY_TEST_MIRROR_FOR_CUSTOM; Assert.equal(customSampleSum, histData.sum, "Sum in histogram's correct"); Assert.equal(2, histData.values["1"], "Two samples in the first bucket"); // datetime // Doesn't work over IPC // event var events = Glean.testOnlyIpc.noExtraEvent.testGetValue(); Assert.equal(1, events.length); Assert.equal("test_only.ipc", events[0].category); Assert.equal("no_extra_event", events[0].name); events = Glean.testOnlyIpc.anEvent.testGetValue(); Assert.equal(1, events.length); Assert.equal("test_only.ipc", events[0].category); Assert.equal("an_event", events[0].name); Assert.deepEqual(EVENT_EXTRA, events[0].extra); TelemetryTestUtils.assertEvents( [ [ "telemetry.test", "not_expired_optout", "object1", undefined, undefined, ], ["telemetry.test", "mirror_with_extra", "object1", null, EVENT_EXTRA], ], { category: "telemetry.test" }, { process: "content" } ); // labeled_boolean // Doesn't work over IPC // labeled_counter const counters = Glean.testOnlyIpc.aLabeledCounter; Assert.equal(counters.a_label.testGetValue(), A_LABEL_COUNT); Assert.equal(counters.another_label.testGetValue(), ANOTHER_LABEL_COUNT); Assert.throws( () => Glean.testOnlyIpc.anotherLabeledCounter.__other__.testGetValue(), /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, "Invalid labels record errors, which throw" ); let value = keyedScalarValue( "telemetry.test.another_mirror_for_labeled_counter", "content" ); Assert.deepEqual( { a_label: A_LABEL_COUNT, another_label: ANOTHER_LABEL_COUNT, }, value ); value = keyedScalarValue( "telemetry.test.mirror_for_labeled_counter", "content" ); Assert.deepEqual( { ["1".repeat(72)]: INVALID_COUNTERS, }, value ); // labeled_string // Doesn't work over IPC // memory_distribution const memoryData = Glean.testOnlyIpc.aMemoryDist.testGetValue(); const memorySum = MEMORIES.reduce((acc, a) => acc + a, 0); // The sum's in bytes, but the metric's in KB Assert.equal(memorySum * 1024, memoryData.sum); for (let [bucket, count] of Object.entries(memoryData.values)) { // We could assert instead, but let's skip to save the logspam. if (count == 0) { continue; } Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket)); } const memoryHist = histSnapshot.content.TELEMETRY_TEST_LINEAR; Assert.equal( memorySum, memoryHist.sum, "Histogram's in `memory_unit` units" ); Assert.equal(2, memoryHist.values["1"], "Samples are in the right bucket"); // quantity // Doesn't work over IPC // rate Assert.deepEqual( { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR }, Glean.testOnlyIpc.irate.testGetValue() ); Assert.deepEqual( { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR }, keyedScalarValue("telemetry.test.mirror_for_rate", "content") ); // string // Doesn't work over IPC // string_list // Note: this will break if string list ever rearranges its items. const cheesyStrings = Glean.testOnlyIpc.aStringList.testGetValue(); Assert.deepEqual(cheesyStrings, [CHEESY_STRING, CHEESIER_STRING]); // Note: this will break if keyed scalars rearrange their items. Assert.deepEqual( { [CHEESY_STRING]: true, [CHEESIER_STRING]: true, }, keyedScalarValue("telemetry.test.keyed_boolean_kind", "content") ); // timespan // Doesn't work over IPC // timing_distribution const NANOS_IN_MILLIS = 1e6; const EPSILON = 40000; // bug 1701949 const times = Glean.testOnlyIpc.aTimingDist.testGetValue(); Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON); // We can't guarantee any specific time values (thank you clocks), // but we can assert there are only two samples. Assert.equal( 2, Object.entries(times.values).reduce( (acc, [bucket, count]) => acc + count, 0 ) ); const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL; Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds."); // Both values, 10 and 5, are truncated by a cast in AccumulateTimeDelta // Minimally downcast 9. + 4. could realistically result in 13. Assert.equal( 2, Object.entries(timingHist.values).reduce( (acc, [bucket, count]) => acc + count, 0 ), "Only two samples" ); // uuid // Doesn't work over IPC } );