diff options
Diffstat (limited to 'toolkit/components/processtools/tests')
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 |