/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // This tests the public Telemetry API for submitting Health pings. "use strict"; ChromeUtils.defineESModuleGetters(this, { TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs", }); function checkHealthPingStructure(ping, expectedFailuresDict) { let payload = ping.payload; Assert.equal( ping.type, TelemetryHealthPing.HEALTH_PING_TYPE, "Should have recorded a health ping." ); for (let [key, value] of Object.entries(expectedFailuresDict)) { Assert.deepEqual( payload[key], value, "Should have recorded correct entry with key: " + key ); } } function fakeHealthSchedulerTimer(set, clear) { let { Policy } = ChromeUtils.importESModule( "resource://gre/modules/HealthPing.sys.mjs" ); Policy.setSchedulerTickTimeout = set; Policy.clearSchedulerTickTimeout = clear; } add_setup(async function setup() { // Trigger a proper telemetry init. do_get_profile(true); // Make sure we don't generate unexpected pings due to pref changes. await setEmptyPrefWatchlist(); Services.prefs.setBoolPref( TelemetryUtils.Preferences.HealthPingEnabled, true ); await TelemetryController.testSetup(); PingServer.start(); TelemetrySend.setServer("http://localhost:" + PingServer.port); Services.prefs.setStringPref( TelemetryUtils.Preferences.Server, "http://localhost:" + PingServer.port ); }); registerCleanupFunction(async function cleanup() { await PingServer.stop(); }); add_task(async function test_sendImmediately() { PingServer.clearRequests(); TelemetryHealthPing.testReset(); await TelemetryHealthPing.recordSendFailure("testProblem"); let ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.SEND_FAILURE]: { testProblem: 1, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.IMMEDIATE, }); }); add_task(async function test_sendOnDelay() { PingServer.clearRequests(); TelemetryHealthPing.testReset(); // This first failure should immediately trigger a ping. After this, subsequent failures should be throttled. await TelemetryHealthPing.recordSendFailure("testFailure"); let testPing = await PingServer.promiseNextPing(); Assert.equal( testPing.type, TelemetryHealthPing.HEALTH_PING_TYPE, "Should have recorded a health ping." ); // Retrieve delayed call back. let pingSubmissionCallBack = null; fakeHealthSchedulerTimer( callBack => (pingSubmissionCallBack = callBack), () => {} ); // Record two failures, health ping must not be send now. await TelemetryHealthPing.recordSendFailure("testFailure"); await TelemetryHealthPing.recordSendFailure("testFailure"); // Wait for sending delayed health ping. await pingSubmissionCallBack(); let ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.SEND_FAILURE]: { testFailure: 2, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.DELAYED, }); }); add_task(async function test_sendOverSizedPing() { TelemetryHealthPing.testReset(); PingServer.clearRequests(); let OVER_SIZED_PING_TYPE = "over-sized-ping"; let overSizedData = generateRandomString(2 * 1024 * 1024); await TelemetryController.submitExternalPing(OVER_SIZED_PING_TYPE, { data: overSizedData, }); let ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: { [OVER_SIZED_PING_TYPE]: 1, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.IMMEDIATE, }); }); add_task(async function test_sendOnlyTopTenDiscardedPings() { TelemetryHealthPing.testReset(); await TelemetrySend.reset(); PingServer.clearRequests(); let PING_TYPE = "sort-discarded"; // This first failure should immediately trigger a ping. After this, subsequent failures should be throttled. await TelemetryHealthPing.recordSendFailure("testFailure"); let testPing = await PingServer.promiseNextPing(); Assert.equal( testPing.type, TelemetryHealthPing.HEALTH_PING_TYPE, "Should have recorded a health ping." ); // Retrieve delayed call back. let pingSubmissionCallBack = null; fakeHealthSchedulerTimer( callBack => (pingSubmissionCallBack = callBack), () => {} ); // Add failures for (let i = 1; i < 12; i++) { for (let j = 1; j < i; j++) { TelemetryHealthPing.recordDiscardedPing(PING_TYPE + i); } } await TelemetrySend.reset(); await pingSubmissionCallBack(); let ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.DELAYED, [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: { [PING_TYPE + 11]: 10, [PING_TYPE + 10]: 9, [PING_TYPE + 9]: 8, [PING_TYPE + 8]: 7, [PING_TYPE + 7]: 6, [PING_TYPE + 6]: 5, [PING_TYPE + 5]: 4, [PING_TYPE + 4]: 3, [PING_TYPE + 3]: 2, [PING_TYPE + 2]: 1, }, }); }); add_task(async function test_discardedForSizePending() { TelemetryHealthPing.testReset(); PingServer.clearRequests(); const PING_TYPE = "discarded-for-size-pending"; const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24"; // Create a pending oversized ping. let overSizedPayload = generateRandomString(2 * 1024 * 1024); const OVERSIZED_PING = { id: OVERSIZED_PING_ID, type: PING_TYPE, creationDate: new Date().toISOString(), // Generate a 2MB string to use as the ping payload. payload: overSizedPayload, }; // Test loadPendingPing. await TelemetryStorage.savePendingPing(OVERSIZED_PING); // Try to manually load the oversized ping. await Assert.rejects( TelemetryStorage.loadPendingPing(OVERSIZED_PING_ID), /loadPendingPing - exceeded the maximum ping size/, "The oversized ping should have been pruned." ); let ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: { "": 1, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.IMMEDIATE, }); // Test _scanPendingPings. TelemetryHealthPing.testReset(); await TelemetryStorage.savePendingPing(OVERSIZED_PING); await TelemetryStorage.loadPendingPingList(); ping = await PingServer.promiseNextPing(); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: { "": 1, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.IMMEDIATE, }); }); add_task(async function test_usePingSenderOnShutdown() { if ( gIsAndroid || (AppConstants.platform == "linux" && !Services.appinfo.is64Bit) ) { // We don't support the pingsender on Android, yet, see bug 1335917. // We also don't support the pingsender testing on Treeherder for // Linux 32 bit (due to missing libraries). So skip it there too. // See bug 1310703 comment 78. return; } TelemetryHealthPing.testReset(); await TelemetrySend.reset(); PingServer.clearRequests(); // This first failure should immediately trigger a ping. // After this, subsequent failures should be throttled. await TelemetryHealthPing.recordSendFailure("testFailure"); await PingServer.promiseNextPing(); TelemetryHealthPing.recordSendFailure("testFailure"); let nextRequest = PingServer.promiseNextRequest(); await TelemetryController.testReset(); await TelemetryController.testShutdown(); let request = await nextRequest; let ping = decodeRequestPayload(request); checkHealthPingStructure(ping, { [TelemetryHealthPing.FailureType.SEND_FAILURE]: { testFailure: 1, }, os: TelemetryHealthPing.OsInfo, reason: TelemetryHealthPing.Reason.SHUT_DOWN, }); // Check that the health ping is sent at shutdown using the pingsender. Assert.equal( request.getHeader("User-Agent"), "pingsender/1.0", "Should have received the correct user agent string." ); Assert.equal( request.getHeader("X-PingSender-Version"), "1.0", "Should have received the correct PingSender version string." ); });