зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1636927 - Sanity checks for about:processes;r=florian
Differential Revision: https://phabricator.services.mozilla.com/D75390
This commit is contained in:
Родитель
c7de928dee
Коммит
0932a07c69
|
@ -684,7 +684,9 @@ var Control = {
|
|||
let process = row.process;
|
||||
this._sortThreads(process.threads);
|
||||
for (let thread of process.threads) {
|
||||
View.appendThreadRow(thread);
|
||||
// Enrich `elt` with a property `thread`, used for testing.
|
||||
let elt = View.appendThreadRow(thread);
|
||||
elt.thread = thread;
|
||||
}
|
||||
},
|
||||
_sortThreads(threads) {
|
||||
|
|
|
@ -8,3 +8,5 @@ with Files('**'):
|
|||
BUG_COMPONENT = ('Toolkit', 'Performance Monitoring')
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
[browser_aboutprocesses.js]
|
|
@ -0,0 +1,351 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// A bunch of assumptions we make about the behavior of the parent process,
|
||||
// and which we use as sanity checks. If Firefox evolves, we will need to
|
||||
// update these values.
|
||||
const HARDCODED_ASSUMPTIONS_PROCESS = {
|
||||
minimalNumberOfThreads: 10,
|
||||
maximalNumberOfThreads: 1000,
|
||||
minimalCPUPercentage: 0.0001,
|
||||
maximalCPUPercentage: 1000,
|
||||
minimalCPUTotalDurationMS: 100,
|
||||
maximalCPUTotalDurationMS: 10000000,
|
||||
minimalRAMBytesUsage: 1024 * 1024 /* 1 Megabyte */,
|
||||
maximalRAMBytesUsage: 1024 * 1024 * 1024 * 10 /* 10 Gigabytes */,
|
||||
};
|
||||
|
||||
const HARDCODED_ASSUMPTIONS_THREAD = {
|
||||
minimalCPUPercentage: 0,
|
||||
maximalCPUPercentage: 100,
|
||||
minimalCPUTotalDurationMS: 0,
|
||||
maximalCPUTotalDurationMS: 10000000,
|
||||
};
|
||||
|
||||
// How close we accept our rounding up/down.
|
||||
const APPROX_FACTOR = 1.1;
|
||||
const MS_PER_NS = 1000000;
|
||||
const MEMORY_REGEXP = /([0-9.,]+)(TB|GB|MB|KB|B)( \(([-+]?)([0-9.,]+)(GB|MB|KB|B)\))?/;
|
||||
//Example: "383.55MB (-12.5MB)"
|
||||
const CPU_REGEXP = /(\~0%|idle|[0-9.,]+%|[?]) \(([0-9.,]+) ?(ms)\)/;
|
||||
//Example: "13% (4,470ms)"
|
||||
|
||||
function isCloseEnough(value, expected) {
|
||||
if (value < 0 || expected < 0) {
|
||||
throw new Error(`Invalid isCloseEnough(${value}, ${expected})`);
|
||||
}
|
||||
if (Math.round(value) == Math.round(expected)) {
|
||||
return true;
|
||||
}
|
||||
if (expected == 0) {
|
||||
return false;
|
||||
}
|
||||
let ratio = value / expected;
|
||||
if (ratio <= APPROX_FACTOR && ratio >= 1 / APPROX_FACTOR) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getMemoryMultiplier(unit, sign = "+") {
|
||||
let multiplier;
|
||||
switch (sign) {
|
||||
case "+":
|
||||
multiplier = 1;
|
||||
break;
|
||||
case "-":
|
||||
multiplier = -1;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid sign: " + sign);
|
||||
}
|
||||
switch (unit) {
|
||||
case "B":
|
||||
break;
|
||||
case "KB":
|
||||
multiplier *= 1024;
|
||||
break;
|
||||
case "MB":
|
||||
multiplier *= 1024 * 1024;
|
||||
break;
|
||||
case "GB":
|
||||
multiplier *= 1024 * 1024 * 1024;
|
||||
break;
|
||||
case "TB":
|
||||
multiplier *= 1024 * 1024 * 1024 * 1024;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid memory unit: " + unit);
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
function getTimeMultiplier(unit) {
|
||||
if (unit == "ms") {
|
||||
return 1;
|
||||
}
|
||||
throw new Error("Invalid time unit: " + unit);
|
||||
}
|
||||
function testCpu(string, total, slope, assumptions) {
|
||||
info(`Testing CPU display ${string} vs total ${total}, slope ${slope}`);
|
||||
let [, extractedPercentage, extractedTotal, extractedUnit] = CPU_REGEXP.exec(
|
||||
string
|
||||
);
|
||||
|
||||
switch (extractedPercentage) {
|
||||
case "idle":
|
||||
Assert.equal(slope, 0, "Idle means exactly 0%");
|
||||
// Nothing else to do here.
|
||||
return;
|
||||
case "~0%":
|
||||
Assert.ok(slope > 0 && slope < 0.0001);
|
||||
break;
|
||||
case "?":
|
||||
Assert.ok(slope == null);
|
||||
// Nothing else to do here.
|
||||
return;
|
||||
default: {
|
||||
// `Number.parseFloat("99%")` returns `99`.
|
||||
let computedPercentage = Number.parseFloat(extractedPercentage);
|
||||
Assert.ok(
|
||||
isCloseEnough(computedPercentage, slope * 100),
|
||||
`The displayed approximation of the slope is reasonable: ${computedPercentage} vs ${slope *
|
||||
100}`
|
||||
);
|
||||
// Also, sanity checks.
|
||||
Assert.ok(
|
||||
computedPercentage / 100 >= assumptions.minimalCPUPercentage,
|
||||
`Not too little: ${computedPercentage / 100} >=? ${
|
||||
assumptions.minimalCPUPercentage
|
||||
} `
|
||||
);
|
||||
Assert.ok(
|
||||
computedPercentage / 100 <= assumptions.maximalCPUPercentage,
|
||||
`Not too much: ${computedPercentage / 100} <=? ${
|
||||
assumptions.maximalCPUPercentage
|
||||
} `
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let totalMS = total / MS_PER_NS;
|
||||
let computedTotal =
|
||||
// We produce localized numbers, with "," as a thousands separator in en-US builds,
|
||||
// but `parseFloat` doesn't understand the ",", so we need to remove it
|
||||
// before parsing.
|
||||
Number.parseFloat(extractedTotal.replace(/,/g, "")) *
|
||||
getTimeMultiplier(extractedUnit);
|
||||
Assert.ok(
|
||||
isCloseEnough(computedTotal, totalMS),
|
||||
`The displayed approximation of the total duration is reasonable: ${computedTotal} vs ${totalMS}`
|
||||
);
|
||||
Assert.ok(
|
||||
totalMS <= assumptions.maximalCPUTotalDurationMS &&
|
||||
totalMS >= assumptions.minimalCPUTotalDurationMS,
|
||||
`The total number of MS is reasonable ${totalMS}: [${assumptions.minimalCPUTotalDurationMS}, ${assumptions.maximalCPUTotalDurationMS}]`
|
||||
);
|
||||
}
|
||||
|
||||
function testMemory(string, total, delta, assumptions) {
|
||||
Assert.ok(
|
||||
true,
|
||||
`Testing memory display ${string} vs total ${total}, delta ${delta}`
|
||||
);
|
||||
let extracted = MEMORY_REGEXP.exec(string);
|
||||
Assert.notEqual(
|
||||
extracted,
|
||||
null,
|
||||
`Can we parse ${string} with ${MEMORY_REGEXP}?`
|
||||
);
|
||||
let [
|
||||
,
|
||||
extractedTotal,
|
||||
extractedUnit,
|
||||
,
|
||||
extractedDeltaSign,
|
||||
extractedDeltaTotal,
|
||||
extractedDeltaUnit,
|
||||
] = extracted;
|
||||
let extractedTotalNumber = Number.parseFloat(extractedTotal);
|
||||
Assert.ok(
|
||||
extractedTotalNumber > 0 && extractedTotalNumber < 1024,
|
||||
`Unitless total memory use is in (0, 1024): ${extractedTotal}`
|
||||
);
|
||||
|
||||
// Now check that the conversion was meaningful.
|
||||
let computedTotal = getMemoryMultiplier(extractedUnit) * extractedTotalNumber;
|
||||
Assert.ok(
|
||||
isCloseEnough(computedTotal, total),
|
||||
`The displayed approximation of the total amount of memory is reasonable: ${computedTotal} vs ${total}`
|
||||
);
|
||||
Assert.ok(
|
||||
assumptions.minimalRAMBytesUsage <= computedTotal &&
|
||||
computedTotal <= assumptions.maximalRAMBytesUsage,
|
||||
`The total amount amount of memory is reasonable: ${computedTotal} in [${assumptions.minimalRAMBytesUsage}, ${assumptions.maximalRAMBytesUsage}]`
|
||||
);
|
||||
|
||||
if (extractedDeltaSign == null) {
|
||||
Assert.ok(delta == 0);
|
||||
return;
|
||||
}
|
||||
let deltaTotalNumber = Number.parseFloat(extractedDeltaTotal);
|
||||
Assert.ok(
|
||||
deltaTotalNumber > 0 && deltaTotalNumber < 1024,
|
||||
`Unitless delta memory use is in (0, 1024): ${extractedDeltaTotal}`
|
||||
);
|
||||
Assert.ok(
|
||||
["B", "KB", "MB"].includes(extractedDeltaUnit),
|
||||
`Delta unit is reasonable: ${extractedDeltaUnit}`
|
||||
);
|
||||
|
||||
// Now check that the conversion was meaningful.
|
||||
// Let's just check that the number displayed is within 10% of `delta`.
|
||||
let computedDelta =
|
||||
getMemoryMultiplier(extractedDeltaUnit, extractedDeltaSign) *
|
||||
extractedDeltaTotal;
|
||||
Assert.equal(
|
||||
computedDelta >= 0,
|
||||
delta >= 0,
|
||||
`Delta has the right sign: ${computedDelta} vs ${delta}`
|
||||
);
|
||||
Assert.ok(
|
||||
isCloseEnough(Math.abs(computedDelta), Math.abs(delta)),
|
||||
`The displayed approximation of the delta amount of memory is reasonable: ${computedDelta} vs ${delta}`
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function testAboutProcesses() {
|
||||
info("Setting up about:processes");
|
||||
let tabAboutProcesses = (gBrowser.selectedTab = BrowserTestUtils.addTab(
|
||||
gBrowser,
|
||||
"about:processes"
|
||||
));
|
||||
|
||||
await BrowserTestUtils.browserLoaded(tabAboutProcesses.linkedBrowser);
|
||||
|
||||
let doc = tabAboutProcesses.linkedBrowser.contentDocument;
|
||||
let tbody = doc.getElementById("process-tbody");
|
||||
|
||||
// Wait until the table has first been populated.
|
||||
await TestUtils.waitForCondition(() => tbody.childElementCount);
|
||||
|
||||
// And wait for another update using a mutation observer, to give our newly created test tab some time
|
||||
// to burn some CPU.
|
||||
await new Promise(resolve => {
|
||||
let observer = new doc.ownerGlobal.MutationObserver(() => {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
});
|
||||
observer.observe(tbody, { childList: true });
|
||||
});
|
||||
|
||||
// Find the row for the browser process.
|
||||
let row = tbody.firstChild;
|
||||
while (row && row.children[1].textContent != "browser") {
|
||||
row = row.nextSibling;
|
||||
}
|
||||
|
||||
Assert.ok(row, "found a table row for the browser");
|
||||
let children = row.children;
|
||||
let pidContent = children[0].textContent;
|
||||
let memoryResidentContent = children[2].textContent;
|
||||
let memoryVirtualContent = children[3].textContent;
|
||||
let cpuUserContent = children[4].textContent;
|
||||
let cpuKernelContent = children[5].textContent;
|
||||
let numberOfThreadsContent = children[6].textContent;
|
||||
|
||||
info("Sanity checks: pid");
|
||||
let pid = Number.parseInt(pidContent);
|
||||
Assert.ok(pid > 0);
|
||||
Assert.equal(pid, row.process.pid);
|
||||
|
||||
info("Sanity checks: memory resident");
|
||||
testMemory(
|
||||
memoryResidentContent,
|
||||
row.process.totalResidentSize,
|
||||
row.process.deltaResidentSize,
|
||||
HARDCODED_ASSUMPTIONS_PROCESS
|
||||
);
|
||||
|
||||
info("Sanity checks: memory virtual");
|
||||
testMemory(
|
||||
memoryVirtualContent,
|
||||
row.process.totalVirtualMemorySize,
|
||||
row.process.deltaVirtualMemorySize,
|
||||
HARDCODED_ASSUMPTIONS_PROCESS
|
||||
);
|
||||
|
||||
info("Sanity checks: CPU (user)");
|
||||
testCpu(
|
||||
cpuUserContent,
|
||||
row.process.totalCpuUser,
|
||||
row.process.slopeCpuUser,
|
||||
HARDCODED_ASSUMPTIONS_PROCESS
|
||||
);
|
||||
|
||||
info("Sanity checks: CPU (kernel)");
|
||||
testCpu(
|
||||
cpuKernelContent,
|
||||
row.process.totalCpuKernel,
|
||||
row.process.slopeCpuKernel,
|
||||
HARDCODED_ASSUMPTIONS_PROCESS
|
||||
);
|
||||
|
||||
info("Sanity checks: number of threads");
|
||||
let numberOfThreads = Number.parseInt(numberOfThreadsContent);
|
||||
Assert.ok(
|
||||
numberOfThreads >= HARDCODED_ASSUMPTIONS_PROCESS.minimalNumberOfThreads
|
||||
);
|
||||
Assert.ok(
|
||||
numberOfThreads <= HARDCODED_ASSUMPTIONS_PROCESS.maximalNumberOfThreads
|
||||
);
|
||||
|
||||
info("Testing that we can open the list of threads");
|
||||
let twisty = row.getElementsByClassName("twisty")[0];
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
twisty,
|
||||
{},
|
||||
tabAboutProcesses.linkedBrowser.contentWindow
|
||||
);
|
||||
|
||||
let numberOfThreadsFound = 0;
|
||||
for (
|
||||
let threadRow = row.nextSibling;
|
||||
threadRow && threadRow.classList.contains("thread");
|
||||
threadRow = threadRow.nextSibling
|
||||
) {
|
||||
let children = threadRow.children;
|
||||
let tidContent = children[0].textContent;
|
||||
let cpuUserContent = children[4].textContent;
|
||||
let cpuKernelContent = children[5].textContent;
|
||||
++numberOfThreadsFound;
|
||||
|
||||
info("Sanity checks: tid");
|
||||
let tid = Number.parseInt(tidContent);
|
||||
Assert.ok(tid > 0);
|
||||
Assert.equal(tid, threadRow.thread.tid);
|
||||
|
||||
info("Sanity checks: CPU (user)");
|
||||
testCpu(
|
||||
cpuUserContent,
|
||||
threadRow.thread.totalCpuUser,
|
||||
threadRow.thread.slopeCpuUser,
|
||||
HARDCODED_ASSUMPTIONS_THREAD
|
||||
);
|
||||
|
||||
info("Sanity checks: CPU (kernel)");
|
||||
testCpu(
|
||||
cpuKernelContent,
|
||||
threadRow.thread.totalCpuKernel,
|
||||
threadRow.thread.slopeCpuKernel,
|
||||
HARDCODED_ASSUMPTIONS_THREAD
|
||||
);
|
||||
}
|
||||
|
||||
Assert.equal(numberOfThreads, numberOfThreadsFound);
|
||||
|
||||
BrowserTestUtils.removeTab(tabAboutProcesses);
|
||||
});
|
Загрузка…
Ссылка в новой задаче