Memory Leak Tooling
Contents
Memory Leak Tooling#
This package is using fuite to execute a memory leak scenarios.
Under the hood fuite uses Puppeteer to control a Chrome instance. So scenarios must use Puppeteer API.
Running test#
To run the test, you will need to
Start JupyterLab after compiling it at a reference state with the following command
jupyter lab --config memory-leaks/jupyter_lab_config.py
Run the tests against the reference
cd memory-leaks
yarn install
yarn start-jlab &
yarn test:mocha
Stop JupyterLab
The benchmark scenarios are:
notebook: Create a new notebook and delete it (memory-leaks/tests/notebook.mjs).
file-editor: Create a new text file (memory-leaks/tests/file-editor.mjs).
cell:
Add a cell (for all 3 types) (memory-leaks/tests/cell.mjs)
Move a cell by drag-and-drop (for all 3 types) (memory-leaks/tests/cell-motion.mjs)
Each fuite scenario is stored in a specific file and can be run separately using:
npx fuite http://localhost:9999/lab?reset -s memory-leaks/tests/notebook.mjs
You can run in headed mode with the -d
flag. And you can change the number
of iteration with -i <number>
option.
Don’t forget the
?reset
query arguments to ensure the workspace is reset between each iteration.
Finding leaks#
From the direct report of fuite
, you may have stack trace to look at for growing collections. But
may not be sufficient to find all leaks. For that, you can use the
three heap snapshot technique
introduced by the GMail team. The steps to carry out are:
Take a heap snapshot
Do some actions that are creating memory leaks; e.g. opening and closing a new notebook.
Take a second heap snapshot
Do the same actions as in step 2; e.g. opening and closing a new notebook.
Take a third heap snapshot
Assuming you are using Chrome-based browser, filter the objects to those allocated between snapshots 1 and 2 in snapshot 3’s Summary view.
This will grant you access to objects leaking in memory. From there:
Examine the retaining path of leaked objects in the lower half of the Profiles panel
If the allocation site cannot be easily inferred (i.e. event listeners):
Instrument the constructor of the retaining object via the JS console to save the stack trace for allocations Gmail team proposed the following snippet:
window.__save_el = goog.events.Listener goog.events.Listener = function () { this._stack = new Error().stack;} goog.events.Listener.prototype = window.__save_el.prototype
Rerun the scenario
Expand objects in the retaining tree to see the stack trace:
$0._stack
Fix it!
Properly dispose of the retaining object
unlisten() to event listeners