Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: debug OR os == 'android' OR release_or_beta OR os == 'mac' OR win11_2009 && bits == 32 OR os == 'win' && msix
- This test failed 2 times in the preceding 30 days. quicksearch this test
- Manifest: toolkit/components/backgroundhangmonitor/tests/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
const { TelemetryUtils } = ChromeUtils.importESModule(
"resource://gre/modules/TelemetryUtils.sys.mjs"
);
function ensureProfilerInitialized() {
// Starting and stopping the profiler with the "stackwalk" flag will cause the
// profiler's stackwalking features to be synchronously initialized. This
// should prevent us from not initializing BHR quickly enough.
let features = ["stackwalk"];
Services.profiler.StartProfiler(1000, 10, features);
Services.profiler.StopProfiler();
}
add_task(async function test_BHRObserver() {
if (!Services.telemetry.canRecordExtended) {
ok("Hang reporting not enabled.");
return;
}
ensureProfilerInitialized();
let telSvc =
Cc["@mozilla.org/bhr-telemetry-service;1"].getService().wrappedJSObject;
ok(telSvc, "Should have BHRTelemetryService");
let beforeLen = telSvc.payload.hangs.length;
if (Services.appinfo.OS === "Linux" || Services.appinfo.OS === "Android") {
// We use the rt_tgsigqueueinfo syscall on Linux which requires a
// certain kernel version. It's not an error if the system running
// the test is older than that.
let kernel =
Services.sysinfo.get("kernel_version") || Services.sysinfo.get("version");
if (Services.vc.compare(kernel, "2.6.31") < 0) {
ok("Hang reporting not supported for old kernel.");
return;
}
}
let hangsPromise = new Promise(resolve => {
let hangs = [];
const onThreadHang = subject => {
let hang = subject.QueryInterface(Ci.nsIHangDetails);
if (hang.thread.startsWith("Gecko")) {
hangs.push(hang);
if (hangs.length >= 3) {
Services.obs.removeObserver(onThreadHang, "bhr-thread-hang");
resolve(hangs);
}
}
};
Services.obs.addObserver(onThreadHang, "bhr-thread-hang");
});
// We're going to trigger two hangs, of various lengths. One should be a
// transient hang, and the other a permanent hang. We'll wait for the hangs to
// be recorded.
executeSoon(() => {
let startTime = Date.now();
// eslint-disable-next-line no-empty
while (Date.now() - startTime < 10000) {}
});
executeSoon(() => {
let startTime = Date.now();
// eslint-disable-next-line no-empty
while (Date.now() - startTime < 1000) {}
});
Services.prefs.setBoolPref(
TelemetryUtils.Preferences.OverridePreRelease,
true
);
let childDone = run_test_in_child("child_cause_hang.js");
// Now we wait for the hangs to have their bhr-thread-hang message fired for
// them, collect them, and analyize the response.
let hangs = await hangsPromise;
equal(hangs.length, 3);
hangs.forEach(hang => {
Assert.greater(hang.duration, 0);
ok(hang.thread == "Gecko" || hang.thread == "Gecko_Child");
equal(typeof hang.runnableName, "string");
// hang.stack
ok(Array.isArray(hang.stack));
ok(!!hang.stack.length);
hang.stack.forEach(entry => {
// Each stack frame entry is either a native or pseudostack entry. A
// native stack entry is an array with module index (number), and offset
// (hex string), while the pseudostack entry is a bare string.
if (Array.isArray(entry)) {
equal(entry.length, 2);
equal(typeof entry[0], "number");
equal(typeof entry[1], "string");
} else {
equal(typeof entry, "string");
}
});
// hang.modules
ok(Array.isArray(hang.modules));
hang.modules.forEach(module => {
ok(Array.isArray(module));
equal(module.length, 2);
equal(typeof module[0], "string");
equal(typeof module[1], "string");
});
// hang.annotations
ok(Array.isArray(hang.annotations));
hang.annotations.forEach(annotation => {
ok(Array.isArray(annotation));
equal(annotation.length, 2);
equal(typeof annotation[0], "string");
equal(typeof annotation[1], "string");
});
});
// Check that the telemetry service collected pings which make sense
Assert.greaterOrEqual(telSvc.payload.hangs.length - beforeLen, 3);
ok(Array.isArray(telSvc.payload.modules));
telSvc.payload.modules.forEach(module => {
ok(Array.isArray(module));
equal(module.length, 2);
equal(typeof module[0], "string");
equal(typeof module[1], "string");
});
telSvc.payload.hangs.forEach(hang => {
Assert.greater(hang.duration, 0);
ok(hang.thread == "Gecko" || hang.thread == "Gecko_Child");
equal(typeof hang.runnableName, "string");
// hang.stack
ok(Array.isArray(hang.stack));
ok(!!hang.stack.length);
hang.stack.forEach(entry => {
// Each stack frame entry is either a native or pseudostack entry. A
// native stack entry is an array with module index (number), and offset
// (hex string), while the pseudostack entry is a bare string.
if (Array.isArray(entry)) {
equal(entry.length, 2);
equal(typeof entry[0], "number");
Assert.less(entry[0], telSvc.payload.modules.length);
equal(typeof entry[1], "string");
} else {
equal(typeof entry, "string");
}
});
// hang.annotations
equal(typeof hang.annotations, "object");
Object.keys(hang.annotations).forEach(key => {
equal(typeof hang.annotations[key], "string");
});
});
do_send_remote_message("bhr_hangs_detected");
await childDone;
});