@memlab/core 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/lib/Config.d.ts +6 -2
- package/dist/lib/Config.js +13 -2
- package/dist/lib/FileManager.d.ts +2 -0
- package/dist/lib/FileManager.js +1 -0
- package/dist/lib/HeapAnalyzer.d.ts +2 -2
- package/dist/lib/HeapAnalyzer.js +27 -60
- package/dist/lib/InternalValueSetter.d.ts +2 -0
- package/dist/lib/InternalValueSetter.js +2 -0
- package/dist/lib/Types.d.ts +363 -6
- package/dist/lib/Types.js +1 -0
- package/dist/lib/Utils.js +24 -1
- package/dist/lib/leak-filters/BaseLeakFilter.rule.d.ts +24 -0
- package/dist/lib/leak-filters/BaseLeakFilter.rule.js +22 -0
- package/dist/lib/leak-filters/LeakFilterRuleList.d.ts +13 -0
- package/dist/lib/leak-filters/LeakFilterRuleList.js +33 -0
- package/dist/lib/leak-filters/LeakObjectFilter.d.ts +19 -0
- package/dist/lib/leak-filters/LeakObjectFilter.js +36 -0
- package/dist/lib/leak-filters/rules/FilterByExternalFilter.rule.d.ts +19 -0
- package/dist/lib/leak-filters/rules/FilterByExternalFilter.rule.js +27 -0
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.d.ts +20 -0
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.js +40 -0
- package/dist/lib/leak-filters/rules/FilterHermesNode.rule.d.ts +16 -0
- package/dist/lib/leak-filters/rules/FilterHermesNode.rule.js +27 -0
- package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.d.ts +19 -0
- package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +27 -0
- package/dist/lib/leak-filters/rules/FilterStackTraceFrame.rule.d.ts +19 -0
- package/dist/lib/leak-filters/rules/FilterStackTraceFrame.rule.js +28 -0
- package/dist/lib/leak-filters/rules/FilterTrivialNode.rule.d.ts +20 -0
- package/dist/lib/leak-filters/rules/FilterTrivialNode.rule.js +33 -0
- package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.d.ts +20 -0
- package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.js +37 -0
- package/dist/trace-cluster/TraceBucket.d.ts +1 -0
- package/dist/trace-cluster/TraceBucket.js +12 -1
- package/package.json +1 -1
- package/dist/__tests__/parser/HeapParser.test.d.ts.map +0 -1
- package/dist/__tests__/parser/NodeHeap.test.d.ts.map +0 -1
- package/dist/__tests__/parser/StringNode.test.d.ts.map +0 -1
- package/dist/__tests__/parser/traverse/HeapNodeTraverse.test.d.ts.map +0 -1
- package/dist/__tests__/utils/utils.test.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/BaseOption.d.ts.map +0 -1
- package/dist/lib/BrowserInfo.d.ts.map +0 -1
- package/dist/lib/Config.d.ts.map +0 -1
- package/dist/lib/Console.d.ts.map +0 -1
- package/dist/lib/Constant.d.ts.map +0 -1
- package/dist/lib/FileManager.d.ts.map +0 -1
- package/dist/lib/HeapAnalyzer.d.ts.map +0 -1
- package/dist/lib/HeapParser.d.ts.map +0 -1
- package/dist/lib/InternalValueSetter.d.ts.map +0 -1
- package/dist/lib/NodeHeap.d.ts.map +0 -1
- package/dist/lib/ProcessManager.d.ts.map +0 -1
- package/dist/lib/Serializer.d.ts.map +0 -1
- package/dist/lib/StringLoader.d.ts.map +0 -1
- package/dist/lib/Types.d.ts.map +0 -1
- package/dist/lib/Utils.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapEdge.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapLocation.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapNode.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapSnapshot.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapStringNode.d.ts.map +0 -1
- package/dist/lib/heap-data/HeapUtils.d.ts.map +0 -1
- package/dist/logger/LeakClusterLogger.d.ts.map +0 -1
- package/dist/logger/LeakTraceDetailsLogger.d.ts.map +0 -1
- package/dist/modes/BaseMode.d.ts.map +0 -1
- package/dist/modes/InteractionTestMode.d.ts.map +0 -1
- package/dist/modes/MeasureMode.d.ts.map +0 -1
- package/dist/modes/RunningModes.d.ts.map +0 -1
- package/dist/paths/TraceFinder.d.ts.map +0 -1
- package/dist/trace-cluster/ClusterUtils.d.ts.map +0 -1
- package/dist/trace-cluster/ClusterUtilsHelper.d.ts.map +0 -1
- package/dist/trace-cluster/ClusteringHeuristics.d.ts.map +0 -1
- package/dist/trace-cluster/EvalutationMetric.d.ts.map +0 -1
- package/dist/trace-cluster/TraceBucket.d.ts.map +0 -1
- package/dist/trace-cluster/TraceElement.d.ts.map +0 -1
- package/dist/trace-cluster/strategies/TraceAsClusterStrategy.d.ts.map +0 -1
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.d.ts.map +0 -1
package/dist/lib/Types.d.ts
CHANGED
|
@@ -10,20 +10,32 @@
|
|
|
10
10
|
import { ParsedArgs } from 'minimist';
|
|
11
11
|
import type { LaunchOptions, Page } from 'puppeteer';
|
|
12
12
|
import type { ErrorHandling, MemLabConfig } from './Config';
|
|
13
|
+
/** @internal */
|
|
13
14
|
export declare type AnyValue = any;
|
|
15
|
+
/** @internal */
|
|
14
16
|
export declare type RecordValue = string | number | boolean | null | RecordValue[] | {
|
|
15
17
|
[key: string]: RecordValue;
|
|
16
18
|
};
|
|
19
|
+
/** @internal */
|
|
17
20
|
export declare type Nullable<T> = T | null;
|
|
21
|
+
/** @internal */
|
|
18
22
|
export declare type Optional<T> = Nullable<T> | undefined;
|
|
23
|
+
/** @internal */
|
|
19
24
|
export declare type AnyRecord = Record<string, RecordValue>;
|
|
25
|
+
/** @internal */
|
|
20
26
|
export declare type AnyAyncFunction = (...args: AnyValue[]) => Promise<AnyValue>;
|
|
27
|
+
/** @internal */
|
|
21
28
|
export declare type AnyFunction = (...args: AnyValue[]) => AnyValue;
|
|
29
|
+
/** @internal */
|
|
22
30
|
export declare type AnyOptions = Record<string, unknown>;
|
|
31
|
+
/** @internal */
|
|
23
32
|
export declare type UnusedOptions = Record<string, never>;
|
|
33
|
+
/** @internal */
|
|
24
34
|
export declare type Command = [string, string[], AnyOptions];
|
|
25
35
|
export declare type Predicator<T> = (node: T) => boolean;
|
|
26
|
-
|
|
36
|
+
/** @internal */
|
|
37
|
+
export declare type HeapNodeIdSet = Set<number>;
|
|
38
|
+
/** @internal */
|
|
27
39
|
export declare type HaltOrThrowOptions = {
|
|
28
40
|
printErrorBeforeHalting?: boolean;
|
|
29
41
|
errorHandling?: ErrorHandling;
|
|
@@ -31,10 +43,12 @@ export declare type HaltOrThrowOptions = {
|
|
|
31
43
|
secondaryMessageToPrint?: string;
|
|
32
44
|
printCallback?: () => void;
|
|
33
45
|
};
|
|
46
|
+
/** @internal */
|
|
34
47
|
export declare type CLIOptions = {
|
|
35
48
|
cliArgs: ParsedArgs;
|
|
36
49
|
configFromOptions?: AnyRecord;
|
|
37
50
|
};
|
|
51
|
+
/** @internal */
|
|
38
52
|
export declare type XvfbType = {
|
|
39
53
|
start: (callback: (error: Error) => AnyValue | null) => void;
|
|
40
54
|
stop: (callback: (error: Error) => AnyValue | null) => void;
|
|
@@ -42,6 +56,7 @@ export declare type XvfbType = {
|
|
|
42
56
|
stopSync: () => void;
|
|
43
57
|
display: () => string;
|
|
44
58
|
};
|
|
59
|
+
/** @internal */
|
|
45
60
|
export declare type CLIArgs = {
|
|
46
61
|
verbose: boolean;
|
|
47
62
|
app: string;
|
|
@@ -71,11 +86,11 @@ export declare type CLIArgs = {
|
|
|
71
86
|
'local-puppeteer': boolean;
|
|
72
87
|
'snapshot-dir': string;
|
|
73
88
|
};
|
|
74
|
-
declare type
|
|
89
|
+
export declare type Cookies = Array<{
|
|
75
90
|
name: string;
|
|
76
91
|
value: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
92
|
+
}>;
|
|
93
|
+
/** @internal */
|
|
79
94
|
export interface IE2EScenarioSynthesizer {
|
|
80
95
|
getAppName(): string;
|
|
81
96
|
getOrigin(): Nullable<string>;
|
|
@@ -99,9 +114,11 @@ export interface IE2EScenarioSynthesizer {
|
|
|
99
114
|
getExtraOperationsForStep(_stepInfo: E2EStepInfo): E2EOperation[];
|
|
100
115
|
synthesis(baseline: string, target: string, intermediates: string[], options: E2ESynthesizerOptions): IE2EScenarioVisitPlan;
|
|
101
116
|
}
|
|
117
|
+
/** @internal */
|
|
102
118
|
export interface E2EScenarioSynthesizerConstructor {
|
|
103
119
|
new (config: Config): IE2EScenarioSynthesizer;
|
|
104
120
|
}
|
|
121
|
+
/** @internal */
|
|
105
122
|
export interface IRunningMode {
|
|
106
123
|
setConfig(config: Config): void;
|
|
107
124
|
beforeRunning(visitPlan: IE2EScenarioVisitPlan): void;
|
|
@@ -117,37 +134,337 @@ export interface IRunningMode {
|
|
|
117
134
|
getAdditionalMetrics(page: Page, tabInfo?: E2EStepInfo): Promise<E2EStepInfo['metrics']>;
|
|
118
135
|
postProcessData(visitPlan: IE2EScenarioVisitPlan): void;
|
|
119
136
|
}
|
|
137
|
+
/** @internal */
|
|
120
138
|
export declare type Config = MemLabConfig;
|
|
139
|
+
/** @internal */
|
|
121
140
|
export declare type QuickExperiment = {
|
|
122
141
|
universe: string;
|
|
123
142
|
experiment: string;
|
|
124
143
|
group: string;
|
|
125
144
|
};
|
|
145
|
+
/**
|
|
146
|
+
* The type for defining custom leak-filtering logic.
|
|
147
|
+
* * **Examples**:
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const scenario = {
|
|
150
|
+
*
|
|
151
|
+
* };
|
|
152
|
+
*
|
|
153
|
+
* let map = Object.create(null);
|
|
154
|
+
* const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
|
|
155
|
+
* map = initializeMapUsingSnapshot(snapshot);
|
|
156
|
+
* };
|
|
157
|
+
*
|
|
158
|
+
* // duplicated string with size > 1KB as memory leak
|
|
159
|
+
* const leakFilter = (node: IHeapNode): boolean => {
|
|
160
|
+
* if (node.type !== 'string' || node.retainedSize < 1000) {
|
|
161
|
+
* return false;
|
|
162
|
+
* }
|
|
163
|
+
* const str = utils.getStringNodeValue(node);
|
|
164
|
+
* return map[str] > 1;
|
|
165
|
+
* };
|
|
166
|
+
*
|
|
167
|
+
* export default {beforeLeakFilter, leakFilter};
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
126
170
|
export interface ILeakFilter {
|
|
127
171
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
128
172
|
leakFilter: LeakFilterCallback;
|
|
129
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Lifecycle function callback that is invoked initially once before calling any
|
|
176
|
+
* leak filter function.
|
|
177
|
+
*
|
|
178
|
+
* @param snaphost - heap snapshot see {@link IHeapSnapshot}
|
|
179
|
+
* @param leakedNodeIds - the set of leaked object (node) ids.
|
|
180
|
+
*/
|
|
130
181
|
export declare type InitLeakFilterCallback = (snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Callback that can be used to define a logic to filter the
|
|
184
|
+
* leaked objects. The callback is only called for every node
|
|
185
|
+
* allocated but not released from the target interaction
|
|
186
|
+
* in the heap snapshot.
|
|
187
|
+
*
|
|
188
|
+
* @param node - the node that is kept alive in the memory in the heap snapshot
|
|
189
|
+
* @param snapshot - the snapshot of target interaction
|
|
190
|
+
* @param leakedNodeIds - the set of leaked node ids
|
|
191
|
+
*
|
|
192
|
+
* @returns the value indicating whether the given node in the snapshot
|
|
193
|
+
* should be considered as leaked.
|
|
194
|
+
* * **Examples**:
|
|
195
|
+
* ```javascript
|
|
196
|
+
* // any node in the heap snapshot that is greater than 1MB
|
|
197
|
+
* function leakFilter(node, _snapshot, _leakedNodeIds) {
|
|
198
|
+
* return node.retainedSize > 1000000;
|
|
199
|
+
* };
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
131
202
|
export declare type LeakFilterCallback = (node: IHeapNode, snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => boolean;
|
|
203
|
+
/**
|
|
204
|
+
* The callback defines browser interactions which are
|
|
205
|
+
* used by memlab to interact with the web app under test.
|
|
206
|
+
*/
|
|
132
207
|
export declare type InteractionsCallback = (page: Page, args?: OperationArgs) => Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Test scenario specifies how you want a E2E test to interact with a web browser.
|
|
210
|
+
* The test scenario can be saved as a `.js` file and passed to the `memlab
|
|
211
|
+
* run --scenario` command:
|
|
212
|
+
* ```javascript
|
|
213
|
+
* // save as test.js and use in terminal:
|
|
214
|
+
* // $ memlab run --scenario test.js
|
|
215
|
+
*
|
|
216
|
+
* module.exports = {
|
|
217
|
+
* url: () => 'https://www.npmjs.com/',
|
|
218
|
+
* action: async () => ... ,
|
|
219
|
+
* back: async () => ... ,
|
|
220
|
+
* };
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* The test scenario instance can also be passed to the
|
|
224
|
+
* [`run` API](../modules/api_src#run) exported by `@memlab/api`.
|
|
225
|
+
* ```typescript
|
|
226
|
+
* const {run} = require('@memlab/api');
|
|
227
|
+
*
|
|
228
|
+
* (async function () {
|
|
229
|
+
* const scenario = {
|
|
230
|
+
* url: () => 'https://www.facebook.com',
|
|
231
|
+
* action: async () => ... ,
|
|
232
|
+
* back: async () => ... ,
|
|
233
|
+
* };
|
|
234
|
+
* const leaks = await run({scenario});
|
|
235
|
+
* })();
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
133
238
|
export interface IScenario {
|
|
239
|
+
/** @internal */
|
|
134
240
|
name?: () => string;
|
|
241
|
+
/** @internal */
|
|
135
242
|
app?: () => string;
|
|
136
|
-
|
|
243
|
+
/**
|
|
244
|
+
* If the page you are running memlab against requires authentication or
|
|
245
|
+
* specific cookie(s) to be set, you can pass them as
|
|
246
|
+
* a list of <name, value> pairs.
|
|
247
|
+
* @returns cookie list
|
|
248
|
+
* * **Examples**:
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const scenario = {
|
|
251
|
+
* url: () => 'https://www.facebook.com/',
|
|
252
|
+
* cookies: () => [
|
|
253
|
+
* {"name":"cm_j","value":"none"},
|
|
254
|
+
* {"name":"datr","value":"yJvIY..."},
|
|
255
|
+
* {"name":"c_user","value":"8917..."},
|
|
256
|
+
* {"name":"xs","value":"95:9WQ..."},
|
|
257
|
+
* // ...
|
|
258
|
+
* ],
|
|
259
|
+
* };
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
cookies?: () => Cookies;
|
|
263
|
+
/**
|
|
264
|
+
* String value of the initial url of the page.
|
|
265
|
+
*
|
|
266
|
+
* @returns the string value of the initial url
|
|
267
|
+
* * **Examples**:
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const scenario = {
|
|
270
|
+
* url: () => 'https://www.npmjs.com/',
|
|
271
|
+
* };
|
|
272
|
+
* ```
|
|
273
|
+
* If a test scenario only specifies the `url` callback (without the `action`
|
|
274
|
+
* callback), memlab will try to detect memory leaks from the initial page
|
|
275
|
+
* load. All objects allocated by the initial page load will be candidates
|
|
276
|
+
* for memory leak filtering.
|
|
277
|
+
*/
|
|
137
278
|
url: () => string;
|
|
279
|
+
/**
|
|
280
|
+
* `action` is the callback function that defines the interaction
|
|
281
|
+
* where you want to trigger memory leaks after the initial page load.
|
|
282
|
+
* All JS objects in browser allocated by the browser interactions triggered
|
|
283
|
+
* from the `action` callback will be candidates for memory leak filtering.
|
|
284
|
+
*
|
|
285
|
+
* * **Parameters**:
|
|
286
|
+
* * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
|
|
287
|
+
* object, which provides APIs to interact with the web browser
|
|
288
|
+
*
|
|
289
|
+
* * **Examples**:
|
|
290
|
+
* ```typescript
|
|
291
|
+
* const scenario = {
|
|
292
|
+
* url: () => 'https://www.npmjs.com/',
|
|
293
|
+
* action: async (page) => {
|
|
294
|
+
* await page.click('a[href="/link"]');
|
|
295
|
+
* },
|
|
296
|
+
* back: async (page) => {
|
|
297
|
+
* await page.click('a[href="/back"]');
|
|
298
|
+
* },
|
|
299
|
+
* }
|
|
300
|
+
* ```
|
|
301
|
+
* Note: always clean up external puppeteer references to JS objects
|
|
302
|
+
* in the browser context.
|
|
303
|
+
* ```typescript
|
|
304
|
+
* const scenario = {
|
|
305
|
+
* url: () => 'https://www.npmjs.com/',
|
|
306
|
+
* action: async (page) => {
|
|
307
|
+
* const elements = await page.$x("//button[contains(., 'Text in Button')]");
|
|
308
|
+
* const [button] = elements;
|
|
309
|
+
* if (button) {
|
|
310
|
+
* await button.click();
|
|
311
|
+
* }
|
|
312
|
+
* // dispose external references to JS objects in browser context
|
|
313
|
+
* await promise.all(elements.map(e => e.dispose()));
|
|
314
|
+
* },
|
|
315
|
+
* back: async (page) => ... ,
|
|
316
|
+
* }
|
|
317
|
+
```
|
|
318
|
+
*/
|
|
138
319
|
action?: InteractionsCallback;
|
|
320
|
+
/**
|
|
321
|
+
* `back` is the callback function that specifies how memlab should
|
|
322
|
+
* back/revert the `action` callback. Think of it as an undo action.
|
|
323
|
+
*
|
|
324
|
+
* * **Examples**:
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const scenario = {
|
|
327
|
+
* url: () => 'https://www.npmjs.com/',
|
|
328
|
+
* action: async (page) => {
|
|
329
|
+
* await page.click('a[href="/link"]');
|
|
330
|
+
* },
|
|
331
|
+
* back: async (page) => {
|
|
332
|
+
* await page.click('a[href="/back"]');
|
|
333
|
+
* },
|
|
334
|
+
* }
|
|
335
|
+
* ```
|
|
336
|
+
* Check out [this page](/docs/how-memlab-works) on why
|
|
337
|
+
* memlab needs to undo/revert the `action` callback.
|
|
338
|
+
*/
|
|
139
339
|
back?: InteractionsCallback;
|
|
340
|
+
/**
|
|
341
|
+
* Specifies how many **extra** `action` and `back` actions performed by memlab.
|
|
342
|
+
* * **Examples**:
|
|
343
|
+
* ```typescript
|
|
344
|
+
* module.exports = {
|
|
345
|
+
* url: () => ... ,
|
|
346
|
+
* action: async (page) => ... ,
|
|
347
|
+
* back: async (page) => ... ,
|
|
348
|
+
* // browser interaction: two additional [ action -> back ]
|
|
349
|
+
* // init-load -> action -> back -> action -> back -> action -> back
|
|
350
|
+
* repeat: () => 2,
|
|
351
|
+
* };
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
140
354
|
repeat?: () => number;
|
|
355
|
+
/**
|
|
356
|
+
* Optional callback function that checks if the web page is loaded
|
|
357
|
+
* after for initial page loading and subsequent browser interactions.
|
|
358
|
+
*
|
|
359
|
+
* If this callback is not provided, memlab by default
|
|
360
|
+
* considers a navigation to be finished when there are no network
|
|
361
|
+
* connections for at least 500ms.
|
|
362
|
+
*
|
|
363
|
+
* * **Parameters**:
|
|
364
|
+
* * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
|
|
365
|
+
* object, which provides APIs to interact with the web browser
|
|
366
|
+
* * **Returns**: a boolean value, if it returns `true`, memlab will consider
|
|
367
|
+
* the navigation completes, if it returns `false`, memlab will keep calling
|
|
368
|
+
* this callback until it returns `true`. This is an async callback, you can
|
|
369
|
+
* also `await` and returns `true` until some async logic is resolved.
|
|
370
|
+
* * **Examples**:
|
|
371
|
+
* ```typescript
|
|
372
|
+
* module.exports = {
|
|
373
|
+
* url: () => ... ,
|
|
374
|
+
* action: async (page) => ... ,
|
|
375
|
+
* back: async (page) => ... ,
|
|
376
|
+
* isPageLoaded: async (page) => {
|
|
377
|
+
* await page.waitForNavigation({
|
|
378
|
+
* // consider navigation to be finished when there are
|
|
379
|
+
* // no more than 2 network connections for at least 500 ms.
|
|
380
|
+
* waitUntil: 'networkidle2',
|
|
381
|
+
* // Maximum navigation time in milliseconds
|
|
382
|
+
* timeout: 5000,
|
|
383
|
+
* });
|
|
384
|
+
* return true;
|
|
385
|
+
* },
|
|
386
|
+
* };
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
141
389
|
isPageLoaded?: CheckPageLoadCallback;
|
|
390
|
+
/**
|
|
391
|
+
* Lifecycle function callback that is invoked initially once before
|
|
392
|
+
* the subsequent `leakFilter` function calls. This callback could
|
|
393
|
+
* be used to initialize some data stores or to do some one-off
|
|
394
|
+
* preprocessings.
|
|
395
|
+
*
|
|
396
|
+
* * **Parameters**:
|
|
397
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
398
|
+
* all browser interactions are done.
|
|
399
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
400
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
401
|
+
* allocated by the `action` call but not released after the `back` call
|
|
402
|
+
* in browser.
|
|
403
|
+
*
|
|
404
|
+
* * **Examples**:
|
|
405
|
+
* ```typescript
|
|
406
|
+
* module.exports = {
|
|
407
|
+
* url: () => ... ,
|
|
408
|
+
* action: async (page) => ... ,
|
|
409
|
+
* back: async (page) => ... ,
|
|
410
|
+
* beforeLeakFilter: (snapshot, leakedNodeIds) {
|
|
411
|
+
* // initialize some data stores
|
|
412
|
+
* },
|
|
413
|
+
* };
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
142
416
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
417
|
+
/**
|
|
418
|
+
* This callback that defines how you want to filter out the
|
|
419
|
+
* leaked objects. The callback is called for every node (JS heap
|
|
420
|
+
* object in browser) allocated by the `action` callback, but not
|
|
421
|
+
* released after the `back` callback. Those objects could be caches
|
|
422
|
+
* that are retained in memory on purpose, or they are memory leaks.
|
|
423
|
+
*
|
|
424
|
+
* This optional callback allows you to define your own algorithm
|
|
425
|
+
* to cherry pick memory leaks for specific JS program under test.
|
|
426
|
+
*
|
|
427
|
+
* If this optional callback is not defined, memlab will use its
|
|
428
|
+
* built-in leak filter, which considers detached DOM elements
|
|
429
|
+
* and unmounted Fiber nodes (detached from React Fiber tree) as
|
|
430
|
+
* memory leaks.
|
|
431
|
+
*
|
|
432
|
+
* * **Parameters**:
|
|
433
|
+
* * node: `IHeapNode` | one of the heap object allocated but not released.
|
|
434
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
435
|
+
* all browser interactions are done.
|
|
436
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
437
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
438
|
+
* allocated by the `action` call but not released after the `back` call
|
|
439
|
+
* in browser.
|
|
440
|
+
*
|
|
441
|
+
* * **Returns**: the boolean value indicating whether the given node in
|
|
442
|
+
* the snapshot should be considered as leaked.
|
|
443
|
+
*
|
|
444
|
+
* * **Examples**:
|
|
445
|
+
* ```typescript
|
|
446
|
+
* module.exports = {
|
|
447
|
+
* url: () => ... ,
|
|
448
|
+
* action: async (page) => ... ,
|
|
449
|
+
* back: async (page) => ... ,
|
|
450
|
+
* leakFilter(node, snapshot, leakedNodeIds) {
|
|
451
|
+
* // any unreleased node (JS heap object) with 1MB+
|
|
452
|
+
* // retained size is considered a memory leak
|
|
453
|
+
* return node.retainedSize > 1000000;
|
|
454
|
+
* },
|
|
455
|
+
* };
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
143
458
|
leakFilter?: LeakFilterCallback;
|
|
144
459
|
}
|
|
460
|
+
/** @internal */
|
|
145
461
|
export declare type LeakTracePathItem = {
|
|
146
462
|
node?: IHeapNode;
|
|
147
463
|
edge?: IHeapEdge;
|
|
148
464
|
next?: LeakTracePathItem;
|
|
149
465
|
edgeRetainSize?: number;
|
|
150
466
|
};
|
|
467
|
+
/** @internal */
|
|
151
468
|
export declare type TraceCluster = {
|
|
152
469
|
id?: number;
|
|
153
470
|
path: LeakTracePathItem;
|
|
@@ -157,11 +474,13 @@ export declare type TraceCluster = {
|
|
|
157
474
|
leakedNodeIds?: HeapNodeIdSet;
|
|
158
475
|
clusterMetaInfo?: TraceClusterMetaInfo;
|
|
159
476
|
};
|
|
477
|
+
/** @internal */
|
|
160
478
|
export declare type TraceClusterDiff = {
|
|
161
479
|
staleClusters: TraceCluster[];
|
|
162
480
|
clustersToAdd: TraceCluster[];
|
|
163
481
|
allClusters: TraceCluster[][];
|
|
164
482
|
};
|
|
483
|
+
/** @internal */
|
|
165
484
|
export declare type LeakTraceElement = {
|
|
166
485
|
kind: string;
|
|
167
486
|
id?: number;
|
|
@@ -169,12 +488,15 @@ export declare type LeakTraceElement = {
|
|
|
169
488
|
name_or_index?: string | number;
|
|
170
489
|
type: string;
|
|
171
490
|
};
|
|
491
|
+
/** @internal */
|
|
172
492
|
export declare type LeakTrace = LeakTraceElement[];
|
|
493
|
+
/** @internal */
|
|
173
494
|
export declare type TraceDiff = {
|
|
174
495
|
staleClusters: LeakTrace[];
|
|
175
496
|
clustersToAdd: LeakTrace[];
|
|
176
497
|
allClusters: LeakTrace[][];
|
|
177
498
|
};
|
|
499
|
+
/** @internal */
|
|
178
500
|
export declare type TraceClusterMetaInfo = {
|
|
179
501
|
cluster_id: number;
|
|
180
502
|
creation_time: number;
|
|
@@ -189,14 +511,17 @@ export declare type TraceClusterMetaInfo = {
|
|
|
189
511
|
leak_trace_handle?: string;
|
|
190
512
|
meta_data: string;
|
|
191
513
|
};
|
|
514
|
+
/** @internal */
|
|
192
515
|
export interface E2EInteraction {
|
|
193
516
|
kind: string;
|
|
194
517
|
timeout?: number;
|
|
195
518
|
}
|
|
519
|
+
/** @internal */
|
|
196
520
|
export declare type E2EOperation = E2EInteraction & {
|
|
197
521
|
selector: string;
|
|
198
522
|
act(page: Page, opArgs?: OperationArgs): Promise<void>;
|
|
199
523
|
};
|
|
524
|
+
/** @internal */
|
|
200
525
|
export declare type E2ESynthesizerOptions = {
|
|
201
526
|
name?: string;
|
|
202
527
|
type?: string;
|
|
@@ -212,11 +537,17 @@ export declare type E2ESynthesizerOptions = {
|
|
|
212
537
|
gk_enable?: string[];
|
|
213
538
|
gk_disable?: string[];
|
|
214
539
|
};
|
|
540
|
+
/** @internal */
|
|
215
541
|
export interface IDataBuilder {
|
|
216
542
|
className: string;
|
|
217
543
|
state: Record<string, AnyValue>;
|
|
218
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Callback function to provide if the page is loaded.
|
|
547
|
+
* @param page - puppeteer's [Page](https://pptr.dev/api/puppeteer.page/) object.
|
|
548
|
+
*/
|
|
219
549
|
export declare type CheckPageLoadCallback = (page: Page) => Promise<boolean>;
|
|
550
|
+
/** @internal */
|
|
220
551
|
export interface IE2EScenarioVisitPlan {
|
|
221
552
|
name: string;
|
|
222
553
|
appName: string;
|
|
@@ -230,6 +561,7 @@ export interface IE2EScenarioVisitPlan {
|
|
|
230
561
|
dataBuilder: Optional<IDataBuilder>;
|
|
231
562
|
isPageLoaded?: CheckPageLoadCallback;
|
|
232
563
|
}
|
|
564
|
+
/** @internal */
|
|
233
565
|
export declare type OperationArgs = {
|
|
234
566
|
isPageLoaded?: CheckPageLoadCallback;
|
|
235
567
|
showProgress?: boolean;
|
|
@@ -240,6 +572,7 @@ export declare type OperationArgs = {
|
|
|
240
572
|
warmup?: boolean;
|
|
241
573
|
noWaitAfterPageLoad?: boolean;
|
|
242
574
|
};
|
|
575
|
+
/** @internal */
|
|
243
576
|
export interface IE2EStepBasic {
|
|
244
577
|
name: string;
|
|
245
578
|
url: string;
|
|
@@ -252,6 +585,7 @@ export interface IE2EStepBasic {
|
|
|
252
585
|
interactions: E2EOperation | Array<E2EOperation | InteractionsCallback>;
|
|
253
586
|
postInteractions?: E2EOperation | Array<E2EOperation | InteractionsCallback>;
|
|
254
587
|
}
|
|
588
|
+
/** @internal */
|
|
255
589
|
export declare type E2EStepInfo = IE2EStepBasic & {
|
|
256
590
|
snapshot: boolean;
|
|
257
591
|
screenshot: boolean;
|
|
@@ -260,6 +594,7 @@ export declare type E2EStepInfo = IE2EStepBasic & {
|
|
|
260
594
|
delay?: number;
|
|
261
595
|
metrics: Record<string, number>;
|
|
262
596
|
};
|
|
597
|
+
/** @internal */
|
|
263
598
|
export interface IBrowserInfo {
|
|
264
599
|
_browserVersion: string;
|
|
265
600
|
_puppeteerConfig: LaunchOptions;
|
|
@@ -353,16 +688,27 @@ export interface IHeapNodes {
|
|
|
353
688
|
forEach(callback: (node: IHeapNode, index: number) => void | boolean): void;
|
|
354
689
|
forEachTraceable(callback: (node: IHeapNode, index: number) => void | boolean): void;
|
|
355
690
|
}
|
|
691
|
+
/** @internal */
|
|
356
692
|
export declare type HeapNodeFields = string[];
|
|
693
|
+
/** @internal */
|
|
357
694
|
export declare type HeapNodeTypes = string[];
|
|
695
|
+
/** @internal */
|
|
358
696
|
export declare type RawHeapNodeTypes = Array<HeapNodeTypes | string>;
|
|
697
|
+
/** @internal */
|
|
359
698
|
export declare type HeapEdgeFields = string[];
|
|
699
|
+
/** @internal */
|
|
360
700
|
export declare type HeapEdgeTypes = string[] | string;
|
|
701
|
+
/** @internal */
|
|
361
702
|
export declare type RawHeapEdgeTypes = Array<HeapEdgeTypes | string>;
|
|
703
|
+
/** @internal */
|
|
362
704
|
export declare type HeapTraceFunctionInfoFields = string[];
|
|
705
|
+
/** @internal */
|
|
363
706
|
export declare type HeapTraceNodeFields = string[];
|
|
707
|
+
/** @internal */
|
|
364
708
|
export declare type HeapSampleFields = string[];
|
|
709
|
+
/** @internal */
|
|
365
710
|
export declare type HeapLocationFields = string[];
|
|
711
|
+
/** @internal */
|
|
366
712
|
export declare type HeapSnapshotMeta = {
|
|
367
713
|
node_fields: HeapNodeFields;
|
|
368
714
|
node_types: RawHeapNodeTypes;
|
|
@@ -373,12 +719,14 @@ export declare type HeapSnapshotMeta = {
|
|
|
373
719
|
sample_fields: HeapSampleFields;
|
|
374
720
|
location_fields: HeapLocationFields;
|
|
375
721
|
};
|
|
722
|
+
/** @internal */
|
|
376
723
|
export declare type HeapSnapshotInfo = {
|
|
377
724
|
meta: HeapSnapshotMeta;
|
|
378
725
|
node_count: number;
|
|
379
726
|
edge_count: number;
|
|
380
727
|
trace_function_count: number;
|
|
381
728
|
};
|
|
729
|
+
/** @internal */
|
|
382
730
|
export declare type RawHeapSnapshot = {
|
|
383
731
|
snapshot: HeapSnapshotInfo;
|
|
384
732
|
nodes: number[];
|
|
@@ -389,12 +737,15 @@ export declare type RawHeapSnapshot = {
|
|
|
389
737
|
locations: number[];
|
|
390
738
|
strings: string[];
|
|
391
739
|
};
|
|
740
|
+
/** @internal */
|
|
392
741
|
export interface ISerializedInfo {
|
|
393
742
|
[key: string]: string | number | boolean | ISerializedInfo;
|
|
394
743
|
}
|
|
744
|
+
/** @internal */
|
|
395
745
|
export declare type NumericDictionary = {
|
|
396
746
|
[index: number]: number;
|
|
397
747
|
};
|
|
748
|
+
/** @internal */
|
|
398
749
|
export interface IOveralHeapInfo {
|
|
399
750
|
fiberNodeSize: number;
|
|
400
751
|
regularFiberNodeSize: number;
|
|
@@ -402,31 +753,37 @@ export interface IOveralHeapInfo {
|
|
|
402
753
|
alternateFiberNodeSize: number;
|
|
403
754
|
error: number;
|
|
404
755
|
}
|
|
756
|
+
/** @internal */
|
|
405
757
|
export interface IOveralLeakInfo extends Partial<IOveralHeapInfo> {
|
|
406
758
|
leakedSize: number;
|
|
407
759
|
leakedFiberNodeSize: number;
|
|
408
760
|
leakedAlternateFiberNodeSize: number;
|
|
409
761
|
}
|
|
762
|
+
/** @internal */
|
|
410
763
|
export interface IMemoryAnalystOptions {
|
|
411
764
|
snapshotDir?: string;
|
|
412
765
|
minSnapshots?: number;
|
|
413
766
|
}
|
|
767
|
+
/** @internal */
|
|
414
768
|
export interface IMemoryAnalystSnapshotDiff {
|
|
415
769
|
leakedHeapNodeIdSet: HeapNodeIdSet;
|
|
416
770
|
snapshot: IHeapSnapshot;
|
|
417
771
|
listOfLeakedHeapNodeIdSet: Array<HeapNodeIdSet>;
|
|
418
772
|
}
|
|
773
|
+
/** @internal */
|
|
419
774
|
export interface IMemoryAnalystHeapNodeLeakSummary extends Pick<IHeapNode, 'name' | 'type' | 'retainedSize'> {
|
|
420
775
|
count: number;
|
|
421
776
|
}
|
|
777
|
+
/** @internal */
|
|
422
778
|
export interface IMemoryAnalystHeapNodeReferrenceStat {
|
|
423
779
|
numberOfEdgesToNode: number;
|
|
424
780
|
source: IHeapNode;
|
|
425
781
|
edge: IHeapEdge;
|
|
426
782
|
}
|
|
783
|
+
/** @internal */
|
|
427
784
|
export interface IClusterStrategy {
|
|
428
785
|
diffTraces: (newLeakTraces: LeakTrace[], existingLeakTraces: LeakTrace[]) => TraceDiff;
|
|
429
786
|
}
|
|
787
|
+
/** @internal */
|
|
430
788
|
export declare type ErrorWithMessage = Pick<Error, 'message'>;
|
|
431
|
-
export {};
|
|
432
789
|
//# sourceMappingURL=Types.d.ts.map
|
package/dist/lib/Types.js
CHANGED
package/dist/lib/Utils.js
CHANGED
|
@@ -356,7 +356,7 @@ function getNodesIdSet(snapshot) {
|
|
|
356
356
|
return set;
|
|
357
357
|
}
|
|
358
358
|
// given a set of nodes S, return a subset S' where
|
|
359
|
-
// no nodes are
|
|
359
|
+
// no nodes are dominated by nodes in S
|
|
360
360
|
function getConditionalDominatorIds(ids, snapshot, condCb) {
|
|
361
361
|
const dominatorIds = new Set();
|
|
362
362
|
// set all node ids
|
|
@@ -863,6 +863,10 @@ function isInterestingPath(p) {
|
|
|
863
863
|
if (Config_1.default.hideBrowserLeak && shadowRootRetainsDetachedElement(p)) {
|
|
864
864
|
return false;
|
|
865
865
|
}
|
|
866
|
+
// if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
|
|
867
|
+
if (Config_1.default.hideBrowserLeak && styleEngineRetainsDetachedElement(p)) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
866
870
|
return true;
|
|
867
871
|
}
|
|
868
872
|
// return true if the heap node represents JS object or closure
|
|
@@ -923,6 +927,25 @@ function shadowRootRetainsDetachedElement(path) {
|
|
|
923
927
|
// check if the node is a detached element
|
|
924
928
|
return !!p && isDetachedDOMNode(p.node);
|
|
925
929
|
}
|
|
930
|
+
// check if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
|
|
931
|
+
function styleEngineRetainsDetachedElement(path) {
|
|
932
|
+
let p = path;
|
|
933
|
+
// find the StyleEngine
|
|
934
|
+
while (p && p.node && p.node.name !== 'StyleEngine') {
|
|
935
|
+
p = p.next;
|
|
936
|
+
if (!p) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
p = p.next;
|
|
941
|
+
// StyleEngine is not poining to InternalNode
|
|
942
|
+
if (!p || !p.node || p.node.name !== 'InternalNode') {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
p = p.next;
|
|
946
|
+
// check if the InternalNode is pointing to a detached element
|
|
947
|
+
return !!p && isDetachedDOMNode(p.node);
|
|
948
|
+
}
|
|
926
949
|
function pathHasDetachedHTMLNode(path) {
|
|
927
950
|
if (!path) {
|
|
928
951
|
return false;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @emails oncall+ws_labs
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
import type { MemLabConfig } from '../Config';
|
|
11
|
+
import type { HeapNodeIdSet, IHeapNode, IHeapSnapshot } from '../Types';
|
|
12
|
+
/**
|
|
13
|
+
* Every leak object filter rule needs to give a label
|
|
14
|
+
* to each object passed to the filter
|
|
15
|
+
*/
|
|
16
|
+
export declare enum LeakDecision {
|
|
17
|
+
LEAK = "leak",
|
|
18
|
+
MAYBE_LEAK = "maybe-leak",
|
|
19
|
+
NOT_LEAK = "not-leak"
|
|
20
|
+
}
|
|
21
|
+
export interface ILeakObjectFilterRule {
|
|
22
|
+
filter(config: MemLabConfig, node: IHeapNode, snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet): LeakDecision;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=BaseLeakFilter.rule.d.ts.map
|