facebook / memlab

A framework for finding JavaScript memory leaks and analyzing heap snapshots
https://facebook.github.io/memlab/
MIT License
4.35k stars 118 forks source link

Discussion: How to map traced leaks to React components, or HTML DOM path #47

Closed Ckins closed 1 year ago

Ckins commented 1 year ago

Hi memlab team, Is there any tricks / instruction for mapping console output to react code / html components? I did some homework by reading https://facebook.github.io/memlab/docs/guides/guides-detached-dom However the case I met is not as straightforward as shown in the tutorial.

Detailed: Questions:

  1. In addition to [window], I also see [(GC roots)], [ < synthetic > ] . How to interpret these kinds?
  2. How should I translated the below output to a HTML path? In the tutorial, it seems there is --leaked_objects, I don't see this tag in the below output.
    
    --Similar leaks in this run: 1--
    --Retained size of leaked objects: 192 bytes--
    [Window] (native) @263277 [6.8KB]
    --4 (element)--->  [HTMLDocument] (native) @263275 [21.5KB]
    --12 (element)--->  [Range] (native) @2340276992 [96 bytes]
    --3 (element)--->  [HTMLDivElement] (native) @262245 [140 bytes]
    --4 (element)--->  [HTMLDivElement] (native) @262385 [228 bytes]
    --3 (element)--->  [HTMLDivElement] (native) @262251 [704 bytes]
    --__reactInternalInstance$vcd965b8sak (property)--->  [FiberNode div HostComponent] (object) @1608501 [304 bytes]
    --child (property)--->  [FiberNode ClassComponent] (object) @1608615 [220 bytes]
    --_debugOwner (property)--->  [FiberNode FunctionComponent] (object) @1373349 [3.2KB]
    --return (property)--->  [FiberNode Modal FunctionComponent] (object) @1373295 [1KB]
    --pendingProps (property)--->  [Object] (object) @1239415 [60 bytes]
    --children (property)--->  [Object] (object) @1239305 [388 bytes]
    --_owner (property)--->  [FiberNode FunctionComponent] (object) @1252139 [192 bytes]
    --_debugOwner (property)--->  [FiberNode FunctionComponent] (object) @1235961 [192 bytes]
    --firstEffect (property)--->  [Detached FiberNode render ForwardRef] (object) @2894599 [192 bytes]
Thanks a lot.

Here is the whole console output:

page-load [75.7MB] (baseline) [s1] > action-on-page [69.4MB] (target) [s2] > revert [67.5MB] (final) [s3]
------6 clusters------

--Similar leaks in this run: 1182-- --Retained size of leaked objects: 781.1KB-- [(GC roots)] (synthetic) @3 [819.1KB] --13 (element)---> [(Global handles)] (synthetic) @29 [10.9KB] --37 / DevTools console (internal)---> [Detached HTMLSpanElement] (native) @2875845 [781.1KB] --6 (element)---> [Detached HTMLDivElement] (native) @2875843 [160 bytes] --8 (element)---> [Detached HTMLDivElement] (native) @2875841 [160 bytes] --9 (element)---> [Detached HTMLDivElement] (native) @2875709 [160 bytes] --6 (element)---> [Detached HTMLFormElement] (native) @2875701 [792 bytes] --7 (element)---> [Detached InternalNode] (native) @2347418720 [0 byte] --8 (element)---> [Detached HTMLInputElement] (native) @2875595 [1.1KB] --16 (element)---> [Detached InternalNode] (native) @2340601344 [328 bytes] --1 (element)---> [Detached ShadowRoot] (native) @2340276032 [328 bytes]

