Rewild Your Web
web browser dweb
at main 216 lines 6.8 kB view raw
1<!DOCTYPE html> 2<html> 3 <head> 4 <title>about:memory</title> 5 <script> 6 document.addEventListener("DOMContentLoaded", start); 7 8 function insertNode(root, report) { 9 let currentNode = root; 10 for (let path of report.path) { 11 if (!currentNode[path]) { 12 currentNode[path] = { total: 0, container: true }; 13 } 14 currentNode = currentNode[path]; 15 currentNode.total += report.size; 16 } 17 currentNode.size = report.size; 18 currentNode.container = false; 19 } 20 21 function formatBytes(bytes) { 22 if (bytes < 1024) { 23 return bytes + " B"; 24 } else if (bytes < 1024 * 1024) { 25 return (bytes / 1024).toFixed(2) + " KiB"; 26 } else if (bytes < 1024 * 1024 * 1024) { 27 return (bytes / (1024 * 1024)).toFixed(2) + " MiB"; 28 } else { 29 return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GiB"; 30 } 31 } 32 33 function formattedSize(size) { 34 // Use enough padding to take into account the "MiB" part. 35 return formatBytes(size).padStart(10); 36 } 37 38 function convertNodeToDOM(node, name = null) { 39 let result = document.createDocumentFragment(); 40 41 if (node.container) { 42 let details = document.createElement("details"); 43 let summary = document.createElement("summary"); 44 summary.textContent = `${formattedSize(node.total)} -- ${name}`; 45 details.append(summary); 46 result.append(details); 47 48 // Add the children in descending order of total size. 49 let entries = Object.entries(node) 50 .filter((item) => { 51 return !["total", "size", "container"].includes(item[0]); 52 }) 53 .sort((a, b) => b[1].total - a[1].total) 54 .forEach((item) => 55 details.append(convertNodeToDOM(item[1], item[0])) 56 ); 57 } else { 58 let inner = document.createElement("div"); 59 inner.textContent = `${formattedSize(node.size)} -- ${name}`; 60 result.append(inner); 61 } 62 63 return result; 64 } 65 66 function reportsForProcess(processReport) { 67 let explicitRoot = {}; 68 let nonExplicitRoot = {}; 69 70 let jemallocHeapReportedSize = 0; 71 let systemHeapReportedSize = 0; 72 73 let jemallocHeapAllocatedSize = NaN; 74 let systemHeapAllocatedSize = NaN; 75 76 // In content processes, get the list of urls. 77 let urls = new Set(); 78 79 processReport.reports.forEach((report) => { 80 if (report.path[0].startsWith("url(")) { 81 // This can be a list of urls. 82 let path_urls = report.path[0].slice(4, -1).split(", "); 83 path_urls.forEach((url) => urls.add(url)); 84 } 85 86 // Add "explicit" to the start of the path, when appropriate. 87 if (report.kind.startsWith("Explicit")) { 88 report.path.unshift("explicit"); 89 } 90 91 // Update the reported fractions of the heaps, when appropriate. 92 if (report.kind == "ExplicitJemallocHeapSize") { 93 jemallocHeapReportedSize += report.size; 94 } else if (report.kind == "ExplicitSystemHeapSize") { 95 systemHeapReportedSize += report.size; 96 } 97 98 // Record total size of the heaps, when we see them. 99 if (report.path.length == 1) { 100 if (report.path[0] == "jemalloc-heap-allocated") { 101 jemallocHeapAllocatedSize = report.size; 102 } else if (report.path[0] == "system-heap-allocated") { 103 systemHeapAllocatedSize = report.size; 104 } 105 } 106 107 // Insert this report at the proper position. 108 insertNode( 109 report.kind.startsWith("Explicit") ? explicitRoot : nonExplicitRoot, 110 report 111 ); 112 }); 113 114 // Compute and insert the heap-unclassified values. 115 if (!isNaN(jemallocHeapAllocatedSize)) { 116 insertNode(explicitRoot, { 117 path: ["explicit", "jemalloc-heap-unclassified"], 118 size: jemallocHeapAllocatedSize - jemallocHeapReportedSize, 119 }); 120 } 121 if (!isNaN(systemHeapAllocatedSize)) { 122 insertNode(explicitRoot, { 123 path: ["explicit", "system-heap-unclassified"], 124 size: systemHeapAllocatedSize - systemHeapReportedSize, 125 }); 126 } 127 128 // Create the DOM structure for each process report: 129 // <div class=process> <h4>...<h4> <pre> ...</pre> </div> 130 let container = document.createElement("div"); 131 container.classList.add("process"); 132 let reportTitle = document.createElement("h4"); 133 reportTitle.textContent = `${ 134 processReport.isMainProcess ? "Main Process" : "Content Process" 135 } (pid ${processReport.pid}) ${[...urls.values()].join(", ")}`; 136 137 container.append(reportTitle); 138 let reportNode = document.createElement("pre"); 139 reportNode.classList.add("report"); 140 container.append(reportNode); 141 142 reportNode.append(convertNodeToDOM(explicitRoot.explicit, "explicit")); 143 144 for (let prop in nonExplicitRoot) { 145 reportNode.append(convertNodeToDOM(nonExplicitRoot[prop], prop)); 146 } 147 148 // Make sure we always put the main process first. 149 if (processReport.isMainProcess) { 150 window.reports.prepend(container); 151 } else { 152 window.reports.append(container); 153 } 154 } 155 156 function start() { 157 window.startButton.onclick = async () => { 158 let content = await navigator.servo.reportMemory(); 159 let reports = JSON.parse(content); 160 if (reports.error) { 161 console.error(reports.error); 162 return; 163 } 164 window.reports.innerHTML = ""; 165 window.reports.classList.remove("hidden"); 166 167 if (!Array.isArray(reports)) { 168 console.error("Unexpected memory report format!"); 169 return; 170 } 171 172 reports.forEach(reportsForProcess); 173 }; 174 } 175 </script> 176 <style> 177 html { 178 font-family: sans-serif; 179 } 180 181 details, 182 details div { 183 margin-left: 1em; 184 } 185 186 summary:hover { 187 cursor: pointer; 188 } 189 190 div.process { 191 margin: 0.5em; 192 border: 2px solid gray; 193 border-radius: 10px; 194 padding: 5px; 195 background-color: lightgray; 196 } 197 198 .report { 199 line-height: 1.5em; 200 } 201 202 .report > details { 203 margin-bottom: 1em; 204 } 205 206 .hidden { 207 display: none; 208 } 209 </style> 210 </head> 211 <body> 212 <h2>Memory Reports</h2> 213 <button id="startButton">Measure</button> 214 <div id="reports" class="hidden"></div> 215 </body> 216</html>