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 e2e
Measures the total number of arrays.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure array-count --only base
Measures 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 base
Measures the total number of classes.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure class-count --only base
Measures 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 base
Measures dom nodes, jsEventListeners and documents.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure dom-counters --only base
Measures the total number of dom nodes.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure dom-node-count --only base
Measures the total number of event listeners.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure event-listener-count --only base
Measures the event listeners.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure event-listeners --only base
Measures the total number of functions.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure function-count --only base
Measures 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 base
Measures heap usage.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure heap-usage --only base
Measures the number of instances of each class.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure instance-counts --only base
Measures the number of intersection observers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure intersection-observer-count --only base
Measures 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 base
Measures the total number of MediaQueryLists.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure media-query-list-count --only base
Measures the total number of MutationObservers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure mutation-observer-count --only base
Measures the count of each function.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure named-function-count --only base
Measures 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 base
Measures the total number of Promises.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure promise-count --only base
Measures the total number of Regex instances.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure regex-count --only base
Measures the total number of ResizeObservers.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure resize-observer-count --only base
Measures 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 base
Measures the total number of Timeouts.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure set-timeout --only base
Measures the total number of WeakMaps.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure weak-map-count --only base
Measures the total number of WeakSets.
node packages/cli/bin/test.js --cwd packages/e2e --check-leaks --measure-after --measure weak-set-count --only base
Measures 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
// before
[
{
"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",
],
},
]
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 |
GettingStarted | microsoft/vscode#216858 | Review |
SettingEnumRenderer | microsoft/vscode#216855 | Fixed |
SettingsEditor2 | microsoft/vscode#216763 | Fixed |
This project is based on the jest cli, playwright and fuite.