@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 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
- ## Full documentation
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)
@@ -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;
@@ -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
  }
@@ -13,8 +13,8 @@ const InternalValueSetter_1 = require("./InternalValueSetter");
13
13
  const constants = {
14
14
  isFB: false,
15
15
  isFRL: false,
16
- defaultEngine: 'v8',
17
- supportedEngines: ['v8', 'hermes'],
16
+ defaultEngine: 'V8',
17
+ supportedEngines: ['V8', 'hermes'],
18
18
  supportedBrowsers: Object.create(null),
19
19
  internalDir: 'fb-internal',
20
20
  monoRepoDir: '',
@@ -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 = 'v8';
105
+ let engine = 'V8';
106
106
  snapshot.nodes.forEach((node) => {
107
107
  if (node.type === 'object' && node.name.startsWith('Object(')) {
108
108
  engine = 'hermes';
@@ -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);
@@ -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`).
152
160
  *
153
- * let map = Object.create(null);
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 = 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,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
- 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
+ */
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
- dominatorNode: IHeapNode | null;
862
- 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 */
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
- 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
+ */
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
- 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
+ */
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.findReference((edge) => edge.name_or_index === edgeName &&
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.findReference(edge => typeof edge.name_or_index === 'string' &&
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
- findReference(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
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[];
@@ -131,7 +131,7 @@ class HeapNode {
131
131
  }
132
132
  }
133
133
  }
134
- findReference(predicate) {
134
+ findAnyReference(predicate) {
135
135
  let found = null;
136
136
  this.forEachReference((edge) => {
137
137
  if (predicate(edge)) {
@@ -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.findReference((edge) => edge.type === 'weak' && edge.toNode.name !== 'system / Oddball');
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).toNode.nodeIndex;
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.nodeIndex;
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
- findReference(): Nullable<IHeapEdge>;
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
- findReference() {
74
- throw new Error('NodeRecord.findReference is not implemented');
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",