| <!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 reportsForProcess(processReport) { | |
| let explicitRoot = {}; | |
| let nonExplicitRoot = {}; | |
| let jemallocHeapReportedSize = 0; | |
| let systemHeapReportedSize = 0; | |
| let jemallocHeapAllocatedSize = NaN; | |
| let systemHeapAllocatedSize = NaN; | |
| // In content processes, get the list of urls. | |
| let urls = new Set(); | |
| processReport.reports.forEach((report) => { | |
| if (report.path[0].startsWith("url(")) { | |
| // This can be a list of urls. | |
| let path_urls = report.path[0].slice(4, -1).split(", "); | |
| path_urls.forEach((url) => urls.add(url)); | |
| } | |
| // 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, | |
| }); | |
| } | |
| // Create the DOM structure for each process report: | |
| // <div class=process> <h4>...<h4> <pre> ...</pre> </div> | |
| let container = document.createElement("div"); | |
| container.classList.add("process"); | |
| let reportTitle = document.createElement("h4"); | |
| reportTitle.textContent = `${ | |
| processReport.isMainProcess ? "Main Process" : "Content Process" | |
| } (pid ${processReport.pid}) ${[...urls.values()].join(", ")}`; | |
| container.append(reportTitle); | |
| let reportNode = document.createElement("pre"); | |
| reportNode.classList.add("report"); | |
| container.append(reportNode); | |
| reportNode.append(convertNodeToDOM(explicitRoot.explicit, "explicit")); | |
| for (let prop in nonExplicitRoot) { | |
| reportNode.append(convertNodeToDOM(nonExplicitRoot[prop], prop)); | |
| } | |
| // Make sure we always put the main process first. | |
| if (processReport.isMainProcess) { | |
| window.reports.prepend(container); | |
| } else { | |
| window.reports.append(container); | |
| } | |
| } | |
| 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.reports.innerHTML = ""; | |
| window.reports.classList.remove("hidden"); | |
| if (!Array.isArray(reports)) { | |
| console.error("Unexpected memory report format!"); | |
| return; | |
| } | |
| reports.forEach(reportsForProcess); | |
| }; | |
| } | |
| </script> | |
| <style> | |
| html { | |
| font-family: sans-serif; | |
| } | |
| details, | |
| details div { | |
| margin-left: 1em; | |
| } | |
| summary:hover { | |
| cursor: pointer; | |
| } | |
| div.process { | |
| margin: 0.5em; | |
| border: 2px solid gray; | |
| border-radius: 10px; | |
| padding: 5px; | |
| background-color: lightgray; | |
| } | |
| .report { | |
| line-height: 1.5em; | |
| } | |
| .report > details { | |
| margin-bottom: 1em; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Memory Reports</h2> | |
| <button id="startButton">Measure</button> | |
| <div id="reports" class="hidden"></div> | |
| </body> | |
| </html> |