summaryrefslogtreecommitdiffstats
path: root/toolkit/components/processtools/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/processtools/tests')
-rw-r--r--toolkit/components/processtools/tests/browser/browser.ini12
-rw-r--r--toolkit/components/processtools/tests/browser/browser_test_powerMetrics.js400
-rw-r--r--toolkit/components/processtools/tests/browser/browser_test_procinfo.js165
-rw-r--r--toolkit/components/processtools/tests/browser/dummy.html20
-rw-r--r--toolkit/components/processtools/tests/xpcshell/test_process_kill.js52
-rw-r--r--toolkit/components/processtools/tests/xpcshell/test_total_cpu_time.js122
-rw-r--r--toolkit/components/processtools/tests/xpcshell/test_total_cpu_time_child.js10
-rw-r--r--toolkit/components/processtools/tests/xpcshell/xpcshell.ini9
8 files changed, 790 insertions, 0 deletions
diff --git a/toolkit/components/processtools/tests/browser/browser.ini b/toolkit/components/processtools/tests/browser/browser.ini
new file mode 100644
index 0000000000..3b32afefab
--- /dev/null
+++ b/toolkit/components/processtools/tests/browser/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+prefs=
+ media.rdd-process.enabled=true
+
+support-files =
+ dummy.html
+
+[browser_test_powerMetrics.js]
+skip-if =
+ win11_2009 && !debug # bug 1819845
+[browser_test_procinfo.js]
+skip-if = (ccov && os == "linux") # https://bugzilla.mozilla.org/show_bug.cgi?id=1608080
diff --git a/toolkit/components/processtools/tests/browser/browser_test_powerMetrics.js b/toolkit/components/processtools/tests/browser/browser_test_powerMetrics.js
new file mode 100644
index 0000000000..14573fd8a2
--- /dev/null
+++ b/toolkit/components/processtools/tests/browser/browser_test_powerMetrics.js
@@ -0,0 +1,400 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Return a web-based URL for a given file based on the testing directory.
+ * @param {String} fileName
+ * file that caller wants its web-based url
+ */
+function GetTestWebBasedURL(fileName) {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+ ) + fileName
+ );
+}
+
+const kNS_PER_MS = 1000000;
+
+function printProcInfo(procInfo) {
+ info(
+ ` pid: ${procInfo.pid}, type = parent, cpu time = ${
+ procInfo.cpuTime / kNS_PER_MS
+ }ms`
+ );
+ for (let child of procInfo.children) {
+ info(
+ ` pid: ${child.pid}, type = ${child.type}, cpu time = ${
+ child.cpuTime / kNS_PER_MS
+ }ms`
+ );
+ }
+}
+
+// It would be nice to have an API to list all the statically defined labels of
+// a labeled_counter. Hopefully bug 1672273 will help with this.
+const kGleanProcessTypeLabels = [
+ "parent.active",
+ "parent.active.playing-audio",
+ "parent.active.playing-video",
+ "parent.inactive",
+ "parent.inactive.playing-audio",
+ "parent.inactive.playing-video",
+ "prealloc",
+ "privilegedabout",
+ "rdd",
+ "socket",
+ "web.background",
+ "web.background-perceivable",
+ "web.foreground",
+ "extension",
+ "gpu",
+ "gmplugin",
+ "utility",
+ "__other__",
+];
+
+async function getChildCpuTime(pid) {
+ return (await ChromeUtils.requestProcInfo()).children.find(p => p.pid == pid)
+ .cpuTime;
+}
+
+add_task(async () => {
+ // Temporarily open a new foreground tab to make the current tab a background
+ // tab, and burn some CPU time in it while it's in the background.
+ const kBusyWaitForMs = 50;
+ let cpuTimeSpentOnBackgroundTab;
+ let firstBrowser = gBrowser.selectedTab.linkedBrowser;
+ let processPriorityChangedPromise = BrowserTestUtils.contentTopicObserved(
+ firstBrowser.browsingContext,
+ "ipc:process-priority-changed"
+ );
+ await BrowserTestUtils.withNewTab(
+ GetTestWebBasedURL("dummy.html"),
+ async () => {
+ await processPriorityChangedPromise;
+ // We can't be sure that a busy loop lasting for a specific duration
+ // will use the same amount of CPU time, as that would require a core
+ // to be fully available for our busy loop, which is unlikely on single
+ // core hardware.
+ // To be able to have a predictable amount of CPU time used, we need to
+ // check using ChromeUtils.requestProcInfo how much CPU time has actually
+ // been spent.
+ let pid = firstBrowser.frameLoader.remoteTab.osPid;
+ let initalCpuTime = await getChildCpuTime(pid);
+ let afterCpuTime;
+ do {
+ await SpecialPowers.spawn(
+ firstBrowser,
+ [kBusyWaitForMs],
+ async kBusyWaitForMs => {
+ let startTime = Date.now();
+ while (Date.now() - startTime < 10) {
+ // Burn CPU time...
+ }
+ }
+ );
+ afterCpuTime = await getChildCpuTime(pid);
+ } while (afterCpuTime - initalCpuTime < kBusyWaitForMs * kNS_PER_MS);
+ cpuTimeSpentOnBackgroundTab = Math.floor(
+ (afterCpuTime - initalCpuTime) / kNS_PER_MS
+ );
+ }
+ );
+
+ let beforeProcInfo = await ChromeUtils.requestProcInfo();
+ await Services.fog.testFlushAllChildren();
+
+ let cpuTimeByType = {},
+ gpuTimeByType = {};
+ for (let label of kGleanProcessTypeLabels) {
+ cpuTimeByType[label] =
+ Glean.power.cpuTimePerProcessTypeMs[label].testGetValue();
+ gpuTimeByType[label] =
+ Glean.power.gpuTimePerProcessTypeMs[label].testGetValue();
+ }
+ let totalCpuTime = Glean.power.totalCpuTimeMs.testGetValue();
+ let totalGpuTime = Glean.power.totalGpuTimeMs.testGetValue();
+
+ let afterProcInfo = await ChromeUtils.requestProcInfo();
+
+ info("CPU time from ProcInfo before calling testFlushAllChildren:");
+ printProcInfo(beforeProcInfo);
+
+ info("CPU time for each label:");
+ let totalCpuTimeByType = 0;
+ for (let label of kGleanProcessTypeLabels) {
+ totalCpuTimeByType += cpuTimeByType[label] ?? 0;
+ info(` ${label} = ${cpuTimeByType[label]}`);
+ }
+
+ info("CPU time from ProcInfo after calling testFlushAllChildren:");
+ printProcInfo(afterProcInfo);
+
+ Assert.equal(
+ totalCpuTimeByType,
+ totalCpuTime,
+ "The sum of CPU time used by all process types should match totalCpuTimeMs"
+ );
+
+ // In infra the parent process time will be reported as parent.inactive,
+ // but when running the test locally the user might move the mouse a bit.
+ let parentTime =
+ (cpuTimeByType["parent.active"] || 0) +
+ (cpuTimeByType["parent.inactive"] || 0);
+ Assert.greaterOrEqual(
+ parentTime,
+ Math.floor(beforeProcInfo.cpuTime / kNS_PER_MS),
+ "reported parent cpu time should be at least what the first requestProcInfo returned"
+ );
+ Assert.lessOrEqual(
+ parentTime,
+ Math.ceil(afterProcInfo.cpuTime / kNS_PER_MS),
+ "reported parent cpu time should be at most what the second requestProcInfo returned"
+ );
+
+ kGleanProcessTypeLabels
+ .filter(label => label.startsWith("parent.") && label.includes(".playing-"))
+ .forEach(label => {
+ Assert.strictEqual(
+ cpuTimeByType[label],
+ undefined,
+ `no media was played so the CPU time for ${label} should be undefined`
+ );
+ });
+
+ if (beforeProcInfo.children.some(p => p.type == "preallocated")) {
+ Assert.greaterOrEqual(
+ cpuTimeByType.prealloc,
+ beforeProcInfo.children.reduce(
+ (time, p) =>
+ time +
+ (p.type == "preallocated" ? Math.floor(p.cpuTime / kNS_PER_MS) : 0),
+ 0
+ ),
+ "reported cpu time for preallocated content processes should be at least the sum of what the first requestProcInfo returned."
+ );
+ // We can't compare with the values returned by the second requestProcInfo
+ // call because one preallocated content processes has been turned into
+ // a normal content process when we opened a tab.
+ } else {
+ info(
+ "no preallocated content process existed when we started our test, but some might have existed before"
+ );
+ }
+
+ if (beforeProcInfo.children.some(p => p.type == "privilegedabout")) {
+ Assert.greaterOrEqual(
+ cpuTimeByType.privilegedabout,
+ 1,
+ "we used some CPU time in a foreground tab, but don't know how much as the process might have started as preallocated"
+ );
+ }
+
+ for (let label of [
+ "rdd",
+ "socket",
+ "extension",
+ "gpu",
+ "gmplugin",
+ "utility",
+ ]) {
+ if (!kGleanProcessTypeLabels.includes(label)) {
+ Assert.ok(
+ false,
+ `coding error in the test, ${label} isn't part of ${kGleanProcessTypeLabels.join(
+ ", "
+ )}`
+ );
+ }
+ if (beforeProcInfo.children.some(p => p.type == label)) {
+ Assert.greaterOrEqual(
+ cpuTimeByType[label],
+ Math.floor(
+ beforeProcInfo.children.find(p => p.type == label).cpuTime /
+ kNS_PER_MS
+ ),
+ "reported cpu time for " +
+ label +
+ " process should be at least what the first requestProcInfo returned."
+ );
+ Assert.lessOrEqual(
+ cpuTimeByType[label],
+ Math.ceil(
+ afterProcInfo.children.find(p => p.type == label).cpuTime / kNS_PER_MS
+ ),
+ "reported cpu time for " +
+ label +
+ " process should be at most what the second requestProcInfo returned."
+ );
+ } else {
+ info(
+ "no " +
+ label +
+ " process existed when we started our test, but some might have existed before"
+ );
+ }
+ }
+
+ Assert.greaterOrEqual(
+ cpuTimeByType["web.background"],
+ cpuTimeSpentOnBackgroundTab,
+ "web.background should be at least the time we spent."
+ );
+
+ Assert.greaterOrEqual(
+ cpuTimeByType["web.foreground"],
+ 1,
+ "we used some CPU time in a foreground tab, but don't know how much"
+ );
+
+ // We only have web.background-perceivable CPU time if a muted video was
+ // played in a background tab.
+ Assert.strictEqual(
+ cpuTimeByType["web.background-perceivable"],
+ undefined,
+ "CPU time should only be recorded in the web.background-perceivable label"
+ );
+
+ // __other__ should be undefined, if it is not, we have a missing label in the metrics.yaml file.
+ Assert.strictEqual(
+ cpuTimeByType.__other__,
+ undefined,
+ "no CPU time should be recorded in the __other__ label"
+ );
+
+ info("GPU time for each label:");
+ let totalGpuTimeByType = undefined;
+ for (let label of kGleanProcessTypeLabels) {
+ if (gpuTimeByType[label] !== undefined) {
+ totalGpuTimeByType = (totalGpuTimeByType || 0) + gpuTimeByType[label];
+ }
+ info(` ${label} = ${gpuTimeByType[label]}`);
+ }
+
+ Assert.equal(
+ totalGpuTimeByType,
+ totalGpuTime,
+ "The sum of GPU time used by all process types should match totalGpuTimeMs"
+ );
+
+ // __other__ should be undefined, if it is not, we have a missing label in the metrics.yaml file.
+ Assert.strictEqual(
+ gpuTimeByType.__other__,
+ undefined,
+ "no GPU time should be recorded in the __other__ label"
+ );
+
+ // Now test per-thread CPU time.
+ // We don't test parentActive as the user is not marked active on infra.
+ let processTypes = [
+ "parentInactive",
+ "contentBackground",
+ "contentForeground",
+ ];
+ if (beforeProcInfo.children.some(p => p.type == "gpu")) {
+ processTypes.push("gpuProcess");
+ }
+ // The list of accepted labels is not accessible to the JS code, so test only the main thread.
+ const kThreadName = "geckomain";
+ if (AppConstants.NIGHTLY_BUILD) {
+ for (let processType of processTypes) {
+ Assert.greater(
+ Glean.powerCpuMsPerThread[processType][kThreadName].testGetValue(),
+ 0,
+ `some CPU time should have been recorded for the ${processType} main thread`
+ );
+ Assert.greater(
+ Glean.powerWakeupsPerThread[processType][kThreadName].testGetValue(),
+ 0,
+ `some thread wake ups should have been recorded for the ${processType} main thread`
+ );
+ }
+ } else {
+ // We are not recording per thread CPU use outside of the Nightly channel.
+ for (let processType of processTypes) {
+ Assert.equal(
+ Glean.powerCpuMsPerThread[processType][kThreadName].testGetValue(),
+ undefined,
+ `no CPU time should have been recorded for the ${processType} main thread`
+ );
+ Assert.equal(
+ Glean.powerWakeupsPerThread[processType][kThreadName].testGetValue(),
+ undefined,
+ `no thread wake ups should have been recorded for the ${processType} main thread`
+ );
+ }
+ }
+});
+
+add_task(async function test_tracker_power() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+ let initialValues = [];
+ for (let trackerType of [
+ "ad",
+ "analytics",
+ "cryptomining",
+ "fingerprinting",
+ "social",
+ "unknown",
+ ]) {
+ initialValues[trackerType] =
+ Glean.power.cpuTimePerTrackerTypeMs[trackerType].testGetValue() || 0;
+ }
+
+ await BrowserTestUtils.withNewTab(
+ GetTestWebBasedURL("dummy.html"),
+ async () => {
+ // Load a tracker in a subframe, as we only record CPU time used by third party trackers.
+ await SpecialPowers.spawn(
+ gBrowser.selectedTab.linkedBrowser,
+ [
+ GetTestWebBasedURL("dummy.html").replace(
+ "example.org",
+ "trackertest.org"
+ ),
+ ],
+ async frameUrl => {
+ let iframe = content.document.createElement("iframe");
+ iframe.setAttribute("src", frameUrl);
+ await new content.Promise(resolve => {
+ iframe.onload = resolve;
+ content.document.body.appendChild(iframe);
+ });
+ }
+ );
+ }
+ );
+
+ await Services.fog.testFlushAllChildren();
+
+ let unknownTrackerCPUTime =
+ Glean.power.cpuTimePerTrackerTypeMs.unknown.testGetValue() || 0;
+ Assert.greater(
+ unknownTrackerCPUTime,
+ initialValues.unknown,
+ "The CPU time of unknown trackers should have increased"
+ );
+
+ for (let trackerType of [
+ "ad",
+ "analytics",
+ "cryptomining",
+ "fingerprinting",
+ "social",
+ ]) {
+ Assert.equal(
+ Glean.power.cpuTimePerTrackerTypeMs[trackerType].testGetValue() || 0,
+ initialValues[trackerType],
+ `no new CPU time should have been recorded for ${trackerType} trackers`
+ );
+ }
+});
diff --git a/toolkit/components/processtools/tests/browser/browser_test_procinfo.js b/toolkit/components/processtools/tests/browser/browser_test_procinfo.js
new file mode 100644
index 0000000000..4201060bd0
--- /dev/null
+++ b/toolkit/components/processtools/tests/browser/browser_test_procinfo.js
@@ -0,0 +1,165 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const DUMMY_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "/dummy.html";
+
+const HAS_THREAD_NAMES =
+ AppConstants.platform != "win" ||
+ AppConstants.isPlatformAndVersionAtLeast("win", 10);
+const isFissionEnabled = SpecialPowers.useRemoteSubframes;
+
+const SAMPLE_SIZE = 10;
+const NS_PER_MS = 1000000;
+
+function checkProcessCpuTime(proc) {
+ Assert.greater(proc.cpuTime, 0, "Got some cpu time");
+
+ let cpuThreads = 0;
+ for (let thread of proc.threads) {
+ cpuThreads += Math.floor(thread.cpuTime / NS_PER_MS);
+ }
+ Assert.greater(cpuThreads, 0, "Got some cpu time in the threads");
+ let processCpuTime = Math.ceil(proc.cpuTime / NS_PER_MS);
+ if (AppConstants.platform == "win" && processCpuTime < cpuThreads) {
+ // On Windows, our test jobs likely run in VMs without constant TSC,
+ // so we might have low precision CPU time measurements.
+ const MAX_DISCREPENCY = 100;
+ Assert.ok(
+ cpuThreads - processCpuTime < MAX_DISCREPENCY,
+ `on Windows, we accept a discrepency of up to ${MAX_DISCREPENCY}ms between the process CPU time and the sum of its threads' CPU time, process CPU time: ${processCpuTime}, sum of thread CPU time: ${cpuThreads}`
+ );
+ } else {
+ Assert.greaterOrEqual(
+ processCpuTime,
+ cpuThreads,
+ "The total CPU time of the process should be at least the sum of the CPU time spent by the still alive threads"
+ );
+ }
+}
+
+add_task(async function test_proc_info() {
+ // Open a few `about:home` tabs, they'll end up in `privilegedabout`.
+ let tabsAboutHome = [];
+ for (let i = 0; i < 5; ++i) {
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:home");
+ tabsAboutHome.push(tab);
+ gBrowser.selectedTab = tab;
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: DUMMY_URL },
+ async function (browser) {
+ // We test `SAMPLE_SIZE` times to increase a tad the chance of encountering race conditions.
+ for (let z = 0; z < SAMPLE_SIZE; z++) {
+ let parentProc = await ChromeUtils.requestProcInfo();
+
+ Assert.equal(
+ parentProc.type,
+ "browser",
+ "Parent proc type should be browser"
+ );
+
+ checkProcessCpuTime(parentProc);
+
+ // Under Windows, thread names appeared with Windows 10.
+ if (HAS_THREAD_NAMES) {
+ Assert.ok(
+ parentProc.threads.some(thread => thread.name),
+ "At least one of the threads of the parent process is named"
+ );
+ }
+
+ Assert.ok(parentProc.memory > 0, "Memory was set");
+
+ // While it's very unlikely that the parent will disappear while we're running
+ // tests, some children can easily vanish. So we go twice through the list of
+ // children. Once to test stuff that all process data respects the invariants
+ // that don't care whether we have a race condition and once to test that at
+ // least one well-known process that should not be able to vanish during
+ // the test respects all the invariants.
+ for (let childProc of parentProc.children) {
+ Assert.notEqual(
+ childProc.type,
+ "browser",
+ "Child proc type should not be browser"
+ );
+
+ // We set the `childID` for child processes that have a `ContentParent`/`ContentChild`
+ // actor hierarchy.
+ if (childProc.type.startsWith("web")) {
+ Assert.notEqual(
+ childProc.childID,
+ 0,
+ "Child proc should have been set"
+ );
+ }
+ Assert.notEqual(
+ childProc.type,
+ "unknown",
+ "Child proc type should be known"
+ );
+ if (childProc.type == "webIsolated") {
+ Assert.notEqual(
+ childProc.origin || "",
+ "",
+ "Child process should have an origin"
+ );
+ }
+
+ checkProcessCpuTime(childProc);
+ }
+
+ // We only check other properties on the `privilegedabout` subprocess, which
+ // as of this writing is always active and available.
+ var hasPrivilegedAbout = false;
+ var numberOfAboutTabs = 0;
+ for (let childProc of parentProc.children) {
+ if (childProc.type != "privilegedabout") {
+ continue;
+ }
+ hasPrivilegedAbout = true;
+ Assert.ok(childProc.memory > 0, "Memory was set");
+
+ for (var win of childProc.windows) {
+ if (win.documentURI.spec != "about:home") {
+ // We're only interested in about:home for this test.
+ continue;
+ }
+ numberOfAboutTabs++;
+ Assert.ok(
+ win.outerWindowId > 0,
+ `ContentParentID should be > 0 ${win.outerWindowId}`
+ );
+ if (win.documentTitle) {
+ // Unfortunately, we sometimes reach this point before the document is fully loaded, so
+ // `win.documentTitle` may still be empty.
+ Assert.equal(win.documentTitle, "New Tab");
+ }
+ }
+ Assert.ok(
+ numberOfAboutTabs >= tabsAboutHome.length,
+ "We have found at least as many about:home tabs as we opened"
+ );
+
+ // Once we have verified the privileged about process, bailout.
+ break;
+ }
+
+ Assert.ok(
+ hasPrivilegedAbout,
+ "We have found the privileged about process"
+ );
+ }
+
+ for (let tab of tabsAboutHome) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+ );
+});
diff --git a/toolkit/components/processtools/tests/browser/dummy.html b/toolkit/components/processtools/tests/browser/dummy.html
new file mode 100644
index 0000000000..e69dad24d4
--- /dev/null
+++ b/toolkit/components/processtools/tests/browser/dummy.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+<div id="holder" class="">Holder</div>
+<script>
+ let text = "";
+ for (let i = 0; i < 1000; i++) {
+ text += "more";
+ // eslint-disable-next-line no-unsanitized/property
+ document.getElementById("holder").innerHTML = text;
+ }
+ document.getElementById("holder").classList.add("loaded");
+</script>
+</body>
+</html>
diff --git a/toolkit/components/processtools/tests/xpcshell/test_process_kill.js b/toolkit/components/processtools/tests/xpcshell/test_process_kill.js
new file mode 100644
index 0000000000..9781efaffc
--- /dev/null
+++ b/toolkit/components/processtools/tests/xpcshell/test_process_kill.js
@@ -0,0 +1,52 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const { Subprocess } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+);
+
+const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+);
+
+let PYTHON;
+
+// Find Python.
+add_task(async function setup() {
+ PYTHON = await Subprocess.pathSearch(Services.env.get("PYTHON"));
+});
+
+// Ensure that killing a process... kills the process.
+add_task(async function test_subprocess_kill() {
+ // We launch Python, as it's a long-running process and it exists
+ // on all desktop platforms on which we run tests.
+ let proc = await Subprocess.call({
+ command: PYTHON,
+ arguments: [],
+ });
+
+ let isTerminated = false;
+
+ proc.wait().then(() => {
+ isTerminated = true;
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+ Assert.ok(
+ !isTerminated,
+ "We haven't killed the process yet, it should still be running."
+ );
+
+ // Time to kill the process.
+ ProcessTools.kill(proc.pid);
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+ Assert.ok(
+ isTerminated,
+ "We have killed the process already, it shouldn't be running anymore."
+ );
+});
diff --git a/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time.js b/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time.js
new file mode 100644
index 0000000000..00f1ea2b6c
--- /dev/null
+++ b/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time.js
@@ -0,0 +1,122 @@
+"use strict";
+
+var cpuThreadCount;
+
+add_task(async function setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ Services.fog.initializeFOG();
+
+ cpuThreadCount = (await Services.sysinfo.processInfo).count;
+});
+
+async function getCpuTimeFromProcInfo() {
+ const NS_PER_MS = 1000000;
+ let cpuTimeForProcess = p => p.cpuTime / NS_PER_MS;
+ let procInfo = await ChromeUtils.requestProcInfo();
+ return (
+ cpuTimeForProcess(procInfo) +
+ procInfo.children.map(cpuTimeForProcess).reduce((a, b) => a + b, 0)
+ );
+}
+
+add_task(async function test_totalCpuTime_in_parent() {
+ let startTime = Date.now();
+
+ let initialProcInfoCpuTime = Math.floor(await getCpuTimeFromProcInfo());
+ await Services.fog.testFlushAllChildren();
+
+ let initialCpuTime = Glean.power.totalCpuTimeMs.testGetValue();
+ Assert.greater(
+ initialCpuTime,
+ 0,
+ "The CPU time used by starting the test harness should be more than 0"
+ );
+ let initialProcInfoCpuTime2 = Math.ceil(await getCpuTimeFromProcInfo());
+
+ Assert.greaterOrEqual(
+ initialCpuTime,
+ initialProcInfoCpuTime,
+ "The CPU time reported through Glean should be at least as much as the CPU time reported by ProcInfo before asking Glean for the data"
+ );
+ Assert.lessOrEqual(
+ initialCpuTime,
+ initialProcInfoCpuTime2,
+ "The CPU time reported through Glean should be no more than the CPU time reported by ProcInfo after asking Glean for the data"
+ );
+
+ // 50 is an arbitrary value, but the resolution is 16ms on Windows,
+ // so this needs to be significantly more than 16.
+ const kBusyWaitForMs = 50;
+ while (Date.now() - startTime < kBusyWaitForMs) {
+ // Burn CPU time...
+ }
+
+ let additionalProcInfoCpuTime =
+ Math.floor(await getCpuTimeFromProcInfo()) - initialProcInfoCpuTime2;
+ await Services.fog.testFlushAllChildren();
+
+ let additionalCpuTime =
+ Glean.power.totalCpuTimeMs.testGetValue() - initialCpuTime;
+ info(
+ `additional CPU time according to ProcInfo: ${additionalProcInfoCpuTime}ms and Glean ${additionalCpuTime}ms`
+ );
+
+ // On a machine where the CPU is very busy, our busy wait loop might burn less
+ // CPU than expected if other processes are being scheduled instead of us.
+ let expectedAdditionalCpuTime = Math.min(
+ additionalProcInfoCpuTime,
+ kBusyWaitForMs
+ );
+ Assert.greaterOrEqual(
+ additionalCpuTime,
+ expectedAdditionalCpuTime,
+ `The total CPU time should have increased by at least ${expectedAdditionalCpuTime}ms`
+ );
+ let wallClockTime = Date.now() - startTime;
+ Assert.lessOrEqual(
+ additionalCpuTime,
+ wallClockTime * cpuThreadCount,
+ `The total CPU time should have increased by at most the wall clock time (${wallClockTime}ms) * ${cpuThreadCount} CPU threads`
+ );
+});
+
+add_task(async function test_totalCpuTime_in_child() {
+ const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
+
+ let startTime = Date.now();
+ await Services.fog.testFlushAllChildren();
+ let initialCpuTime = Glean.power.totalCpuTimeMs.testGetValue();
+
+ let initialProcInfoCpuTime = await getCpuTimeFromProcInfo();
+ run_test_in_child("test_total_cpu_time_child.js");
+ let burntCpuTime = await do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
+ let additionalProcInfoCpuTime =
+ (await getCpuTimeFromProcInfo()) - initialProcInfoCpuTime;
+
+ await Services.fog.testFlushAllChildren();
+ let additionalCpuTime =
+ Glean.power.totalCpuTimeMs.testGetValue() - initialCpuTime;
+ info(
+ `additional CPU time according to ProcInfo: ${additionalProcInfoCpuTime}ms and Glean ${additionalCpuTime}ms`
+ );
+
+ // On a machine where the CPU is very busy, our busy wait loop might burn less
+ // CPU than expected if other processes are being scheduled instead of us.
+ let expectedAdditionalCpuTime = Math.min(
+ Math.floor(additionalProcInfoCpuTime),
+ burntCpuTime
+ );
+ Assert.greaterOrEqual(
+ additionalCpuTime,
+ expectedAdditionalCpuTime,
+ `The total CPU time should have increased by at least ${expectedAdditionalCpuTime}ms`
+ );
+ let wallClockTime = Date.now() - startTime;
+ Assert.lessOrEqual(
+ additionalCpuTime,
+ wallClockTime * cpuThreadCount,
+ `The total CPU time should have increased by at most the wall clock time (${wallClockTime}ms) * ${cpuThreadCount} CPU threads`
+ );
+});
diff --git a/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time_child.js b/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time_child.js
new file mode 100644
index 0000000000..88ac480a09
--- /dev/null
+++ b/toolkit/components/processtools/tests/xpcshell/test_total_cpu_time_child.js
@@ -0,0 +1,10 @@
+const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
+
+function run_test() {
+ const kBusyWaitForMs = 50;
+ let startTime = Date.now();
+ while (Date.now() - startTime < kBusyWaitForMs) {
+ // Burn CPU time...
+ }
+ do_send_remote_message(MESSAGE_CHILD_TEST_DONE, kBusyWaitForMs);
+}
diff --git a/toolkit/components/processtools/tests/xpcshell/xpcshell.ini b/toolkit/components/processtools/tests/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..bd9bdaac93
--- /dev/null
+++ b/toolkit/components/processtools/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+firefox-appdir = browser
+subprocess = true
+
+[test_process_kill.js]
+skip-if = os == 'android'
+[test_total_cpu_time.js]
+skip-if = socketprocess_networking
+support-files = test_total_cpu_time_child.js