--Similar leaks in this run: 2-- --Retained size of leaked objects: 352 bytes-- [] (synthetic) @1 [72.2MB] --2 (shortcut)---> [Window / http://localhost:3000/] (object) @9821 [28.5KB] --FullCalendarVDom (property)---> [Object] (object) @1017785 [544 bytes] --unmountComponentAtNode (property)---> [unmountComponentAtNode] (closure) @887173 [68 bytes] --context (internal)---> [] (object) @886495 [181KB] --currentFiber (variable)---> [FiberNode HostRoot] (object) @2936353 [960 bytes] --firstEffect (property)---> [Detached FiberNode render MemoComponent] (object) @2936359 [192 bytes]

--Similar leaks in this run: 2-- --Retained size of leaked objects: 192 bytes-- [] (synthetic) @1 [72.2MB] --2 (shortcut)---> [Window / http://localhost:3000/] (object) @9821 [28.5KB] --asap (property)---> [asap] (closure) @981181 [760 bytes] --context (internal)---> [] (object) @981185 [624 bytes] --microtask (variable)---> [] (closure) @2461961 [536 bytes] --context (internal)---> [] (object) @838179 [504 bytes] --node (variable)---> [Detached Text] (native) @263405 [396 bytes] --3 (element)---> [Detached InternalNode] (native) @2347375360 [272 bytes] --1 (element)---> [Detached InternalNode] (native) @2340565184 [272 bytes] --1 (element)---> [Detached InternalNode] (native) @2340565344 [272 bytes] --1 (element)---> [Detached MutationObserverRegistration] (native) @2340565504 [272 bytes] --1 (element)---> [Detached MutationObserver] (native) @1005061568 [192 bytes] --1 (element)---> [Detached MutationObserver::Delegate] (native) @1005061408 [80 bytes]

--Similar leaks in this run: 2-- --Retained size of leaked objects: 192 bytes-- [] (synthetic) @1 [72.2MB] --2 (shortcut)---> [Window / http://localhost:3000/] (object) @9821 [28.5KB] --Observable (property)---> [Observable] (closure) @981213 [3.3KB] --context (internal)---> [] (object) @981227 [2KB] --microtask (variable)---> [] (closure) @2461925 [536 bytes] --context (internal)---> [] (object) @838181 [504 bytes] --node (variable)---> [Detached Text] (native) @263411 [396 bytes] --3 (element)---> [Detached InternalNode] (native) @2347375680 [272 bytes] --1 (element)---> [Detached InternalNode] (native) @2340839104 [272 bytes] --1 (element)---> [Detached InternalNode] (native) @2340560864 [272 bytes] --1 (element)---> [Detached MutationObserverRegistration] (native) @2340560704 [272 bytes] --1 (element)---> [Detached MutationObserver] (native) @1005062048 [192 bytes] --1 (element)---> [Detached MutationObserver::Delegate] (native) @1005061888 [80 bytes]

--Similar leaks in this run: 1-- --Retained size of leaked objects: 192 bytes-- [Window] (native) @263277 [6.8KB] --4 (element)---> [HTMLDocument] (native) @263275 [21.5KB] --12 (element)---> [Range] (native) @2340276992 [96 bytes] --3 (element)---> [HTMLDivElement] (native) @262245 [140 bytes] --4 (element)---> [HTMLDivElement] (native) @262385 [228 bytes] --3 (element)---> [HTMLDivElement] (native) @262251 [704 bytes] --__reactInternalInstance$vcd965b8sak (property)---> [FiberNode div HostComponent] (object) @1608501 [304 bytes] --child (property)---> [FiberNode ClassComponent] (object) @1608615 [220 bytes] --_debugOwner (property)---> [FiberNode FunctionComponent] (object) @1373349 [3.2KB] --return (property)---> [FiberNode Modal FunctionComponent] (object) @1373295 [1KB] --pendingProps (property)---> [Object] (object) @1239415 [60 bytes] --children (property)---> [Object] (object) @1239305 [388 bytes] --_owner (property)---> [FiberNode FunctionComponent] (object) @1252139 [192 bytes] --_debugOwner (property)---> [FiberNode FunctionComponent] (object) @1235961 [192 bytes] --firstEffect (property)---> [Detached FiberNode render ForwardRef] (object) @2894599 [192 bytes]

--Similar leaks in this run: 1-- --Retained size of leaked objects: 48 bytes-- [Window] (native) @263277 [6.8KB] --4 (element)---> [HTMLDocument] (native) @263275 [21.5KB] --jQuery360089431674213560691 (property)---> [Object] (object) @1021743 [2.5KB] --handle (property)---> [] (closure) @263137 [52 bytes] --context (internal)---> [] (object) @1577477 [20 bytes] --previous (internal)---> [] (object) @320449 [18.5KB] --support (variable)---> [Object] (object) @837833 [1.2KB] --reliableTrDimensions (property)---> [reliableTrDimensions] (closure) @1368867 [68 bytes] --context (internal)---> [] (object) @2206413 [640 bytes] --div (variable)---> [Detached HTMLDivElement] (native) @263591 [188 bytes] --3 (element)---> [Detached InternalNode] (native) @995444160 [48 bytes] --1 (element)---> [Detached CSSStyleDeclaration] (native) @995444000 [48 bytes]

JacksonGL commented 1 year ago

@Ckins

Is there any tricks / instruction for mapping console output to react code / html components?

There are a few ways:

  1. Open Chrome DevTool, load the final heap snapshot taken by memlab (located inside $(memlab get-default-work-dir)/data/cur) and search for the leaked object ID (e.g., @995444000)
  2. Use the following command to interactive with the leak trace in terminal
    memlab view-heap --node-id <leaked object ID>

    Either way, you can view object properties in objects and closure variables defined in closure context, so you can search object names, function names in your code base to locate the source. In case object names and function name is too general, we often do a conjunction search of all the object properties in objects and closure variables defined in closure context to narrow down the scope (which can quickly narrow down to the location most of the time in our use case).

PS: The internal UI mentioned in the engineering blog post has a few shortcuts to make the process easier (integrated with Meta infra so not open sourced). Just an idea if anyone is interested in integrating with their own infra.

JacksonGL commented 1 year ago

How should I translated the below output to a HTML path? In the tutorial, it seems there is --leaked_objects, I don't see this tag in the below output.

This is specific to debugging React. In either memlab view-heap or Chrome DevTool, follow the return chain of the React fiber nodes. Each Fiber node should have a state node that reveals some information about the component or the associated DOM elements, which gives you some clues about the HTML element.

JacksonGL commented 1 year ago

In addition to [window], I also see [(GC roots)], [ < synthetic > ] . How to interpret these kinds?

MemLab faithfully presents the reference chain in V8 heap snapshots (serialization of C++ data structure in v8 engine), those are V8 internal data structure for organizing the heap, most of the time we can ignore those objects (with type internal, native etc.) and focus on the JavaScript references.

LuciNyan commented 1 year ago

Hi! @JacksonGL. I just wanted to let you know that memlab is fantastic! But, as you say, it would be easier to use with a UI. So I wonder if making the UI open source will be part of the plans. If not, can I contribute an open-source UI to this repo?

JacksonGL commented 1 year ago

@LuciNyan Thanks for your contributions. The internal UI is integrated with Meta infra, so there is no plan to open source the UI in the near future. Can you explain what kind of UI you have in mind? Is it a web app, electron app, or extending the Chrome DevTools?

LuciNyan commented 1 year ago

@LuciNyan Thanks for your contributions. The internal UI is integrated with Meta infra, so there is no plan to open source the UI in the near future. Can you explain what kind of UI you have in mind? Is it a web app, electron app, or extending the Chrome DevTools?

I'm thinking of a web app or a chrome extension. I read that blog you mentioned, but I don't think I know enough details about how you guys use memlab. So I'm still lacking a detailed vision of the exact functionality of this UI.

By the way, I think memlab is a powerful tool. Large enterprises may have some internal tools to monitor and troubleshoot memory leaks. But for small to medium-sized teams, There is not yet a mature tool. Memlab can fill this gap.

I want to do something to make memlab easier to use. I currently have this in mind:

  1. A web UI, which might be like what you have integrated into Meta infra.
  2. Provide interfaces that make it easier for users to integrate memlab into existing tests (e.g., Playwright or Jest)

What do you think?

JacksonGL commented 1 year ago

I agree having a better user interface and APIs for integration with other test frameworks would make MemLab easier to use.

A web UI, which might be like what you have integrated into Meta infra.

The scope and implementation of Web UI can vary depending on what functionalities to include. Here are some ideas: For basic use cases, adding or extending either one of the following user interfaces could assist memory leak debugging:

For more extended use cases, the Web UI could be a memory leak monitoring and management system. This may involve integration with CI/CD, code searching/viewing system, and backend data stores depending on different orgs' infra.

Provide interfaces that make it easier for users to integrate memlab into existing tests (e.g., Playwright or Jest)

For integration with different testing frameworks, this could be implemented by providing APIs that take heap snapshots based on each testing framework (e.g., Playwright, Cypress, Jest). Calling those test-framework-specific APIs in existing tests dumps heap snapshots and meta data in the format that can be processed by the memlab heap analyzer.

Please also check out the discussion in #35.

LuciNyan commented 1 year ago

Hi! @JacksonGL. Thanks for the detailed advice! This sounds great! I'm sure memlab will become an even more influential tool!