Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"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(
) + 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
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 () => {
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],
null,
`no media was played so the CPU time for ${label} should be null`
);
});
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)) {
function getCpuTime(procInfo) {
return (
procInfo.children.find(p => p.type == label).cpuTime / kNS_PER_MS
);
}
let beforeCpuTime = Math.floor(getCpuTime(beforeProcInfo));
let afterCpuTime = Math.ceil(getCpuTime(afterProcInfo));
let cpuTime = cpuTimeByType[label];
if (afterCpuTime == 0) {
Assert.equal(
cpuTime,
null,
"The " + label + " process used less than 1ms of CPU time."
);
} else if (beforeCpuTime == 0 && cpuTime === null) {
info(
"The " +
label +
" process might have used used less than 1ms of CPU time."
);
} else {
Assert.greaterOrEqual(
cpuTime,
beforeCpuTime,
"reported cpu time for " +
label +
" process should be at least what the first requestProcInfo returned."
);
Assert.lessOrEqual(
cpuTime,
afterCpuTime,
"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"],
null,
"CPU time should only be recorded in the web.background-perceivable label"
);
// __other__ should be null, if it is not, we have a missing label in the metrics.yaml file.
Assert.strictEqual(
cpuTimeByType.__other__,
null,
"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] !== null) {
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 null, if it is not, we have a missing label in the metrics.yaml file.
Assert.strictEqual(
gpuTimeByType.__other__,
null,
"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(),
null,
`no CPU time should have been recorded for the ${processType} main thread`
);
Assert.equal(
Glean.powerWakeupsPerThread[processType][kThreadName].testGetValue(),
null,
`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",
"itisatracker.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`
);
}
});