diff options
Diffstat (limited to 'toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js')
-rw-r--r-- | toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js b/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js new file mode 100644 index 0000000000..9b81e57255 --- /dev/null +++ b/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js @@ -0,0 +1,164 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +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 => { + ok(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 + ok(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 => { + ok(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"); + ok(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; +}); |