summaryrefslogtreecommitdiffstats
path: root/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js')
-rw-r--r--toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js164
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;
+});