@memlab/core 1.1.1 → 1.1.4

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.
@@ -89,6 +89,7 @@ export declare type CLIArgs = {
89
89
  export declare type Cookies = Array<{
90
90
  name: string;
91
91
  value: string;
92
+ domain?: string;
92
93
  }>;
93
94
  /** @internal */
94
95
  export interface IE2EScenarioSynthesizer {
@@ -96,7 +97,7 @@ export interface IE2EScenarioSynthesizer {
96
97
  getOrigin(): Nullable<string>;
97
98
  getDomain(): string;
98
99
  getDomainPrefixes(): string[];
99
- getCookieFile(visitPlan: IE2EScenarioVisitPlan): string | null;
100
+ getCookieFile(visitPlan: IE2EScenarioVisitPlan): Nullable<string>;
100
101
  getAvailableSteps(): IE2EStepBasic[];
101
102
  getNodeNameBlocklist(): string[];
102
103
  getEdgeNameBlocklist(): string[];
@@ -143,16 +144,49 @@ export declare type QuickExperiment = {
143
144
  group: string;
144
145
  };
145
146
  /**
146
- * The type for defining custom leak-filtering logic.
147
- * * **Examples**:
148
- * ```typescript
149
- * const scenario = {
147
+ * The `ILeakFilter` interface allows you to define a leak detector and
148
+ * customize the leak filtering logic in memlab (instead of using the
149
+ * built-in leak filters).
150
150
  *
151
- * };
151
+ * Use the leak filter definition in command line interface to filter
152
+ * leaks detected from browser interactions
153
+ * ```bash
154
+ * memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
155
+ * ```
156
+ *
157
+ * If you have already run `memlab run` or `memlab snapshot` which saved
158
+ * heap snapshot and other meta data on disk, use the following command
159
+ * to filter leaks based on those saved heap snapshots (query the default
160
+ * data location by `memlab get-default-work-dir`).
161
+ *
162
+ * ```bash
163
+ * memlab find-leaks --leak-filter <PATH TO leak-filter.js>
164
+ * ```
165
+ * Here is an example TypeScript file defining a leak filter.
166
+ * The command line interface only accepts compiled JavaScript file.
167
+ * You can also define the leak filter in JavaScript (without the
168
+ * type annotations.
169
+ *
170
+ * ```typescript
171
+ * import {IHeapNode, IHeapSnapshot, HeapNodeIdSet, utils} from '@memlab/core';
152
172
  *
153
- * let map = Object.create(null);
173
+ * function initMap(snapshot: IHeapSnapshot): Record<string, number> {
174
+ * const map = Object.create(null);
175
+ * snapshot.nodes.forEach(node => {
176
+ * if (node.type !== 'string') {
177
+ * return;
178
+ * }
179
+ * const str = utils.getStringNodeValue(node);
180
+ * if (str in map) {
181
+ * ++map[str];
182
+ * } else {
183
+ * map[str] = 1;
184
+ * }
185
+ * });
186
+ * return map;
187
+ * }
154
188
  * const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
155
- * map = initializeMapUsingSnapshot(snapshot);
189
+ * map = initMap(snapshot);
156
190
  * };
157
191
  *
158
192
  * // duplicated string with size > 1KB as memory leak
@@ -168,7 +202,81 @@ export declare type QuickExperiment = {
168
202
  * ```
169
203
  */
170
204
  export interface ILeakFilter {
205
+ /**
206
+ * Lifecycle function callback that is invoked initially once before
207
+ * the subsequent `leakFilter` function calls. This callback could
208
+ * be used to initialize some data stores or any one-off
209
+ * preprocessings.
210
+ *
211
+ * * **Parameters**:
212
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
213
+ * all browser interactions are done.
214
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
215
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
216
+ * allocated by the `action` call but not released after the `back` call
217
+ * in browser.
218
+ *
219
+ * * **Examples**:
220
+ * ```typescript
221
+ * module.exports = {
222
+ * beforeLeakFilter: (snapshot, leakedNodeIds) {
223
+ * // initialize some data stores
224
+ * },
225
+ * leakFilter(node, snapshot, leakedNodeIds) {
226
+ * // use the data stores
227
+ * },
228
+ * };
229
+ * ```
230
+ */
171
231
  beforeLeakFilter?: InitLeakFilterCallback;
232
+ /**
233
+ * This callback defines how you want to filter out the
234
+ * leaked objects. The callback is called for every node (JS heap
235
+ * object in browser) allocated by the `action` callback, but not
236
+ * released after the `back` callback. Those objects could be caches
237
+ * that are retained in memory on purpose, or they are memory leaks.
238
+ *
239
+ * This optional callback allows you to define your own algorithm
240
+ * to cherry pick memory leaks for specific JS program under test.
241
+ *
242
+ * If this optional callback is not defined, memlab will use its
243
+ * built-in leak filter, which considers detached DOM elements
244
+ * and unmounted Fiber nodes (detached from React Fiber tree) as
245
+ * memory leaks.
246
+ *
247
+ * * **Parameters**:
248
+ * * node: `IHeapNode` | one of the heap object allocated but not released.
249
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
250
+ * all browser interactions are done.
251
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
252
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
253
+ * allocated by the `action` call but not released after the `back` call
254
+ * in browser.
255
+ *
256
+ * * **Returns**: the boolean value indicating whether the given node in
257
+ * the snapshot should be considered as leaked.
258
+ *
259
+ *
260
+ * ```javascript
261
+ * // save as leak-filter.js
262
+ * module.exports = {
263
+ * leakFilter(node, snapshot, leakedNodeIds) {
264
+ * // any unreleased node (JS heap object) with 1MB+
265
+ * // retained size is considered a memory leak
266
+ * return node.retainedSize > 1000000;
267
+ * },
268
+ * };
269
+ * ```
270
+ *
271
+ * Use the leak filter definition in command line interface:
272
+ * ```bash
273
+ * memlab find-leaks --leak-filter <PATH TO leak-filter.js>
274
+ * ```
275
+ *
276
+ * ```bash
277
+ * memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
278
+ * ```
279
+ */
172
280
  leakFilter: LeakFilterCallback;
173
281
  }
174
282
  /**
@@ -217,6 +325,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
217
325
  * url: () => 'https://www.npmjs.com/',
218
326
  * action: async () => ... ,
219
327
  * back: async () => ... ,
328
+ * cookies: () => ... , // optional
329
+ * repeat: () => ... , // optional
330
+ * ...
220
331
  * };
221
332
  * ```
222
333
  *
@@ -230,6 +341,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
230
341
  * url: () => 'https://www.facebook.com',
231
342
  * action: async () => ... ,
232
343
  * back: async () => ... ,
344
+ * cookies: () => ... , // optional
345
+ * repeat: () => ... , // optional
346
+ * ...
233
347
  * };
234
348
  * const leaks = await run({scenario});
235
349
  * })();
@@ -243,20 +357,30 @@ export interface IScenario {
243
357
  /**
244
358
  * If the page you are running memlab against requires authentication or
245
359
  * specific cookie(s) to be set, you can pass them as
246
- * a list of <name, value> pairs.
360
+ * a list of `<name, value, domain>` tuples.
361
+ *
362
+ * **Note**: please make sure that you provide the correct `domain` field for
363
+ * the cookies tuples. If no `domain` field is specified, memlab will try
364
+ * to fill in a domain based on the `url` callback.
365
+ * For example, when the `domain` field is absent,
366
+ * memlab will auto fill in `.facebook.com` as domain base
367
+ * on the initial page load's url: `https://www.facebook.com/`.
368
+ *
247
369
  * @returns cookie list
248
370
  * * **Examples**:
249
371
  * ```typescript
250
372
  * const scenario = {
251
373
  * url: () => 'https://www.facebook.com/',
252
374
  * cookies: () => [
253
- * {"name":"cm_j","value":"none"},
254
- * {"name":"datr","value":"yJvIY..."},
255
- * {"name":"c_user","value":"8917..."},
256
- * {"name":"xs","value":"95:9WQ..."},
375
+ * {name:'cm_j', value: 'none', domain: '.facebook.com'},
376
+ * {name:'datr', value: 'yJvIY...', domain: '.facebook.com'},
377
+ * {name:'c_user', value: '8917...', domain: '.facebook.com'},
378
+ * {name:'xs', value: '95:9WQ...', domain: '.facebook.com'},
257
379
  * // ...
258
380
  * ],
259
381
  * };
382
+ *
383
+ * module.exports = scenario;
260
384
  * ```
261
385
  */
262
386
  cookies?: () => Cookies;
@@ -269,6 +393,8 @@ export interface IScenario {
269
393
  * const scenario = {
270
394
  * url: () => 'https://www.npmjs.com/',
271
395
  * };
396
+ *
397
+ * module.exports = scenario;
272
398
  * ```
273
399
  * If a test scenario only specifies the `url` callback (without the `action`
274
400
  * callback), memlab will try to detect memory leaks from the initial page
@@ -297,6 +423,8 @@ export interface IScenario {
297
423
  * await page.click('a[href="/back"]');
298
424
  * },
299
425
  * }
426
+ *
427
+ * module.exports = scenario;
300
428
  * ```
301
429
  * Note: always clean up external puppeteer references to JS objects
302
430
  * in the browser context.
@@ -314,6 +442,8 @@ export interface IScenario {
314
442
  * },
315
443
  * back: async (page) => ... ,
316
444
  * }
445
+ *
446
+ * module.exports = scenario;
317
447
  ```
318
448
  */
319
449
  action?: InteractionsCallback;
@@ -354,7 +484,7 @@ export interface IScenario {
354
484
  repeat?: () => number;
355
485
  /**
356
486
  * Optional callback function that checks if the web page is loaded
357
- * after for initial page loading and subsequent browser interactions.
487
+ * for the initial page load and subsequent browser interactions.
358
488
  *
359
489
  * If this callback is not provided, memlab by default
360
490
  * considers a navigation to be finished when there are no network
@@ -390,7 +520,7 @@ export interface IScenario {
390
520
  /**
391
521
  * Lifecycle function callback that is invoked initially once before
392
522
  * the subsequent `leakFilter` function calls. This callback could
393
- * be used to initialize some data stores or to do some one-off
523
+ * be used to initialize some data stores or to any one-off
394
524
  * preprocessings.
395
525
  *
396
526
  * * **Parameters**:
@@ -415,7 +545,7 @@ export interface IScenario {
415
545
  */
416
546
  beforeLeakFilter?: InitLeakFilterCallback;
417
547
  /**
418
- * This callback that defines how you want to filter out the
548
+ * This callback defines how you want to filter out the
419
549
  * leaked objects. The callback is called for every node (JS heap
420
550
  * object in browser) allocated by the `action` callback, but not
421
551
  * released after the `back` callback. Those objects could be caches
@@ -606,86 +736,818 @@ export declare type RunMetaInfo = {
606
736
  type: string;
607
737
  browserInfo: IBrowserInfo;
608
738
  };
739
+ /**
740
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
741
+ * and graph edges are JS references among JS heap objects. For more details
742
+ * on the structure of nodes and edges in the heap graph, check out
743
+ * {@link IHeapNode} and {@link IHeapEdge}.
744
+ */
609
745
  export interface IHeapSnapshot {
746
+ /** @internal */
610
747
  snapshot: RawHeapSnapshot;
748
+ /**
749
+ * A pseudo array containing all heap graph nodes (JS objects in heap).
750
+ * A JS heap could contain millions of heap objects, so memlab uses
751
+ * a pseudo array as the collection of all the heap objects. The pseudo
752
+ * array provides API to query and traverse all heap objects.
753
+ *
754
+ * * **Examples**:
755
+ * ```typescript
756
+ * import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
757
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
758
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
759
+ *
760
+ * (async function () {
761
+ * const heapFile = dumpNodeHeapSnapshot();
762
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
763
+ *
764
+ * // get the total number of heap objects
765
+ * heap.nodes.length;
766
+ *
767
+ * heap.nodes.forEach((node: IHeapNode) => {
768
+ * // traverse each heap object
769
+ * });
770
+ * })();
771
+ * ```
772
+ */
611
773
  nodes: IHeapNodes;
774
+ /**
775
+ * A pseudo array containing all heap graph edges (references to heap objects
776
+ * in heap). A JS heap could contain millions of references, so memlab uses
777
+ * a pseudo array as the collection of all the heap edges. The pseudo
778
+ * array provides API to query and traverse all heap references.
779
+ *
780
+ * * **Examples**:
781
+ * ```typescript
782
+ * import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
783
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
784
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
785
+ *
786
+ * (async function () {
787
+ * const heapFile = dumpNodeHeapSnapshot();
788
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
789
+ *
790
+ * // get the total number of heap references
791
+ * heap.edges.length;
792
+ *
793
+ * heap.edges.forEach((edge: IHeapEdge) => {
794
+ * // traverse each reference in the heap
795
+ * });
796
+ * })();
797
+ * ```
798
+ */
612
799
  edges: IHeapEdges;
800
+ /**
801
+ * If you have the id of a heap node (JS object in heap), use this API
802
+ * to get an {@link IHeapNode} associated with the id.
803
+ * @param id id of the heap node (JS object in heap) you would like to query
804
+ * @returns the API returns `null` if no heap object has the specified id.
805
+ *
806
+ * * **Examples**:
807
+ * ```typescript
808
+ * import type {IHeapSnapshot} from '@memlab/core';
809
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
810
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
811
+ *
812
+ * (async function () {
813
+ * const heapFile = dumpNodeHeapSnapshot();
814
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
815
+ *
816
+ * const node = heap.getNodeById(351);
817
+ * node?.id; // should be 351
818
+ * })();
819
+ * ```
820
+ */
613
821
  getNodeById(id: number): Nullable<IHeapNode>;
614
- clearShortestPathInfo(): void;
822
+ /**
823
+ * Search for the heap and check if there is any JS object instance with
824
+ * a specified constructor name.
825
+ * @param className The contructor name of the object instance
826
+ * @returns `true` if there is at least one such object in the heap
827
+ *
828
+ * * **Examples**: you can write a jest unit test with memory assertions:
829
+ * ```typescript
830
+ * // save as example.test.ts
831
+ * import type {IHeapSnapshot, Nullable} from '@memlab/core';
832
+ * import {config, getNodeInnocentHeap} from '@memlab/core';
833
+ *
834
+ * class TestObject {
835
+ * public arr1 = [1, 2, 3];
836
+ * public arr2 = ['1', '2', '3'];
837
+ * }
838
+ *
839
+ * test('memory test with heap assertion', async () => {
840
+ * config.muteConsole = true; // no console output
841
+ *
842
+ * let obj: Nullable<TestObject> = new TestObject();
843
+ * // get a heap snapshot of the current program state
844
+ * let heap: IHeapSnapshot = await getNodeInnocentHeap();
845
+ *
846
+ * // call some function that may add references to obj
847
+ * rabbitHole(obj)
848
+ *
849
+ * expect(heap.hasObjectWithClassName('TestObject')).toBe(true);
850
+ * obj = null;
851
+ *
852
+ * heap = await getNodeInnocentHeap();
853
+ * // if rabbitHole does not have any side effect that
854
+ * // adds new references to obj, then obj can be GCed
855
+ * expect(heap.hasObjectWithClassName('TestObject')).toBe(false);
856
+ *
857
+ * }, 30000);
858
+ * ```
859
+ */
615
860
  hasObjectWithClassName(className: string): boolean;
861
+ /**
862
+ * Search for the heap and get one of the JS object instances with
863
+ * a specified constructor name (if there is any).
864
+ * @param className The contructor name of the object instance
865
+ * @returns a handle pointing to any one of the object instances, returns
866
+ * `null` if no such object exists in the heap.
867
+ *
868
+ * * **Examples**:
869
+ * ```typescript
870
+ * import type {IHeapSnapshot} from '@memlab/core';
871
+ * import {getNodeInnocentHeap} from '@memlab/core';
872
+ *
873
+ * class TestObject {
874
+ * public arr1 = [1, 2, 3];
875
+ * public arr2 = ['1', '2', '3'];
876
+ * }
877
+ *
878
+ * (async function () {
879
+ * const obj = new TestObject();
880
+ * // get a heap snapshot of the current program state
881
+ * const heap: IHeapSnapshot = await getNodeInnocentHeap();
882
+ *
883
+ * const node = heap.getAnyObjectWithClassName('TestObject');
884
+ * console.log(node?.name); // should be 'TestObject'
885
+ * })();
886
+ * ```
887
+ */
616
888
  getAnyObjectWithClassName(className: string): Nullable<IHeapNode>;
889
+ /**
890
+ * Search for the heap and check if there is any JS object instance with
891
+ * a specified property name.
892
+ * @param nameOrIndex The property name (string) or element index (number)
893
+ * on the object instance
894
+ * @returns returns `true` if there is at least one such object in the heap
895
+ *
896
+ * * **Examples**:
897
+ * ```typescript
898
+ * import type {IHeapSnapshot} from '@memlab/core';
899
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
900
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
901
+ *
902
+ * (async function () {
903
+ * // eslint-disable-next-line @typescript-eslint/no-unused-vars
904
+ * const object = {'memlab-test-heap-property': 'memlab-test-heap-value'};
905
+ *
906
+ * const heapFile = dumpNodeHeapSnapshot();
907
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
908
+ *
909
+ * // should be true
910
+ * console.log(heap.hasObjectWithPropertyName('memlab-test-heap-property'));
911
+ * })();
912
+ * ```
913
+ */
617
914
  hasObjectWithPropertyName(nameOrIndex: string | number): boolean;
915
+ /**
916
+ * Search for the heap and check if there is any JS object instance with
917
+ * a marker tagged by {@link tagObject}.
918
+ *
919
+ * The `tagObject` API does not modify the object instance in any way
920
+ * (e.g., no additional or hidden properties added to the tagged object).
921
+ *
922
+ * @param tag marker name on the object instances tagged by {@link tagObject}
923
+ * @returns returns `true` if there is at least one such object in the heap
924
+ *
925
+ * ```typescript
926
+ * import type {IHeapSnapshot, AnyValue} from '@memlab/core';
927
+ * import {config, getNodeInnocentHeap, tagObject} from '@memlab/core';
928
+ *
929
+ * test('memory test', async () => {
930
+ * config.muteConsole = true;
931
+ * const o1: AnyValue = {};
932
+ * let o2: AnyValue = {};
933
+ *
934
+ * // tag o1 with marker: "memlab-mark-1", does not modify o1 in any way
935
+ * tagObject(o1, 'memlab-mark-1');
936
+ * // tag o2 with marker: "memlab-mark-2", does not modify o2 in any way
937
+ * tagObject(o2, 'memlab-mark-2');
938
+ *
939
+ * o2 = null;
940
+ *
941
+ * const heap: IHeapSnapshot = await getNodeInnocentHeap();
942
+ *
943
+ * // expect object with marker "memlab-mark-1" exists
944
+ * expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
945
+ *
946
+ * // expect object with marker "memlab-mark-2" can be GCed
947
+ * expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
948
+ *
949
+ * }, 30000);
950
+ * ```
951
+ */
618
952
  hasObjectWithTag(tag: string): boolean;
953
+ /** @internal */
954
+ clearShortestPathInfo(): void;
619
955
  }
956
+ /**
957
+ * An `IHeapLocation` instance contains a source location information
958
+ * associated with a JS heap object.
959
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
960
+ * and graph edges are JS references among JS heap objects.
961
+ *
962
+ * @readonly it is not recommended to modify any `IHeapLocation` instance
963
+ *
964
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
965
+ * {@link getHeapFromFile} API.
966
+ *
967
+ * ```typescript
968
+ * import type {IHeapSnapshot, IHeapNode, IHeapLocation} from '@memlab/core';
969
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
970
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
971
+ *
972
+ * (async function () {
973
+ * const heapFile = dumpNodeHeapSnapshot();
974
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
975
+ *
976
+ * // iterate over each node (heap object)
977
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
978
+ * const location: Nullable<IHeapLocation> = node.location;
979
+ * if (location) {
980
+ * // use the location API here
981
+ * location.line;
982
+ * // ...
983
+ * }
984
+ * });
985
+ * })();
986
+ * ```
987
+ */
620
988
  export interface IHeapLocation {
989
+ /**
990
+ * get the {@link IHeapSnapshot} containing this location instance
991
+ */
621
992
  snapshot: IHeapSnapshot;
993
+ /**
994
+ * get the script ID of the source file
995
+ */
622
996
  script_id: number;
997
+ /**
998
+ * get the line number
999
+ */
623
1000
  line: number;
1001
+ /**
1002
+ * get the column number
1003
+ */
624
1004
  column: number;
625
1005
  }
1006
+ /** @internal */
626
1007
  export interface IHeapEdgeBasic {
1008
+ /**
1009
+ * name of the JS reference. If this is a reference to an array element
1010
+ * or internal table element, it is an numeric index
1011
+ */
627
1012
  name_or_index: number | string;
1013
+ /**
1014
+ * type of the JS reference, all types:
1015
+ * `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak`
1016
+ */
628
1017
  type: string;
629
1018
  }
1019
+ /**
1020
+ * An `IHeapEdge` instance represents a JS reference in a heap snapshot.
1021
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1022
+ * and graph edges are JS references among JS heap objects.
1023
+ *
1024
+ * @readonly it is not recommended to modify any `IHeapEdge` instance
1025
+ *
1026
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1027
+ * {@link getHeapFromFile} API.
1028
+ *
1029
+ * ```typescript
1030
+ * import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
1031
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1032
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1033
+ *
1034
+ * (async function () {
1035
+ * const heapFile = dumpNodeHeapSnapshot();
1036
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1037
+ *
1038
+ * // iterate over each edge (JS reference in heap)
1039
+ * heap.edges.forEach((edge: IHeapEdge, i: number) => {
1040
+ * // use the heap edge APIs here
1041
+ * const nameOrIndex = edge.name_or_index;
1042
+ * // ...
1043
+ * });
1044
+ * })();
1045
+ * ```
1046
+ */
630
1047
  export interface IHeapEdge extends IHeapEdgeBasic {
1048
+ /**
1049
+ * get the {@link IHeapSnapshot} containing this JS reference
1050
+ */
631
1051
  snapshot: IHeapSnapshot;
1052
+ /**
1053
+ * index of this JS reference inside the `edge.snapshot.edges` pseudo array
1054
+ */
632
1055
  edgeIndex: number;
1056
+ /**
1057
+ * if `true`, means this is a reference to an array element
1058
+ * or internal table element (`edge.name_or_index` will return a number),
1059
+ * otherwise this is a reference with a string name (`edge.name_or_index`
1060
+ * will return a string)
1061
+ */
633
1062
  is_index: boolean;
1063
+ /**
1064
+ * the index of the JS heap object pointed to by this reference
1065
+ */
634
1066
  to_node: number;
1067
+ /**
1068
+ * returns an {@link IHeapNode} instance representing the JS heap object
1069
+ * pointed to by this reference
1070
+ */
635
1071
  toNode: IHeapNode;
1072
+ /**
1073
+ * returns an {@link IHeapNode} instance representing the hosting
1074
+ * JS heap object where this reference starts
1075
+ */
636
1076
  fromNode: IHeapNode;
637
1077
  }
1078
+ /**
1079
+ * A pseudo array containing all heap graph edges (references to heap objects
1080
+ * in heap). A JS heap could contain millions of references, so memlab uses
1081
+ * a pseudo array as the collection of all the heap edges. The pseudo
1082
+ * array provides API to query and traverse all heap references.
1083
+ *
1084
+ * @readonly modifying this pseudo array is not recommended
1085
+ *
1086
+ * * **Examples**:
1087
+ * ```typescript
1088
+ * import type {IHeapSnapshot, IHeapEdges} from '@memlab/core';
1089
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1090
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1091
+ *
1092
+ * (async function () {
1093
+ * const heapFile = dumpNodeHeapSnapshot();
1094
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1095
+ *
1096
+ * const edges: IHeapEdges = heap.edges;
1097
+ * edges.length;
1098
+ * edges.get(0);
1099
+ * edges.forEach((edge, i) => {
1100
+ * if (stopIteration) {
1101
+ * return false;
1102
+ * }
1103
+ * });
1104
+ * })();
1105
+ * ```
1106
+ */
638
1107
  export interface IHeapEdges {
1108
+ /**
1109
+ * The total number of edges in heap graph (or JS references in heap
1110
+ * snapshot).
1111
+ */
639
1112
  length: number;
640
- get(index: number): IHeapEdge;
1113
+ /**
1114
+ * get an {@link IHeapEdge} element at the specified index
1115
+ * @param index the index of an element in the pseudo array, the index ranges
1116
+ * from 0 to array length - 1. Notice that this is not the heap node id.
1117
+ * @returns When 0 <= `index` < array.length, this API returns the element
1118
+ * at the specified index, otherwise it returns `null`.
1119
+ */
1120
+ get(index: number): Nullable<IHeapEdge>;
1121
+ /**
1122
+ * Iterate over all array elements and apply the callback
1123
+ * to each element in ascending order of element index.
1124
+ * @param callback the callback does not need to return any value, if
1125
+ * the callback returns `false` when iterating on element at index `i`,
1126
+ * then all elements after `i` won't be iterated.
1127
+ */
641
1128
  forEach(callback: (edge: IHeapEdge, index: number) => void | boolean): void;
642
1129
  }
1130
+ /** @internal */
643
1131
  export interface IHeapNodeBasic {
1132
+ /**
1133
+ * the type of the heap node object. All possible types:
1134
+ * This is engine-specific, for example all types in V8:
1135
+ * `hidden`, `array`, `string`, `object`, `code`, `closure`, `regexp`,
1136
+ * `number`, `native`, `synthetic`, `concatenated string`, `sliced string`,
1137
+ * `symbol`, `bigint`
1138
+ */
644
1139
  type: string;
1140
+ /**
1141
+ * this is the `name` field associated with the heap object,
1142
+ * for JS object instances (type `object`), `name` is the constructor's name
1143
+ * of the object instance. for `string`, `name` is the string value.
1144
+ */
645
1145
  name: string;
1146
+ /**
1147
+ * unique id of the heap object
1148
+ */
646
1149
  id: number;
647
1150
  }
648
1151
  export declare type EdgeIterationCallback = (edge: IHeapEdge) => Optional<{
649
1152
  stop: boolean;
650
1153
  }>;
1154
+ /**
1155
+ * An `IHeapNode` instance represents a JS heap object in a heap snapshot.
1156
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1157
+ * and graph edges are JS references among JS heap objects.
1158
+ *
1159
+ * @readonly it is not recommended to modify any `IHeapNode` instance
1160
+ *
1161
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1162
+ * {@link getHeapFromFile} API.
1163
+ *
1164
+ * ```typescript
1165
+ * import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
1166
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1167
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1168
+ *
1169
+ * (async function () {
1170
+ * const heapFile = dumpNodeHeapSnapshot();
1171
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1172
+ *
1173
+ * // iterate over each node (heap object)
1174
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
1175
+ * // use the heap node APIs here
1176
+ * const id = node.id;
1177
+ * const type = node.type;
1178
+ * // ...
1179
+ * });
1180
+ * })();
1181
+ * ```
1182
+ */
651
1183
  export interface IHeapNode extends IHeapNodeBasic {
1184
+ /**
1185
+ * get the {@link IHeapSnapshot} containing this heap object
1186
+ */
652
1187
  snapshot: IHeapSnapshot;
1188
+ /**
1189
+ * * If the heap object is a DOM element and the DOM element is detached
1190
+ * from the DOM tree, `is_detached` will be `true`;
1191
+ * * If the heap object is a React Fiber node and the Fiber node is unmounted
1192
+ * from the React Fiber tree, `is_detached` will be `true`;
1193
+ * otherwise it will be `false`
1194
+ */
653
1195
  is_detached: boolean;
1196
+ /** @internal */
654
1197
  detachState: number;
1198
+ /** @internal */
655
1199
  markAsDetached(): void;
1200
+ /** @internal */
656
1201
  attributes: number;
1202
+ /**
1203
+ * The *shallow size* of the heap object (i.e., the size of memory that is held
1204
+ * by the object itself.). For difference between **shallow size** and
1205
+ * **retained size**, check out
1206
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
1207
+ */
657
1208
  self_size: number;
1209
+ /**
1210
+ * The total number of outgoing JS references (including engine-internal,
1211
+ * native, and JS references).
1212
+ */
658
1213
  edge_count: number;
1214
+ /** @internal */
659
1215
  trace_node_id: number;
1216
+ /**
1217
+ * Get a JS array containing all outgoing JS references from this heap object
1218
+ * (including engine-internal, native, and JS references).
1219
+ */
660
1220
  references: IHeapEdge[];
1221
+ /**
1222
+ * Get a JS array containing all incoming JS references pointing to this heap
1223
+ * object (including engine-internal, native, and JS references).
1224
+ */
661
1225
  referrers: IHeapEdge[];
1226
+ /**
1227
+ * The incoming edge which leads to the parent node
1228
+ * on the shortest path to GC root.
1229
+ */
662
1230
  pathEdge: IHeapEdge | null;
1231
+ /**
1232
+ * index of this heap object inside the `node.snapshot.nodes` pseudo array
1233
+ */
663
1234
  nodeIndex: number;
1235
+ /**
1236
+ * The *retained size* of the heap object (i.e., the total size of memory that
1237
+ * could be released if this object is released). For difference between
1238
+ * **retained size** and **shallow size**, check out
1239
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
1240
+ */
664
1241
  retainedSize: number;
665
- dominatorNode: IHeapNode | null;
666
- location: IHeapLocation | null;
1242
+ /**
1243
+ * get the dominator node of this node. If the dominator node gets released
1244
+ * there will be no path from GC to this node, and therefore this node can
1245
+ * also be released.
1246
+ * For more information on what a dominator node is, please check out
1247
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators).
1248
+ */
1249
+ dominatorNode: Nullable<IHeapNode>;
1250
+ /**
1251
+ * source location information of this heap object (if it is recorded by
1252
+ * the heap snapshot).
1253
+ */
1254
+ location: Nullable<IHeapLocation>;
1255
+ /** @internal */
667
1256
  highlight?: boolean;
1257
+ /**
1258
+ * check if this a string node (normal string node, concatenated string node
1259
+ * or sliced string node)
1260
+ */
668
1261
  isString: boolean;
1262
+ /**
1263
+ * convert to an {@link IHeapStringNode} object if this node is a string node.
1264
+ * The {@link IHeapStringNode} object supports querying the string content
1265
+ * inside the string node.
1266
+ */
669
1267
  toStringNode(): Nullable<IHeapStringNode>;
1268
+ /**
1269
+ * executes a provided callback once for each JavaScript reference in the
1270
+ * hosting node (or outgoing edges from the node)
1271
+ * @param callback the callback for each outgoing JavaScript reference
1272
+ * @returns this API returns void
1273
+ *
1274
+ * * **Examples**:
1275
+ * ```typescript
1276
+ * node.forEachReference((edge: IHeapEdge) => {
1277
+ * // process edge ...
1278
+ *
1279
+ * // if no need to iterate over remaining edges after
1280
+ * // the current edge in the node.references list
1281
+ * return {stop: true};
1282
+ * });
1283
+ * ```
1284
+ */
670
1285
  forEachReference(callback: EdgeIterationCallback): void;
1286
+ /**
1287
+ * executes a provided callback once for each JavaScript reference pointing
1288
+ * to the hosting node (or incoming edges to the node)
1289
+ * @param callback the callback for each incoming JavaScript reference
1290
+ * @returns this API returns void
1291
+ *
1292
+ * * **Examples**:
1293
+ * ```typescript
1294
+ * node.forEachReferrer((edge: IHeapEdge) => {
1295
+ * // process edge ...
1296
+ *
1297
+ * // if no need to iterate over remaining edges after
1298
+ * // the current edge in the node.referrers list
1299
+ * return {stop: true};
1300
+ * });
1301
+ * ```
1302
+ */
671
1303
  forEachReferrer(callback: EdgeIterationCallback): void;
672
- findReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1304
+ /**
1305
+ * executes a provided predicate callback once for each JavaScript reference
1306
+ * in the hosting node (or outgoing edges from the node) until the predicate
1307
+ * returns `true`
1308
+ * @param predicate the callback for each outgoing JavaScript reference
1309
+ * @returns the first outgoing edge for which the predicate returns `true`,
1310
+ * otherwise returns `null` if no such edge is found.
1311
+ *
1312
+ * * **Examples**:
1313
+ * ```typescript
1314
+ * const reference = node.findAnyReference((edge: IHeapEdge) => {
1315
+ * // find the outgoing reference with name "ref"
1316
+ * return edge.name_or_index === 'ref';
1317
+ * });
1318
+ * ```
1319
+ */
1320
+ findAnyReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1321
+ /**
1322
+ * executes a provided predicate callback once for each JavaScript reference
1323
+ * pointing to the hosting node (or incoming edges to the node) until the
1324
+ * predicate returns `true`
1325
+ * @param predicate the callback for each incoming JavaScript reference
1326
+ * @returns the first incoming edge for which the predicate returns `true`,
1327
+ * otherwise returns `null` if no such edge is found.
1328
+ *
1329
+ * * **Examples**:
1330
+ * ```typescript
1331
+ * const referrer = node.findAnyReferrer((edge: IHeapEdge) => {
1332
+ * // find the incoming reference with name "ref"
1333
+ * return edge.name_or_index === 'ref';
1334
+ * });
1335
+ * ```
1336
+ */
673
1337
  findAnyReferrer: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1338
+ /**
1339
+ * executes a provided predicate callback once for each JavaScript reference
1340
+ * pointing to the hosting node (or incoming edges to the node)
1341
+ * @param predicate the callback for each incoming JavaScript reference
1342
+ * @returns an array containing all the incoming edges for which the
1343
+ * predicate returns `true`, otherwise returns an empty array if no such
1344
+ * edge is found.
1345
+ *
1346
+ * * **Examples**:
1347
+ * ```typescript
1348
+ * const referrers = node.findReferrers((edge: IHeapEdge) => {
1349
+ * // find all the incoming references with name "ref"
1350
+ * return edge.name_or_index === 'ref';
1351
+ * });
1352
+ * ```
1353
+ */
674
1354
  findReferrers: (predicate: Predicator<IHeapEdge>) => IHeapEdge[];
1355
+ /**
1356
+ * Given a JS reference's name and type, this API finds an outgoing JS
1357
+ * reference from the hosting node.
1358
+ * @param edgeName the name of the outgoing JavaScript reference
1359
+ * @param edgeType optional parameter specifying the type of the outgoing
1360
+ * JavaScript reference
1361
+ * @returns the outgoing edge that meets the specification
1362
+ *
1363
+ * * **Examples**:
1364
+ * ```typescript
1365
+ * // find the internal reference to node's hidden class
1366
+ * const reference = node.getReference('map', 'hidden');
1367
+ * ```
1368
+ */
675
1369
  getReference: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
1370
+ /**
1371
+ * Given a JS reference's name and type, this API finds the outgoing JS
1372
+ * reference from the hosting node, and returns the JS heap object pointed to
1373
+ * by the outgoing JS reference.
1374
+ * @param edgeName the name of the outgoing JavaScript reference
1375
+ * @param edgeType optional parameter specifying the type of the outgoing
1376
+ * JavaScript reference
1377
+ * @returns the node pointed to by the outgoing reference that meets
1378
+ * the specification
1379
+ *
1380
+ * * **Examples**:
1381
+ * ```typescript
1382
+ * // find the node's hidden class
1383
+ * const hiddenClassNode = node.getReferenceNode('map', 'hidden');
1384
+ * // this is equivalent to
1385
+ * const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode;
1386
+ * ```
1387
+ */
676
1388
  getReferenceNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
1389
+ /**
1390
+ * Given a JS reference's name and type, this API finds an incoming JS
1391
+ * reference pointing to the hosting node.
1392
+ * @param edgeName the name of the incoming JavaScript reference
1393
+ * @param edgeType optional parameter specifying the type of the incoming
1394
+ * JavaScript reference
1395
+ * @returns the incoming edge that meets the specification
1396
+ *
1397
+ * * **Examples**:
1398
+ * ```typescript
1399
+ * // find one of the JS reference named "ref" pointing to node
1400
+ * const reference = node.getAnyReferrer('ref', 'property');
1401
+ * ```
1402
+ */
677
1403
  getAnyReferrer: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
1404
+ /**
1405
+ * Given a JS reference's name and type, this API finds one of the incoming JS
1406
+ * references pointing to the hosting node, and returns the JS heap object
1407
+ * containing the incoming reference.
1408
+ * @param edgeName the name of the incoming JavaScript reference
1409
+ * @param edgeType optional parameter specifying the type of the incoming
1410
+ * JavaScript reference
1411
+ * @returns the node containing the incoming JS reference that meets
1412
+ * the specification
1413
+ *
1414
+ * * **Examples**:
1415
+ * ```typescript
1416
+ * // find one of the JS heap object with a JS reference
1417
+ * // named "ref" pointing to node
1418
+ * const n1 = node.getAnyReferrerNode('ref', 'property');
1419
+ * // this is equivalent to
1420
+ * const n2 = node.getAnyReferrer('ref', 'property')?.fromNode;
1421
+ * ```
1422
+ */
678
1423
  getAnyReferrerNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
1424
+ /**
1425
+ * Given a JS reference's name and type, this API finds all the incoming JS
1426
+ * reference pointing to the hosting node.
1427
+ * @param edgeName the name of the incoming JavaScript reference
1428
+ * @param edgeType optional parameter specifying the type of the incoming
1429
+ * JavaScript reference
1430
+ * @returns an array containing all the incoming edges that
1431
+ * meet the specification
1432
+ *
1433
+ * * **Examples**:
1434
+ * ```typescript
1435
+ * // find all of of the JS reference named "ref" pointing to node
1436
+ * const referrers = node.getReferrers('ref', 'property');
1437
+ * ```
1438
+ */
679
1439
  getReferrers: (edgeName: string | number, edgeType?: string) => IHeapEdge[];
1440
+ /**
1441
+ * Given a JS reference's name and type, this API finds all of the incoming JS
1442
+ * references pointing to the hosting node, and returns an array containing
1443
+ * the hosting node for each of the incoming JS references.
1444
+ * @param edgeName the name of the incoming JavaScript reference
1445
+ * @param edgeType optional parameter specifying the type of the incoming
1446
+ * JavaScript reference
1447
+ * @returns an array containing the hosting nodes, with each node corresponds
1448
+ * to each incoming JS reference that meets the specification
1449
+ *
1450
+ * * **Examples**:
1451
+ * ```typescript
1452
+ * // find all of the JS heap object with a JS reference
1453
+ * // named "ref" pointing to node
1454
+ * const nodes1 = node.getReferrerNodes('ref', 'property');
1455
+ * // this is equivalent to
1456
+ * const nodes2 = node.getReferrers('ref', 'property')
1457
+ * .map(edge => edge.fromNode);
1458
+ * ```
1459
+ */
680
1460
  getReferrerNodes: (edgeName: string | number, edgeType?: string) => IHeapNode[];
681
1461
  }
1462
+ /**
1463
+ * An `IHeapStringNode` instance represents a JS string in a heap snapshot.
1464
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1465
+ * and graph edges are JS references among JS heap objects.
1466
+ *
1467
+ * @readonly it is not recommended to modify any `IHeapStringNode` instance
1468
+ *
1469
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1470
+ * {@link getHeapFromFile} API.
1471
+ *
1472
+ * ```typescript
1473
+ * import type {IHeapSnapshot, IHeapNode, IHeapStringNode} from '@memlab/core';
1474
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1475
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1476
+ *
1477
+ * (async function () {
1478
+ * const heapFile = dumpNodeHeapSnapshot();
1479
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1480
+ *
1481
+ * // iterate over each node (heap object)
1482
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
1483
+ * if (node.isString) {
1484
+ * const stringNode: IheapStringNode = node.toStringNode();
1485
+ * // get the string value
1486
+ * stringNode.stringValue;
1487
+ * }
1488
+ * });
1489
+ * })();
1490
+ * ```
1491
+ */
682
1492
  export interface IHeapStringNode extends IHeapNode {
1493
+ /**
1494
+ * get the string value of the JS string heap object associated with
1495
+ * this `IHeapStringNode` instance in heap
1496
+ */
683
1497
  stringValue: string;
684
1498
  }
1499
+ /**
1500
+ * A pseudo array containing all heap graph nodes (JS objects
1501
+ * in heap). A JS heap could contain millions of objects, so memlab uses
1502
+ * a pseudo array as the collection of all the heap nodes. The pseudo
1503
+ * array provides API to query and traverse all heap objects.
1504
+ *
1505
+ * @readonly modifying this pseudo array is not recommended
1506
+ *
1507
+ * * **Examples**:
1508
+ * ```typescript
1509
+ * import type {IHeapSnapshot, IHeapNodes} from '@memlab/core';
1510
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1511
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1512
+ *
1513
+ * (async function () {
1514
+ * const heapFile = dumpNodeHeapSnapshot();
1515
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1516
+ *
1517
+ * const nodes: IHeapNodes = heap.nodes;
1518
+ * nodes.length;
1519
+ * nodes.get(0);
1520
+ * nodes.forEach((node, i) => {
1521
+ * if (stopIteration) {
1522
+ * return false;
1523
+ * }
1524
+ * });
1525
+ * })();
1526
+ * ```
1527
+ */
685
1528
  export interface IHeapNodes {
1529
+ /**
1530
+ * The total number of nodes in heap graph (or JS objects in heap
1531
+ * snapshot).
1532
+ */
686
1533
  length: number;
687
- get(index: number): IHeapNode;
1534
+ /**
1535
+ * get an {@link IHeapNode} element at the specified index
1536
+ * @param index the index of an element in the pseudo array, the index ranges
1537
+ * from 0 to array length - 1. Notice that this is not the heap node id.
1538
+ * @returns When 0 <= `index` < array.length, this API returns the element
1539
+ * at the specified index, otherwise it returns `null`.
1540
+ */
1541
+ get(index: number): Nullable<IHeapNode>;
1542
+ /**
1543
+ * Iterate over all array elements and apply the callback
1544
+ * to each element in ascending order of element index.
1545
+ * @param callback the callback does not need to return any value, if
1546
+ * the callback returns `false` when iterating on element at index `i`,
1547
+ * then all elements after `i` won't be iterated.
1548
+ */
688
1549
  forEach(callback: (node: IHeapNode, index: number) => void | boolean): void;
1550
+ /** @internal */
689
1551
  forEachTraceable(callback: (node: IHeapNode, index: number) => void | boolean): void;
690
1552
  }
691
1553
  /** @internal */