Rewild Your Web
at main 223 lines 7.0 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.gcButton.onclick = async () => { 158 window.gcButton.disabled = true; 159 navigator.servo.garbageCollectAllContexts(); 160 window.gcButton.disabled = false; 161 }; 162 163 window.startButton.onclick = async () => { 164 let content = await navigator.servo.reportMemory(); 165 let reports = JSON.parse(content); 166 if (reports.error) { 167 console.error(reports.error); 168 return; 169 } 170 window.reports.innerHTML = ""; 171 window.reports.classList.remove("hidden"); 172 173 if (!Array.isArray(reports)) { 174 console.error("Unexpected memory report format!"); 175 return; 176 } 177 178 reports.forEach(reportsForProcess); 179 }; 180 } 181 </script> 182 <style> 183 html { 184 font-family: sans-serif; 185 } 186 187 details, 188 details div { 189 margin-left: 1em; 190 } 191 192 summary:hover { 193 cursor: pointer; 194 } 195 196 div.process { 197 margin: 0.5em; 198 border: 2px solid gray; 199 border-radius: 10px; 200 padding: 5px; 201 background-color: lightgray; 202 } 203 204 .report { 205 line-height: 1.5em; 206 } 207 208 .report > details { 209 margin-bottom: 1em; 210 } 211 212 .hidden { 213 display: none; 214 } 215 </style> 216 </head> 217 <body> 218 <h2>Memory Reports</h2> 219 <button id="startButton">Measure</button> 220 <button id="gcButton">Force GC</button> 221 <div id="reports" class="hidden"></div> 222 </body> 223</html>