Rewild Your Web
web
browser
dweb
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>