forked from
me.webbeef.org/browser.html
Rewild Your Web
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>