mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-07 19:59:18 +02:00
Differential Revision: https://phabricator.services.mozilla.com/D66128 --HG-- extra : moz-landing-system : lando
403 lines
12 KiB
JavaScript
403 lines
12 KiB
JavaScript
/* 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.import("resource://gre/modules/TelemetryController.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
ChromeUtils.import(
|
|
"resource://testing-common/TelemetryArchiveTesting.jsm",
|
|
this
|
|
);
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"TelemetryHealthPing",
|
|
"resource://gre/modules/HealthPing.jsm"
|
|
);
|
|
|
|
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 telemetryHealthPing = ChromeUtils.import(
|
|
"resource://gre/modules/HealthPing.jsm",
|
|
null
|
|
);
|
|
telemetryHealthPing.Policy.setSchedulerTickTimeout = set;
|
|
telemetryHealthPing.Policy.clearSchedulerTickTimeout = clear;
|
|
}
|
|
|
|
async function waitForConditionWithPromise(
|
|
promiseFn,
|
|
timeoutMsg,
|
|
tryCount = 30
|
|
) {
|
|
const SINGLE_TRY_TIMEOUT = 100;
|
|
let tries = 0;
|
|
do {
|
|
try {
|
|
return await promiseFn();
|
|
} catch (ex) {}
|
|
await new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve));
|
|
} while (++tries <= tryCount);
|
|
throw new Error(timeoutMsg);
|
|
}
|
|
|
|
function fakeSendSubmissionTimeout(timeOut) {
|
|
let telemetryHealthPing = ChromeUtils.import(
|
|
"resource://gre/modules/TelemetrySend.jsm",
|
|
null
|
|
);
|
|
telemetryHealthPing.Policy.pingSubmissionTimeout = () => timeOut;
|
|
}
|
|
|
|
add_task(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();
|
|
Preferences.set(TelemetryUtils.Preferences.HealthPingEnabled, true);
|
|
|
|
await TelemetryController.testSetup();
|
|
PingServer.start();
|
|
TelemetrySend.setServer("http://localhost:" + PingServer.port);
|
|
Preferences.set(
|
|
TelemetryUtils.Preferences.Server,
|
|
"http://localhost:" + PingServer.port
|
|
);
|
|
});
|
|
|
|
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_healthPingOnTop() {
|
|
PingServer.clearRequests();
|
|
TelemetryHealthPing.testReset();
|
|
|
|
let PING_TYPE = "priority-ping";
|
|
|
|
// Fake now to be in throttled state.
|
|
let now = fakeNow(2050, 1, 2, 0, 0, 0);
|
|
fakeMidnightPingFuzzingDelay(60 * 1000);
|
|
|
|
for (let value of [PING_TYPE, PING_TYPE, "health", PING_TYPE]) {
|
|
TelemetryController.submitExternalPing(value, {});
|
|
}
|
|
|
|
// Now trigger sending pings again.
|
|
fakeNow(futureDate(now, 5 * 60 * 1000));
|
|
await TelemetrySend.notifyCanUpload();
|
|
let scheduler = ChromeUtils.import(
|
|
"resource://gre/modules/TelemetrySend.jsm",
|
|
null
|
|
);
|
|
scheduler.SendScheduler.triggerSendingPings(true);
|
|
|
|
let pings = await PingServer.promiseNextPings(4);
|
|
Assert.equal(
|
|
pings[0].type,
|
|
"health",
|
|
"Should have received the health ping first."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_sendOnTimeout() {
|
|
TelemetryHealthPing.testReset();
|
|
await TelemetrySend.reset();
|
|
PingServer.clearRequests();
|
|
let PING_TYPE = "ping-on-timeout";
|
|
|
|
// Disable send retry to make this test more deterministic.
|
|
fakePingSendTimer(
|
|
() => {},
|
|
() => {}
|
|
);
|
|
|
|
// Set up small ping submission timeout to always have timeout error.
|
|
fakeSendSubmissionTimeout(2);
|
|
|
|
await TelemetryController.submitExternalPing(PING_TYPE, {});
|
|
|
|
let response;
|
|
PingServer.registerPingHandler((req, res) => {
|
|
PingServer.resetPingHandler();
|
|
// We don't finish the response yet to make sure to trigger a timeout.
|
|
res.processAsync();
|
|
response = res;
|
|
});
|
|
|
|
// Wait for health ping.
|
|
let ac = new TelemetryArchiveTesting.Checker();
|
|
await ac.promiseInit();
|
|
await waitForConditionWithPromise(() => {
|
|
ac.promiseFindPing("health", []);
|
|
}, "Failed to find health ping");
|
|
|
|
if (response) {
|
|
response.finish();
|
|
}
|
|
|
|
let telemetryHealthPing = ChromeUtils.import(
|
|
"resource://gre/modules/TelemetrySend.jsm",
|
|
null
|
|
);
|
|
fakeSendSubmissionTimeout(telemetryHealthPing.PING_SUBMIT_TIMEOUT_MS);
|
|
PingServer.resetPingHandler();
|
|
TelemetrySend.notifyCanUpload();
|
|
|
|
let pings = await PingServer.promiseNextPings(2);
|
|
let healthPing = pings.find(ping => ping.type === "health");
|
|
checkHealthPingStructure(healthPing, {
|
|
[TelemetryHealthPing.FailureType.SEND_FAILURE]: {
|
|
timeout: 1,
|
|
},
|
|
os: TelemetryHealthPing.OsInfo,
|
|
reason: TelemetryHealthPing.Reason.IMMEDIATE,
|
|
});
|
|
await TelemetryStorage.testClearPendingPings();
|
|
});
|
|
|
|
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]: {
|
|
"<unknown>": 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]: {
|
|
"<unknown>": 1,
|
|
},
|
|
os: TelemetryHealthPing.OsInfo,
|
|
reason: TelemetryHealthPing.Reason.IMMEDIATE,
|
|
});
|
|
});
|
|
|
|
add_task(async function test_usePingSenderOnShutdown() {
|
|
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 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."
|
|
);
|
|
});
|
|
|
|
add_task(async function cleanup() {
|
|
await PingServer.stop();
|
|
});
|