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 ___