Skip to content

[memoryprofiler] Show allocations from threads#26175

Open
matzf wants to merge 1 commit intoemscripten-core:mainfrom
matzf:feature/memoryprofiler-pthread
Open

[memoryprofiler] Show allocations from threads#26175
matzf wants to merge 1 commit intoemscripten-core:mainfrom
matzf:feature/memoryprofiler-pthread

Conversation

@matzf
Copy link

@matzf matzf commented Jan 27, 2026

Support multi-threaded applications in memoryprofiler.

Fixes #18107.

@matzf matzf force-pushed the feature/memoryprofiler-pthread branch from 532aa6f to 95777be Compare January 27, 2026 13:10
@matzf matzf force-pushed the feature/memoryprofiler-pthread branch from 95777be to ba2463e Compare January 27, 2026 15:52
@sbc100 sbc100 requested a review from juj January 27, 2026 17:12
@matzf
Copy link
Author

matzf commented Feb 23, 2026

@juj, friendly ping, you were assigned as reviewer for this PR. Please let me know if you need anything from me to move this forward.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I forgot to send these comments from earlier.

emscripten_trace_record_allocation: (address, size) => {
Module['onMalloc']?.(address, size);
#if MEMORYPROFILER
Module['onMalloc']?.(address, size) || postMessage({cmd: 'callHandler', handler: 'onMalloc', args: [address, size, new Error().stack.toString()]});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can you the JS library function jsStackTrace() instead of new Error().stack.toString()

emscripten_trace_record_free: (address) => {
Module['onFree']?.(address);
#if MEMORYPROFILER
Module['onFree']?.(address) || postMessage({cmd: 'callHandler', handler: 'onFree', args: [address]});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this could be abstracted into a helper function.

var loc = new Error().stack.toString();
if(loc == undefined) {
loc = new Error().stack.toString();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we just expect loc to be defined by the caller? i.e. can you instead assert(loc) here, and then all the callsites can be in charge of calling new Error().stack.toString()

@sbc100 sbc100 changed the title memoryprofiler: show allocations from threads [memoryprofiler] Show allocations from threads Feb 23, 2026

emscripten_trace_record_allocation: (address, size) => {
Module['onMalloc']?.(address, size);
#if MEMORYPROFILER
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this tracing API could be used for things other than the MEMORYPROFILER before. Is the onMalloc callback only for MEMORYPROFILER?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally the tracing api was used for a generic user facing thing. We had another collaborator develop a custom tracing api for example. So onMalloc is something that was intended to be a generic notification.

MEMORYPROFILER then grew as a feature that was bundled into the repository itself.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At Unity, I've implemented a separate memory allocation profiler that also uses these onMalloc hooks, that is separate from the MEMORYPROFILER feature.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this code here should not be wrapped in #if MEMORYPROFILER right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juj, presumably your mechanism still uses this file? i.e. it uses -sEMSCRIPTEN_TRACING?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point - this code location should not be gated inside MEMORYPROFILER: this is the generic support path, and MEMORYPROFILER is just one implementation that uses the tracing API.

@juj
Copy link
Collaborator

juj commented Feb 23, 2026

LGTM. One note of concern I have though is that we still have an unfixed design problem/regression from #19683 (comment) , which revolves around the callHandlers abstraction.

In that discussion I had made the recommendation to remove the callHandlers mechanism altogether (with the thinking that we'd do it while we still can). This adds one more usage of it, effectively making the semantics of shutting down a program more difficult.

Still, this is "only" for debug code, and I don't have a good solution for the above issue, so maybe that is not a good thing to block on for this.

emscripten_trace_record_reallocation: (old_address, new_address, size) => {
Module['onRealloc']?.(old_address, new_address, size);
#if MEMORYPROFILER
Module['onRealloc']?.(old_address, new_address, size) || postMessage({cmd: 'callHandler', handler: 'onRealloc', args: [old_address, new_address, size, new Error().stack.toString()]});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS syntax question/clarification: how does Module['onRealloc']?.(...) || postMessage(...) work exactly?

Here it is used with the intent of saying

if (Module['onRealloc']) Module['onRealloc'](...); // if we are on main thread, call onRealloc
else postMessage(...); // otherwise, dispatch to call handler

though my understanding of JS parsing here says that the written code construct instead does this:

var ret = Module['onRealloc'] && Module['onRealloc'](...);
if (!ret) postMessage(...);

which does not seem the correct thing to do?

On worker thread, ret will always be undefined, so the postMessage() will fire.

But on the main thread, it seems like if the return value of Module['onRealloc']() function is undefined, then the main thread will *also* postMessage(), which should not happen. (I think it will cause a JS error, since main thread does not have a "postmessage-to-parent" construct?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memoryprofiler doesn't support multi-threaded application

3 participants