var emscriptenMemoryProfiler = {
// If true, walks all allocated pointers at graphing time to print a detailed memory fragmentation map. If false, used
// memory is only graphed in one block (at the bottom of DYNAMIC memory space). Set this to false to improve performance at the expense of
// accuracy.
detailedHeapUsage: true,
// Allocations of memory blocks larger than this threshold will get their detailed callstack captured and logged at runtime.
// Warning: This can be extremely slow. Set to a very very large value like 1024*1024*1024*4 to disable.
// Disabled if stack information is not available in this browser
trackedCallstackMinSizeBytes: (typeof new Error().stack === 'undefined') ? 1024*1024*1024*4 : 16*1024*1024,
// Controls whether all outstanding allocation are printed to html page by callstack.
allocateStatistics: false,
// If allocateStatistics = true, then all callstacks that have recorded more than the following number of allocations will be printed to html page.
allocateStatisticsMinReported: 100,
// If true, we hook into Runtime.stackAlloc to be able to catch better estimate of the maximum used STACK space.
// You might only ever want to set this to false for performance reasons. Since stack allocations may occur often, this might impact performance.
hookStackAlloc: true,
// How often the log page is refreshed.
uiUpdateIntervalMsecs: 2000,
// Tracks data for the allocation statistics.
allocationSiteStatistics: {},
allocationSitePtrs: {},
// Stores an associative array of records HEAP ptr -> size so that we can retrieve how much memory was freed in calls to
// _free() and decrement the tracked usage accordingly.
// E.g. allocatedPtrSizes[address] returns the size of the heap pointer starting at 'address'.
allocatedPtrSizes: {},
// Conceptually same as the above array, except this one tracks only pointers that were allocated during the application preRun step, which
// corresponds to the data added to the VFS with --preload-file.
preRunMallocs: {},
// Once set to true, preRun is finished and the above array is not touched anymore.
pagePreRunIsFinished: false,
// Stores an associative array of records HEAP ptr -> function string name so that we can identify each allocated pointer
// by the location in code the allocation occurred in.
callstackOfAllocatedPtr: {},
// Stores an associative array of accumulated amount of memory allocated per location.
// E.g. allocatedBytesPerCallstack[callstack_function_name_string] returns the total number of allocated bytes performed from 'callstack_function_name_string'.
allocatedBytesPerCallstack: {},
// Grand total of memory currently allocated via malloc(). Decremented on free()s.
totalMemoryAllocated: 0,
// The running count of the number of times malloc() and free() have been called in the app. Used to keep track of # of currently alive pointers.
// TODO: Perhaps in the future give a statistic of allocations per second to see how trashing memory usage is.
totalTimesMallocCalled: 0,
totalTimesFreeCalled: 0,
// Tracks the highest seen location of the STACKTOP variable.
stackTopWatermark: 0,
// The canvas DOM element to which to draw the allocation map.
canvas: null,
// The 2D drawing context on the canvas.
drawContext: null,
// Converts number f to string with at most two decimals, without redundant trailing zeros.
truncDec: function truncDec(f) {
var str = f.toFixed(2);
if (str.indexOf('.00', str.length-3) !== -1) return str.substr(0, str.length-3);
else if (str.indexOf('0', str.length-1) !== -1) return str.substr(0, str.length-1);
else return str;
},
// Converts a number of bytes pretty-formatted as a string.
formatBytes: function formatBytes(bytes) {
if (bytes >= 1000*1024*1024) return this.truncDec(bytes/(1024*1024*1024)) + ' GB';
else if (bytes >= 1000*1024) return this.truncDec(bytes/(1024*1024)) + ' MB';
else if (bytes >= 1000) return this.truncDec(bytes/1024) + ' KB';
else return this.truncDec(bytes) + ' B';
},
onMalloc: function onMalloc(ptr, size) {
// Gather global stats.
this.totalMemoryAllocated += size;
++this.totalTimesMallocCalled;
this.stackTopWatermark = Math.max(this.stackTopWatermark, STACKTOP);
// Remember the size of the allocated block to know how much will be _free()d later.
this.allocatedPtrSizes[ptr] = size;
// Also track if this was a _malloc performed at preRun time.
if (!this.pagePreRunIsFinished) this.preRunMallocs[ptr] = size;
if (this.allocateStatistics) {
var loc = new Error().stack.toString();
var str = loc;
if (!this.allocationSiteStatistics[str]) this.allocationSiteStatistics[str] = 1;
else ++this.allocationSiteStatistics[str];
this.allocationSitePtrs[ptr] = loc;
}
// If this is a large enough allocation, track its detailed callstack info.
if (size > this.trackedCallstackMinSizeBytes) {
// Get the caller function as string.
var loc = new Error().stack.toString();
var nl = loc.indexOf('\n')+1;
loc = loc.substr(nl);
loc = loc.replace(/\n/g, '
');
var caller = loc;
this.callstackOfAllocatedPtr[ptr] = caller;
if (this.allocatedBytesPerCallstack[caller] > 0) this.allocatedBytesPerCallstack[caller] += size;
else this.allocatedBytesPerCallstack[caller] = size;
}
},
onFree: function onFree(ptr) {
if (!ptr) return;
// Decrement global stats.
var sz = this.allocatedPtrSizes[ptr];
this.totalMemoryAllocated -= sz;
delete this.allocatedPtrSizes[ptr];
delete this.preRunMallocs[ptr]; // Also free if this happened to be a _malloc performed at preRun time.
this.stackTopWatermark = Math.max(this.stackTopWatermark, STACKTOP);
if (this.allocateStatistics) {
var loc = this.allocationSitePtrs[ptr];
if (loc) {
var str = loc;
--this.allocationSiteStatistics[str];
}
this.allocationSitePtrs[ptr] = null;
}
// Decrement per-alloc stats if this was a large allocation.
if (sz > this.trackedCallstackMinSizeBytes) {
var caller = this.callstackOfAllocatedPtr[ptr];
delete this.callstackOfAllocatedPtr[ptr];
this.allocatedBytesPerCallstack[caller] -= sz;
if (this.allocatedBytesPerCallstack[caller] <= 0) delete this.allocatedBytesPerCallstack[caller];
}
++this.totalTimesFreeCalled;
},
onRealloc: function onRealloc(oldAddress, newAddress, size) {
onFree(oldAddress);
onMalloc(newAddress, size);
},
onPreloadComplete: function onPreloadComplete() {
this.pagePreRunIsFinished = true;
},
// Installs startup hook and periodic UI update timer.
initialize: function initialize() {
// Inject the memoryprofiler hooks.
Module['onMalloc'] = function onMalloc(ptr, size) { emscriptenMemoryProfiler.onMalloc(ptr, size); };
Module['onRealloc'] = function onRealloc(oldAddress, newAddress, size) { emscriptenMemoryProfiler.onRealloc(oldAddress, newAddress, size); };
Module['onFree'] = function onFree(ptr) { emscriptenMemoryProfiler.onFree(ptr); };
// Add a tracking mechanism to detect when VFS loading is complete.
Module['preRun'].push(function() { emscriptenMemoryProfiler.onPreloadComplete(); });
if (this.hookStackAlloc) {
// Inject stack allocator.
var prevStackAlloc = Runtime.stackAlloc;
function hookedStackAlloc(size) {
emscriptenMemoryProfiler.stackTopWatermark = Math.max(emscriptenMemoryProfiler.stackTopWatermark, STACKTOP + size);
return prevStackAlloc(size);
}
Runtime.stackAlloc = hookedStackAlloc;
}
memoryprofiler = document.getElementById('memoryprofiler');
if (!memoryprofiler) {
var div = document.createElement("div");
div.innerHTML = "