From d8b8311b6a71d0b0cb991fce33fc9a31d28e59b3 Mon Sep 17 00:00:00 2001 From: Liang Gong Date: Mon, 29 Jan 2024 14:20:19 -0800 Subject: [PATCH] feat(e2e): support filtering references for retainer traces (#106) Summary: This diff adds a new callback that can be used to define a logic to decide whether a reference should be considered as part of the retainer trace. The callback is called for every reference (edge) in the heap snapshot. The callback accepts the following parameters * `edge` - the reference (edge) that is considered for calcualting the retainer trace * `snapshot` - the snapshot of target interaction * `isReferenceUsedByDefault` - MemLab has its own default logic for whether a reference should be considered as part of the retainer trace, if this parameter is true, it means MemLab will consider this reference when calculating the retainer trace. The return value indicates whether the given reference should be considered when calculating the retainer trace. Note that when this callback returns true, the reference will only be considered as a candidate for retainer trace, so it may or may not be included in the retainer trace; however, if this callback returns false, the reference will be excluded. Note that by excluding a dominator reference of an object (i.e., an edge that must be traveled through to reach the heap object from GC roots), the object will be considered as unreachable in the heap graph; and therefore, the reference and heap object will not be included in the retainer trace detection and retainer size calculation. ``` // save as leak-filter.js module.exports = { retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { // exclude react fiber references if (edge.name_or_index.toString().startsWith('__reactFiber$')) { return false; } return true; } }; ``` Use the leak filter definition in command line interface: ``` memlab find-leaks --leak-filter ``` ``` memlab run --scenario --leak-filter ``` Differential Revision: D53167155 fbshipit-source-id: b9006fd309276452acbe216c577f34c28b071ffa --- .../__tests__/API/E2EFindMemoryLeaks.test.ts | 42 +---- .../__tests__/API/E2EMemoryLeakFilter.test.ts | 146 ++++++++++++++++++ packages/core/src/lib/Config.ts | 23 ++- packages/core/src/lib/Types.ts | 139 ++++++++++++++++- .../rules/FilterByExternalFilter.rule.ts | 10 +- packages/core/src/paths/TraceFinder.ts | 29 +++- .../api/interfaces/core_src.IBrowserInfo.md | 6 +- .../docs/api/interfaces/core_src.IHeapEdge.md | 18 +-- .../api/interfaces/core_src.IHeapEdges.md | 6 +- .../api/interfaces/core_src.IHeapLocation.md | 12 +- .../docs/api/interfaces/core_src.IHeapNode.md | 64 ++++---- .../api/interfaces/core_src.IHeapNodes.md | 6 +- .../api/interfaces/core_src.IHeapSnapshot.md | 18 +-- .../interfaces/core_src.IHeapStringNode.md | 66 ++++---- .../api/interfaces/core_src.ILeakFilter.md | 64 +++++++- .../docs/api/interfaces/core_src.IScenario.md | 68 ++++++-- website/docs/api/modules/core_src.md | 45 +++++- 17 files changed, 594 insertions(+), 168 deletions(-) create mode 100644 packages/api/src/__tests__/API/E2EMemoryLeakFilter.test.ts diff --git a/packages/api/src/__tests__/API/E2EFindMemoryLeaks.test.ts b/packages/api/src/__tests__/API/E2EFindMemoryLeaks.test.ts index 2b13a7e38..2b2b8f579 100644 --- a/packages/api/src/__tests__/API/E2EFindMemoryLeaks.test.ts +++ b/packages/api/src/__tests__/API/E2EFindMemoryLeaks.test.ts @@ -17,7 +17,12 @@ import os from 'os'; import path from 'path'; import fs from 'fs-extra'; import {run} from '../../index'; -import {scenario, testSetup, testTimeout} from './lib/E2ETestSettings'; +import { + getUniqueID, + scenario, + testSetup, + testTimeout, +} from './lib/E2ETestSettings'; beforeEach(testSetup); @@ -61,39 +66,6 @@ test( testTimeout, ); -test( - 'self-defined leak detector can find TestObject', - async () => { - const selfDefinedScenario: IScenario = { - app: (): string => 'test-spa', - url: (): string => '', - action: async (page: Page): Promise => - await page.click('[data-testid="link-4"]'), - leakFilter: (node: IHeapNode) => { - return node.name === 'TestObject' && node.type === 'object'; - }, - }; - - const workDir = path.join(os.tmpdir(), 'memlab-api-test', `${process.pid}`); - fs.mkdirsSync(workDir); - - const result = await run({ - scenario: selfDefinedScenario, - evalInBrowserAfterInitLoad: injectDetachedDOMElements, - workDir, - }); - // detected all different leak trace cluster - expect(result.leaks.length).toBe(1); - // expect all traces are found - expect( - result.leaks.some(leak => JSON.stringify(leak).includes('_randomObject')), - ); - const reader = result.runResult; - expect(path.resolve(reader.getRootDirectory())).toBe(path.resolve(workDir)); - }, - testTimeout, -); - function injectDetachedDOMElementsWithPrompt() { // @ts-ignore window.injectHookForLink4 = () => { @@ -139,7 +111,7 @@ test( }, }; - const workDir = path.join(os.tmpdir(), 'memlab-api-test', `${process.pid}`); + const workDir = path.join(os.tmpdir(), 'memlab-api-test', getUniqueID()); fs.mkdirsSync(workDir); const result = await run({ diff --git a/packages/api/src/__tests__/API/E2EMemoryLeakFilter.test.ts b/packages/api/src/__tests__/API/E2EMemoryLeakFilter.test.ts new file mode 100644 index 000000000..5856dc40c --- /dev/null +++ b/packages/api/src/__tests__/API/E2EMemoryLeakFilter.test.ts @@ -0,0 +1,146 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @oncall web_perf_infra + */ + +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import type {Page} from 'puppeteer'; +import type {IHeapEdge, IHeapNode, IScenario} from '@memlab/core'; + +import os from 'os'; +import path from 'path'; +import fs from 'fs-extra'; +import {run} from '../../index'; +import {getUniqueID, testSetup, testTimeout} from './lib/E2ETestSettings'; + +beforeEach(testSetup); + +function injectDetachedDOMElements() { + // @ts-ignore + window.injectHookForLink4 = () => { + class TestObject { + key: 'value'; + } + const arr = []; + for (let i = 0; i < 23; ++i) { + arr.push(document.createElement('div')); + } + // @ts-ignore + window.__injectedValue = arr; + // @ts-ignore + window._path_1 = {x: {y: document.createElement('div')}}; + // @ts-ignore + window._path_2 = new Set([document.createElement('div')]); + // @ts-ignore + window._randomObject = [new TestObject()]; + }; +} + +test( + 'self-defined leak detector can find TestObject', + async () => { + const selfDefinedScenario: IScenario = { + app: (): string => 'test-spa', + url: (): string => '', + action: async (page: Page): Promise => + await page.click('[data-testid="link-4"]'), + leakFilter: (node: IHeapNode) => { + return node.name === 'TestObject' && node.type === 'object'; + }, + }; + + const workDir = path.join(os.tmpdir(), 'memlab-api-test', getUniqueID()); + fs.mkdirsSync(workDir); + + const result = await run({ + scenario: selfDefinedScenario, + evalInBrowserAfterInitLoad: injectDetachedDOMElements, + workDir, + }); + // detected all different leak trace cluster + expect(result.leaks.length).toBe(1); + // expect all traces are found + expect( + result.leaks.some(leak => JSON.stringify(leak).includes('_randomObject')), + ); + const reader = result.runResult; + expect(path.resolve(reader.getRootDirectory())).toBe(path.resolve(workDir)); + }, + testTimeout, +); + +test( + 'self-defined retainer trace filter work as expected (part 1)', + async () => { + const selfDefinedScenario: IScenario = { + app: (): string => 'test-spa', + url: (): string => '', + action: async (page: Page): Promise => + await page.click('[data-testid="link-4"]'), + retainerReferenceFilter: (edge: IHeapEdge) => { + return edge.name_or_index !== '_path_1'; + }, + }; + + const workDir = path.join(os.tmpdir(), 'memlab-api-test', getUniqueID()); + fs.mkdirsSync(workDir); + + const result = await run({ + scenario: selfDefinedScenario, + evalInBrowserAfterInitLoad: injectDetachedDOMElements, + workDir, + }); + // detected all different leak trace cluster + expect(result.leaks.length).toBe(1); + // expect the none of the traces to include _path_1 + expect( + result.leaks.every(leak => !JSON.stringify(leak).includes('_path_1')), + ); + // expect some of the traces to include _path_2 + expect(result.leaks.some(leak => JSON.stringify(leak).includes('_path_2'))); + const reader = result.runResult; + expect(path.resolve(reader.getRootDirectory())).toBe(path.resolve(workDir)); + }, + testTimeout, +); + +test( + 'self-defined retainer trace filter work as expected (part 2)', + async () => { + const selfDefinedScenario: IScenario = { + app: (): string => 'test-spa', + url: (): string => '', + action: async (page: Page): Promise => + await page.click('[data-testid="link-4"]'), + retainerReferenceFilter: (edge: IHeapEdge) => { + return edge.name_or_index !== '_path_2'; + }, + }; + + const workDir = path.join(os.tmpdir(), 'memlab-api-test', getUniqueID()); + fs.mkdirsSync(workDir); + + const result = await run({ + scenario: selfDefinedScenario, + evalInBrowserAfterInitLoad: injectDetachedDOMElements, + workDir, + }); + // detected all different leak trace cluster + expect(result.leaks.length).toBe(1); + // expect the none of the traces to include _path_2 + expect( + result.leaks.every(leak => !JSON.stringify(leak).includes('_path_2')), + ); + // expect some of the traces to include _path_1 + expect(result.leaks.some(leak => JSON.stringify(leak).includes('_path_1'))); + const reader = result.runResult; + expect(path.resolve(reader.getRootDirectory())).toBe(path.resolve(workDir)); + }, + testTimeout, +); diff --git a/packages/core/src/lib/Config.ts b/packages/core/src/lib/Config.ts index 8cde409d2..cb5d99d4a 100644 --- a/packages/core/src/lib/Config.ts +++ b/packages/core/src/lib/Config.ts @@ -619,17 +619,30 @@ export class MemLabConfig { if (scenario == null) { return; } + let hasCallback = false; + const externalFilter: ILeakFilter = {}; // set leak filter - const {leakFilter, beforeLeakFilter} = scenario; - if (typeof leakFilter !== 'function') { - return; + const {leakFilter, beforeLeakFilter, retainerReferenceFilter} = scenario; + if (typeof leakFilter === 'function') { + hasCallback = true; + externalFilter.leakFilter = leakFilter; } - this.externalLeakFilter = {leakFilter}; // set leak filter init callback if (typeof beforeLeakFilter === 'function') { - this.externalLeakFilter.beforeLeakFilter = beforeLeakFilter; + hasCallback = true; + externalFilter.beforeLeakFilter = beforeLeakFilter; + } + + // set retainer reference filter callback + if (typeof retainerReferenceFilter === 'function') { + hasCallback = true; + externalFilter.retainerReferenceFilter = retainerReferenceFilter; + } + + if (hasCallback) { + this.externalLeakFilter = externalFilter; } } diff --git a/packages/core/src/lib/Types.ts b/packages/core/src/lib/Types.ts index 3b31a1200..b1eb7fcae 100644 --- a/packages/core/src/lib/Types.ts +++ b/packages/core/src/lib/Types.ts @@ -390,7 +390,7 @@ export interface ILeakFilter { * in browser. * * * **Examples**: - * ```typescript + * ```javascript * module.exports = { * beforeLeakFilter: (snapshot, leakedNodeIds) { * // initialize some data stores @@ -429,7 +429,7 @@ export interface ILeakFilter { * * **Returns**: the boolean value indicating whether the given node in * the snapshot should be considered as leaked. * - * + * * **Examples**: * ```javascript * // save as leak-filter.js * module.exports = { @@ -450,7 +450,61 @@ export interface ILeakFilter { * memlab run --scenario --leak-filter * ``` */ - leakFilter: LeakFilterCallback; + leakFilter?: LeakFilterCallback; + /** + * Callback that can be used to define a logic to decide whether + * a reference should be considered as part of the retainer trace. + * The callback is called for every reference (edge) in the heap snapshot. + * + * For concrete examples, check out {@link leakFilter}. + * + * * **Parameters**: + * * edge - the reference (edge) that is considered + * for calcualting the retainer trace + * * snapshot - the snapshot of target interaction + * * isReferenceUsedByDefault - MemLab has its own default logic for + * whether a reference should be considered as part of the retainer trace, + * if this parameter is true, it means MemLab will consider this reference + * when calculating the retainer trace. + * + * * **Returns**: the value indicating whether the given reference should be + * considered when calculating the retainer trace. Note that when this + * callback returns true, the reference will only be considered as a candidate + * for retainer trace, so it may or may not be included in the retainer trace; + * however, if this callback returns false, the reference will be excluded. + * + * Note that by excluding a dominator reference of an object (i.e., an edge + * that must be traveled through to reach the heap object from GC roots), + * the object will be considered as unreachable in the heap graph; and + * therefore, the reference and heap object will not be included in the + * retainer trace detection and retainer size calculation. + * + * * **Examples**: + * ```javascript + * // save as leak-filter.js + * module.exports = { + * retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + * // exclude react fiber references + * if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + * return false; + * } + * // exclude other references here + * // ... + * return true; + * } + * }; + * ``` + * + * Use the leak filter definition in command line interface: + * ```bash + * memlab find-leaks --leak-filter + * ``` + * + * ```bash + * memlab run --scenario --leak-filter + * ``` + */ + retainerReferenceFilter?: ReferenceFilterCallback; } /** @@ -494,6 +548,42 @@ export type LeakFilterCallback = ( leakedNodeIds: HeapNodeIdSet, ) => boolean; +/** + * Callback that can be used to define a logic to decide whether + * a reference should be filtered (included) for some + * calculations (e.g., retainer trace calculation) + * + * For concrete examples, check out {@link leakFilter}. + * + * @param edge - the reference (edge) that is considered + * for calcualting the retainer trace + * @param snapshot - the snapshot of target interaction + * @param isReferenceUsedByDefault - MemLab has its own default logic for + * whether a reference should be filtered (included), if this parameter is true, + * it means MemLab will consider this reference for inclusion + * + * @returns the value indicating whether the given reference should be + * filtered (i.e., included) + * + * * **Examples**: + * ```javascript + * // exclude react fiber references + * function retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + * if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + * return false; + * } + * // exclude other references here + * // ... + * return true; + * }; + * ``` + */ +export type ReferenceFilterCallback = ( + edge: IHeapEdge, + snapshot: IHeapSnapshot, + isReferenceUsedByDefault: boolean, +) => boolean; + /** * The callback defines browser interactions which are * used by memlab to interact with the web app under test. @@ -848,6 +938,49 @@ export interface IScenario { * ``` */ leakFilter?: LeakFilterCallback; + /** + * Callback that can be used to define a logic to decide whether + * a reference should be considered as part of the retainer trace. + * The callback is called for every reference (edge) in the heap snapshot. + * + * For concrete examples, check out {@link leakFilter}. + * + * * **Parameters**: + * * edge - the reference (edge) that is considered + * for calcualting the retainer trace + * * snapshot - the snapshot of target interaction + * * isReferenceUsedByDefault - MemLab has its own default logic for + * whether a reference should be considered as part of the retainer trace, + * if this parameter is true, it means MemLab will consider this reference + * when calculating the retainer trace. + * + * * **Returns**: the value indicating whether the given reference should be + * considered when calculating the retainer trace. Note that when this + * callback returns true, the reference will only be considered as a candidate + * for retainer trace, so it may or may not be included in the retainer trace; + * however, if this callback returns false, the reference will be excluded. + * + * Note that by excluding a dominator reference of an object (i.e., an edge + * that must be traveled through to reach the heap object from GC roots), + * the object will be considered as unreachable in the heap graph; and + * therefore, the reference and heap object will not be included in the + * retainer trace detection and retainer size calculation. + * + * * **Examples**: + * ```javascript + * // save as leak-filter.js + * module.exports = { + * retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + * // exclude react fiber references + * if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + * return false; + * } + * return true; + * } + * }; + * ``` + */ + retainerReferenceFilter?: ReferenceFilterCallback; } /** @internal */ diff --git a/packages/core/src/lib/leak-filters/rules/FilterByExternalFilter.rule.ts b/packages/core/src/lib/leak-filters/rules/FilterByExternalFilter.rule.ts index 1c4c02645..15e7a1c4e 100644 --- a/packages/core/src/lib/leak-filters/rules/FilterByExternalFilter.rule.ts +++ b/packages/core/src/lib/leak-filters/rules/FilterByExternalFilter.rule.ts @@ -23,11 +23,11 @@ export class FilterByExternalFilterRule extends LeakObjectFilterRuleBase { snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet, ): LeakDecision { - if (config.externalLeakFilter) { - return config.externalLeakFilter.leakFilter(node, snapshot, leakedNodeIds) - ? LeakDecision.LEAK - : LeakDecision.NOT_LEAK; + if (config.externalLeakFilter?.leakFilter == null) { + return LeakDecision.MAYBE_LEAK; } - return LeakDecision.MAYBE_LEAK; + return config.externalLeakFilter.leakFilter(node, snapshot, leakedNodeIds) + ? LeakDecision.LEAK + : LeakDecision.NOT_LEAK; } } diff --git a/packages/core/src/paths/TraceFinder.ts b/packages/core/src/paths/TraceFinder.ts index eafd9f891..6f186266c 100644 --- a/packages/core/src/paths/TraceFinder.ts +++ b/packages/core/src/paths/TraceFinder.ts @@ -89,7 +89,7 @@ class TraceFinder { visitedIDs.add(node.id); for (const edge of node.references) { - if (!this.shouldTraverseEdge(edge, traverseOption)) { + if (!this.shouldTraverseEdge(edge, snapshot, traverseOption)) { continue; } @@ -318,6 +318,7 @@ class TraceFinder { edges: IHeapEdges, postOrderInfo: PostOrderMapping, flags: Uint32Array, + snapshot: IHeapSnapshot, ): Uint32Array { const {postOrderIndex2NodeIndex, nodeIndex2PostOrderIndex} = postOrderInfo; const nodeCount = nodes.length; @@ -409,7 +410,7 @@ class TraceFinder { ) { return; } - if (!this.shouldTraverseEdge(edge)) { + if (!this.shouldTraverseEdge(edge, snapshot)) { return; } let referrerPostOrderIndex = @@ -517,7 +518,26 @@ class TraceFinder { return false; } - shouldTraverseEdge(edge: IHeapEdge, options: AnyOptions = {}): boolean { + shouldTraverseEdge( + edge: IHeapEdge, + snapshot: IHeapSnapshot, + options: AnyOptions = {}, + ): boolean { + const shouldTraverseByDefault = this.shouldTraverseNodeByInternalStandard( + edge, + options, + ); + const externalFilter = config.externalLeakFilter?.retainerReferenceFilter; + if (externalFilter != null) { + return externalFilter(edge, snapshot, shouldTraverseByDefault); + } + return shouldTraverseByDefault; + } + + private shouldTraverseNodeByInternalStandard( + edge: IHeapEdge, + options: AnyOptions = {}, + ): boolean { if (this.isBlockListedEdge(edge)) { return false; } @@ -602,6 +622,7 @@ class TraceFinder { snapshot.edges, postOrderInfo, flags, + snapshot, ); // step 3: calculate retained sizes info.overwrite('calculating dominators and retained sizes ..'); @@ -650,7 +671,7 @@ class TraceFinder { if (toNode.hasPathEdge) { continue; } - if (!this.shouldTraverseEdge(edge, traverseOption)) { + if (!this.shouldTraverseEdge(edge, snapshot, traverseOption)) { continue; } if (this.shouldIgnoreEdgeInTraceFinding(edge)) { diff --git a/website/docs/api/interfaces/core_src.IBrowserInfo.md b/website/docs/api/interfaces/core_src.IBrowserInfo.md index cb022fd66..0e0fb980f 100644 --- a/website/docs/api/interfaces/core_src.IBrowserInfo.md +++ b/website/docs/api/interfaces/core_src.IBrowserInfo.md @@ -16,7 +16,7 @@ through [RunMetaInfo](../modules/core_src.md#runmetainfo). browser version * **Source**: - * core/src/lib/Types.ts:1038 + * core/src/lib/Types.ts:1171 ___ @@ -25,7 +25,7 @@ ___ all web console output * **Source**: - * core/src/lib/Types.ts:1046 + * core/src/lib/Types.ts:1179 ___ @@ -34,4 +34,4 @@ ___ configuration for puppeteer * **Source**: - * core/src/lib/Types.ts:1042 + * core/src/lib/Types.ts:1175 diff --git a/website/docs/api/interfaces/core_src.IHeapEdge.md b/website/docs/api/interfaces/core_src.IHeapEdge.md index bc056ec12..322ebde60 100644 --- a/website/docs/api/interfaces/core_src.IHeapEdge.md +++ b/website/docs/api/interfaces/core_src.IHeapEdge.md @@ -45,7 +45,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; index of this JS reference inside the `edge.snapshot.edges` pseudo array * **Source**: - * core/src/lib/Types.ts:1463 + * core/src/lib/Types.ts:1596 ___ @@ -55,7 +55,7 @@ returns an [IHeapNode](core_src.IHeapNode.md) instance representing the hosting JS heap object where this reference starts * **Source**: - * core/src/lib/Types.ts:1484 + * core/src/lib/Types.ts:1617 ___ @@ -67,7 +67,7 @@ otherwise this is a reference with a string name (`edge.name_or_index` will return a string) * **Source**: - * core/src/lib/Types.ts:1470 + * core/src/lib/Types.ts:1603 ___ @@ -77,7 +77,7 @@ name of the JS reference. If this is a reference to an array element or internal table element, it is an numeric index * **Source**: - * core/src/lib/Types.ts:1419 + * core/src/lib/Types.ts:1552 ___ @@ -86,7 +86,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this JS reference * **Source**: - * core/src/lib/Types.ts:1459 + * core/src/lib/Types.ts:1592 ___ @@ -96,7 +96,7 @@ returns an [IHeapNode](core_src.IHeapNode.md) instance representing the JS heap pointed to by this reference * **Source**: - * core/src/lib/Types.ts:1479 + * core/src/lib/Types.ts:1612 ___ @@ -105,7 +105,7 @@ ___ the index of the JS heap object pointed to by this reference * **Source**: - * core/src/lib/Types.ts:1474 + * core/src/lib/Types.ts:1607 ___ @@ -115,7 +115,7 @@ type of the JS reference, all types: `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak` * **Source**: - * core/src/lib/Types.ts:1424 + * core/src/lib/Types.ts:1557 ## Methods @@ -133,4 +133,4 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1494 + * core/src/lib/Types.ts:1627 diff --git a/website/docs/api/interfaces/core_src.IHeapEdges.md b/website/docs/api/interfaces/core_src.IHeapEdges.md index 9fba3c6b3..ad4be0cdb 100644 --- a/website/docs/api/interfaces/core_src.IHeapEdges.md +++ b/website/docs/api/interfaces/core_src.IHeapEdges.md @@ -41,7 +41,7 @@ The total number of edges in heap graph (or JS references in heap snapshot). * **Source**: - * core/src/lib/Types.ts:1531 + * core/src/lib/Types.ts:1664 ## Methods @@ -54,7 +54,7 @@ to each element in ascending order of element index. * `callback`: (`edge`: [`IHeapEdge`](core_src.IHeapEdge.md), `index`: `number`) => `boolean` \| `void` | the callback does not need to return any value, if the callback returns `false` when iterating on element at index `i`, then all elements after `i` won't be iterated. * **Returns**: `void` * **Source**: - * core/src/lib/Types.ts:1547 + * core/src/lib/Types.ts:1680 ___ @@ -68,4 +68,4 @@ get an [IHeapEdge](core_src.IHeapEdge.md) element at the specified index at the specified index, otherwise it returns `null`. * **Source**: - * core/src/lib/Types.ts:1539 + * core/src/lib/Types.ts:1672 diff --git a/website/docs/api/interfaces/core_src.IHeapLocation.md b/website/docs/api/interfaces/core_src.IHeapLocation.md index 6173c237f..ad42ebd83 100644 --- a/website/docs/api/interfaces/core_src.IHeapLocation.md +++ b/website/docs/api/interfaces/core_src.IHeapLocation.md @@ -43,7 +43,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; get the column number * **Source**: - * core/src/lib/Types.ts:1400 + * core/src/lib/Types.ts:1533 ___ @@ -52,7 +52,7 @@ ___ get the line number * **Source**: - * core/src/lib/Types.ts:1396 + * core/src/lib/Types.ts:1529 ___ @@ -61,7 +61,7 @@ ___ get the heap object this location this location represents * **Source**: - * core/src/lib/Types.ts:1388 + * core/src/lib/Types.ts:1521 ___ @@ -70,7 +70,7 @@ ___ get the script ID of the source file * **Source**: - * core/src/lib/Types.ts:1392 + * core/src/lib/Types.ts:1525 ___ @@ -79,7 +79,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this location instance * **Source**: - * core/src/lib/Types.ts:1384 + * core/src/lib/Types.ts:1517 ## Methods @@ -97,4 +97,4 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1410 + * core/src/lib/Types.ts:1543 diff --git a/website/docs/api/interfaces/core_src.IHeapNode.md b/website/docs/api/interfaces/core_src.IHeapNode.md index e41d194d5..810d4124c 100644 --- a/website/docs/api/interfaces/core_src.IHeapNode.md +++ b/website/docs/api/interfaces/core_src.IHeapNode.md @@ -52,7 +52,7 @@ For more information on what a dominator node is, please check out [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators). * **Source**: - * core/src/lib/Types.ts:1688 + * core/src/lib/Types.ts:1821 ___ @@ -62,7 +62,7 @@ The total number of outgoing JS references (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1642 + * core/src/lib/Types.ts:1775 ___ @@ -72,7 +72,7 @@ returns true if the heap node has been set an incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1664 + * core/src/lib/Types.ts:1797 ___ @@ -81,7 +81,7 @@ ___ unique id of the heap object * **Source**: - * core/src/lib/Types.ts:1569 + * core/src/lib/Types.ts:1702 ___ @@ -91,7 +91,7 @@ check if this a string node (normal string node, concatenated string node or sliced string node) * **Source**: - * core/src/lib/Types.ts:1700 + * core/src/lib/Types.ts:1833 ___ @@ -104,7 +104,7 @@ from the React Fiber tree, `is_detached` will be `true`; otherwise it will be `false` * **Source**: - * core/src/lib/Types.ts:1624 + * core/src/lib/Types.ts:1757 ___ @@ -114,7 +114,7 @@ source location information of this heap object (if it is recorded by the heap snapshot). * **Source**: - * core/src/lib/Types.ts:1693 + * core/src/lib/Types.ts:1826 ___ @@ -125,7 +125,7 @@ for JS object instances (type `object`), `name` is the constructor's name of the object instance. for `string`, `name` is the string value. * **Source**: - * core/src/lib/Types.ts:1565 + * core/src/lib/Types.ts:1698 ___ @@ -134,7 +134,7 @@ ___ index of this heap object inside the `node.snapshot.nodes` pseudo array * **Source**: - * core/src/lib/Types.ts:1673 + * core/src/lib/Types.ts:1806 ___ @@ -144,7 +144,7 @@ Get the number of all incoming references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1659 + * core/src/lib/Types.ts:1792 ___ @@ -154,7 +154,7 @@ The incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1669 + * core/src/lib/Types.ts:1802 ___ @@ -164,7 +164,7 @@ Get a JS array containing all outgoing JS references from this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1649 + * core/src/lib/Types.ts:1782 ___ @@ -174,7 +174,7 @@ Get a JS array containing all incoming JS references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1654 + * core/src/lib/Types.ts:1787 ___ @@ -186,7 +186,7 @@ could be released if this object is released). For difference between [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1680 + * core/src/lib/Types.ts:1813 ___ @@ -198,7 +198,7 @@ by the object itself.). For difference between **shallow size** and [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1637 + * core/src/lib/Types.ts:1770 ___ @@ -207,7 +207,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this heap object * **Source**: - * core/src/lib/Types.ts:1616 + * core/src/lib/Types.ts:1749 ___ @@ -220,7 +220,7 @@ This is engine-specific, for example all types in V8: `symbol`, `bigint` * **Source**: - * core/src/lib/Types.ts:1559 + * core/src/lib/Types.ts:1692 ## Methods @@ -244,7 +244,7 @@ const reference = node.findAnyReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1769 + * core/src/lib/Types.ts:1902 ___ @@ -268,7 +268,7 @@ const referrer = node.findAnyReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1786 + * core/src/lib/Types.ts:1919 ___ @@ -293,7 +293,7 @@ const referrer = node.findAnyReferrerNode((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:1804 + * core/src/lib/Types.ts:1937 ___ @@ -318,7 +318,7 @@ const referrerNodes = node.findReferrerNodes((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:1839 + * core/src/lib/Types.ts:1972 ___ @@ -342,7 +342,7 @@ const referrers = node.findReferrers((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1821 + * core/src/lib/Types.ts:1954 ___ @@ -367,7 +367,7 @@ node.forEachReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1734 + * core/src/lib/Types.ts:1867 ___ @@ -392,7 +392,7 @@ node.forEachReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1752 + * core/src/lib/Types.ts:1885 ___ @@ -413,7 +413,7 @@ const reference = node.getAnyReferrer('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:1894 + * core/src/lib/Types.ts:2027 ___ @@ -439,7 +439,7 @@ const n2 = node.getAnyReferrer('ref', 'property')?.fromNode; ``` * **Source**: - * core/src/lib/Types.ts:1917 + * core/src/lib/Types.ts:2050 ___ @@ -460,7 +460,7 @@ const reference = node.getReference('map', 'hidden'); ``` * **Source**: - * core/src/lib/Types.ts:1854 + * core/src/lib/Types.ts:1987 ___ @@ -485,7 +485,7 @@ const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode; ``` * **Source**: - * core/src/lib/Types.ts:1876 + * core/src/lib/Types.ts:2009 ___ @@ -512,7 +512,7 @@ const nodes2 = node.getReferrers('ref', 'property') ``` * **Source**: - * core/src/lib/Types.ts:1957 + * core/src/lib/Types.ts:2090 ___ @@ -534,7 +534,7 @@ const referrers = node.getReferrers('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:1936 + * core/src/lib/Types.ts:2069 ___ @@ -552,7 +552,7 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1716 + * core/src/lib/Types.ts:1849 ___ @@ -564,4 +564,4 @@ inside the string node. * **Returns**: [`Nullable`](../modules/core_src.md#nullable)<[`IHeapStringNode`](core_src.IHeapStringNode.md)\> * **Source**: - * core/src/lib/Types.ts:1706 + * core/src/lib/Types.ts:1839 diff --git a/website/docs/api/interfaces/core_src.IHeapNodes.md b/website/docs/api/interfaces/core_src.IHeapNodes.md index 66ff8d586..74a7d538d 100644 --- a/website/docs/api/interfaces/core_src.IHeapNodes.md +++ b/website/docs/api/interfaces/core_src.IHeapNodes.md @@ -41,7 +41,7 @@ The total number of nodes in heap graph (or JS objects in heap snapshot). * **Source**: - * core/src/lib/Types.ts:2035 + * core/src/lib/Types.ts:2168 ## Methods @@ -54,7 +54,7 @@ to each element in ascending order of element index. * `callback`: (`node`: [`IHeapNode`](core_src.IHeapNode.md), `index`: `number`) => `boolean` \| `void` | the callback does not need to return any value, if the callback returns `false` when iterating on element at index `i`, then all elements after `i` won't be iterated. * **Returns**: `void` * **Source**: - * core/src/lib/Types.ts:2051 + * core/src/lib/Types.ts:2184 ___ @@ -68,4 +68,4 @@ get an [IHeapNode](core_src.IHeapNode.md) element at the specified index at the specified index, otherwise it returns `null`. * **Source**: - * core/src/lib/Types.ts:2043 + * core/src/lib/Types.ts:2176 diff --git a/website/docs/api/interfaces/core_src.IHeapSnapshot.md b/website/docs/api/interfaces/core_src.IHeapSnapshot.md index d300bd0de..f99f4b642 100644 --- a/website/docs/api/interfaces/core_src.IHeapSnapshot.md +++ b/website/docs/api/interfaces/core_src.IHeapSnapshot.md @@ -39,7 +39,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1138 + * core/src/lib/Types.ts:1271 ___ @@ -70,7 +70,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1112 + * core/src/lib/Types.ts:1245 ## Methods @@ -105,7 +105,7 @@ class TestObject { ``` * **Source**: - * core/src/lib/Types.ts:1279 + * core/src/lib/Types.ts:1412 ___ @@ -134,7 +134,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1160 + * core/src/lib/Types.ts:1293 ___ @@ -166,7 +166,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1212 + * core/src/lib/Types.ts:1345 ___ @@ -198,7 +198,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1186 + * core/src/lib/Types.ts:1319 ___ @@ -244,7 +244,7 @@ test('memory test with heap assertion', async () => { ``` * **Source**: - * core/src/lib/Types.ts:1251 + * core/src/lib/Types.ts:1384 ___ @@ -276,7 +276,7 @@ import {getFullHeapFromFile} from '@memlab/heap-analysis'; ``` * **Source**: - * core/src/lib/Types.ts:1305 + * core/src/lib/Types.ts:1438 ___ @@ -320,4 +320,4 @@ test('memory test', async () => { ``` * **Source**: - * core/src/lib/Types.ts:1343 + * core/src/lib/Types.ts:1476 diff --git a/website/docs/api/interfaces/core_src.IHeapStringNode.md b/website/docs/api/interfaces/core_src.IHeapStringNode.md index 6da8b4f21..59a0706df 100644 --- a/website/docs/api/interfaces/core_src.IHeapStringNode.md +++ b/website/docs/api/interfaces/core_src.IHeapStringNode.md @@ -51,7 +51,7 @@ For more information on what a dominator node is, please check out [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators). * **Source**: - * core/src/lib/Types.ts:1688 + * core/src/lib/Types.ts:1821 ___ @@ -61,7 +61,7 @@ The total number of outgoing JS references (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1642 + * core/src/lib/Types.ts:1775 ___ @@ -71,7 +71,7 @@ returns true if the heap node has been set an incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1664 + * core/src/lib/Types.ts:1797 ___ @@ -80,7 +80,7 @@ ___ unique id of the heap object * **Source**: - * core/src/lib/Types.ts:1569 + * core/src/lib/Types.ts:1702 ___ @@ -90,7 +90,7 @@ check if this a string node (normal string node, concatenated string node or sliced string node) * **Source**: - * core/src/lib/Types.ts:1700 + * core/src/lib/Types.ts:1833 ___ @@ -103,7 +103,7 @@ from the React Fiber tree, `is_detached` will be `true`; otherwise it will be `false` * **Source**: - * core/src/lib/Types.ts:1624 + * core/src/lib/Types.ts:1757 ___ @@ -113,7 +113,7 @@ source location information of this heap object (if it is recorded by the heap snapshot). * **Source**: - * core/src/lib/Types.ts:1693 + * core/src/lib/Types.ts:1826 ___ @@ -124,7 +124,7 @@ for JS object instances (type `object`), `name` is the constructor's name of the object instance. for `string`, `name` is the string value. * **Source**: - * core/src/lib/Types.ts:1565 + * core/src/lib/Types.ts:1698 ___ @@ -133,7 +133,7 @@ ___ index of this heap object inside the `node.snapshot.nodes` pseudo array * **Source**: - * core/src/lib/Types.ts:1673 + * core/src/lib/Types.ts:1806 ___ @@ -143,7 +143,7 @@ Get the number of all incoming references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1659 + * core/src/lib/Types.ts:1792 ___ @@ -153,7 +153,7 @@ The incoming edge which leads to the parent node on the shortest path to GC root. * **Source**: - * core/src/lib/Types.ts:1669 + * core/src/lib/Types.ts:1802 ___ @@ -163,7 +163,7 @@ Get a JS array containing all outgoing JS references from this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1649 + * core/src/lib/Types.ts:1782 ___ @@ -173,7 +173,7 @@ Get a JS array containing all incoming JS references pointing to this heap object (including engine-internal, native, and JS references). * **Source**: - * core/src/lib/Types.ts:1654 + * core/src/lib/Types.ts:1787 ___ @@ -185,7 +185,7 @@ could be released if this object is released). For difference between [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1680 + * core/src/lib/Types.ts:1813 ___ @@ -197,7 +197,7 @@ by the object itself.). For difference between **shallow size** and [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes). * **Source**: - * core/src/lib/Types.ts:1637 + * core/src/lib/Types.ts:1770 ___ @@ -206,7 +206,7 @@ ___ get the [IHeapSnapshot](core_src.IHeapSnapshot.md) containing this heap object * **Source**: - * core/src/lib/Types.ts:1616 + * core/src/lib/Types.ts:1749 ___ @@ -216,7 +216,7 @@ get the string value of the JS string heap object associated with this `IHeapStringNode` instance in heap * **Source**: - * core/src/lib/Types.ts:1998 + * core/src/lib/Types.ts:2131 ___ @@ -229,7 +229,7 @@ This is engine-specific, for example all types in V8: `symbol`, `bigint` * **Source**: - * core/src/lib/Types.ts:1559 + * core/src/lib/Types.ts:1692 ## Methods @@ -253,7 +253,7 @@ const reference = node.findAnyReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1769 + * core/src/lib/Types.ts:1902 ___ @@ -277,7 +277,7 @@ const referrer = node.findAnyReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1786 + * core/src/lib/Types.ts:1919 ___ @@ -302,7 +302,7 @@ const referrer = node.findAnyReferrerNode((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:1804 + * core/src/lib/Types.ts:1937 ___ @@ -327,7 +327,7 @@ const referrerNodes = node.findReferrerNodes((node: IHeapNode) => { ``` * **Source**: - * core/src/lib/Types.ts:1839 + * core/src/lib/Types.ts:1972 ___ @@ -351,7 +351,7 @@ const referrers = node.findReferrers((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1821 + * core/src/lib/Types.ts:1954 ___ @@ -376,7 +376,7 @@ node.forEachReference((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1734 + * core/src/lib/Types.ts:1867 ___ @@ -401,7 +401,7 @@ node.forEachReferrer((edge: IHeapEdge) => { ``` * **Source**: - * core/src/lib/Types.ts:1752 + * core/src/lib/Types.ts:1885 ___ @@ -422,7 +422,7 @@ const reference = node.getAnyReferrer('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:1894 + * core/src/lib/Types.ts:2027 ___ @@ -448,7 +448,7 @@ const n2 = node.getAnyReferrer('ref', 'property')?.fromNode; ``` * **Source**: - * core/src/lib/Types.ts:1917 + * core/src/lib/Types.ts:2050 ___ @@ -469,7 +469,7 @@ const reference = node.getReference('map', 'hidden'); ``` * **Source**: - * core/src/lib/Types.ts:1854 + * core/src/lib/Types.ts:1987 ___ @@ -494,7 +494,7 @@ const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode; ``` * **Source**: - * core/src/lib/Types.ts:1876 + * core/src/lib/Types.ts:2009 ___ @@ -521,7 +521,7 @@ const nodes2 = node.getReferrers('ref', 'property') ``` * **Source**: - * core/src/lib/Types.ts:1957 + * core/src/lib/Types.ts:2090 ___ @@ -543,7 +543,7 @@ const referrers = node.getReferrers('ref', 'property'); ``` * **Source**: - * core/src/lib/Types.ts:1936 + * core/src/lib/Types.ts:2069 ___ @@ -561,7 +561,7 @@ captured by the hosting object. * `...args`: `any`[] * **Returns**: `string` * **Source**: - * core/src/lib/Types.ts:1716 + * core/src/lib/Types.ts:1849 ___ @@ -573,4 +573,4 @@ inside the string node. * **Returns**: [`Nullable`](../modules/core_src.md#nullable)<[`IHeapStringNode`](core_src.IHeapStringNode.md)\> * **Source**: - * core/src/lib/Types.ts:1706 + * core/src/lib/Types.ts:1839 diff --git a/website/docs/api/interfaces/core_src.ILeakFilter.md b/website/docs/api/interfaces/core_src.ILeakFilter.md index 019934926..c4a964ed2 100644 --- a/website/docs/api/interfaces/core_src.ILeakFilter.md +++ b/website/docs/api/interfaces/core_src.ILeakFilter.md @@ -80,7 +80,7 @@ preprocessings. in browser. * **Examples**: -```typescript +```javascript module.exports = { beforeLeakFilter: (snapshot, leakedNodeIds) { // initialize some data stores @@ -96,7 +96,7 @@ module.exports = { ___ -### **leakFilter**: [`LeakFilterCallback`](../modules/core_src.md#leakfiltercallback) +### `Optional` **leakFilter**: [`LeakFilterCallback`](../modules/core_src.md#leakfiltercallback) This callback defines how you want to filter out the leaked objects. The callback is called for every node (JS heap @@ -124,6 +124,7 @@ memory leaks. * **Returns**: the boolean value indicating whether the given node in the snapshot should be considered as leaked. +* **Examples**: ```javascript // save as leak-filter.js module.exports = { @@ -146,3 +147,62 @@ memlab run --scenario --leak-filter * **Source**: * core/src/lib/Types.ts:453 + +___ + +### `Optional` **retainerReferenceFilter**: [`ReferenceFilterCallback`](../modules/core_src.md#referencefiltercallback) + +Callback that can be used to define a logic to decide whether +a reference should be considered as part of the retainer trace. +The callback is called for every reference (edge) in the heap snapshot. + +For concrete examples, check out [leakFilter](core_src.ILeakFilter.md#leakfilter). + +* **Parameters**: + * edge - the reference (edge) that is considered + for calcualting the retainer trace + * snapshot - the snapshot of target interaction + * isReferenceUsedByDefault - MemLab has its own default logic for + whether a reference should be considered as part of the retainer trace, + if this parameter is true, it means MemLab will consider this reference + when calculating the retainer trace. + +* **Returns**: the value indicating whether the given reference should be +considered when calculating the retainer trace. Note that when this +callback returns true, the reference will only be considered as a candidate +for retainer trace, so it may or may not be included in the retainer trace; +however, if this callback returns false, the reference will be excluded. + +Note that by excluding a dominator reference of an object (i.e., an edge +that must be traveled through to reach the heap object from GC roots), +the object will be considered as unreachable in the heap graph; and +therefore, the reference and heap object will not be included in the +retainer trace detection and retainer size calculation. + +* **Examples**: +```javascript +// save as leak-filter.js +module.exports = { + retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + // exclude react fiber references + if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + return false; + } + // exclude other references here + // ... + return true; + } +}; +``` + +Use the leak filter definition in command line interface: +```bash +memlab find-leaks --leak-filter +``` + +```bash +memlab run --scenario --leak-filter +``` + + * **Source**: + * core/src/lib/Types.ts:507 diff --git a/website/docs/api/interfaces/core_src.IScenario.md b/website/docs/api/interfaces/core_src.IScenario.md index fb8b52530..e4957ad72 100644 --- a/website/docs/api/interfaces/core_src.IScenario.md +++ b/website/docs/api/interfaces/core_src.IScenario.md @@ -89,7 +89,7 @@ module.exports = scenario; ``` * **Source**: - * core/src/lib/Types.ts:705 + * core/src/lib/Types.ts:795 ___ @@ -119,7 +119,7 @@ Check out [this page](/docs/how-memlab-works) on why memlab needs to undo/revert the `action` callback. * **Source**: - * core/src/lib/Types.ts:730 + * core/src/lib/Types.ts:820 ___ @@ -153,7 +153,7 @@ module.exports = scenario; ``` * **Source**: - * core/src/lib/Types.ts:611 + * core/src/lib/Types.ts:701 ___ @@ -185,7 +185,7 @@ module.exports = { ``` * **Source**: - * core/src/lib/Types.ts:808 + * core/src/lib/Types.ts:898 ___ @@ -226,7 +226,7 @@ module.exports = { ``` * **Source**: - * core/src/lib/Types.ts:781 + * core/src/lib/Types.ts:871 ___ @@ -273,7 +273,55 @@ module.exports = { ``` * **Source**: - * core/src/lib/Types.ts:850 + * core/src/lib/Types.ts:940 + +___ + +### `Optional` **retainerReferenceFilter**: [`ReferenceFilterCallback`](../modules/core_src.md#referencefiltercallback) + +Callback that can be used to define a logic to decide whether +a reference should be considered as part of the retainer trace. +The callback is called for every reference (edge) in the heap snapshot. + +For concrete examples, check out [leakFilter](core_src.IScenario.md#leakfilter). + +* **Parameters**: + * edge - the reference (edge) that is considered + for calcualting the retainer trace + * snapshot - the snapshot of target interaction + * isReferenceUsedByDefault - MemLab has its own default logic for + whether a reference should be considered as part of the retainer trace, + if this parameter is true, it means MemLab will consider this reference + when calculating the retainer trace. + +* **Returns**: the value indicating whether the given reference should be +considered when calculating the retainer trace. Note that when this +callback returns true, the reference will only be considered as a candidate +for retainer trace, so it may or may not be included in the retainer trace; +however, if this callback returns false, the reference will be excluded. + +Note that by excluding a dominator reference of an object (i.e., an edge +that must be traveled through to reach the heap object from GC roots), +the object will be considered as unreachable in the heap graph; and +therefore, the reference and heap object will not be included in the +retainer trace detection and retainer size calculation. + +* **Examples**: +```javascript +// save as leak-filter.js +module.exports = { + retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + // exclude react fiber references + if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + return false; + } + return true; + } +}; +``` + + * **Source**: + * core/src/lib/Types.ts:983 ___ @@ -308,7 +356,7 @@ module.exports = scenario; ``` * **Source**: - * core/src/lib/Types.ts:659 + * core/src/lib/Types.ts:749 ## Methods @@ -343,7 +391,7 @@ module.exports = scenario; ``` * **Source**: - * core/src/lib/Types.ts:582 + * core/src/lib/Types.ts:672 ___ @@ -364,7 +412,7 @@ module.exports = { * **Returns**: `number` * **Source**: - * core/src/lib/Types.ts:745 + * core/src/lib/Types.ts:835 ___ @@ -387,4 +435,4 @@ load. All objects allocated by the initial page load will be candidates for memory leak filtering. * **Source**: - * core/src/lib/Types.ts:629 + * core/src/lib/Types.ts:719 diff --git a/website/docs/api/modules/core_src.md b/website/docs/api/modules/core_src.md index 39478da59..f9582d4ef 100644 --- a/website/docs/api/modules/core_src.md +++ b/website/docs/api/modules/core_src.md @@ -34,7 +34,7 @@ this callback until it returns `true`. This is an async callback, you can also `await` and returns `true` until some async logic is resolved. * **Source**: - * core/src/lib/Types.ts:972 + * core/src/lib/Types.ts:1105 ___ @@ -86,7 +86,7 @@ or [forEachReferrer](../interfaces/core_src.IHeapNode.md#foreachreferrer). * **Returns**: [`Optional`](core_src.md#optional)<{ `stop`: `boolean` }\> \| `void` | this API returns void * **Source**: - * core/src/lib/Types.ts:1579 + * core/src/lib/Types.ts:1712 ___ @@ -102,7 +102,7 @@ For concrete example, check out [beforeLeakFilter](../interfaces/core_src.ILeakF * **Returns**: `void` * **Source**: - * core/src/lib/Types.ts:464 + * core/src/lib/Types.ts:518 ___ @@ -118,7 +118,7 @@ For concrete examples, check out [action](../interfaces/core_src.IScenario.md#ac * **Returns**: `Promise`<`void`\> | no return value * **Source**: - * core/src/lib/Types.ts:507 + * core/src/lib/Types.ts:597 ___ @@ -146,7 +146,7 @@ function leakFilter(node, _snapshot, _leakedNodeIds) { ``` * **Source**: - * core/src/lib/Types.ts:491 + * core/src/lib/Types.ts:545 ___ @@ -259,6 +259,39 @@ and [findReferrers](../interfaces/core_src.IHeapNode.md#findreferrers). ___ +### **ReferenceFilterCallback**: (`edge`: [`IHeapEdge`](../interfaces/core_src.IHeapEdge.md), `snapshot`: [`IHeapSnapshot`](../interfaces/core_src.IHeapSnapshot.md), `isReferenceUsedByDefault`: `boolean`) => `boolean` + +Callback that can be used to define a logic to decide whether +a reference should be filtered (included) for some +calculations (e.g., retainer trace calculation) + +For concrete examples, check out [leakFilter](../interfaces/core_src.ILeakFilter.md#leakfilter). + + * **Parameters**: + * `edge`: [`IHeapEdge`](../interfaces/core_src.IHeapEdge.md) | the reference (edge) that is considered for calcualting the retainer trace + * `snapshot`: [`IHeapSnapshot`](../interfaces/core_src.IHeapSnapshot.md) | the snapshot of target interaction + * `isReferenceUsedByDefault`: `boolean` | MemLab has its own default logic for whether a reference should be filtered (included), if this parameter is true, it means MemLab will consider this reference for inclusion + * **Returns**: `boolean` | the value indicating whether the given reference should be +filtered (i.e., included) + +* **Examples**: +```javascript +// exclude react fiber references +function retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { + if (edge.name_or_index.toString().startsWith('__reactFiber$')) { + return false; + } + // exclude other references here + // ... + return true; +}; +``` + + * **Source**: + * core/src/lib/Types.ts:581 + +___ + ### **RunMetaInfo**: `Object` This data structure holds the information about memlab run. @@ -270,7 +303,7 @@ You can retrieve the instance of this type through [getRunMetaInfo](../classes/a | `type` | `string` | type of the memlab run | * **Source**: - * core/src/lib/Types.ts:1053 + * core/src/lib/Types.ts:1186 ___