Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/memoryprofiler.js
6170 views
1
/**
2
* @license
3
* Copyright 2015 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
#if MEMORYPROFILER
8
9
var emscriptenMemoryProfiler = {
10
// If true, walks all allocated pointers at graphing time to print a detailed
11
// memory fragmentation map. If false, used memory is only graphed in one
12
// block (at the bottom of DYNAMIC memory space). Set this to false to improve
13
// performance at the expense of accuracy.
14
detailedHeapUsage: true,
15
16
// Allocations of memory blocks larger than this threshold will get their
17
// detailed callstack captured and logged at runtime.
18
trackedCallstackMinSizeBytes: (typeof new Error().stack == 'undefined') ? Infinity : 16*1024*1024,
19
20
// Allocations from call sites having more than this many outstanding
21
// allocated pointers will get their detailed callstack captured and logged at
22
// runtime.
23
trackedCallstackMinAllocCount: (typeof new Error().stack == 'undefined') ? Infinity : 10000,
24
25
// If true, we hook into stackAlloc to be able to catch better estimate of the
26
// maximum used STACK space. You might only ever want to set this to false
27
// for performance reasons. Since stack allocations may occur often, this
28
// might impact performance.
29
hookStackAlloc: true,
30
31
// How often the log page is refreshed.
32
uiUpdateIntervalMsecs: 2000,
33
34
// Tracks data for the allocation statistics.
35
allocationsAtLoc: {},
36
allocationSitePtrs: {},
37
38
// Stores an associative array of records HEAP ptr -> size so that we can
39
// retrieve how much memory was freed in calls to _free() and decrement the
40
// tracked usage accordingly.
41
// E.g. sizeOfAllocatedPtr[address] returns the size of the heap pointer
42
// starting at 'address'.
43
sizeOfAllocatedPtr: {},
44
45
// Conceptually same as the above array, except this one tracks only pointers
46
// that were allocated during the application preRun step, which corresponds
47
// to the data added to the VFS with --preload-file.
48
sizeOfPreRunAllocatedPtr: {},
49
50
resizeMemorySources: [],
51
// stack: <string>,
52
// begin: <int>,
53
// end: <int>
54
55
sbrkSources: [],
56
// stack: <string>,
57
// begin: <int>,
58
// end: <int>
59
60
// Once set to true, preRun is finished and the above array is not touched anymore.
61
pagePreRunIsFinished: false,
62
63
// Grand total of memory currently allocated via malloc(). Decremented on free()s.
64
totalMemoryAllocated: 0,
65
66
// The running count of the number of times malloc() and free() have been
67
// called in the app. Used to keep track of # of currently alive pointers.
68
// TODO: Perhaps in the future give a statistic of allocations per second to
69
// see how trashing memory usage is.
70
totalTimesMallocCalled: 0,
71
totalTimesFreeCalled: 0,
72
73
// Tracks the highest seen location of the stack pointer.
74
stackTopWatermark: Infinity,
75
76
// The canvas DOM element to which to draw the allocation map.
77
canvas: null,
78
79
// The 2D drawing context on the canvas.
80
drawContext: null,
81
82
// Converts number f to string with at most two decimals, without redundant trailing zeros.
83
truncDec(f = 0) {
84
var str = f.toFixed(2);
85
if (str.includes('.00', str.length-3)) return str.slice(0, -3);
86
else if (str.includes('0', str.length-1)) return str.slice(0, -1);
87
else return str;
88
},
89
90
// Converts a number of bytes pretty-formatted as a string.
91
formatBytes(bytes) {
92
if (bytes >= 1000*1024*1024) return emscriptenMemoryProfiler.truncDec(bytes/(1024*1024*1024)) + ' GB';
93
else if (bytes >= 1000*1024) return emscriptenMemoryProfiler.truncDec(bytes/(1024*1024)) + ' MB';
94
else if (bytes >= 1000) return emscriptenMemoryProfiler.truncDec(bytes/1024) + ' KB';
95
else return emscriptenMemoryProfiler.truncDec(bytes) + ' B';
96
},
97
98
// HSV values in [0..1[, returns a RGB string in format '#rrggbb'
99
hsvToRgb(h, s, v) {
100
var h_i = (h*6)|0;
101
var f = h*6 - h_i;
102
var p = v * (1 - s);
103
var q = v * (1 - f*s);
104
var t = v * (1 - (1 - f) * s);
105
var r, g, b;
106
switch (h_i) {
107
case 0: r = v; g = t; b = p; break;
108
case 1: r = q; g = v; b = p; break;
109
case 2: r = p; g = v; b = t; break;
110
case 3: r = p; g = q; b = v; break;
111
case 4: r = t; g = p; b = v; break;
112
case 5: r = v; g = p; b = q; break;
113
}
114
function toHex(v) {
115
v = (v*255|0).toString(16);
116
return (v.length == 1) ? '0' + v : v;
117
}
118
return '#' + toHex(r) + toHex(g) + toHex(b);
119
},
120
121
onSbrkGrow(oldLimit, newLimit) {
122
var self = emscriptenMemoryProfiler;
123
// On first sbrk(), account for the initial size.
124
if (self.sbrkSources.length == 0) {
125
self.sbrkSources.push({
126
stack: "initial heap sbrk limit<br>",
127
begin: 0,
128
end: oldLimit,
129
color: self.hsvToRgb(self.sbrkSources.length * 0.618033988749895 % 1, 0.5, 0.95)
130
});
131
}
132
if (newLimit <= oldLimit) return;
133
self.sbrkSources.push({
134
stack: self.filterCallstackForHeapResize(new Error().stack.toString()),
135
begin: oldLimit,
136
end: newLimit,
137
color: self.hsvToRgb(self.sbrkSources.length * 0.618033988749895 % 1, 0.5, 0.95)
138
});
139
},
140
141
onMemoryResize(oldSize, newSize) {
142
var self = emscriptenMemoryProfiler;
143
// On first heap resize, account for the initial size.
144
if (self.resizeMemorySources.length == 0) {
145
self.resizeMemorySources.push({
146
stack: "initial heap size<br>",
147
begin: 0,
148
end: oldSize,
149
color: self.resizeMemorySources.length % 2 ? '#ff00ff' : '#ff80ff'
150
});
151
}
152
if (newSize <= oldSize) return;
153
self.resizeMemorySources.push({
154
stack: self.filterCallstackForHeapResize(new Error().stack.toString()),
155
begin: oldSize,
156
end: newSize,
157
color: self.resizeMemorySources.length % 2 ? '#ff00ff' : '#ff80ff'
158
});
159
console.log('memory resize: ' + oldSize + ' ' + newSize);
160
},
161
162
recordStackWatermark() {
163
if (typeof runtimeInitialized == 'undefined' || runtimeInitialized) {
164
var self = emscriptenMemoryProfiler;
165
self.stackTopWatermark = Math.min(self.stackTopWatermark, _emscripten_stack_get_current());
166
}
167
},
168
169
onMalloc(ptr, size) {
170
if (!ptr) return;
171
if (emscriptenMemoryProfiler.sizeOfAllocatedPtr[ptr])
172
{
173
// Uncomment to debug internal workings of tracing:
174
// console.error('Allocation error in onMalloc! Pointer ' + ptr + ' had already been tracked as allocated!');
175
// console.error('Previous site of allocation: ' + emscriptenMemoryProfiler.allocationSitePtrs[ptr]);
176
// console.error('This doubly attempted site of allocation: ' + new Error().stack.toString());
177
// abort('malloc internal inconsistency!');
178
return;
179
}
180
var self = emscriptenMemoryProfiler;
181
// Gather global stats.
182
self.totalMemoryAllocated += size;
183
++self.totalTimesMallocCalled;
184
185
self.recordStackWatermark();
186
187
// Remember the size of the allocated block to know how much will be _free()d later.
188
self.sizeOfAllocatedPtr[ptr] = size;
189
// Also track if this was a _malloc performed at preRun time.
190
if (!self.pagePreRunIsFinished) self.sizeOfPreRunAllocatedPtr[ptr] = size;
191
192
var loc = new Error().stack.toString();
193
self.allocationsAtLoc[loc] ||= [0, 0, self.filterCallstackForMalloc(loc)];
194
self.allocationsAtLoc[loc][0] += 1;
195
self.allocationsAtLoc[loc][1] += size;
196
self.allocationSitePtrs[ptr] = loc;
197
},
198
199
onFree(ptr) {
200
if (!ptr) return;
201
202
var self = emscriptenMemoryProfiler;
203
204
// Decrement global stats.
205
var sz = self.sizeOfAllocatedPtr[ptr];
206
if (!isNaN(sz)) self.totalMemoryAllocated -= sz;
207
else
208
{
209
// Uncomment to debug internal workings of tracing:
210
// console.error('Detected double free of pointer ' + ptr + ' at location:\n'+ new Error().stack.toString());
211
// abort('double free!');
212
return;
213
}
214
215
self.recordStackWatermark();
216
217
var loc = self.allocationSitePtrs[ptr];
218
if (loc) {
219
var allocsAtThisLoc = self.allocationsAtLoc[loc];
220
if (allocsAtThisLoc) {
221
allocsAtThisLoc[0] -= 1;
222
allocsAtThisLoc[1] -= sz;
223
if (allocsAtThisLoc[0] <= 0) delete self.allocationsAtLoc[loc];
224
}
225
}
226
delete self.allocationSitePtrs[ptr];
227
delete self.sizeOfAllocatedPtr[ptr];
228
delete self.sizeOfPreRunAllocatedPtr[ptr]; // Also free if this happened to be a _malloc performed at preRun time.
229
++self.totalTimesFreeCalled;
230
},
231
232
onRealloc(oldAddress, newAddress, size) {
233
emscriptenMemoryProfiler.onFree(oldAddress);
234
emscriptenMemoryProfiler.onMalloc(newAddress, size);
235
},
236
237
onPreloadComplete() {
238
emscriptenMemoryProfiler.pagePreRunIsFinished = true;
239
},
240
241
// Installs startup hook and periodic UI update timer.
242
initialize() {
243
// Inject the memoryprofiler hooks.
244
Module['onMalloc'] = (ptr, size) => emscriptenMemoryProfiler.onMalloc(ptr, size);
245
Module['onRealloc'] = (oldAddress, newAddress, size) => emscriptenMemoryProfiler.onRealloc(oldAddress, newAddress, size);;
246
Module['onFree'] = (ptr) => emscriptenMemoryProfiler.onFree(ptr);
247
emscriptenMemoryProfiler.recordStackWatermark();
248
249
// Add a tracking mechanism to detect when VFS loading is complete.
250
Module['preRun'] ||= [];
251
Module['preRun'].push(emscriptenMemoryProfiler.onPreloadComplete);
252
253
if (emscriptenMemoryProfiler.hookStackAlloc && typeof stackAlloc == 'function') {
254
// Inject stack allocator.
255
var prevStackAlloc = stackAlloc;
256
var hookedStackAlloc = (size) => {
257
var ptr = prevStackAlloc(size);
258
emscriptenMemoryProfiler.recordStackWatermark();
259
return ptr;
260
};
261
stackAlloc = hookedStackAlloc;
262
}
263
264
if (location.search.toLowerCase().includes('trackbytes=')) {
265
emscriptenMemoryProfiler.trackedCallstackMinSizeBytes = parseInt(location.search.slice(location.search.toLowerCase().indexOf('trackbytes=') + 'trackbytes='.length), undefined /* https://github.com/google/closure-compiler/issues/3230 / https://github.com/google/closure-compiler/issues/3548 */);
266
}
267
if (location.search.toLowerCase().includes('trackcount=')) {
268
emscriptenMemoryProfiler.trackedCallstackMinAllocCount = parseInt(location.search.slice(location.search.toLowerCase().indexOf('trackcount=') + 'trackcount='.length), undefined);
269
}
270
271
emscriptenMemoryProfiler.memoryprofiler_summary = document.getElementById('memoryprofiler_summary');
272
var div;
273
if (!emscriptenMemoryProfiler.memoryprofiler_summary) {
274
div = document.createElement("div");
275
div.className = 'emscripten-memory-profiler-container';
276
div.innerHTML = "<div style='border: 2px solid black; padding: 2px;'><canvas style='border: 1px solid black; margin-left: auto; margin-right: auto; display: block;' id='memoryprofiler_canvas' width='100%' height='50'></canvas><input type='checkbox' id='showHeapResizes' onclick='emscriptenMemoryProfiler.updateUi()'>Display heap and sbrk() resizes. Filter sbrk() and heap resize callstacks by keywords: <input type='text' id='sbrkFilter'>(reopen page with ?sbrkFilter=foo,bar query params to prepopulate this list)<br/>Track all allocation sites larger than <input id='memoryprofiler_min_tracked_alloc_size' type=number value="+emscriptenMemoryProfiler.trackedCallstackMinSizeBytes+"></input> bytes, and all allocation sites with more than <input id='memoryprofiler_min_tracked_alloc_count' type=number value="+emscriptenMemoryProfiler.trackedCallstackMinAllocCount+"></input> outstanding allocations. (visit this page via URL query params foo.html?trackbytes=1000&trackcount=100 to apply custom thresholds starting from page load)<br/><div id='memoryprofiler_summary'></div><input id='memoryprofiler_clear_alloc_stats' type='button' value='Clear alloc stats' ></input><br />Sort allocations by:<select id='memoryProfilerSort'><option value='bytes'>Bytes</option><option value='count'>Count</option><option value='fixed'>Fixed</option></select><div id='memoryprofiler_ptrs'></div>";
277
}
278
var populateHtmlBody = function() {
279
if (div) {
280
document.body.appendChild(div);
281
282
function getValueOfParam(key) {
283
var results = (new RegExp("[\\?&]"+key+"=([^&#]*)")).exec(location.href);
284
return results ? results[1] : '';
285
}
286
// Allow specifying a precreated filter in page URL ?query parameters for convenience.
287
if (document.getElementById('sbrkFilter').value = getValueOfParam('sbrkFilter')) {
288
document.getElementById('showHeapResizes').checked = true;
289
}
290
}
291
var self = emscriptenMemoryProfiler;
292
self.memoryprofiler_summary = document.getElementById('memoryprofiler_summary');
293
self.memoryprofiler_ptrs = document.getElementById('memoryprofiler_ptrs');
294
295
document.getElementById('memoryprofiler_min_tracked_alloc_size').addEventListener("change", function(e) { self.trackedCallstackMinSizeBytes=parseInt(this.value, undefined /* https://github.com/google/closure-compiler/issues/3230 / https://github.com/google/closure-compiler/issues/3548 */); });
296
document.getElementById('memoryprofiler_min_tracked_alloc_count').addEventListener("change", function(e) { self.trackedCallstackMinAllocCount=parseInt(this.value, undefined); });
297
document.getElementById('memoryprofiler_clear_alloc_stats').addEventListener("click", (e) => {self.allocationsAtLoc = {}; self.allocationSitePtrs = {};});
298
self.canvas = document.getElementById('memoryprofiler_canvas');
299
self.canvas.width = document.documentElement.clientWidth - 32;
300
self.drawContext = self.canvas.getContext('2d');
301
302
self.updateUi();
303
setInterval(() => emscriptenMemoryProfiler.updateUi(), self.uiUpdateIntervalMsecs);
304
305
};
306
// User might initialize memoryprofiler in the <head> of a page, when
307
// document.body does not yet exist. In that case, delay initialization
308
// of the memoryprofiler UI until page has loaded
309
if (document.body) populateHtmlBody();
310
else setTimeout(populateHtmlBody, 1000);
311
},
312
313
// Given a pointer 'bytes', compute the linear 1D position on the graph as
314
// pixels, rounding down for start address of a block.
315
bytesToPixelsRoundedDown(bytes) {
316
return (bytes * emscriptenMemoryProfiler.canvas.width * emscriptenMemoryProfiler.canvas.height / HEAP8.length) | 0;
317
},
318
319
// Same as bytesToPixelsRoundedDown, but rounds up for the end address of a
320
// block. The different rounding will guarantee that even 'thin' allocations
321
// should get at least one pixel dot in the graph.
322
bytesToPixelsRoundedUp(bytes) {
323
return ((bytes * emscriptenMemoryProfiler.canvas.width * emscriptenMemoryProfiler.canvas.height + HEAP8.length - 1) / HEAP8.length) | 0;
324
},
325
326
// Graphs a range of allocated memory. The memory range will be drawn as a
327
// top-to-bottom, left-to-right stripes or columns of pixels.
328
fillLine(startBytes, endBytes) {
329
var self = emscriptenMemoryProfiler;
330
var startPixels = self.bytesToPixelsRoundedDown(startBytes);
331
var endPixels = self.bytesToPixelsRoundedUp(endBytes);
332
333
// Starting pos (top-left corner) of this allocation on the graph.
334
var x0 = (startPixels / self.canvas.height) | 0;
335
var y0 = startPixels - x0 * self.canvas.height;
336
// Ending pos (bottom-right corner) of this allocation on the graph.
337
var x1 = (endPixels / self.canvas.height) | 0;
338
var y1 = endPixels - x1 * self.canvas.height;
339
340
// Draw the left side partial column of the allocation block.
341
if (y0 > 0 && x0 < x1) {
342
self.drawContext.fillRect(x0, y0, 1, self.canvas.height - y0);
343
// Proceed to the start of the next full column.
344
y0 = 0;
345
++x0;
346
}
347
// Draw the right side partial column.
348
if (y1 < self.canvas.height && x0 < x1) {
349
self.drawContext.fillRect(x1, 0, 1, y1);
350
// Decrement to the previous full column.
351
y1 = self.canvas.height - 1;
352
--x1;
353
}
354
// After filling the previous leftovers with one-pixel-wide lines, we are
355
// only left with a rectangular shape of full columns to blit.
356
self.drawContext.fillRect(x0, 0, x1 - x0 + 1, self.canvas.height);
357
},
358
359
// Fills a rectangle of given height % that overlaps the byte range given.
360
fillRect(startBytes, endBytes, heightPercentage) {
361
var self = emscriptenMemoryProfiler;
362
var startPixels = self.bytesToPixelsRoundedDown(startBytes);
363
var endPixels = self.bytesToPixelsRoundedUp(endBytes);
364
365
var x0 = (startPixels / self.canvas.height) | 0;
366
var x1 = (endPixels / self.canvas.height) | 0;
367
self.drawContext.fillRect(x0, self.canvas.height * (1.0 - heightPercentage), x1 - x0 + 1, self.canvas.height);
368
},
369
370
countOpenALAudioDataSize() {
371
if (typeof AL == 'undefined' || !AL.currentContext) return 0;
372
373
var totalMemory = 0;
374
375
for (var i in AL.currentContext.buf) {
376
var buffer = AL.currentContext.buf[i];
377
for (var channel = 0; channel < buffer.numberOfChannels; ++channel) totalMemory += buffer.getChannelData(channel).length * 4;
378
}
379
return totalMemory;
380
},
381
382
// Print accurate map of individual allocations. This will show information about
383
// memory fragmentation and allocation sizes.
384
// Warning: This will walk through all allocations, so it is slow!
385
printAllocsWithCyclingColors(colors, allocs) {
386
var colorIndex = 0;
387
for (var i in allocs) {
388
emscriptenMemoryProfiler.drawContext.fillStyle = colors[colorIndex];
389
colorIndex = (colorIndex + 1) % colors.length;
390
var start = i|0;
391
var sz = allocs[start]|0;
392
emscriptenMemoryProfiler.fillLine(start, start + sz);
393
}
394
},
395
396
filterURLsFromCallstack(callstack) {
397
// Hide paths from URLs to make the log more readable
398
callstack = callstack.replace(/@((file)|(http))[\w:\/\.]*\/([\w\.]*)/g, '@$4');
399
callstack = callstack.replace(/\n/g, '<br />');
400
return callstack;
401
},
402
403
// given callstack of func1\nfunc2\nfunc3... and function name, cuts the tail from the callstack
404
// for anything after the function func.
405
filterCallstackAfterFunctionName(callstack, func) {
406
var i = callstack.indexOf(func);
407
if (i != -1) {
408
var end = callstack.indexOf('<br />', i);
409
if (end != -1) {
410
return callstack.slice(0, end);
411
}
412
}
413
return callstack;
414
},
415
416
filterCallstackForMalloc(callstack) {
417
// Do not show Memoryprofiler's own callstacks in the callstack prints.
418
var i = callstack.indexOf('emscripten_trace_record_');
419
if (i != -1) {
420
callstack = callstack.slice(callstack.indexOf('\n', i)+1);
421
}
422
return emscriptenMemoryProfiler.filterURLsFromCallstack(callstack);
423
},
424
425
filterCallstackForHeapResize(callstack) {
426
// Do not show Memoryprofiler's own callstacks in the callstack prints.
427
var i = callstack.indexOf('emscripten_asm_const_iii');
428
var j = callstack.indexOf('growMemory');
429
i = (i == -1) ? j : (j == -1 ? i : Math.min(i, j));
430
if (i != -1) {
431
callstack = callstack.slice(callstack.indexOf('\n', i)+1);
432
}
433
callstack = callstack.replace(/(wasm-function\[\d+\]):0x[0-9a-f]+/g, "$1");
434
return emscriptenMemoryProfiler.filterURLsFromCallstack(callstack);
435
},
436
437
printHeapResizeLog(heapResizes) {
438
var html = '';
439
for (var i = 0; i < heapResizes.length; ++i) {
440
var j = i+1;
441
while (j < heapResizes.length) {
442
if ((heapResizes[j].filteredStack || heapResizes[j].stack) == (heapResizes[i].filteredStack || heapResizes[i].stack)) {
443
++j;
444
} else {
445
break;
446
}
447
}
448
var resizeFirst = heapResizes[i];
449
var resizeLast = heapResizes[j-1];
450
var count = j - i;
451
html += '<div style="background-color: ' + resizeFirst.color + '"><b>' + resizeFirst.begin + '-' + resizeLast.end + ' (' + count + ' times, ' + emscriptenMemoryProfiler.formatBytes(resizeLast.end-resizeFirst.begin) + ')</b>:' + (resizeFirst.filteredStack || resizeFirst.stack) + '</div><br>';
452
i = j-1;
453
}
454
return html;
455
},
456
457
// Main UI update entry point.
458
updateUi() {
459
// It is common to set 'overflow: hidden;' on canvas pages that do WebGL. When MemoryProfiler is being used, there will be a long block of text on the page, so force-enable scrolling.
460
if (document.body.style.overflow != '') document.body.style.overflow = '';
461
function colorBar(color) {
462
return '<span style="padding:0px; border:solid 1px black; width:28px;height:14px; vertical-align:middle; display:inline-block; background-color:'+color+';"></span>';
463
}
464
465
// Naive function to compute how many bits will be needed to represent the number 'n' in binary. This will be our pointer 'word width' in the UI.
466
function nBits(n) {
467
var i = 0;
468
while (n >= 1) {
469
++i;
470
n /= 2;
471
}
472
return i;
473
}
474
475
// Returns i formatted to string as fixed-width hexadecimal.
476
function toHex(i, width) {
477
var str = i.toString(16);
478
while (str.length < width) str = '0' + str;
479
return '0x'+str;
480
}
481
482
var self = emscriptenMemoryProfiler;
483
484
// Poll whether user as changed the browser window, and if so, resize the profiler window and redraw it.
485
if (self.canvas.width != document.documentElement.clientWidth - 32) {
486
self.canvas.width = document.documentElement.clientWidth - 32;
487
}
488
489
if (typeof runtimeInitialized != 'undefined' && !runtimeInitialized) {
490
return;
491
}
492
var stackBase = _emscripten_stack_get_base();
493
var stackMax = _emscripten_stack_get_end();
494
var stackCurrent = _emscripten_stack_get_current();
495
var width = (nBits(HEAP8.length) + 3) / 4; // Pointer 'word width'
496
var html = 'Total HEAP size: ' + self.formatBytes(HEAP8.length) + '.';
497
html += '<br />' + colorBar('#202020') + 'STATIC memory area size: ' + self.formatBytes(stackMax - {{{ GLOBAL_BASE }}});
498
html += '. {{{ GLOBAL_BASE }}}: ' + toHex({{{ GLOBAL_BASE }}}, width);
499
500
html += '<br />' + colorBar('#FF8080') + 'STACK memory area size: ' + self.formatBytes(stackBase - stackMax);
501
html += '. STACK_BASE: ' + toHex(stackBase, width);
502
html += '. STACKTOP: ' + toHex(stackCurrent, width);
503
html += '. STACK_MAX: ' + toHex(stackMax, width) + '.';
504
html += '<br />STACK memory area used now (should be zero): ' + self.formatBytes(stackBase - stackCurrent) + '.' + colorBar('#FFFF00') + ' STACK watermark highest seen usage (approximate lower-bound!): ' + self.formatBytes(stackBase - self.stackTopWatermark);
505
506
var heap_base = ___heap_base;
507
var heap_end = _sbrk({{{ to64('0') }}});
508
html += "<br />DYNAMIC memory area size: " + self.formatBytes(heap_end - heap_base);
509
html += ". start: " + toHex(heap_base, width);
510
html += ". end: " + toHex(heap_end, width) + ".";
511
html += "<br />" + colorBar("#6699CC") + colorBar("#003366") + colorBar("#0000FF") + "DYNAMIC memory area used: " + self.formatBytes(self.totalMemoryAllocated) + " (" + (self.totalMemoryAllocated * 100 / (HEAP8.length - heap_base)).toFixed(2) + "% of all dynamic memory and unallocated heap)";
512
html += "<br />Free memory: " + colorBar("#70FF70") + "DYNAMIC: " + self.formatBytes(heap_end - heap_base - self.totalMemoryAllocated) + ", " + colorBar('#FFFFFF') + 'Unallocated HEAP: ' + self.formatBytes(HEAP8.length - heap_end) + " (" + ((HEAP8.length - heap_base - self.totalMemoryAllocated) * 100 / (HEAP8.length - heap_base)).toFixed(2) + "% of all dynamic memory and unallocated heap)";
513
514
var preloadedMemoryUsed = 0;
515
for (var i in self.sizeOfPreRunAllocatedPtr) preloadedMemoryUsed += self.sizeOfPreRunAllocatedPtr[i]|0;
516
html += '<br />' + colorBar('#FF9900') + colorBar('#FFDD33') + 'Preloaded memory used, most likely memory reserved by files in the virtual filesystem : ' + self.formatBytes(preloadedMemoryUsed);
517
518
html += '<br />OpenAL audio data: ' + self.formatBytes(self.countOpenALAudioDataSize()) + ' (outside HEAP)';
519
html += '<br /># of total malloc()s/free()s performed in app lifetime: ' + self.totalTimesMallocCalled + '/' + self.totalTimesFreeCalled + ' (currently alive pointers: ' + (self.totalTimesMallocCalled-self.totalTimesFreeCalled) + ')';
520
521
// Background clear
522
self.drawContext.fillStyle = "#FFFFFF";
523
self.drawContext.fillRect(0, 0, self.canvas.width, self.canvas.height);
524
525
self.drawContext.fillStyle = "#FF8080";
526
self.fillLine(stackMax, stackBase);
527
528
self.drawContext.fillStyle = "#FFFF00";
529
self.fillLine(self.stackTopWatermark, stackBase);
530
531
self.drawContext.fillStyle = "#FF0000";
532
self.fillLine(stackCurrent, stackBase);
533
534
self.drawContext.fillStyle = "#70FF70";
535
self.fillLine(heap_base, heap_end);
536
537
if (self.detailedHeapUsage) {
538
self.printAllocsWithCyclingColors(["#6699CC", "#003366", "#0000FF"], self.sizeOfAllocatedPtr);
539
self.printAllocsWithCyclingColors(["#FF9900", "#FFDD33"], self.sizeOfPreRunAllocatedPtr);
540
} else {
541
// Print only a single naive blob of individual allocations. This will not be accurate, but is constant-time.
542
self.drawContext.fillStyle = "#0000FF";
543
self.fillLine(heap_base, heap_base + self.totalMemoryAllocated);
544
}
545
546
if (document.getElementById('showHeapResizes').checked) {
547
// Print heap resize traces.
548
for (var i in self.resizeMemorySources) {
549
var resize = self.resizeMemorySources[i];
550
self.drawContext.fillStyle = resize.color;
551
self.fillRect(resize.begin, resize.end, 0.5);
552
}
553
554
// Print sbrk() traces.
555
var uniqueSources = {};
556
var filterWords = document.getElementById('sbrkFilter').value.split(',');
557
for (var i in self.sbrkSources) {
558
var sbrk = self.sbrkSources[i];
559
var stack = sbrk.stack;
560
for (var j in filterWords) {
561
var s = filterWords[j].trim();
562
if (s.length > 0)
563
stack = self.filterCallstackAfterFunctionName(stack, s);
564
}
565
sbrk.filteredStack = stack;
566
uniqueSources[stack] ||= self.hsvToRgb(Object.keys(uniqueSources).length * 0.618033988749895 % 1, 0.5, 0.95);
567
self.drawContext.fillStyle = sbrk.color = uniqueSources[stack];
568
self.fillRect(sbrk.begin, sbrk.end, 0.25);
569
}
570
571
// Print a divider line to make the sbrk()/heap resize block more prominently visible compared to the rest of the allocations.
572
function line(x0, y0, x1, y1) {
573
self.drawContext.beginPath();
574
self.drawContext.moveTo(x0, y0);
575
self.drawContext.lineTo(x1, y1);
576
self.drawContext.lineWidth = 2;
577
self.drawContext.stroke();
578
}
579
if (self.sbrkSources.length > 0) line(0, 0.75*self.canvas.height, self.canvas.width, 0.75*self.canvas.height);
580
if (self.resizeMemorySources.length > 0) line(0, 0.5*self.canvas.height, self.canvas.width, 0.5*self.canvas.height);
581
}
582
583
self.memoryprofiler_summary.innerHTML = html;
584
585
var sort = document.getElementById('memoryProfilerSort');
586
var sortOrder = sort.options[sort.selectedIndex].value;
587
588
html = '';
589
590
// Print out sbrk() and memory resize subdivisions:
591
if (document.getElementById('showHeapResizes').checked) {
592
// Print heap resize traces.
593
html += '<div style="background-color: #c0c0c0"><h4>Heap resize locations:</h4>';
594
html += self.printHeapResizeLog(self.resizeMemorySources);
595
html += '</div>'
596
597
// Print heap sbrk traces.
598
html += '<div style="background-color: #c0c0ff"><h4>Memory sbrk() locations:</h4>';
599
html += self.printHeapResizeLog(self.sbrkSources);
600
html += '</div>'
601
} else {
602
// Print out statistics of individual allocations if they were tracked.
603
if (Object.keys(self.allocationsAtLoc).length > 0) {
604
var calls = [];
605
for (var i in self.allocationsAtLoc) {
606
if (self.allocationsAtLoc[i][0] >= self.trackedCallstackMinAllocCount || self.allocationsAtLoc[i][1] >= self.trackedCallstackMinSizeBytes) {
607
calls.push(self.allocationsAtLoc[i]);
608
}
609
}
610
if (calls.length > 0) {
611
if (sortOrder != 'fixed') {
612
var sortIdx = (sortOrder == 'count') ? 0 : 1;
613
calls.sort((a,b) => b[sortIdx] - a[sortIdx]);
614
}
615
html += '<h4>Allocation sites with more than ' + self.formatBytes(self.trackedCallstackMinSizeBytes) + ' of accumulated allocations, or more than ' + self.trackedCallstackMinAllocCount + ' simultaneously outstanding allocations:</h4>'
616
for (var call of calls) {
617
html += "<b>" + self.formatBytes(call[1]) + '/' + call[0] + " allocs</b>: " + call[2] + "<br />";
618
}
619
}
620
}
621
}
622
self.memoryprofiler_ptrs.innerHTML = html;
623
}
624
};
625
626
// Backwards compatibility with previously compiled code. Don't call this
627
// anymore!
628
function memoryprofiler_add_hooks() {
629
emscriptenMemoryProfiler.initialize();
630
}
631
632
if (globalThis.document && globalThis.window && !globalThis.process) {
633
emscriptenMemoryProfiler.initialize();
634
}
635
636
// Declared in globalThis so that `onclick` handlers work when `-sMODULARIZE=1`
637
globalThis.emscriptenMemoryProfiler = emscriptenMemoryProfiler;
638
639
#endif
640
641