Find memory leaks in vscode to improve robustness and performance.
git clone git@github.com:SimonSiefke/vscode-memory-leak-finder.git &&
cd vscode-memory-leak-finder &&
npm ci &&
npm run e2eMeasures the total number of arrays.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure array-count --only baseMeasures the total number of elements in all arrays.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure array-element-count --only baseMeasures the total number of classes.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure class-count --only baseMeasures the total number of detached dom nodes.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure detached-dom-node-count --only baseMeasures dom nodes, jsEventListeners and documents.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure dom-counters --only baseMeasures the total number of dom nodes.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure dom-node-count --only baseMeasures the total number of edit context.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure edit-context-count --only baseMeasures the total number of event listeners.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure event-listener-count --only baseMeasures the event listeners.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure event-listeners --only baseMeasures the total number of functions.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure function-count --only baseMeasures global variables / global lexical scope names.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure global-lexical-scope-names --only baseMeasures heap usage.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure heap-usage --only baseMeasures the number of instances of each class.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure instance-counts --only baseMeasures the number of intersection observers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure intersection-observer-count --only baseMeasures the total number of elements in all Maps.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure map-size --only baseMeasures the total number of MediaQueryLists.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure media-query-list-count --only baseMeasures the total number of MutationObservers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure mutation-observer-count --only baseMeasures the count of each function.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure named-function-count --only baseMeasures the difference in counts of each function.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure named-function-difference --only baseMeasures the total number of Promises.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure promise-count --only baseMeasures the total number of Regex instances.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure regex-count --only baseMeasures the total number of ResizeObservers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure resize-observer-count --only baseMeasures the total number of elements in all Sets.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure set-size --only baseMeasures the total number of Timeouts.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure set-timeout --only baseMeasures the total number of WeakMaps.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure weak-map-count --only baseMeasures the total number of WeakSets.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure weak-set-count --only baseMeasures the total number of Windows.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure window-count --only base- packages/charts: Visualizations for test output
- packages/cli: Command Line Interface, similar to jest
- packages/devtools-protocol: Functionality related to Chrome Devtools Protocol
- packages/e2e: The e2e test scenarios
- packages/file-watcher-worker: Watch files for changes
- packages/injected-code: Code injected to the page for e2e tests
- packages/memory-leak-finder: Library for finding memory leaks
- packages/memory-leak-worker: Process for finding memory leaks (uses the library from above)
- packages/page-object: Page Object Model to simplify e2e tests
- packages/source-map-worker: Functions for querying original positions and function names using source maps
- packages/test-coordinator: Determines which tests to run, launches VSCode, file-watcher-worker, test-worker, memory-leak-worker, video-recording-worker
- packages/test-worker: Runs tests
- packages/test-worker-commands: Functions used by test-worker
- packages/video-recording-worker: Record screencasts of the tests
Before and after a test is executed, all event listeners are queried using Chrome Devtools Protocol Runtime.queryObjects({ prototypeId: "EventTarget.prototype" }) and DomDebugger.getEventListeners.
We get an array of event listeners before and after, for example
and
// after
[
{
"type": "focusin",
"description": "()=>this.j()",
"objectId": "524841679309534768.4.2930",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:148:37007)",
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map",
],
},
{
"type": "keydown",
"description": "N=>{new P.$qO(N).equals(2)&&N.preventDefault()}",
"objectId": "3680313440875909344.4.4572",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:39878)",
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map",
],
"originalStack": ["/src/vs/base/browser/ui/menu/menu.ts:122:58"],
},
]The before and after arrays are compared to see which event listeners have been added. In the example above, there is one keydown listener more in the after array which is not in the before array.
The tests are structured in a way one would be expect that the number of event listeners before and after the test are equal. For example, when opening and closing the menu, one would expect the number of event listeners stays equal. This is the menu toggle test:
// title-bar-menu-toggle.js
export const run = async ({ TitleBar }) => {
await TitleBar.showMenuFile()
await TitleBar.hideMenuFile()
}Every time the test was executed, event listeners increased by one keydown listener in /src/vs/base/browser/ui/menu/menu.ts:122:58, which indicates a memory leak and in this case was precisely the location of the memory leak.
In other cases, the output for memory leaks might not be quite as clear, but maybe still helpful. This is the output for the notebook-open test (opening and closing a notebook):
[
{
"type": "contextmenu",
"description": "n=>{t.$_O.stop(n,!0)}",
"objectId": "2723967474668247540.4.13637",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:18357)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 1,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionbar.ts:370:117"]
},
{
"type": "-monaco-gesturetap",
"description": "r=>this.onClick(r,!0)",
"objectId": "2723967474668247540.4.13695",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:10198)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 1,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionViewItems.ts:121:68"]
},
{
"type": "mousedown",
"description": "r=>{c||I.$_O.stop(r,!0),this._action.enabled&&r.button===0&&o.classList.add(\"active\")}",
"objectId": "2723967474668247540.4.13697",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:10258)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 1,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionViewItems.ts:123:70"]
},
{
"type": "click",
"description": "r=>{I.$_O.stop(r,!0),this.m&&this.m.isMenu||this.onClick(r)}",
"objectId": "2723967474668247540.4.13699",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:10475)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 1,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionViewItems.ts:145:65"]
},
{
"type": "dblclick",
"description": "r=>{I.$_O.stop(r,!0)}",
"objectId": "2723967474668247540.4.13701",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:10572)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 1,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionViewItems.ts:154:68"]
},
{
"type": "mouseout",
"description": "n=>{I.$_O.stop(n),o.classList.remove(\"active\")}",
"objectId": "2723967474668247540.4.13705",
"stack": [
"listener (file:///home/simon/.cache/repos/vscode-memory-leak-finder/.vscode-test/vscode-linux-x64-1.83.1/resources/app/out/vs/workbench/workbench.desktop.main.js:244:10662)"
],
"sourceMaps": [
"https://ticino.blob.core.windows.net/sourcemaps/f1b07bd25dfad64b0167beb15359ae573aecd2cc/core/vs/workbench/workbench.desktop.main.js.map"
],
"count": 2,
"originalStack": ["/src/vs/base/browser/ui/actionbar/actionViewItems.ts:159:56"]
}
]It seems there is memory leak when opening and closing a notebook. But just looking at the output, it's difficult to say much more. It's not clear where exactly the memory leak is and one might need to look more closely at the actionbar.ts and actionViewItems.ts code.
| Component | Issue | Status |
|---|---|---|
| Menu | microsoft/vscode#195580 | Fixed |
| Dropdown | microsoft/vscode#197767 | Fixed |
| MenuBar | microsoft/vscode#198051 | Fixed |
| DefaultWorkerFactory | microsoft/vscode#198709 | Fixed |
| ExtensionList | microsoft/vscode#198709 | Fixed |
| SimpleFindWidget | microsoft/vscode#199043 | Fixed |
| ColorPickerWidget | microsoft/vscode#199814 | Fixed |
| DiffEditor | microsoft/vscode#200381 | Fixed |
| QuickPick | microsoft/vscode#201320 | Fixed |
| Terminal | xtermjs/xterm.js#4935 | Fixed |
| KeyBindingsEditor | microsoft/vscode#202455 | Fixed |
| NotebookEditorWidget | microsoft/vscode#204756 | Fixed |
| SettingsEditor2 | microsoft/vscode#216763 | Fixed |
| SettingEnumRenderer | microsoft/vscode#216855 | Fixed |
| GettingStarted | microsoft/vscode#216858 | Fixed |
| ExtensionTabs | microsoft/vscode#219726 | Fixed |
| Output | microsoft/vscode#221605 | Fixed |
| StickyScroll | microsoft/vscode#221622 | Fixed |
| SelectBox | microsoft/vscode#221507 | Fixed |
| DebugView | microsoft/vscode#225334 | Fixed |
| SettingsIndicators | microsoft/vscode#236417 | Fixed |
This project is based on the jest cli, playwright and fuite.