@memlab/core 1.1.2 → 1.1.3
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/README.md +3 -2
- package/dist/lib/Config.d.ts +3 -0
- package/dist/lib/Config.js +13 -0
- package/dist/lib/Constant.js +2 -2
- package/dist/lib/HeapParser.js +1 -1
- package/dist/lib/Serializer.js +4 -1
- package/dist/lib/Types.d.ts +674 -21
- package/dist/lib/Utils.js +2 -2
- package/dist/lib/heap-data/HeapNode.d.ts +1 -1
- package/dist/lib/heap-data/HeapNode.js +1 -1
- package/dist/lib/heap-data/HeapSnapshot.js +7 -1
- package/dist/paths/TraceFinder.js +4 -2
- package/dist/trace-cluster/TraceElement.d.ts +1 -1
- package/dist/trace-cluster/TraceElement.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This is the memlab core library. It contains V8/Hermes heap snapshot parser, core algorithms, leak trace clustering, utilities, and config.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
https://facebookincubator.github.io/memlab
|
|
5
|
+
## Online Resources
|
|
6
|
+
* [Official Website and Demo](https://facebookincubator.github.io/memlab)
|
|
7
|
+
* [Documentation](https://facebookincubator.github.io/memlab/docs/intro)
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -171,6 +171,7 @@ export declare class MemLabConfig {
|
|
|
171
171
|
clusterRetainedSizeThreshold: number;
|
|
172
172
|
_isFullRun: boolean;
|
|
173
173
|
_scenario: Optional<IScenario>;
|
|
174
|
+
_isHeadfulBrowser: boolean;
|
|
174
175
|
externalLeakFilter?: Optional<ILeakFilter>;
|
|
175
176
|
monoRepoDir: string;
|
|
176
177
|
muteConsole: boolean;
|
|
@@ -192,6 +193,8 @@ export declare class MemLabConfig {
|
|
|
192
193
|
get isFullRun(): boolean;
|
|
193
194
|
set browser(v: string);
|
|
194
195
|
get browser(): string;
|
|
196
|
+
set isHeadfulBrowser(isHeadful: boolean);
|
|
197
|
+
get isHeadfulBrowser(): boolean;
|
|
195
198
|
get browserBinaryPath(): string;
|
|
196
199
|
set reportLeaksInTimers(flag: boolean);
|
|
197
200
|
get reportLeaksInTimers(): boolean;
|
package/dist/lib/Config.js
CHANGED
|
@@ -54,6 +54,7 @@ class MemLabConfig {
|
|
|
54
54
|
this._deviceManualOverridden = false;
|
|
55
55
|
this._timerNodes = ['Pending activities'];
|
|
56
56
|
this._timerEdges = [];
|
|
57
|
+
this._isHeadfulBrowser = false;
|
|
57
58
|
this.targetApp = Constant_1.default.unset;
|
|
58
59
|
this.targetTab = Constant_1.default.unset;
|
|
59
60
|
this.analysisMode = Constant_1.default.unset;
|
|
@@ -66,6 +67,7 @@ class MemLabConfig {
|
|
|
66
67
|
this.specifiedEngine = false;
|
|
67
68
|
// set puppeteer configuration
|
|
68
69
|
this.puppeteerConfig = {
|
|
70
|
+
headless: !this._isHeadfulBrowser,
|
|
69
71
|
devtools: this.openDevtoolsConsole,
|
|
70
72
|
// IMPORTANT: test ContinuousTest before change this config
|
|
71
73
|
ignoreHTTPSErrors: true,
|
|
@@ -358,6 +360,17 @@ class MemLabConfig {
|
|
|
358
360
|
get browser() {
|
|
359
361
|
return this._browser || 'google-chrome';
|
|
360
362
|
}
|
|
363
|
+
set isHeadfulBrowser(isHeadful) {
|
|
364
|
+
this._isHeadfulBrowser = isHeadful;
|
|
365
|
+
this.puppeteerConfig.headless = !isHeadful;
|
|
366
|
+
if (isHeadful) {
|
|
367
|
+
// if running in headful mode
|
|
368
|
+
this.disableXvfb();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
get isHeadfulBrowser() {
|
|
372
|
+
return this._isHeadfulBrowser;
|
|
373
|
+
}
|
|
361
374
|
get browserBinaryPath() {
|
|
362
375
|
return path_1.default.join(this.browserDir, this.browser);
|
|
363
376
|
}
|
package/dist/lib/Constant.js
CHANGED
|
@@ -13,8 +13,8 @@ const InternalValueSetter_1 = require("./InternalValueSetter");
|
|
|
13
13
|
const constants = {
|
|
14
14
|
isFB: false,
|
|
15
15
|
isFRL: false,
|
|
16
|
-
defaultEngine: '
|
|
17
|
-
supportedEngines: ['
|
|
16
|
+
defaultEngine: 'V8',
|
|
17
|
+
supportedEngines: ['V8', 'hermes'],
|
|
18
18
|
supportedBrowsers: Object.create(null),
|
|
19
19
|
internalDir: 'fb-internal',
|
|
20
20
|
monoRepoDir: '',
|
package/dist/lib/HeapParser.js
CHANGED
|
@@ -102,7 +102,7 @@ function identifyAndSetEngine(snapshot) {
|
|
|
102
102
|
return; // skip if engine type is manually set
|
|
103
103
|
}
|
|
104
104
|
Console_1.default.overwrite('identifying snapshot engine...');
|
|
105
|
-
let engine = '
|
|
105
|
+
let engine = 'V8';
|
|
106
106
|
snapshot.nodes.forEach((node) => {
|
|
107
107
|
if (node.type === 'object' && node.name.startsWith('Object(')) {
|
|
108
108
|
engine = 'hermes';
|
package/dist/lib/Serializer.js
CHANGED
|
@@ -182,6 +182,9 @@ function JSONifyFiberNodeReturnTrace(node, args, options) {
|
|
|
182
182
|
continue;
|
|
183
183
|
}
|
|
184
184
|
const parent = node.snapshot.nodes.get(index);
|
|
185
|
+
if (!parent) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
185
188
|
const parentInfo = getNodeNameInJSON(parent, args);
|
|
186
189
|
key = `${key}: --return (property)---> ${parentInfo}`;
|
|
187
190
|
const info = JSONifyFiberNodeShallow(parent);
|
|
@@ -593,7 +596,7 @@ function summarizeNode(node, options = {}) {
|
|
|
593
596
|
let nodeImpact = '';
|
|
594
597
|
if (nodeRetainSize) {
|
|
595
598
|
nodeImpact = options.color
|
|
596
|
-
? chalk_1.default.grey('[') + chalk_1.default.blue(nodeRetainSize) + chalk_1.default.grey(']')
|
|
599
|
+
? chalk_1.default.grey('[') + chalk_1.default.blue.bold(nodeRetainSize) + chalk_1.default.grey(']')
|
|
597
600
|
: `[${nodeRetainSize}]`;
|
|
598
601
|
}
|
|
599
602
|
const name = summarizeNodeName(node, options);
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export interface IE2EScenarioSynthesizer {
|
|
|
96
96
|
getOrigin(): Nullable<string>;
|
|
97
97
|
getDomain(): string;
|
|
98
98
|
getDomainPrefixes(): string[];
|
|
99
|
-
getCookieFile(visitPlan: IE2EScenarioVisitPlan): string
|
|
99
|
+
getCookieFile(visitPlan: IE2EScenarioVisitPlan): Nullable<string>;
|
|
100
100
|
getAvailableSteps(): IE2EStepBasic[];
|
|
101
101
|
getNodeNameBlocklist(): string[];
|
|
102
102
|
getEdgeNameBlocklist(): string[];
|
|
@@ -143,16 +143,49 @@ export declare type QuickExperiment = {
|
|
|
143
143
|
group: string;
|
|
144
144
|
};
|
|
145
145
|
/**
|
|
146
|
-
* The
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* const scenario = {
|
|
146
|
+
* The `ILeakFilter` interface allows you to define a leak detector and
|
|
147
|
+
* customize the leak filtering logic in memlab (instead of using the
|
|
148
|
+
* built-in leak filters).
|
|
150
149
|
*
|
|
151
|
-
*
|
|
150
|
+
* Use the leak filter definition in command line interface to filter
|
|
151
|
+
* leaks detected from browser interactions
|
|
152
|
+
* ```bash
|
|
153
|
+
* memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* If you have already run `memlab run` or `memlab snapshot` which saved
|
|
157
|
+
* heap snapshot and other meta data on disk, use the following command
|
|
158
|
+
* to filter leaks based on those saved heap snapshots (query the default
|
|
159
|
+
* data location by `memlab get-default-work-dir`).
|
|
152
160
|
*
|
|
153
|
-
*
|
|
161
|
+
* ```bash
|
|
162
|
+
* memlab find-leaks --leak-filter <PATH TO leak-filter.js>
|
|
163
|
+
* ```
|
|
164
|
+
* Here is an example TypeScript file defining a leak filter.
|
|
165
|
+
* The command line interface only accepts compiled JavaScript file.
|
|
166
|
+
* You can also define the leak filter in JavaScript (without the
|
|
167
|
+
* type annotations.
|
|
168
|
+
*
|
|
169
|
+
* ```typescript
|
|
170
|
+
* import {IHeapNode, IHeapSnapshot, HeapNodeIdSet, utils} from '@memlab/core';
|
|
171
|
+
*
|
|
172
|
+
* function initMap(snapshot: IHeapSnapshot): Record<string, number> {
|
|
173
|
+
* const map = Object.create(null);
|
|
174
|
+
* snapshot.nodes.forEach(node => {
|
|
175
|
+
* if (node.type !== 'string') {
|
|
176
|
+
* return;
|
|
177
|
+
* }
|
|
178
|
+
* const str = utils.getStringNodeValue(node);
|
|
179
|
+
* if (str in map) {
|
|
180
|
+
* ++map[str];
|
|
181
|
+
* } else {
|
|
182
|
+
* map[str] = 1;
|
|
183
|
+
* }
|
|
184
|
+
* });
|
|
185
|
+
* return map;
|
|
186
|
+
* }
|
|
154
187
|
* const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
|
|
155
|
-
* map =
|
|
188
|
+
* map = initMap(snapshot);
|
|
156
189
|
* };
|
|
157
190
|
*
|
|
158
191
|
* // duplicated string with size > 1KB as memory leak
|
|
@@ -168,7 +201,81 @@ export declare type QuickExperiment = {
|
|
|
168
201
|
* ```
|
|
169
202
|
*/
|
|
170
203
|
export interface ILeakFilter {
|
|
204
|
+
/**
|
|
205
|
+
* Lifecycle function callback that is invoked initially once before
|
|
206
|
+
* the subsequent `leakFilter` function calls. This callback could
|
|
207
|
+
* be used to initialize some data stores or any one-off
|
|
208
|
+
* preprocessings.
|
|
209
|
+
*
|
|
210
|
+
* * **Parameters**:
|
|
211
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
212
|
+
* all browser interactions are done.
|
|
213
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
214
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
215
|
+
* allocated by the `action` call but not released after the `back` call
|
|
216
|
+
* in browser.
|
|
217
|
+
*
|
|
218
|
+
* * **Examples**:
|
|
219
|
+
* ```typescript
|
|
220
|
+
* module.exports = {
|
|
221
|
+
* beforeLeakFilter: (snapshot, leakedNodeIds) {
|
|
222
|
+
* // initialize some data stores
|
|
223
|
+
* },
|
|
224
|
+
* leakFilter(node, snapshot, leakedNodeIds) {
|
|
225
|
+
* // use the data stores
|
|
226
|
+
* },
|
|
227
|
+
* };
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
171
230
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
231
|
+
/**
|
|
232
|
+
* This callback defines how you want to filter out the
|
|
233
|
+
* leaked objects. The callback is called for every node (JS heap
|
|
234
|
+
* object in browser) allocated by the `action` callback, but not
|
|
235
|
+
* released after the `back` callback. Those objects could be caches
|
|
236
|
+
* that are retained in memory on purpose, or they are memory leaks.
|
|
237
|
+
*
|
|
238
|
+
* This optional callback allows you to define your own algorithm
|
|
239
|
+
* to cherry pick memory leaks for specific JS program under test.
|
|
240
|
+
*
|
|
241
|
+
* If this optional callback is not defined, memlab will use its
|
|
242
|
+
* built-in leak filter, which considers detached DOM elements
|
|
243
|
+
* and unmounted Fiber nodes (detached from React Fiber tree) as
|
|
244
|
+
* memory leaks.
|
|
245
|
+
*
|
|
246
|
+
* * **Parameters**:
|
|
247
|
+
* * node: `IHeapNode` | one of the heap object allocated but not released.
|
|
248
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
249
|
+
* all browser interactions are done.
|
|
250
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
251
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
252
|
+
* allocated by the `action` call but not released after the `back` call
|
|
253
|
+
* in browser.
|
|
254
|
+
*
|
|
255
|
+
* * **Returns**: the boolean value indicating whether the given node in
|
|
256
|
+
* the snapshot should be considered as leaked.
|
|
257
|
+
*
|
|
258
|
+
*
|
|
259
|
+
* ```javascript
|
|
260
|
+
* // save as leak-filter.js
|
|
261
|
+
* module.exports = {
|
|
262
|
+
* leakFilter(node, snapshot, leakedNodeIds) {
|
|
263
|
+
* // any unreleased node (JS heap object) with 1MB+
|
|
264
|
+
* // retained size is considered a memory leak
|
|
265
|
+
* return node.retainedSize > 1000000;
|
|
266
|
+
* },
|
|
267
|
+
* };
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* Use the leak filter definition in command line interface:
|
|
271
|
+
* ```bash
|
|
272
|
+
* memlab find-leaks --leak-filter <PATH TO leak-filter.js>
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* ```bash
|
|
276
|
+
* memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
172
279
|
leakFilter: LeakFilterCallback;
|
|
173
280
|
}
|
|
174
281
|
/**
|
|
@@ -217,6 +324,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
|
|
|
217
324
|
* url: () => 'https://www.npmjs.com/',
|
|
218
325
|
* action: async () => ... ,
|
|
219
326
|
* back: async () => ... ,
|
|
327
|
+
* cookies: () => ... , // optional
|
|
328
|
+
* repeat: () => ... , // optional
|
|
329
|
+
* ...
|
|
220
330
|
* };
|
|
221
331
|
* ```
|
|
222
332
|
*
|
|
@@ -230,6 +340,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
|
|
|
230
340
|
* url: () => 'https://www.facebook.com',
|
|
231
341
|
* action: async () => ... ,
|
|
232
342
|
* back: async () => ... ,
|
|
343
|
+
* cookies: () => ... , // optional
|
|
344
|
+
* repeat: () => ... , // optional
|
|
345
|
+
* ...
|
|
233
346
|
* };
|
|
234
347
|
* const leaks = await run({scenario});
|
|
235
348
|
* })();
|
|
@@ -243,17 +356,21 @@ export interface IScenario {
|
|
|
243
356
|
/**
|
|
244
357
|
* If the page you are running memlab against requires authentication or
|
|
245
358
|
* specific cookie(s) to be set, you can pass them as
|
|
246
|
-
* a list of
|
|
359
|
+
* a list of `<name, value, domain>` tuples.
|
|
360
|
+
*
|
|
361
|
+
* **Note**: please make sure that you provide the correct `domain` field for
|
|
362
|
+
* the cookies tuples.
|
|
363
|
+
*
|
|
247
364
|
* @returns cookie list
|
|
248
365
|
* * **Examples**:
|
|
249
366
|
* ```typescript
|
|
250
367
|
* const scenario = {
|
|
251
368
|
* url: () => 'https://www.facebook.com/',
|
|
252
369
|
* cookies: () => [
|
|
253
|
-
* {
|
|
254
|
-
* {
|
|
255
|
-
* {
|
|
256
|
-
* {
|
|
370
|
+
* {name:'cm_j', value: 'none', domain: '.facebook.com'},
|
|
371
|
+
* {name:'datr', value: 'yJvIY...', domain: '.facebook.com'},
|
|
372
|
+
* {name:'c_user', value: '8917...', domain: '.facebook.com'},
|
|
373
|
+
* {name:'xs', value: '95:9WQ...', domain: '.facebook.com'},
|
|
257
374
|
* // ...
|
|
258
375
|
* ],
|
|
259
376
|
* };
|
|
@@ -354,7 +471,7 @@ export interface IScenario {
|
|
|
354
471
|
repeat?: () => number;
|
|
355
472
|
/**
|
|
356
473
|
* Optional callback function that checks if the web page is loaded
|
|
357
|
-
*
|
|
474
|
+
* for the initial page load and subsequent browser interactions.
|
|
358
475
|
*
|
|
359
476
|
* If this callback is not provided, memlab by default
|
|
360
477
|
* considers a navigation to be finished when there are no network
|
|
@@ -390,7 +507,7 @@ export interface IScenario {
|
|
|
390
507
|
/**
|
|
391
508
|
* Lifecycle function callback that is invoked initially once before
|
|
392
509
|
* the subsequent `leakFilter` function calls. This callback could
|
|
393
|
-
* be used to initialize some data stores or to
|
|
510
|
+
* be used to initialize some data stores or to any one-off
|
|
394
511
|
* preprocessings.
|
|
395
512
|
*
|
|
396
513
|
* * **Parameters**:
|
|
@@ -415,7 +532,7 @@ export interface IScenario {
|
|
|
415
532
|
*/
|
|
416
533
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
417
534
|
/**
|
|
418
|
-
* This callback
|
|
535
|
+
* This callback defines how you want to filter out the
|
|
419
536
|
* leaked objects. The callback is called for every node (JS heap
|
|
420
537
|
* object in browser) allocated by the `action` callback, but not
|
|
421
538
|
* released after the `back` callback. Those objects could be caches
|
|
@@ -606,6 +723,12 @@ export declare type RunMetaInfo = {
|
|
|
606
723
|
type: string;
|
|
607
724
|
browserInfo: IBrowserInfo;
|
|
608
725
|
};
|
|
726
|
+
/**
|
|
727
|
+
* A heap snapshot is generally a graph where graph nodes are JS heap objects
|
|
728
|
+
* and graph edges are JS references among JS heap objects. For more details
|
|
729
|
+
* on the structure of nodes and edges in the heap graph, check out
|
|
730
|
+
* {@link IHeapNode} and {@link IHeapEdge}.
|
|
731
|
+
*/
|
|
609
732
|
export interface IHeapSnapshot {
|
|
610
733
|
/** @internal */
|
|
611
734
|
snapshot: RawHeapSnapshot;
|
|
@@ -779,6 +902,10 @@ export interface IHeapSnapshot {
|
|
|
779
902
|
/**
|
|
780
903
|
* Search for the heap and check if there is any JS object instance with
|
|
781
904
|
* a marker tagged by {@link tagObject}.
|
|
905
|
+
*
|
|
906
|
+
* The `tagObject` API does not modify the object instance in any way
|
|
907
|
+
* (e.g., no additional or hidden properties added to the tagged object).
|
|
908
|
+
*
|
|
782
909
|
* @param tag marker name on the object instances tagged by {@link tagObject}
|
|
783
910
|
* @returns returns `true` if there is at least one such object in the heap
|
|
784
911
|
*
|
|
@@ -813,75 +940,601 @@ export interface IHeapSnapshot {
|
|
|
813
940
|
/** @internal */
|
|
814
941
|
clearShortestPathInfo(): void;
|
|
815
942
|
}
|
|
943
|
+
/**
|
|
944
|
+
* An `IHeapLocation` instance contains a source location information
|
|
945
|
+
* associated with a JS heap object.
|
|
946
|
+
* A heap snapshot is generally a graph where graph nodes are JS heap objects
|
|
947
|
+
* and graph edges are JS references among JS heap objects.
|
|
948
|
+
*
|
|
949
|
+
* @readonly it is not recommended to modify any `IHeapLocation` instance
|
|
950
|
+
*
|
|
951
|
+
* * **Examples**: V8 or hermes heap snapshot can be parsed by the
|
|
952
|
+
* {@link getHeapFromFile} API.
|
|
953
|
+
*
|
|
954
|
+
* ```typescript
|
|
955
|
+
* import type {IHeapSnapshot, IHeapNode, IHeapLocation} from '@memlab/core';
|
|
956
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
957
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
958
|
+
*
|
|
959
|
+
* (async function () {
|
|
960
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
961
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
962
|
+
*
|
|
963
|
+
* // iterate over each node (heap object)
|
|
964
|
+
* heap.nodes.forEach((node: IHeapNode, i: number) => {
|
|
965
|
+
* const location: Nullable<IHeapLocation> = node.location;
|
|
966
|
+
* if (location) {
|
|
967
|
+
* // use the location API here
|
|
968
|
+
* location.line;
|
|
969
|
+
* // ...
|
|
970
|
+
* }
|
|
971
|
+
* });
|
|
972
|
+
* })();
|
|
973
|
+
* ```
|
|
974
|
+
*/
|
|
816
975
|
export interface IHeapLocation {
|
|
976
|
+
/**
|
|
977
|
+
* get the {@link IHeapSnapshot} containing this location instance
|
|
978
|
+
*/
|
|
817
979
|
snapshot: IHeapSnapshot;
|
|
980
|
+
/**
|
|
981
|
+
* get the script ID of the source file
|
|
982
|
+
*/
|
|
818
983
|
script_id: number;
|
|
984
|
+
/**
|
|
985
|
+
* get the line number
|
|
986
|
+
*/
|
|
819
987
|
line: number;
|
|
988
|
+
/**
|
|
989
|
+
* get the column number
|
|
990
|
+
*/
|
|
820
991
|
column: number;
|
|
821
992
|
}
|
|
993
|
+
/** @internal */
|
|
822
994
|
export interface IHeapEdgeBasic {
|
|
995
|
+
/**
|
|
996
|
+
* name of the JS reference. If this is a reference to an array element
|
|
997
|
+
* or internal table element, it is an numeric index
|
|
998
|
+
*/
|
|
823
999
|
name_or_index: number | string;
|
|
1000
|
+
/**
|
|
1001
|
+
* type of the JS reference, all types:
|
|
1002
|
+
* `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak`
|
|
1003
|
+
*/
|
|
824
1004
|
type: string;
|
|
825
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* An `IHeapEdge` instance represents a JS reference in a heap snapshot.
|
|
1008
|
+
* A heap snapshot is generally a graph where graph nodes are JS heap objects
|
|
1009
|
+
* and graph edges are JS references among JS heap objects.
|
|
1010
|
+
*
|
|
1011
|
+
* @readonly it is not recommended to modify any `IHeapEdge` instance
|
|
1012
|
+
*
|
|
1013
|
+
* * **Examples**: V8 or hermes heap snapshot can be parsed by the
|
|
1014
|
+
* {@link getHeapFromFile} API.
|
|
1015
|
+
*
|
|
1016
|
+
* ```typescript
|
|
1017
|
+
* import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
|
|
1018
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
1019
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
1020
|
+
*
|
|
1021
|
+
* (async function () {
|
|
1022
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
1023
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
1024
|
+
*
|
|
1025
|
+
* // iterate over each edge (JS reference in heap)
|
|
1026
|
+
* heap.edges.forEach((edge: IHeapEdge, i: number) => {
|
|
1027
|
+
* // use the heap edge APIs here
|
|
1028
|
+
* const nameOrIndex = edge.name_or_index;
|
|
1029
|
+
* // ...
|
|
1030
|
+
* });
|
|
1031
|
+
* })();
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
826
1034
|
export interface IHeapEdge extends IHeapEdgeBasic {
|
|
1035
|
+
/**
|
|
1036
|
+
* get the {@link IHeapSnapshot} containing this JS reference
|
|
1037
|
+
*/
|
|
827
1038
|
snapshot: IHeapSnapshot;
|
|
1039
|
+
/**
|
|
1040
|
+
* index of this JS reference inside the `edge.snapshot.edges` pseudo array
|
|
1041
|
+
*/
|
|
828
1042
|
edgeIndex: number;
|
|
1043
|
+
/**
|
|
1044
|
+
* if `true`, means this is a reference to an array element
|
|
1045
|
+
* or internal table element (`edge.name_or_index` will return a number),
|
|
1046
|
+
* otherwise this is a reference with a string name (`edge.name_or_index`
|
|
1047
|
+
* will return a string)
|
|
1048
|
+
*/
|
|
829
1049
|
is_index: boolean;
|
|
1050
|
+
/**
|
|
1051
|
+
* the index of the JS heap object pointed to by this reference
|
|
1052
|
+
*/
|
|
830
1053
|
to_node: number;
|
|
1054
|
+
/**
|
|
1055
|
+
* returns an {@link IHeapNode} instance representing the JS heap object
|
|
1056
|
+
* pointed to by this reference
|
|
1057
|
+
*/
|
|
831
1058
|
toNode: IHeapNode;
|
|
1059
|
+
/**
|
|
1060
|
+
* returns an {@link IHeapNode} instance representing the hosting
|
|
1061
|
+
* JS heap object where this reference starts
|
|
1062
|
+
*/
|
|
832
1063
|
fromNode: IHeapNode;
|
|
833
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* A pseudo array containing all heap graph edges (references to heap objects
|
|
1067
|
+
* in heap). A JS heap could contain millions of references, so memlab uses
|
|
1068
|
+
* a pseudo array as the collection of all the heap edges. The pseudo
|
|
1069
|
+
* array provides API to query and traverse all heap references.
|
|
1070
|
+
*
|
|
1071
|
+
* @readonly modifying this pseudo array is not recommended
|
|
1072
|
+
*
|
|
1073
|
+
* * **Examples**:
|
|
1074
|
+
* ```typescript
|
|
1075
|
+
* import type {IHeapSnapshot, IHeapEdges} from '@memlab/core';
|
|
1076
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
1077
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
1078
|
+
*
|
|
1079
|
+
* (async function () {
|
|
1080
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
1081
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
1082
|
+
*
|
|
1083
|
+
* const edges: IHeapEdges = heap.edges;
|
|
1084
|
+
* edges.length;
|
|
1085
|
+
* edges.get(0);
|
|
1086
|
+
* edges.forEach((edge, i) => {
|
|
1087
|
+
* if (stopIteration) {
|
|
1088
|
+
* return false;
|
|
1089
|
+
* }
|
|
1090
|
+
* });
|
|
1091
|
+
* })();
|
|
1092
|
+
* ```
|
|
1093
|
+
*/
|
|
834
1094
|
export interface IHeapEdges {
|
|
1095
|
+
/**
|
|
1096
|
+
* The total number of edges in heap graph (or JS references in heap
|
|
1097
|
+
* snapshot).
|
|
1098
|
+
*/
|
|
835
1099
|
length: number;
|
|
836
|
-
|
|
1100
|
+
/**
|
|
1101
|
+
* get an {@link IHeapEdge} element at the specified index
|
|
1102
|
+
* @param index the index of an element in the pseudo array, the index ranges
|
|
1103
|
+
* from 0 to array length - 1. Notice that this is not the heap node id.
|
|
1104
|
+
* @returns When 0 <= `index` < array.length, this API returns the element
|
|
1105
|
+
* at the specified index, otherwise it returns `null`.
|
|
1106
|
+
*/
|
|
1107
|
+
get(index: number): Nullable<IHeapEdge>;
|
|
1108
|
+
/**
|
|
1109
|
+
* Iterate over all array elements and apply the callback
|
|
1110
|
+
* to each element in ascending order of element index.
|
|
1111
|
+
* @param callback the callback does not need to return any value, if
|
|
1112
|
+
* the callback returns `false` when iterating on element at index `i`,
|
|
1113
|
+
* then all elements after `i` won't be iterated.
|
|
1114
|
+
*/
|
|
837
1115
|
forEach(callback: (edge: IHeapEdge, index: number) => void | boolean): void;
|
|
838
1116
|
}
|
|
1117
|
+
/** @internal */
|
|
839
1118
|
export interface IHeapNodeBasic {
|
|
1119
|
+
/**
|
|
1120
|
+
* the type of the heap node object. All possible types:
|
|
1121
|
+
* This is engine-specific, for example all types in V8:
|
|
1122
|
+
* `hidden`, `array`, `string`, `object`, `code`, `closure`, `regexp`,
|
|
1123
|
+
* `number`, `native`, `synthetic`, `concatenated string`, `sliced string`,
|
|
1124
|
+
* `symbol`, `bigint`
|
|
1125
|
+
*/
|
|
840
1126
|
type: string;
|
|
1127
|
+
/**
|
|
1128
|
+
* this is the `name` field associated with the heap object,
|
|
1129
|
+
* for JS object instances (type `object`), `name` is the constructor's name
|
|
1130
|
+
* of the object instance. for `string`, `name` is the string value.
|
|
1131
|
+
*/
|
|
841
1132
|
name: string;
|
|
1133
|
+
/**
|
|
1134
|
+
* unique id of the heap object
|
|
1135
|
+
*/
|
|
842
1136
|
id: number;
|
|
843
1137
|
}
|
|
844
1138
|
export declare type EdgeIterationCallback = (edge: IHeapEdge) => Optional<{
|
|
845
1139
|
stop: boolean;
|
|
846
1140
|
}>;
|
|
1141
|
+
/**
|
|
1142
|
+
* An `IHeapNode` instance represents a JS heap object in a heap snapshot.
|
|
1143
|
+
* A heap snapshot is generally a graph where graph nodes are JS heap objects
|
|
1144
|
+
* and graph edges are JS references among JS heap objects.
|
|
1145
|
+
*
|
|
1146
|
+
* @readonly it is not recommended to modify any `IHeapNode` instance
|
|
1147
|
+
*
|
|
1148
|
+
* * **Examples**: V8 or hermes heap snapshot can be parsed by the
|
|
1149
|
+
* {@link getHeapFromFile} API.
|
|
1150
|
+
*
|
|
1151
|
+
* ```typescript
|
|
1152
|
+
* import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
|
|
1153
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
1154
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
1155
|
+
*
|
|
1156
|
+
* (async function () {
|
|
1157
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
1158
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
1159
|
+
*
|
|
1160
|
+
* // iterate over each node (heap object)
|
|
1161
|
+
* heap.nodes.forEach((node: IHeapNode, i: number) => {
|
|
1162
|
+
* // use the heap node APIs here
|
|
1163
|
+
* const id = node.id;
|
|
1164
|
+
* const type = node.type;
|
|
1165
|
+
* // ...
|
|
1166
|
+
* });
|
|
1167
|
+
* })();
|
|
1168
|
+
* ```
|
|
1169
|
+
*/
|
|
847
1170
|
export interface IHeapNode extends IHeapNodeBasic {
|
|
1171
|
+
/**
|
|
1172
|
+
* get the {@link IHeapSnapshot} containing this heap object
|
|
1173
|
+
*/
|
|
848
1174
|
snapshot: IHeapSnapshot;
|
|
1175
|
+
/**
|
|
1176
|
+
* * If the heap object is a DOM element and the DOM element is detached
|
|
1177
|
+
* from the DOM tree, `is_detached` will be `true`;
|
|
1178
|
+
* * If the heap object is a React Fiber node and the Fiber node is unmounted
|
|
1179
|
+
* from the React Fiber tree, `is_detached` will be `true`;
|
|
1180
|
+
* otherwise it will be `false`
|
|
1181
|
+
*/
|
|
849
1182
|
is_detached: boolean;
|
|
1183
|
+
/** @internal */
|
|
850
1184
|
detachState: number;
|
|
1185
|
+
/** @internal */
|
|
851
1186
|
markAsDetached(): void;
|
|
1187
|
+
/** @internal */
|
|
852
1188
|
attributes: number;
|
|
1189
|
+
/**
|
|
1190
|
+
* The *shallow size* of the heap object (i.e., the size of memory that is held
|
|
1191
|
+
* by the object itself.). For difference between **shallow size** and
|
|
1192
|
+
* **retained size**, check out
|
|
1193
|
+
* [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
|
|
1194
|
+
*/
|
|
853
1195
|
self_size: number;
|
|
1196
|
+
/**
|
|
1197
|
+
* The total number of outgoing JS references (including engine-internal,
|
|
1198
|
+
* native, and JS references).
|
|
1199
|
+
*/
|
|
854
1200
|
edge_count: number;
|
|
1201
|
+
/** @internal */
|
|
855
1202
|
trace_node_id: number;
|
|
1203
|
+
/**
|
|
1204
|
+
* Get a JS array containing all outgoing JS references from this heap object
|
|
1205
|
+
* (including engine-internal, native, and JS references).
|
|
1206
|
+
*/
|
|
856
1207
|
references: IHeapEdge[];
|
|
1208
|
+
/**
|
|
1209
|
+
* Get a JS array containing all incoming JS references pointing to this heap
|
|
1210
|
+
* object (including engine-internal, native, and JS references).
|
|
1211
|
+
*/
|
|
857
1212
|
referrers: IHeapEdge[];
|
|
1213
|
+
/**
|
|
1214
|
+
* The incoming edge which leads to the parent node
|
|
1215
|
+
* on the shortest path to GC root.
|
|
1216
|
+
*/
|
|
858
1217
|
pathEdge: IHeapEdge | null;
|
|
1218
|
+
/**
|
|
1219
|
+
* index of this heap object inside the `node.snapshot.nodes` pseudo array
|
|
1220
|
+
*/
|
|
859
1221
|
nodeIndex: number;
|
|
1222
|
+
/**
|
|
1223
|
+
* The *retained size* of the heap object (i.e., the total size of memory that
|
|
1224
|
+
* could be released if this object is released). For difference between
|
|
1225
|
+
* **retained size** and **shallow size**, check out
|
|
1226
|
+
* [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
|
|
1227
|
+
*/
|
|
860
1228
|
retainedSize: number;
|
|
861
|
-
|
|
862
|
-
|
|
1229
|
+
/**
|
|
1230
|
+
* get the dominator node of this node. If the dominator node gets released
|
|
1231
|
+
* there will be no path from GC to this node, and therefore this node can
|
|
1232
|
+
* also be released.
|
|
1233
|
+
* For more information on what a dominator node is, please check out
|
|
1234
|
+
* [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators).
|
|
1235
|
+
*/
|
|
1236
|
+
dominatorNode: Nullable<IHeapNode>;
|
|
1237
|
+
/**
|
|
1238
|
+
* source location information of this heap object (if it is recorded by
|
|
1239
|
+
* the heap snapshot).
|
|
1240
|
+
*/
|
|
1241
|
+
location: Nullable<IHeapLocation>;
|
|
1242
|
+
/** @internal */
|
|
863
1243
|
highlight?: boolean;
|
|
1244
|
+
/**
|
|
1245
|
+
* check if this a string node (normal string node, concatenated string node
|
|
1246
|
+
* or sliced string node)
|
|
1247
|
+
*/
|
|
864
1248
|
isString: boolean;
|
|
1249
|
+
/**
|
|
1250
|
+
* convert to an {@link IHeapStringNode} object if this node is a string node.
|
|
1251
|
+
* The {@link IHeapStringNode} object supports querying the string content
|
|
1252
|
+
* inside the string node.
|
|
1253
|
+
*/
|
|
865
1254
|
toStringNode(): Nullable<IHeapStringNode>;
|
|
1255
|
+
/**
|
|
1256
|
+
* executes a provided callback once for each JavaScript reference in the
|
|
1257
|
+
* hosting node (or outgoing edges from the node)
|
|
1258
|
+
* @param callback the callback for each outgoing JavaScript reference
|
|
1259
|
+
* @returns this API returns void
|
|
1260
|
+
*
|
|
1261
|
+
* * **Examples**:
|
|
1262
|
+
* ```typescript
|
|
1263
|
+
* node.forEachReference((edge: IHeapEdge) => {
|
|
1264
|
+
* // process edge ...
|
|
1265
|
+
*
|
|
1266
|
+
* // if no need to iterate over remaining edges after
|
|
1267
|
+
* // the current edge in the node.references list
|
|
1268
|
+
* return {stop: true};
|
|
1269
|
+
* });
|
|
1270
|
+
* ```
|
|
1271
|
+
*/
|
|
866
1272
|
forEachReference(callback: EdgeIterationCallback): void;
|
|
1273
|
+
/**
|
|
1274
|
+
* executes a provided callback once for each JavaScript reference pointing
|
|
1275
|
+
* to the hosting node (or incoming edges to the node)
|
|
1276
|
+
* @param callback the callback for each incoming JavaScript reference
|
|
1277
|
+
* @returns this API returns void
|
|
1278
|
+
*
|
|
1279
|
+
* * **Examples**:
|
|
1280
|
+
* ```typescript
|
|
1281
|
+
* node.forEachReferrer((edge: IHeapEdge) => {
|
|
1282
|
+
* // process edge ...
|
|
1283
|
+
*
|
|
1284
|
+
* // if no need to iterate over remaining edges after
|
|
1285
|
+
* // the current edge in the node.referrers list
|
|
1286
|
+
* return {stop: true};
|
|
1287
|
+
* });
|
|
1288
|
+
* ```
|
|
1289
|
+
*/
|
|
867
1290
|
forEachReferrer(callback: EdgeIterationCallback): void;
|
|
868
|
-
|
|
1291
|
+
/**
|
|
1292
|
+
* executes a provided predicate callback once for each JavaScript reference
|
|
1293
|
+
* in the hosting node (or outgoing edges from the node) until the predicate
|
|
1294
|
+
* returns `true`
|
|
1295
|
+
* @param predicate the callback for each outgoing JavaScript reference
|
|
1296
|
+
* @returns the first outgoing edge for which the predicate returns `true`,
|
|
1297
|
+
* otherwise returns `null` if no such edge is found.
|
|
1298
|
+
*
|
|
1299
|
+
* * **Examples**:
|
|
1300
|
+
* ```typescript
|
|
1301
|
+
* const reference = node.findAnyReference((edge: IHeapEdge) => {
|
|
1302
|
+
* // find the outgoing reference with name "ref"
|
|
1303
|
+
* return edge.name_or_index === 'ref';
|
|
1304
|
+
* });
|
|
1305
|
+
* ```
|
|
1306
|
+
*/
|
|
1307
|
+
findAnyReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
|
|
1308
|
+
/**
|
|
1309
|
+
* executes a provided predicate callback once for each JavaScript reference
|
|
1310
|
+
* pointing to the hosting node (or incoming edges to the node) until the
|
|
1311
|
+
* predicate returns `true`
|
|
1312
|
+
* @param predicate the callback for each incoming JavaScript reference
|
|
1313
|
+
* @returns the first incoming edge for which the predicate returns `true`,
|
|
1314
|
+
* otherwise returns `null` if no such edge is found.
|
|
1315
|
+
*
|
|
1316
|
+
* * **Examples**:
|
|
1317
|
+
* ```typescript
|
|
1318
|
+
* const referrer = node.findAnyReferrer((edge: IHeapEdge) => {
|
|
1319
|
+
* // find the incoming reference with name "ref"
|
|
1320
|
+
* return edge.name_or_index === 'ref';
|
|
1321
|
+
* });
|
|
1322
|
+
* ```
|
|
1323
|
+
*/
|
|
869
1324
|
findAnyReferrer: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
|
|
1325
|
+
/**
|
|
1326
|
+
* executes a provided predicate callback once for each JavaScript reference
|
|
1327
|
+
* pointing to the hosting node (or incoming edges to the node)
|
|
1328
|
+
* @param predicate the callback for each incoming JavaScript reference
|
|
1329
|
+
* @returns an array containing all the incoming edges for which the
|
|
1330
|
+
* predicate returns `true`, otherwise returns an empty array if no such
|
|
1331
|
+
* edge is found.
|
|
1332
|
+
*
|
|
1333
|
+
* * **Examples**:
|
|
1334
|
+
* ```typescript
|
|
1335
|
+
* const referrers = node.findReferrers((edge: IHeapEdge) => {
|
|
1336
|
+
* // find all the incoming references with name "ref"
|
|
1337
|
+
* return edge.name_or_index === 'ref';
|
|
1338
|
+
* });
|
|
1339
|
+
* ```
|
|
1340
|
+
*/
|
|
870
1341
|
findReferrers: (predicate: Predicator<IHeapEdge>) => IHeapEdge[];
|
|
1342
|
+
/**
|
|
1343
|
+
* Given a JS reference's name and type, this API finds an outgoing JS
|
|
1344
|
+
* reference from the hosting node.
|
|
1345
|
+
* @param edgeName the name of the outgoing JavaScript reference
|
|
1346
|
+
* @param edgeType optional parameter specifying the type of the outgoing
|
|
1347
|
+
* JavaScript reference
|
|
1348
|
+
* @returns the outgoing edge that meets the specification
|
|
1349
|
+
*
|
|
1350
|
+
* * **Examples**:
|
|
1351
|
+
* ```typescript
|
|
1352
|
+
* // find the internal reference to node's hidden class
|
|
1353
|
+
* const reference = node.getReference('map', 'hidden');
|
|
1354
|
+
* ```
|
|
1355
|
+
*/
|
|
871
1356
|
getReference: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
|
|
1357
|
+
/**
|
|
1358
|
+
* Given a JS reference's name and type, this API finds the outgoing JS
|
|
1359
|
+
* reference from the hosting node, and returns the JS heap object pointed to
|
|
1360
|
+
* by the outgoing JS reference.
|
|
1361
|
+
* @param edgeName the name of the outgoing JavaScript reference
|
|
1362
|
+
* @param edgeType optional parameter specifying the type of the outgoing
|
|
1363
|
+
* JavaScript reference
|
|
1364
|
+
* @returns the node pointed to by the outgoing reference that meets
|
|
1365
|
+
* the specification
|
|
1366
|
+
*
|
|
1367
|
+
* * **Examples**:
|
|
1368
|
+
* ```typescript
|
|
1369
|
+
* // find the node's hidden class
|
|
1370
|
+
* const hiddenClassNode = node.getReferenceNode('map', 'hidden');
|
|
1371
|
+
* // this is equivalent to
|
|
1372
|
+
* const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode;
|
|
1373
|
+
* ```
|
|
1374
|
+
*/
|
|
872
1375
|
getReferenceNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
|
|
1376
|
+
/**
|
|
1377
|
+
* Given a JS reference's name and type, this API finds an incoming JS
|
|
1378
|
+
* reference pointing to the hosting node.
|
|
1379
|
+
* @param edgeName the name of the incoming JavaScript reference
|
|
1380
|
+
* @param edgeType optional parameter specifying the type of the incoming
|
|
1381
|
+
* JavaScript reference
|
|
1382
|
+
* @returns the incoming edge that meets the specification
|
|
1383
|
+
*
|
|
1384
|
+
* * **Examples**:
|
|
1385
|
+
* ```typescript
|
|
1386
|
+
* // find one of the JS reference named "ref" pointing to node
|
|
1387
|
+
* const reference = node.getAnyReferrer('ref', 'property');
|
|
1388
|
+
* ```
|
|
1389
|
+
*/
|
|
873
1390
|
getAnyReferrer: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
|
|
1391
|
+
/**
|
|
1392
|
+
* Given a JS reference's name and type, this API finds one of the incoming JS
|
|
1393
|
+
* references pointing to the hosting node, and returns the JS heap object
|
|
1394
|
+
* containing the incoming reference.
|
|
1395
|
+
* @param edgeName the name of the incoming JavaScript reference
|
|
1396
|
+
* @param edgeType optional parameter specifying the type of the incoming
|
|
1397
|
+
* JavaScript reference
|
|
1398
|
+
* @returns the node containing the incoming JS reference that meets
|
|
1399
|
+
* the specification
|
|
1400
|
+
*
|
|
1401
|
+
* * **Examples**:
|
|
1402
|
+
* ```typescript
|
|
1403
|
+
* // find one of the JS heap object with a JS reference
|
|
1404
|
+
* // named "ref" pointing to node
|
|
1405
|
+
* const n1 = node.getAnyReferrerNode('ref', 'property');
|
|
1406
|
+
* // this is equivalent to
|
|
1407
|
+
* const n2 = node.getAnyReferrer('ref', 'property')?.fromNode;
|
|
1408
|
+
* ```
|
|
1409
|
+
*/
|
|
874
1410
|
getAnyReferrerNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
|
|
1411
|
+
/**
|
|
1412
|
+
* Given a JS reference's name and type, this API finds all the incoming JS
|
|
1413
|
+
* reference pointing to the hosting node.
|
|
1414
|
+
* @param edgeName the name of the incoming JavaScript reference
|
|
1415
|
+
* @param edgeType optional parameter specifying the type of the incoming
|
|
1416
|
+
* JavaScript reference
|
|
1417
|
+
* @returns an array containing all the incoming edges that
|
|
1418
|
+
* meet the specification
|
|
1419
|
+
*
|
|
1420
|
+
* * **Examples**:
|
|
1421
|
+
* ```typescript
|
|
1422
|
+
* // find all of of the JS reference named "ref" pointing to node
|
|
1423
|
+
* const referrers = node.getReferrers('ref', 'property');
|
|
1424
|
+
* ```
|
|
1425
|
+
*/
|
|
875
1426
|
getReferrers: (edgeName: string | number, edgeType?: string) => IHeapEdge[];
|
|
1427
|
+
/**
|
|
1428
|
+
* Given a JS reference's name and type, this API finds all of the incoming JS
|
|
1429
|
+
* references pointing to the hosting node, and returns an array containing
|
|
1430
|
+
* the hosting node for each of the incoming JS references.
|
|
1431
|
+
* @param edgeName the name of the incoming JavaScript reference
|
|
1432
|
+
* @param edgeType optional parameter specifying the type of the incoming
|
|
1433
|
+
* JavaScript reference
|
|
1434
|
+
* @returns an array containing the hosting nodes, with each node corresponds
|
|
1435
|
+
* to each incoming JS reference that meets the specification
|
|
1436
|
+
*
|
|
1437
|
+
* * **Examples**:
|
|
1438
|
+
* ```typescript
|
|
1439
|
+
* // find all of the JS heap object with a JS reference
|
|
1440
|
+
* // named "ref" pointing to node
|
|
1441
|
+
* const nodes1 = node.getReferrerNodes('ref', 'property');
|
|
1442
|
+
* // this is equivalent to
|
|
1443
|
+
* const nodes2 = node.getReferrers('ref', 'property')
|
|
1444
|
+
* .map(edge => edge.fromNode);
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
876
1447
|
getReferrerNodes: (edgeName: string | number, edgeType?: string) => IHeapNode[];
|
|
877
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* An `IHeapStringNode` instance represents a JS string in a heap snapshot.
|
|
1451
|
+
* A heap snapshot is generally a graph where graph nodes are JS heap objects
|
|
1452
|
+
* and graph edges are JS references among JS heap objects.
|
|
1453
|
+
*
|
|
1454
|
+
* @readonly it is not recommended to modify any `IHeapStringNode` instance
|
|
1455
|
+
*
|
|
1456
|
+
* * **Examples**: V8 or hermes heap snapshot can be parsed by the
|
|
1457
|
+
* {@link getHeapFromFile} API.
|
|
1458
|
+
*
|
|
1459
|
+
* ```typescript
|
|
1460
|
+
* import type {IHeapSnapshot, IHeapNode, IHeapStringNode} from '@memlab/core';
|
|
1461
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
1462
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
1463
|
+
*
|
|
1464
|
+
* (async function () {
|
|
1465
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
1466
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
1467
|
+
*
|
|
1468
|
+
* // iterate over each node (heap object)
|
|
1469
|
+
* heap.nodes.forEach((node: IHeapNode, i: number) => {
|
|
1470
|
+
* if (node.isString) {
|
|
1471
|
+
* const stringNode: IheapStringNode = node.toStringNode();
|
|
1472
|
+
* // get the string value
|
|
1473
|
+
* stringNode.stringValue;
|
|
1474
|
+
* }
|
|
1475
|
+
* });
|
|
1476
|
+
* })();
|
|
1477
|
+
* ```
|
|
1478
|
+
*/
|
|
878
1479
|
export interface IHeapStringNode extends IHeapNode {
|
|
1480
|
+
/**
|
|
1481
|
+
* get the string value of the JS string heap object associated with
|
|
1482
|
+
* this `IHeapStringNode` instance in heap
|
|
1483
|
+
*/
|
|
879
1484
|
stringValue: string;
|
|
880
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* A pseudo array containing all heap graph nodes (JS objects
|
|
1488
|
+
* in heap). A JS heap could contain millions of objects, so memlab uses
|
|
1489
|
+
* a pseudo array as the collection of all the heap nodes. The pseudo
|
|
1490
|
+
* array provides API to query and traverse all heap objects.
|
|
1491
|
+
*
|
|
1492
|
+
* @readonly modifying this pseudo array is not recommended
|
|
1493
|
+
*
|
|
1494
|
+
* * **Examples**:
|
|
1495
|
+
* ```typescript
|
|
1496
|
+
* import type {IHeapSnapshot, IHeapNodes} from '@memlab/core';
|
|
1497
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
1498
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
1499
|
+
*
|
|
1500
|
+
* (async function () {
|
|
1501
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
1502
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
1503
|
+
*
|
|
1504
|
+
* const nodes: IHeapNodes = heap.nodes;
|
|
1505
|
+
* nodes.length;
|
|
1506
|
+
* nodes.get(0);
|
|
1507
|
+
* nodes.forEach((node, i) => {
|
|
1508
|
+
* if (stopIteration) {
|
|
1509
|
+
* return false;
|
|
1510
|
+
* }
|
|
1511
|
+
* });
|
|
1512
|
+
* })();
|
|
1513
|
+
* ```
|
|
1514
|
+
*/
|
|
881
1515
|
export interface IHeapNodes {
|
|
1516
|
+
/**
|
|
1517
|
+
* The total number of nodes in heap graph (or JS objects in heap
|
|
1518
|
+
* snapshot).
|
|
1519
|
+
*/
|
|
882
1520
|
length: number;
|
|
883
|
-
|
|
1521
|
+
/**
|
|
1522
|
+
* get an {@link IHeapNode} element at the specified index
|
|
1523
|
+
* @param index the index of an element in the pseudo array, the index ranges
|
|
1524
|
+
* from 0 to array length - 1. Notice that this is not the heap node id.
|
|
1525
|
+
* @returns When 0 <= `index` < array.length, this API returns the element
|
|
1526
|
+
* at the specified index, otherwise it returns `null`.
|
|
1527
|
+
*/
|
|
1528
|
+
get(index: number): Nullable<IHeapNode>;
|
|
1529
|
+
/**
|
|
1530
|
+
* Iterate over all array elements and apply the callback
|
|
1531
|
+
* to each element in ascending order of element index.
|
|
1532
|
+
* @param callback the callback does not need to return any value, if
|
|
1533
|
+
* the callback returns `false` when iterating on element at index `i`,
|
|
1534
|
+
* then all elements after `i` won't be iterated.
|
|
1535
|
+
*/
|
|
884
1536
|
forEach(callback: (node: IHeapNode, index: number) => void | boolean): void;
|
|
1537
|
+
/** @internal */
|
|
885
1538
|
forEachTraceable(callback: (node: IHeapNode, index: number) => void | boolean): void;
|
|
886
1539
|
}
|
|
887
1540
|
/** @internal */
|
package/dist/lib/Utils.js
CHANGED
|
@@ -581,14 +581,14 @@ function getEdgeByNameAndType(node, edgeName, type) {
|
|
|
581
581
|
if (!node) {
|
|
582
582
|
return null;
|
|
583
583
|
}
|
|
584
|
-
return node.
|
|
584
|
+
return node.findAnyReference((edge) => edge.name_or_index === edgeName &&
|
|
585
585
|
(type === undefined || edge.type === type));
|
|
586
586
|
}
|
|
587
587
|
function getEdgeStartsWithName(node, prefix) {
|
|
588
588
|
if (!node) {
|
|
589
589
|
return null;
|
|
590
590
|
}
|
|
591
|
-
return node.
|
|
591
|
+
return node.findAnyReference(edge => typeof edge.name_or_index === 'string' &&
|
|
592
592
|
edge.name_or_index.startsWith(prefix));
|
|
593
593
|
}
|
|
594
594
|
function isStringNode(node) {
|
|
@@ -31,7 +31,7 @@ export default class HeapNode implements IHeapNode {
|
|
|
31
31
|
get trace_node_id(): number;
|
|
32
32
|
get references(): HeapEdge[];
|
|
33
33
|
forEachReference(callback: EdgeIterationCallback): void;
|
|
34
|
-
|
|
34
|
+
findAnyReference(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
|
|
35
35
|
findAnyReferrer(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
|
|
36
36
|
findReferrers(predicate: Predicator<IHeapEdge>): IHeapEdge[];
|
|
37
37
|
get referrers(): HeapEdge[];
|
|
@@ -82,6 +82,9 @@ class HeapSnapshot {
|
|
|
82
82
|
this.nodes = {
|
|
83
83
|
length: self._nodeCount,
|
|
84
84
|
get(idx) {
|
|
85
|
+
if (idx < 0 || idx >= self._nodeCount) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
85
88
|
return new HeapNode_1.default(self, idx);
|
|
86
89
|
},
|
|
87
90
|
forEach(cb) {
|
|
@@ -109,6 +112,9 @@ class HeapSnapshot {
|
|
|
109
112
|
this.edges = {
|
|
110
113
|
length: self._edgeCount,
|
|
111
114
|
get(idx) {
|
|
115
|
+
if (idx < 0 || idx >= self._edgeCount) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
112
118
|
return new HeapEdge_1.default(self, idx);
|
|
113
119
|
},
|
|
114
120
|
forEach(cb) {
|
|
@@ -180,7 +186,7 @@ class HeapSnapshot {
|
|
|
180
186
|
return false;
|
|
181
187
|
}
|
|
182
188
|
// check if the table has any weak reference to any object
|
|
183
|
-
const ref = table.
|
|
189
|
+
const ref = table.findAnyReference((edge) => edge.type === 'weak' && edge.toNode.name !== 'system / Oddball');
|
|
184
190
|
return ref != null;
|
|
185
191
|
}
|
|
186
192
|
getNodeById(id) {
|
|
@@ -159,7 +159,8 @@ class TraceFinder {
|
|
|
159
159
|
if (!Utils_1.default.isEssentialEdge(nodeIndex, edgeType, rootNodeIndex)) {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
const childNodeIndex = forwardEdges.get(edgeIndex)
|
|
162
|
+
const childNodeIndex = forwardEdges.get(edgeIndex)
|
|
163
|
+
.toNode.nodeIndex;
|
|
163
164
|
if (visited[childNodeIndex]) {
|
|
164
165
|
continue;
|
|
165
166
|
}
|
|
@@ -278,7 +279,8 @@ class TraceFinder {
|
|
|
278
279
|
if (!Utils_1.default.isEssentialEdge(ROOT_NODE_INDEX, edgeType, ROOT_NODE_INDEX)) {
|
|
279
280
|
continue;
|
|
280
281
|
}
|
|
281
|
-
const childNodeIndex = forwardEdges.get(edgeIndex).toNode
|
|
282
|
+
const childNodeIndex = forwardEdges.get(edgeIndex).toNode
|
|
283
|
+
.nodeIndex;
|
|
282
284
|
nodesWithOutdatedDominatorInfo[nodeIndex2PostOrderIndex[childNodeIndex]] = 1;
|
|
283
285
|
}
|
|
284
286
|
// now iterate through all nodes in the heap
|
|
@@ -34,7 +34,7 @@ export declare class NodeRecord implements IHeapNode {
|
|
|
34
34
|
get referrers(): IHeapEdge[];
|
|
35
35
|
toStringNode(): IHeapStringNode;
|
|
36
36
|
forEachReferrer(_callback: EdgeIterationCallback): void;
|
|
37
|
-
|
|
37
|
+
findAnyReference(): Nullable<IHeapEdge>;
|
|
38
38
|
findAnyReferrer(): Nullable<IHeapEdge>;
|
|
39
39
|
findReferrers(): IHeapEdge[];
|
|
40
40
|
set pathEdge(r: IHeapEdge);
|
|
@@ -70,8 +70,8 @@ class NodeRecord {
|
|
|
70
70
|
_callback) {
|
|
71
71
|
throw new Error('NodeRecord.forEachReferrer is not implemented');
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
throw new Error('NodeRecord.
|
|
73
|
+
findAnyReference() {
|
|
74
|
+
throw new Error('NodeRecord.findAnyReference is not implemented');
|
|
75
75
|
}
|
|
76
76
|
findAnyReferrer() {
|
|
77
77
|
throw new Error('NodeRecord.findAnyReferrer is not implemented');
|