@memlab/core 1.1.0 → 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.
@@ -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 | null;
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 type for defining custom leak-filtering logic.
147
- * * **Examples**:
148
- * ```typescript
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`).
160
+ *
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';
152
171
  *
153
- * let map = Object.create(null);
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 = initializeMapUsingSnapshot(snapshot);
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 <name, value> pairs.
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
- * {"name":"cm_j","value":"none"},
254
- * {"name":"datr","value":"yJvIY..."},
255
- * {"name":"c_user","value":"8917..."},
256
- * {"name":"xs","value":"95:9WQ..."},
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
- * after for initial page loading and subsequent browser interactions.
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 do some one-off
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 that defines how you want to filter out the
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,86 +723,818 @@ 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 {
733
+ /** @internal */
610
734
  snapshot: RawHeapSnapshot;
735
+ /**
736
+ * A pseudo array containing all heap graph nodes (JS objects in heap).
737
+ * A JS heap could contain millions of heap objects, so memlab uses
738
+ * a pseudo array as the collection of all the heap objects. The pseudo
739
+ * array provides API to query and traverse all heap objects.
740
+ *
741
+ * * **Examples**:
742
+ * ```typescript
743
+ * import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
744
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
745
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
746
+ *
747
+ * (async function () {
748
+ * const heapFile = dumpNodeHeapSnapshot();
749
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
750
+ *
751
+ * // get the total number of heap objects
752
+ * heap.nodes.length;
753
+ *
754
+ * heap.nodes.forEach((node: IHeapNode) => {
755
+ * // traverse each heap object
756
+ * });
757
+ * })();
758
+ * ```
759
+ */
611
760
  nodes: IHeapNodes;
761
+ /**
762
+ * A pseudo array containing all heap graph edges (references to heap objects
763
+ * in heap). A JS heap could contain millions of references, so memlab uses
764
+ * a pseudo array as the collection of all the heap edges. The pseudo
765
+ * array provides API to query and traverse all heap references.
766
+ *
767
+ * * **Examples**:
768
+ * ```typescript
769
+ * import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
770
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
771
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
772
+ *
773
+ * (async function () {
774
+ * const heapFile = dumpNodeHeapSnapshot();
775
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
776
+ *
777
+ * // get the total number of heap references
778
+ * heap.edges.length;
779
+ *
780
+ * heap.edges.forEach((edge: IHeapEdge) => {
781
+ * // traverse each reference in the heap
782
+ * });
783
+ * })();
784
+ * ```
785
+ */
612
786
  edges: IHeapEdges;
787
+ /**
788
+ * If you have the id of a heap node (JS object in heap), use this API
789
+ * to get an {@link IHeapNode} associated with the id.
790
+ * @param id id of the heap node (JS object in heap) you would like to query
791
+ * @returns the API returns `null` if no heap object has the specified id.
792
+ *
793
+ * * **Examples**:
794
+ * ```typescript
795
+ * import type {IHeapSnapshot} from '@memlab/core';
796
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
797
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
798
+ *
799
+ * (async function () {
800
+ * const heapFile = dumpNodeHeapSnapshot();
801
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
802
+ *
803
+ * const node = heap.getNodeById(351);
804
+ * node?.id; // should be 351
805
+ * })();
806
+ * ```
807
+ */
613
808
  getNodeById(id: number): Nullable<IHeapNode>;
614
- clearShortestPathInfo(): void;
809
+ /**
810
+ * Search for the heap and check if there is any JS object instance with
811
+ * a specified constructor name.
812
+ * @param className The contructor name of the object instance
813
+ * @returns `true` if there is at least one such object in the heap
814
+ *
815
+ * * **Examples**: you can write a jest unit test with memory assertions:
816
+ * ```typescript
817
+ * // save as example.test.ts
818
+ * import type {IHeapSnapshot, Nullable} from '@memlab/core';
819
+ * import {config, getNodeInnocentHeap} from '@memlab/core';
820
+ *
821
+ * class TestObject {
822
+ * public arr1 = [1, 2, 3];
823
+ * public arr2 = ['1', '2', '3'];
824
+ * }
825
+ *
826
+ * test('memory test with heap assertion', async () => {
827
+ * config.muteConsole = true; // no console output
828
+ *
829
+ * let obj: Nullable<TestObject> = new TestObject();
830
+ * // get a heap snapshot of the current program state
831
+ * let heap: IHeapSnapshot = await getNodeInnocentHeap();
832
+ *
833
+ * // call some function that may add references to obj
834
+ * rabbitHole(obj)
835
+ *
836
+ * expect(heap.hasObjectWithClassName('TestObject')).toBe(true);
837
+ * obj = null;
838
+ *
839
+ * heap = await getNodeInnocentHeap();
840
+ * // if rabbitHole does not have any side effect that
841
+ * // adds new references to obj, then obj can be GCed
842
+ * expect(heap.hasObjectWithClassName('TestObject')).toBe(false);
843
+ *
844
+ * }, 30000);
845
+ * ```
846
+ */
615
847
  hasObjectWithClassName(className: string): boolean;
848
+ /**
849
+ * Search for the heap and get one of the JS object instances with
850
+ * a specified constructor name (if there is any).
851
+ * @param className The contructor name of the object instance
852
+ * @returns a handle pointing to any one of the object instances, returns
853
+ * `null` if no such object exists in the heap.
854
+ *
855
+ * * **Examples**:
856
+ * ```typescript
857
+ * import type {IHeapSnapshot} from '@memlab/core';
858
+ * import {getNodeInnocentHeap} from '@memlab/core';
859
+ *
860
+ * class TestObject {
861
+ * public arr1 = [1, 2, 3];
862
+ * public arr2 = ['1', '2', '3'];
863
+ * }
864
+ *
865
+ * (async function () {
866
+ * const obj = new TestObject();
867
+ * // get a heap snapshot of the current program state
868
+ * const heap: IHeapSnapshot = await getNodeInnocentHeap();
869
+ *
870
+ * const node = heap.getAnyObjectWithClassName('TestObject');
871
+ * console.log(node?.name); // should be 'TestObject'
872
+ * })();
873
+ * ```
874
+ */
616
875
  getAnyObjectWithClassName(className: string): Nullable<IHeapNode>;
876
+ /**
877
+ * Search for the heap and check if there is any JS object instance with
878
+ * a specified property name.
879
+ * @param nameOrIndex The property name (string) or element index (number)
880
+ * on the object instance
881
+ * @returns returns `true` if there is at least one such object in the heap
882
+ *
883
+ * * **Examples**:
884
+ * ```typescript
885
+ * import type {IHeapSnapshot} from '@memlab/core';
886
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
887
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
888
+ *
889
+ * (async function () {
890
+ * // eslint-disable-next-line @typescript-eslint/no-unused-vars
891
+ * const object = {'memlab-test-heap-property': 'memlab-test-heap-value'};
892
+ *
893
+ * const heapFile = dumpNodeHeapSnapshot();
894
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
895
+ *
896
+ * // should be true
897
+ * console.log(heap.hasObjectWithPropertyName('memlab-test-heap-property'));
898
+ * })();
899
+ * ```
900
+ */
617
901
  hasObjectWithPropertyName(nameOrIndex: string | number): boolean;
902
+ /**
903
+ * Search for the heap and check if there is any JS object instance with
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
+ *
909
+ * @param tag marker name on the object instances tagged by {@link tagObject}
910
+ * @returns returns `true` if there is at least one such object in the heap
911
+ *
912
+ * ```typescript
913
+ * import type {IHeapSnapshot, AnyValue} from '@memlab/core';
914
+ * import {config, getNodeInnocentHeap, tagObject} from '@memlab/core';
915
+ *
916
+ * test('memory test', async () => {
917
+ * config.muteConsole = true;
918
+ * const o1: AnyValue = {};
919
+ * let o2: AnyValue = {};
920
+ *
921
+ * // tag o1 with marker: "memlab-mark-1", does not modify o1 in any way
922
+ * tagObject(o1, 'memlab-mark-1');
923
+ * // tag o2 with marker: "memlab-mark-2", does not modify o2 in any way
924
+ * tagObject(o2, 'memlab-mark-2');
925
+ *
926
+ * o2 = null;
927
+ *
928
+ * const heap: IHeapSnapshot = await getNodeInnocentHeap();
929
+ *
930
+ * // expect object with marker "memlab-mark-1" exists
931
+ * expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
932
+ *
933
+ * // expect object with marker "memlab-mark-2" can be GCed
934
+ * expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
935
+ *
936
+ * }, 30000);
937
+ * ```
938
+ */
618
939
  hasObjectWithTag(tag: string): boolean;
940
+ /** @internal */
941
+ clearShortestPathInfo(): void;
619
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
+ */
620
975
  export interface IHeapLocation {
976
+ /**
977
+ * get the {@link IHeapSnapshot} containing this location instance
978
+ */
621
979
  snapshot: IHeapSnapshot;
980
+ /**
981
+ * get the script ID of the source file
982
+ */
622
983
  script_id: number;
984
+ /**
985
+ * get the line number
986
+ */
623
987
  line: number;
988
+ /**
989
+ * get the column number
990
+ */
624
991
  column: number;
625
992
  }
993
+ /** @internal */
626
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
+ */
627
999
  name_or_index: number | string;
1000
+ /**
1001
+ * type of the JS reference, all types:
1002
+ * `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak`
1003
+ */
628
1004
  type: string;
629
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
+ */
630
1034
  export interface IHeapEdge extends IHeapEdgeBasic {
1035
+ /**
1036
+ * get the {@link IHeapSnapshot} containing this JS reference
1037
+ */
631
1038
  snapshot: IHeapSnapshot;
1039
+ /**
1040
+ * index of this JS reference inside the `edge.snapshot.edges` pseudo array
1041
+ */
632
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
+ */
633
1049
  is_index: boolean;
1050
+ /**
1051
+ * the index of the JS heap object pointed to by this reference
1052
+ */
634
1053
  to_node: number;
1054
+ /**
1055
+ * returns an {@link IHeapNode} instance representing the JS heap object
1056
+ * pointed to by this reference
1057
+ */
635
1058
  toNode: IHeapNode;
1059
+ /**
1060
+ * returns an {@link IHeapNode} instance representing the hosting
1061
+ * JS heap object where this reference starts
1062
+ */
636
1063
  fromNode: IHeapNode;
637
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
+ */
638
1094
  export interface IHeapEdges {
1095
+ /**
1096
+ * The total number of edges in heap graph (or JS references in heap
1097
+ * snapshot).
1098
+ */
639
1099
  length: number;
640
- get(index: number): IHeapEdge;
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
+ */
641
1115
  forEach(callback: (edge: IHeapEdge, index: number) => void | boolean): void;
642
1116
  }
1117
+ /** @internal */
643
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
+ */
644
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
+ */
645
1132
  name: string;
1133
+ /**
1134
+ * unique id of the heap object
1135
+ */
646
1136
  id: number;
647
1137
  }
648
1138
  export declare type EdgeIterationCallback = (edge: IHeapEdge) => Optional<{
649
1139
  stop: boolean;
650
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
+ */
651
1170
  export interface IHeapNode extends IHeapNodeBasic {
1171
+ /**
1172
+ * get the {@link IHeapSnapshot} containing this heap object
1173
+ */
652
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
+ */
653
1182
  is_detached: boolean;
1183
+ /** @internal */
654
1184
  detachState: number;
1185
+ /** @internal */
655
1186
  markAsDetached(): void;
1187
+ /** @internal */
656
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
+ */
657
1195
  self_size: number;
1196
+ /**
1197
+ * The total number of outgoing JS references (including engine-internal,
1198
+ * native, and JS references).
1199
+ */
658
1200
  edge_count: number;
1201
+ /** @internal */
659
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
+ */
660
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
+ */
661
1212
  referrers: IHeapEdge[];
1213
+ /**
1214
+ * The incoming edge which leads to the parent node
1215
+ * on the shortest path to GC root.
1216
+ */
662
1217
  pathEdge: IHeapEdge | null;
1218
+ /**
1219
+ * index of this heap object inside the `node.snapshot.nodes` pseudo array
1220
+ */
663
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
+ */
664
1228
  retainedSize: number;
665
- dominatorNode: IHeapNode | null;
666
- location: IHeapLocation | null;
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 */
667
1243
  highlight?: boolean;
1244
+ /**
1245
+ * check if this a string node (normal string node, concatenated string node
1246
+ * or sliced string node)
1247
+ */
668
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
+ */
669
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
+ */
670
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
+ */
671
1290
  forEachReferrer(callback: EdgeIterationCallback): void;
672
- findReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
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
+ */
673
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
+ */
674
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
+ */
675
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
+ */
676
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
+ */
677
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
+ */
678
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
+ */
679
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
+ */
680
1447
  getReferrerNodes: (edgeName: string | number, edgeType?: string) => IHeapNode[];
681
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
+ */
682
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
+ */
683
1484
  stringValue: string;
684
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
+ */
685
1515
  export interface IHeapNodes {
1516
+ /**
1517
+ * The total number of nodes in heap graph (or JS objects in heap
1518
+ * snapshot).
1519
+ */
686
1520
  length: number;
687
- get(index: number): IHeapNode;
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
+ */
688
1536
  forEach(callback: (node: IHeapNode, index: number) => void | boolean): void;
1537
+ /** @internal */
689
1538
  forEachTraceable(callback: (node: IHeapNode, index: number) => void | boolean): void;
690
1539
  }
691
1540
  /** @internal */