mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
This patch exposes a servo internal DOM API that is only made available to about: pages on the navigator object to request memory reports. The about:memory page itself is loaded like other html resources (eg. bad cert, net error) and makes use of this new API. On the implementation side, notable changes: - components/script/routed_promise.rs abstracts the setup used to fulfill a promise when the work needs to be routed through the constellation. The goal is to migrate other similar promise APIs in followup (eg. dom/webgpu/gpu.rs, bluetooth.rs). - a new message is added to request a report from the memory reporter, and the memory reporter creates a json representation of the set of memory reports. - the post-processing of memory reports is done in Javascript in the about-memory.html page, providing the same results as the current Rust code that outputs to stdout. We can decide later if we want to remove the current output. Signed-off-by: webbeef <me@webbeef.org>
177 lines
5.3 KiB
HTML
177 lines
5.3 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>about:memory</title>
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", start);
|
|
|
|
function insertNode(root, report) {
|
|
let currentNode = root;
|
|
for (let path of report.path) {
|
|
if (!currentNode[path]) {
|
|
currentNode[path] = { total: 0, container: true };
|
|
}
|
|
currentNode = currentNode[path];
|
|
currentNode.total += report.size;
|
|
}
|
|
currentNode.size = report.size;
|
|
currentNode.container = false;
|
|
}
|
|
|
|
function formatBytes(bytes) {
|
|
if (bytes < 1024) {
|
|
return bytes + " B";
|
|
} else if (bytes < 1024 * 1024) {
|
|
return (bytes / 1024).toFixed(2) + " KiB";
|
|
} else if (bytes < 1024 * 1024 * 1024) {
|
|
return (bytes / (1024 * 1024)).toFixed(2) + " MiB";
|
|
} else {
|
|
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GiB";
|
|
}
|
|
}
|
|
|
|
function formattedSize(size) {
|
|
// Use enough padding to take into account the "MiB" part.
|
|
return formatBytes(size).padStart(10);
|
|
}
|
|
|
|
function convertNodeToDOM(node, name = null) {
|
|
let result = document.createDocumentFragment();
|
|
|
|
if (node.container) {
|
|
let details = document.createElement("details");
|
|
let summary = document.createElement("summary");
|
|
summary.textContent = `${formattedSize(node.total)} -- ${name}`;
|
|
details.append(summary);
|
|
result.append(details);
|
|
|
|
// Add the children in descending order of total size.
|
|
let entries = Object.entries(node)
|
|
.filter((item) => {
|
|
return !["total", "size", "container"].includes(item[0]);
|
|
})
|
|
.sort((a, b) => b[1].total - a[1].total)
|
|
.forEach((item) =>
|
|
details.append(convertNodeToDOM(item[1], item[0]))
|
|
);
|
|
} else {
|
|
let inner = document.createElement("div");
|
|
inner.textContent = `${formattedSize(node.size)} -- ${name}`;
|
|
result.append(inner);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function start() {
|
|
window.startButton.onclick = async () => {
|
|
let content = await navigator.servo.reportMemory();
|
|
let reports = JSON.parse(content);
|
|
if (reports.error) {
|
|
console.error(reports.error);
|
|
return;
|
|
}
|
|
window.report.innerHTML = "";
|
|
window.report.classList.remove("hidden");
|
|
|
|
let explicitRoot = {};
|
|
let nonExplicitRoot = {};
|
|
|
|
let jemallocHeapReportedSize = 0;
|
|
let systemHeapReportedSize = 0;
|
|
|
|
let jemallocHeapAllocatedSize = NaN;
|
|
let systemHeapAllocatedSize = NaN;
|
|
|
|
reports.forEach((report) => {
|
|
// Add "explicit" to the start of the path, when appropriate.
|
|
if (report.kind.startsWith("Explicit")) {
|
|
report.path.unshift("explicit");
|
|
}
|
|
|
|
// Update the reported fractions of the heaps, when appropriate.
|
|
if (report.kind == "ExplicitJemallocHeapSize") {
|
|
jemallocHeapReportedSize += report.size;
|
|
} else if (report.kind == "ExplicitSystemHeapSize") {
|
|
systemHeapReportedSize += report.size;
|
|
}
|
|
|
|
// Record total size of the heaps, when we see them.
|
|
if (report.path.length == 1) {
|
|
if (report.path[0] == "jemalloc-heap-allocated") {
|
|
jemallocHeapAllocatedSize = report.size;
|
|
} else if (report.path[0] == "system-heap-allocated") {
|
|
systemHeapAllocatedSize = report.size;
|
|
}
|
|
}
|
|
|
|
// Insert this report at the proper position.
|
|
insertNode(
|
|
report.kind.startsWith("Explicit")
|
|
? explicitRoot
|
|
: nonExplicitRoot,
|
|
report
|
|
);
|
|
});
|
|
|
|
// Compute and insert the heap-unclassified values.
|
|
if (!isNaN(jemallocHeapAllocatedSize)) {
|
|
insertNode(explicitRoot, {
|
|
path: ["explicit", "jemalloc-heap-unclassified"],
|
|
size: jemallocHeapAllocatedSize - jemallocHeapReportedSize,
|
|
});
|
|
}
|
|
if (!isNaN(systemHeapAllocatedSize)) {
|
|
insertNode(explicitRoot, {
|
|
path: ["explicit", "system-heap-unclassified"],
|
|
size: systemHeapAllocatedSize - systemHeapReportedSize,
|
|
});
|
|
}
|
|
|
|
window.report.append(
|
|
convertNodeToDOM(explicitRoot.explicit, "explicit")
|
|
);
|
|
|
|
for (let prop in nonExplicitRoot) {
|
|
window.report.append(convertNodeToDOM(nonExplicitRoot[prop], prop));
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
<style>
|
|
html {
|
|
font-family: sans-serif;
|
|
}
|
|
|
|
details,
|
|
details div {
|
|
margin-left: 1em;
|
|
}
|
|
|
|
summary:hover {
|
|
cursor: pointer;
|
|
}
|
|
|
|
#report {
|
|
line-height: 1.5em;
|
|
border: 2px solid gray;
|
|
border-radius: 10px;
|
|
padding: 5px;
|
|
background-color: lightgray;
|
|
}
|
|
|
|
#report > details {
|
|
margin-bottom: 1em;
|
|
}
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Memory Reports</h2>
|
|
<button id="startButton">Measure</button>
|
|
<pre id="report" class="hidden"></pre>
|
|
</body>
|
|
</html>
|