@memlab/core 1.1.2 → 1.1.5

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)
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@
7
7
  * @emails oncall+ws_labs
8
8
  * @format
9
9
  */
10
+ /** @internal */
11
+ export declare function registerPackage(): Promise<void>;
10
12
  export * from './lib/Types';
11
13
  /** @internal */
12
14
  export { default as config } from './lib/Config';
@@ -42,5 +44,7 @@ export { default as leakClusterLogger } from './logger/LeakClusterLogger';
42
44
  export { default as NormalizedTrace } from './trace-cluster/TraceBucket';
43
45
  /** @internal */
44
46
  export { default as EvaluationMetric } from './trace-cluster/EvalutationMetric';
47
+ /** @internal */
48
+ export * from './lib/PackageInfoLoader';
45
49
  export * from './lib/NodeHeap';
46
50
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -22,11 +22,29 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
22
22
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
23
23
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
24
24
  };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
25
34
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
36
  };
28
37
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = void 0;
38
+ exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0;
39
+ const path_1 = __importDefault(require("path"));
40
+ const PackageInfoLoader_1 = require("./lib/PackageInfoLoader");
41
+ /** @internal */
42
+ function registerPackage() {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ return PackageInfoLoader_1.PackageInfoLoader.registerPackage(path_1.default.join(__dirname, '..'));
45
+ });
46
+ }
47
+ exports.registerPackage = registerPackage;
30
48
  __exportStar(require("./lib/Types"), exports);
31
49
  /** @internal */
32
50
  var Config_1 = require("./lib/Config");
@@ -76,4 +94,6 @@ Object.defineProperty(exports, "NormalizedTrace", { enumerable: true, get: funct
76
94
  /** @internal */
77
95
  var EvalutationMetric_1 = require("./trace-cluster/EvalutationMetric");
78
96
  Object.defineProperty(exports, "EvaluationMetric", { enumerable: true, get: function () { return __importDefault(EvalutationMetric_1).default; } });
97
+ /** @internal */
98
+ __exportStar(require("./lib/PackageInfoLoader"), exports);
79
99
  __exportStar(require("./lib/NodeHeap"), exports);
@@ -8,7 +8,7 @@
8
8
  * @format
9
9
  */
10
10
  import type { LaunchOptions, Permission } from 'puppeteer';
11
- import type { AnyFunction, AnyValue, IClusterStrategy, IRunningMode, IScenario, Nullable, Optional, QuickExperiment, ILeakFilter } from './Types';
11
+ import type { AnyFunction, AnyValue, IClusterStrategy, IRunningMode, IScenario, Nullable, Optional, QuickExperiment, ILeakFilter, IPackageInfo } from './Types';
12
12
  interface BrowserLaunchArgumentOptions {
13
13
  headless?: boolean;
14
14
  userDataDir?: string;
@@ -44,14 +44,19 @@ export declare enum ErrorHandling {
44
44
  }
45
45
  /** @internal */
46
46
  export declare class MemLabConfig {
47
- snapshotHasDetachedness: boolean;
48
- specifiedEngine: boolean;
49
- verbose: boolean;
50
- jsEngine: string;
51
47
  _reportLeaksInTimers: boolean;
52
48
  _deviceManualOverridden: boolean;
53
49
  _timerNodes: string[];
54
50
  _timerEdges: string[];
51
+ _isFullRun: boolean;
52
+ _scenario: Optional<IScenario>;
53
+ _isHeadfulBrowser: boolean;
54
+ _browser: string;
55
+ _packageInfo: IPackageInfo[];
56
+ snapshotHasDetachedness: boolean;
57
+ specifiedEngine: boolean;
58
+ verbose: boolean;
59
+ jsEngine: string;
55
60
  targetApp: string;
56
61
  targetTab: string;
57
62
  analysisMode: string;
@@ -94,7 +99,6 @@ export declare class MemLabConfig {
94
99
  puppeteerConfig: LaunchOptions & BrowserLaunchArgumentOptions & BrowserConnectOptions;
95
100
  openDevtoolsConsole: boolean;
96
101
  emulateDevice: Nullable<Device>;
97
- _browser: string;
98
102
  addEnableGK: Set<string>;
99
103
  addDisableGK: Set<string>;
100
104
  qes: QuickExperiment[];
@@ -169,8 +173,6 @@ export declare class MemLabConfig {
169
173
  oversizeObjectAsLeak: boolean;
170
174
  oversizeThreshold: number;
171
175
  clusterRetainedSizeThreshold: number;
172
- _isFullRun: boolean;
173
- _scenario: Optional<IScenario>;
174
176
  externalLeakFilter?: Optional<ILeakFilter>;
175
177
  monoRepoDir: string;
176
178
  muteConsole: boolean;
@@ -192,6 +194,8 @@ export declare class MemLabConfig {
192
194
  get isFullRun(): boolean;
193
195
  set browser(v: string);
194
196
  get browser(): string;
197
+ set isHeadfulBrowser(isHeadful: boolean);
198
+ get isHeadfulBrowser(): boolean;
195
199
  get browserBinaryPath(): string;
196
200
  set reportLeaksInTimers(flag: boolean);
197
201
  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,
@@ -95,6 +97,8 @@ class MemLabConfig {
95
97
  this.jsEngine = Constant_1.default.defaultEngine;
96
98
  // the default browser (Chromium)
97
99
  this._browser = 'chrome';
100
+ // a list of package information
101
+ this._packageInfo = [];
98
102
  // a set of additional GKs to be enabled
99
103
  this.addEnableGK = new Set();
100
104
  // a set of additional GKs to be disabled
@@ -358,6 +362,17 @@ class MemLabConfig {
358
362
  get browser() {
359
363
  return this._browser || 'google-chrome';
360
364
  }
365
+ set isHeadfulBrowser(isHeadful) {
366
+ this._isHeadfulBrowser = isHeadful;
367
+ this.puppeteerConfig.headless = !isHeadful;
368
+ if (isHeadful) {
369
+ // if running in headful mode
370
+ this.disableXvfb();
371
+ }
372
+ }
373
+ get isHeadfulBrowser() {
374
+ return this._isHeadfulBrowser;
375
+ }
361
376
  get browserBinaryPath() {
362
377
  return path_1.default.join(this.browserDir, this.browser);
363
378
  }
@@ -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';
@@ -0,0 +1,7 @@
1
+ /** @internal */
2
+ export declare class PackageInfoLoader {
3
+ private static registeredPackages;
4
+ private static loadFrom;
5
+ static registerPackage(packageDirectory: string): Promise<void>;
6
+ }
7
+ //# sourceMappingURL=PackageInfoLoader.d.ts.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.PackageInfoLoader = void 0;
16
+ /**
17
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
18
+ *
19
+ * This source code is licensed under the MIT license found in the
20
+ * LICENSE file in the root directory of this source tree.
21
+ *
22
+ * @emails oncall+ws_labs
23
+ * @format
24
+ */
25
+ const fs_extra_1 = __importDefault(require("fs-extra"));
26
+ const path_1 = __importDefault(require("path"));
27
+ const Config_1 = __importDefault(require("./Config"));
28
+ const Utils_1 = __importDefault(require("./Utils"));
29
+ /** @internal */
30
+ class PackageInfoLoader {
31
+ static loadFrom(packageDirectory) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ let exists = yield fs_extra_1.default.pathExists(packageDirectory);
34
+ if (!exists) {
35
+ throw Utils_1.default.haltOrThrow(`package directory doesn't exist: ${packageDirectory}`);
36
+ }
37
+ let packageJSONFile = path_1.default.join(packageDirectory, 'package-oss.json');
38
+ exists = yield fs_extra_1.default.pathExists(packageJSONFile);
39
+ if (!exists) {
40
+ packageJSONFile = path_1.default.join(packageDirectory, 'package.json');
41
+ }
42
+ exists = yield fs_extra_1.default.pathExists(packageJSONFile);
43
+ if (!exists) {
44
+ throw Utils_1.default.haltOrThrow(`package.json doesn't exist: ${packageJSONFile}`);
45
+ }
46
+ try {
47
+ const metaData = yield fs_extra_1.default.readJSON(packageJSONFile, 'UTF-8');
48
+ return Object.assign(Object.assign({}, metaData), { packageLocation: packageDirectory });
49
+ }
50
+ catch (ex) {
51
+ throw Utils_1.default.haltOrThrow(Utils_1.default.getError(ex));
52
+ }
53
+ });
54
+ }
55
+ static registerPackage(packageDirectory) {
56
+ return __awaiter(this, void 0, void 0, function* () {
57
+ if (!PackageInfoLoader.registeredPackages.has(packageDirectory)) {
58
+ PackageInfoLoader.registeredPackages.add(packageDirectory);
59
+ const packageInfo = yield PackageInfoLoader.loadFrom(packageDirectory);
60
+ Config_1.default._packageInfo.push(packageInfo);
61
+ }
62
+ });
63
+ }
64
+ }
65
+ exports.PackageInfoLoader = PackageInfoLoader;
66
+ PackageInfoLoader.registeredPackages = new Set();
@@ -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);
@@ -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[];
@@ -119,6 +120,12 @@ export interface E2EScenarioSynthesizerConstructor {
119
120
  new (config: Config): IE2EScenarioSynthesizer;
120
121
  }
121
122
  /** @internal */
123
+ export interface IPackageInfo {
124
+ name: string;
125
+ version: string;
126
+ packageLocation?: string;
127
+ }
128
+ /** @internal */
122
129
  export interface IRunningMode {
123
130
  setConfig(config: Config): void;
124
131
  beforeRunning(visitPlan: IE2EScenarioVisitPlan): void;
@@ -143,16 +150,49 @@ export declare type QuickExperiment = {
143
150
  group: string;
144
151
  };
145
152
  /**
146
- * The type for defining custom leak-filtering logic.
147
- * * **Examples**:
148
- * ```typescript
149
- * const scenario = {
153
+ * The `ILeakFilter` interface allows you to define a leak detector and
154
+ * customize the leak filtering logic in memlab (instead of using the
155
+ * built-in leak filters).
150
156
  *
151
- * };
157
+ * Use the leak filter definition in command line interface to filter
158
+ * leaks detected from browser interactions
159
+ * ```bash
160
+ * memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
161
+ * ```
162
+ *
163
+ * If you have already run `memlab run` or `memlab snapshot` which saved
164
+ * heap snapshot and other meta data on disk, use the following command
165
+ * to filter leaks based on those saved heap snapshots (query the default
166
+ * data location by `memlab get-default-work-dir`).
167
+ *
168
+ * ```bash
169
+ * memlab find-leaks --leak-filter <PATH TO leak-filter.js>
170
+ * ```
171
+ * Here is an example TypeScript file defining a leak filter.
172
+ * The command line interface only accepts compiled JavaScript file.
173
+ * You can also define the leak filter in JavaScript (without the
174
+ * type annotations.
152
175
  *
153
- * let map = Object.create(null);
176
+ * ```typescript
177
+ * import {IHeapNode, IHeapSnapshot, HeapNodeIdSet, utils} from '@memlab/core';
178
+ *
179
+ * function initMap(snapshot: IHeapSnapshot): Record<string, number> {
180
+ * const map = Object.create(null);
181
+ * snapshot.nodes.forEach(node => {
182
+ * if (node.type !== 'string') {
183
+ * return;
184
+ * }
185
+ * const str = utils.getStringNodeValue(node);
186
+ * if (str in map) {
187
+ * ++map[str];
188
+ * } else {
189
+ * map[str] = 1;
190
+ * }
191
+ * });
192
+ * return map;
193
+ * }
154
194
  * const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
155
- * map = initializeMapUsingSnapshot(snapshot);
195
+ * map = initMap(snapshot);
156
196
  * };
157
197
  *
158
198
  * // duplicated string with size > 1KB as memory leak
@@ -168,7 +208,81 @@ export declare type QuickExperiment = {
168
208
  * ```
169
209
  */
170
210
  export interface ILeakFilter {
211
+ /**
212
+ * Lifecycle function callback that is invoked initially once before
213
+ * the subsequent `leakFilter` function calls. This callback could
214
+ * be used to initialize some data stores or any one-off
215
+ * preprocessings.
216
+ *
217
+ * * **Parameters**:
218
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
219
+ * all browser interactions are done.
220
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
221
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
222
+ * allocated by the `action` call but not released after the `back` call
223
+ * in browser.
224
+ *
225
+ * * **Examples**:
226
+ * ```typescript
227
+ * module.exports = {
228
+ * beforeLeakFilter: (snapshot, leakedNodeIds) {
229
+ * // initialize some data stores
230
+ * },
231
+ * leakFilter(node, snapshot, leakedNodeIds) {
232
+ * // use the data stores
233
+ * },
234
+ * };
235
+ * ```
236
+ */
171
237
  beforeLeakFilter?: InitLeakFilterCallback;
238
+ /**
239
+ * This callback defines how you want to filter out the
240
+ * leaked objects. The callback is called for every node (JS heap
241
+ * object in browser) allocated by the `action` callback, but not
242
+ * released after the `back` callback. Those objects could be caches
243
+ * that are retained in memory on purpose, or they are memory leaks.
244
+ *
245
+ * This optional callback allows you to define your own algorithm
246
+ * to cherry pick memory leaks for specific JS program under test.
247
+ *
248
+ * If this optional callback is not defined, memlab will use its
249
+ * built-in leak filter, which considers detached DOM elements
250
+ * and unmounted Fiber nodes (detached from React Fiber tree) as
251
+ * memory leaks.
252
+ *
253
+ * * **Parameters**:
254
+ * * node: `IHeapNode` | one of the heap object allocated but not released.
255
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
256
+ * all browser interactions are done.
257
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
258
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
259
+ * allocated by the `action` call but not released after the `back` call
260
+ * in browser.
261
+ *
262
+ * * **Returns**: the boolean value indicating whether the given node in
263
+ * the snapshot should be considered as leaked.
264
+ *
265
+ *
266
+ * ```javascript
267
+ * // save as leak-filter.js
268
+ * module.exports = {
269
+ * leakFilter(node, snapshot, leakedNodeIds) {
270
+ * // any unreleased node (JS heap object) with 1MB+
271
+ * // retained size is considered a memory leak
272
+ * return node.retainedSize > 1000000;
273
+ * },
274
+ * };
275
+ * ```
276
+ *
277
+ * Use the leak filter definition in command line interface:
278
+ * ```bash
279
+ * memlab find-leaks --leak-filter <PATH TO leak-filter.js>
280
+ * ```
281
+ *
282
+ * ```bash
283
+ * memlab run --scenario <SCENARIO FILE> --leak-filter <PATH TO leak-filter.js>
284
+ * ```
285
+ */
172
286
  leakFilter: LeakFilterCallback;
173
287
  }
174
288
  /**
@@ -217,6 +331,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
217
331
  * url: () => 'https://www.npmjs.com/',
218
332
  * action: async () => ... ,
219
333
  * back: async () => ... ,
334
+ * cookies: () => ... , // optional
335
+ * repeat: () => ... , // optional
336
+ * ...
220
337
  * };
221
338
  * ```
222
339
  *
@@ -230,6 +347,9 @@ export declare type InteractionsCallback = (page: Page, args?: OperationArgs) =>
230
347
  * url: () => 'https://www.facebook.com',
231
348
  * action: async () => ... ,
232
349
  * back: async () => ... ,
350
+ * cookies: () => ... , // optional
351
+ * repeat: () => ... , // optional
352
+ * ...
233
353
  * };
234
354
  * const leaks = await run({scenario});
235
355
  * })();
@@ -243,20 +363,30 @@ export interface IScenario {
243
363
  /**
244
364
  * If the page you are running memlab against requires authentication or
245
365
  * specific cookie(s) to be set, you can pass them as
246
- * a list of <name, value> pairs.
366
+ * a list of `<name, value, domain>` tuples.
367
+ *
368
+ * **Note**: please make sure that you provide the correct `domain` field for
369
+ * the cookies tuples. If no `domain` field is specified, memlab will try
370
+ * to fill in a domain based on the `url` callback.
371
+ * For example, when the `domain` field is absent,
372
+ * memlab will auto fill in `.facebook.com` as domain base
373
+ * on the initial page load's url: `https://www.facebook.com/`.
374
+ *
247
375
  * @returns cookie list
248
376
  * * **Examples**:
249
377
  * ```typescript
250
378
  * const scenario = {
251
379
  * url: () => 'https://www.facebook.com/',
252
380
  * cookies: () => [
253
- * {"name":"cm_j","value":"none"},
254
- * {"name":"datr","value":"yJvIY..."},
255
- * {"name":"c_user","value":"8917..."},
256
- * {"name":"xs","value":"95:9WQ..."},
381
+ * {name:'cm_j', value: 'none', domain: '.facebook.com'},
382
+ * {name:'datr', value: 'yJvIY...', domain: '.facebook.com'},
383
+ * {name:'c_user', value: '8917...', domain: '.facebook.com'},
384
+ * {name:'xs', value: '95:9WQ...', domain: '.facebook.com'},
257
385
  * // ...
258
386
  * ],
259
387
  * };
388
+ *
389
+ * module.exports = scenario;
260
390
  * ```
261
391
  */
262
392
  cookies?: () => Cookies;
@@ -269,6 +399,8 @@ export interface IScenario {
269
399
  * const scenario = {
270
400
  * url: () => 'https://www.npmjs.com/',
271
401
  * };
402
+ *
403
+ * module.exports = scenario;
272
404
  * ```
273
405
  * If a test scenario only specifies the `url` callback (without the `action`
274
406
  * callback), memlab will try to detect memory leaks from the initial page
@@ -297,6 +429,8 @@ export interface IScenario {
297
429
  * await page.click('a[href="/back"]');
298
430
  * },
299
431
  * }
432
+ *
433
+ * module.exports = scenario;
300
434
  * ```
301
435
  * Note: always clean up external puppeteer references to JS objects
302
436
  * in the browser context.
@@ -314,6 +448,8 @@ export interface IScenario {
314
448
  * },
315
449
  * back: async (page) => ... ,
316
450
  * }
451
+ *
452
+ * module.exports = scenario;
317
453
  ```
318
454
  */
319
455
  action?: InteractionsCallback;
@@ -321,6 +457,10 @@ export interface IScenario {
321
457
  * `back` is the callback function that specifies how memlab should
322
458
  * back/revert the `action` callback. Think of it as an undo action.
323
459
  *
460
+ * * **Parameters**:
461
+ * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
462
+ * object, which provides APIs to interact with the web browser
463
+ *
324
464
  * * **Examples**:
325
465
  * ```typescript
326
466
  * const scenario = {
@@ -354,7 +494,7 @@ export interface IScenario {
354
494
  repeat?: () => number;
355
495
  /**
356
496
  * Optional callback function that checks if the web page is loaded
357
- * after for initial page loading and subsequent browser interactions.
497
+ * for the initial page load and subsequent browser interactions.
358
498
  *
359
499
  * If this callback is not provided, memlab by default
360
500
  * considers a navigation to be finished when there are no network
@@ -390,7 +530,7 @@ export interface IScenario {
390
530
  /**
391
531
  * Lifecycle function callback that is invoked initially once before
392
532
  * the subsequent `leakFilter` function calls. This callback could
393
- * be used to initialize some data stores or to do some one-off
533
+ * be used to initialize some data stores or to any one-off
394
534
  * preprocessings.
395
535
  *
396
536
  * * **Parameters**:
@@ -415,7 +555,7 @@ export interface IScenario {
415
555
  */
416
556
  beforeLeakFilter?: InitLeakFilterCallback;
417
557
  /**
418
- * This callback that defines how you want to filter out the
558
+ * This callback defines how you want to filter out the
419
559
  * leaked objects. The callback is called for every node (JS heap
420
560
  * object in browser) allocated by the `action` callback, but not
421
561
  * released after the `back` callback. Those objects could be caches
@@ -545,6 +685,10 @@ export interface IDataBuilder {
545
685
  /**
546
686
  * Callback function to provide if the page is loaded.
547
687
  * @param page - puppeteer's [Page](https://pptr.dev/api/puppeteer.page/) object.
688
+ * @returns a boolean value, if it returns `true`, memlab will consider
689
+ * the navigation completes, if it returns `false`, memlab will keep calling
690
+ * this callback until it returns `true`. This is an async callback, you can
691
+ * also `await` and returns `true` until some async logic is resolved.
548
692
  */
549
693
  export declare type CheckPageLoadCallback = (page: Page) => Promise<boolean>;
550
694
  /** @internal */
@@ -606,6 +750,12 @@ export declare type RunMetaInfo = {
606
750
  type: string;
607
751
  browserInfo: IBrowserInfo;
608
752
  };
753
+ /**
754
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
755
+ * and graph edges are JS references among JS heap objects. For more details
756
+ * on the structure of nodes and edges in the heap graph, check out
757
+ * {@link IHeapNode} and {@link IHeapEdge}.
758
+ */
609
759
  export interface IHeapSnapshot {
610
760
  /** @internal */
611
761
  snapshot: RawHeapSnapshot;
@@ -779,6 +929,10 @@ export interface IHeapSnapshot {
779
929
  /**
780
930
  * Search for the heap and check if there is any JS object instance with
781
931
  * a marker tagged by {@link tagObject}.
932
+ *
933
+ * The `tagObject` API does not modify the object instance in any way
934
+ * (e.g., no additional or hidden properties added to the tagged object).
935
+ *
782
936
  * @param tag marker name on the object instances tagged by {@link tagObject}
783
937
  * @returns returns `true` if there is at least one such object in the heap
784
938
  *
@@ -813,75 +967,601 @@ export interface IHeapSnapshot {
813
967
  /** @internal */
814
968
  clearShortestPathInfo(): void;
815
969
  }
970
+ /**
971
+ * An `IHeapLocation` instance contains a source location information
972
+ * associated with a JS heap object.
973
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
974
+ * and graph edges are JS references among JS heap objects.
975
+ *
976
+ * @readonly it is not recommended to modify any `IHeapLocation` instance
977
+ *
978
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
979
+ * {@link getHeapFromFile} API.
980
+ *
981
+ * ```typescript
982
+ * import type {IHeapSnapshot, IHeapNode, IHeapLocation} from '@memlab/core';
983
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
984
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
985
+ *
986
+ * (async function () {
987
+ * const heapFile = dumpNodeHeapSnapshot();
988
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
989
+ *
990
+ * // iterate over each node (heap object)
991
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
992
+ * const location: Nullable<IHeapLocation> = node.location;
993
+ * if (location) {
994
+ * // use the location API here
995
+ * location.line;
996
+ * // ...
997
+ * }
998
+ * });
999
+ * })();
1000
+ * ```
1001
+ */
816
1002
  export interface IHeapLocation {
1003
+ /**
1004
+ * get the {@link IHeapSnapshot} containing this location instance
1005
+ */
817
1006
  snapshot: IHeapSnapshot;
1007
+ /**
1008
+ * get the script ID of the source file
1009
+ */
818
1010
  script_id: number;
1011
+ /**
1012
+ * get the line number
1013
+ */
819
1014
  line: number;
1015
+ /**
1016
+ * get the column number
1017
+ */
820
1018
  column: number;
821
1019
  }
1020
+ /** @internal */
822
1021
  export interface IHeapEdgeBasic {
1022
+ /**
1023
+ * name of the JS reference. If this is a reference to an array element
1024
+ * or internal table element, it is an numeric index
1025
+ */
823
1026
  name_or_index: number | string;
1027
+ /**
1028
+ * type of the JS reference, all types:
1029
+ * `context`, `element`, `property`, `internal`, `hidden`, `shortcut`, `weak`
1030
+ */
824
1031
  type: string;
825
1032
  }
1033
+ /**
1034
+ * An `IHeapEdge` instance represents a JS reference in a heap snapshot.
1035
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1036
+ * and graph edges are JS references among JS heap objects.
1037
+ *
1038
+ * @readonly it is not recommended to modify any `IHeapEdge` instance
1039
+ *
1040
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1041
+ * {@link getHeapFromFile} API.
1042
+ *
1043
+ * ```typescript
1044
+ * import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
1045
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1046
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1047
+ *
1048
+ * (async function () {
1049
+ * const heapFile = dumpNodeHeapSnapshot();
1050
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1051
+ *
1052
+ * // iterate over each edge (JS reference in heap)
1053
+ * heap.edges.forEach((edge: IHeapEdge, i: number) => {
1054
+ * // use the heap edge APIs here
1055
+ * const nameOrIndex = edge.name_or_index;
1056
+ * // ...
1057
+ * });
1058
+ * })();
1059
+ * ```
1060
+ */
826
1061
  export interface IHeapEdge extends IHeapEdgeBasic {
1062
+ /**
1063
+ * get the {@link IHeapSnapshot} containing this JS reference
1064
+ */
827
1065
  snapshot: IHeapSnapshot;
1066
+ /**
1067
+ * index of this JS reference inside the `edge.snapshot.edges` pseudo array
1068
+ */
828
1069
  edgeIndex: number;
1070
+ /**
1071
+ * if `true`, means this is a reference to an array element
1072
+ * or internal table element (`edge.name_or_index` will return a number),
1073
+ * otherwise this is a reference with a string name (`edge.name_or_index`
1074
+ * will return a string)
1075
+ */
829
1076
  is_index: boolean;
1077
+ /**
1078
+ * the index of the JS heap object pointed to by this reference
1079
+ */
830
1080
  to_node: number;
1081
+ /**
1082
+ * returns an {@link IHeapNode} instance representing the JS heap object
1083
+ * pointed to by this reference
1084
+ */
831
1085
  toNode: IHeapNode;
1086
+ /**
1087
+ * returns an {@link IHeapNode} instance representing the hosting
1088
+ * JS heap object where this reference starts
1089
+ */
832
1090
  fromNode: IHeapNode;
833
1091
  }
1092
+ /**
1093
+ * A pseudo array containing all heap graph edges (references to heap objects
1094
+ * in heap). A JS heap could contain millions of references, so memlab uses
1095
+ * a pseudo array as the collection of all the heap edges. The pseudo
1096
+ * array provides API to query and traverse all heap references.
1097
+ *
1098
+ * @readonly modifying this pseudo array is not recommended
1099
+ *
1100
+ * * **Examples**:
1101
+ * ```typescript
1102
+ * import type {IHeapSnapshot, IHeapEdges} from '@memlab/core';
1103
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1104
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1105
+ *
1106
+ * (async function () {
1107
+ * const heapFile = dumpNodeHeapSnapshot();
1108
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1109
+ *
1110
+ * const edges: IHeapEdges = heap.edges;
1111
+ * edges.length;
1112
+ * edges.get(0);
1113
+ * edges.forEach((edge, i) => {
1114
+ * if (stopIteration) {
1115
+ * return false;
1116
+ * }
1117
+ * });
1118
+ * })();
1119
+ * ```
1120
+ */
834
1121
  export interface IHeapEdges {
1122
+ /**
1123
+ * The total number of edges in heap graph (or JS references in heap
1124
+ * snapshot).
1125
+ */
835
1126
  length: number;
836
- get(index: number): IHeapEdge;
1127
+ /**
1128
+ * get an {@link IHeapEdge} element at the specified index
1129
+ * @param index the index of an element in the pseudo array, the index ranges
1130
+ * from 0 to array length - 1. Notice that this is not the heap node id.
1131
+ * @returns When 0 <= `index` < array.length, this API returns the element
1132
+ * at the specified index, otherwise it returns `null`.
1133
+ */
1134
+ get(index: number): Nullable<IHeapEdge>;
1135
+ /**
1136
+ * Iterate over all array elements and apply the callback
1137
+ * to each element in ascending order of element index.
1138
+ * @param callback the callback does not need to return any value, if
1139
+ * the callback returns `false` when iterating on element at index `i`,
1140
+ * then all elements after `i` won't be iterated.
1141
+ */
837
1142
  forEach(callback: (edge: IHeapEdge, index: number) => void | boolean): void;
838
1143
  }
1144
+ /** @internal */
839
1145
  export interface IHeapNodeBasic {
1146
+ /**
1147
+ * the type of the heap node object. All possible types:
1148
+ * This is engine-specific, for example all types in V8:
1149
+ * `hidden`, `array`, `string`, `object`, `code`, `closure`, `regexp`,
1150
+ * `number`, `native`, `synthetic`, `concatenated string`, `sliced string`,
1151
+ * `symbol`, `bigint`
1152
+ */
840
1153
  type: string;
1154
+ /**
1155
+ * this is the `name` field associated with the heap object,
1156
+ * for JS object instances (type `object`), `name` is the constructor's name
1157
+ * of the object instance. for `string`, `name` is the string value.
1158
+ */
841
1159
  name: string;
1160
+ /**
1161
+ * unique id of the heap object
1162
+ */
842
1163
  id: number;
843
1164
  }
844
1165
  export declare type EdgeIterationCallback = (edge: IHeapEdge) => Optional<{
845
1166
  stop: boolean;
846
1167
  }>;
1168
+ /**
1169
+ * An `IHeapNode` instance represents a JS heap object in a heap snapshot.
1170
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1171
+ * and graph edges are JS references among JS heap objects.
1172
+ *
1173
+ * @readonly it is not recommended to modify any `IHeapNode` instance
1174
+ *
1175
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1176
+ * {@link getHeapFromFile} API.
1177
+ *
1178
+ * ```typescript
1179
+ * import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
1180
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1181
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1182
+ *
1183
+ * (async function () {
1184
+ * const heapFile = dumpNodeHeapSnapshot();
1185
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1186
+ *
1187
+ * // iterate over each node (heap object)
1188
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
1189
+ * // use the heap node APIs here
1190
+ * const id = node.id;
1191
+ * const type = node.type;
1192
+ * // ...
1193
+ * });
1194
+ * })();
1195
+ * ```
1196
+ */
847
1197
  export interface IHeapNode extends IHeapNodeBasic {
1198
+ /**
1199
+ * get the {@link IHeapSnapshot} containing this heap object
1200
+ */
848
1201
  snapshot: IHeapSnapshot;
1202
+ /**
1203
+ * * If the heap object is a DOM element and the DOM element is detached
1204
+ * from the DOM tree, `is_detached` will be `true`;
1205
+ * * If the heap object is a React Fiber node and the Fiber node is unmounted
1206
+ * from the React Fiber tree, `is_detached` will be `true`;
1207
+ * otherwise it will be `false`
1208
+ */
849
1209
  is_detached: boolean;
1210
+ /** @internal */
850
1211
  detachState: number;
1212
+ /** @internal */
851
1213
  markAsDetached(): void;
1214
+ /** @internal */
852
1215
  attributes: number;
1216
+ /**
1217
+ * The *shallow size* of the heap object (i.e., the size of memory that is held
1218
+ * by the object itself.). For difference between **shallow size** and
1219
+ * **retained size**, check out
1220
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
1221
+ */
853
1222
  self_size: number;
1223
+ /**
1224
+ * The total number of outgoing JS references (including engine-internal,
1225
+ * native, and JS references).
1226
+ */
854
1227
  edge_count: number;
1228
+ /** @internal */
855
1229
  trace_node_id: number;
1230
+ /**
1231
+ * Get a JS array containing all outgoing JS references from this heap object
1232
+ * (including engine-internal, native, and JS references).
1233
+ */
856
1234
  references: IHeapEdge[];
1235
+ /**
1236
+ * Get a JS array containing all incoming JS references pointing to this heap
1237
+ * object (including engine-internal, native, and JS references).
1238
+ */
857
1239
  referrers: IHeapEdge[];
1240
+ /**
1241
+ * The incoming edge which leads to the parent node
1242
+ * on the shortest path to GC root.
1243
+ */
858
1244
  pathEdge: IHeapEdge | null;
1245
+ /**
1246
+ * index of this heap object inside the `node.snapshot.nodes` pseudo array
1247
+ */
859
1248
  nodeIndex: number;
1249
+ /**
1250
+ * The *retained size* of the heap object (i.e., the total size of memory that
1251
+ * could be released if this object is released). For difference between
1252
+ * **retained size** and **shallow size**, check out
1253
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#object_sizes).
1254
+ */
860
1255
  retainedSize: number;
861
- dominatorNode: IHeapNode | null;
862
- location: IHeapLocation | null;
1256
+ /**
1257
+ * get the dominator node of this node. If the dominator node gets released
1258
+ * there will be no path from GC to this node, and therefore this node can
1259
+ * also be released.
1260
+ * For more information on what a dominator node is, please check out
1261
+ * [this doc](https://developer.chrome.com/docs/devtools/memory-problems/memory-101/#dominators).
1262
+ */
1263
+ dominatorNode: Nullable<IHeapNode>;
1264
+ /**
1265
+ * source location information of this heap object (if it is recorded by
1266
+ * the heap snapshot).
1267
+ */
1268
+ location: Nullable<IHeapLocation>;
1269
+ /** @internal */
863
1270
  highlight?: boolean;
1271
+ /**
1272
+ * check if this a string node (normal string node, concatenated string node
1273
+ * or sliced string node)
1274
+ */
864
1275
  isString: boolean;
1276
+ /**
1277
+ * convert to an {@link IHeapStringNode} object if this node is a string node.
1278
+ * The {@link IHeapStringNode} object supports querying the string content
1279
+ * inside the string node.
1280
+ */
865
1281
  toStringNode(): Nullable<IHeapStringNode>;
1282
+ /**
1283
+ * executes a provided callback once for each JavaScript reference in the
1284
+ * hosting node (or outgoing edges from the node)
1285
+ * @param callback the callback for each outgoing JavaScript reference
1286
+ * @returns this API returns void
1287
+ *
1288
+ * * **Examples**:
1289
+ * ```typescript
1290
+ * node.forEachReference((edge: IHeapEdge) => {
1291
+ * // process edge ...
1292
+ *
1293
+ * // if no need to iterate over remaining edges after
1294
+ * // the current edge in the node.references list
1295
+ * return {stop: true};
1296
+ * });
1297
+ * ```
1298
+ */
866
1299
  forEachReference(callback: EdgeIterationCallback): void;
1300
+ /**
1301
+ * executes a provided callback once for each JavaScript reference pointing
1302
+ * to the hosting node (or incoming edges to the node)
1303
+ * @param callback the callback for each incoming JavaScript reference
1304
+ * @returns this API returns void
1305
+ *
1306
+ * * **Examples**:
1307
+ * ```typescript
1308
+ * node.forEachReferrer((edge: IHeapEdge) => {
1309
+ * // process edge ...
1310
+ *
1311
+ * // if no need to iterate over remaining edges after
1312
+ * // the current edge in the node.referrers list
1313
+ * return {stop: true};
1314
+ * });
1315
+ * ```
1316
+ */
867
1317
  forEachReferrer(callback: EdgeIterationCallback): void;
868
- findReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1318
+ /**
1319
+ * executes a provided predicate callback once for each JavaScript reference
1320
+ * in the hosting node (or outgoing edges from the node) until the predicate
1321
+ * returns `true`
1322
+ * @param predicate the callback for each outgoing JavaScript reference
1323
+ * @returns the first outgoing edge for which the predicate returns `true`,
1324
+ * otherwise returns `null` if no such edge is found.
1325
+ *
1326
+ * * **Examples**:
1327
+ * ```typescript
1328
+ * const reference = node.findAnyReference((edge: IHeapEdge) => {
1329
+ * // find the outgoing reference with name "ref"
1330
+ * return edge.name_or_index === 'ref';
1331
+ * });
1332
+ * ```
1333
+ */
1334
+ findAnyReference: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1335
+ /**
1336
+ * executes a provided predicate callback once for each JavaScript reference
1337
+ * pointing to the hosting node (or incoming edges to the node) until the
1338
+ * predicate returns `true`
1339
+ * @param predicate the callback for each incoming JavaScript reference
1340
+ * @returns the first incoming edge for which the predicate returns `true`,
1341
+ * otherwise returns `null` if no such edge is found.
1342
+ *
1343
+ * * **Examples**:
1344
+ * ```typescript
1345
+ * const referrer = node.findAnyReferrer((edge: IHeapEdge) => {
1346
+ * // find the incoming reference with name "ref"
1347
+ * return edge.name_or_index === 'ref';
1348
+ * });
1349
+ * ```
1350
+ */
869
1351
  findAnyReferrer: (predicate: Predicator<IHeapEdge>) => Nullable<IHeapEdge>;
1352
+ /**
1353
+ * executes a provided predicate callback once for each JavaScript reference
1354
+ * pointing to the hosting node (or incoming edges to the node)
1355
+ * @param predicate the callback for each incoming JavaScript reference
1356
+ * @returns an array containing all the incoming edges for which the
1357
+ * predicate returns `true`, otherwise returns an empty array if no such
1358
+ * edge is found.
1359
+ *
1360
+ * * **Examples**:
1361
+ * ```typescript
1362
+ * const referrers = node.findReferrers((edge: IHeapEdge) => {
1363
+ * // find all the incoming references with name "ref"
1364
+ * return edge.name_or_index === 'ref';
1365
+ * });
1366
+ * ```
1367
+ */
870
1368
  findReferrers: (predicate: Predicator<IHeapEdge>) => IHeapEdge[];
1369
+ /**
1370
+ * Given a JS reference's name and type, this API finds an outgoing JS
1371
+ * reference from the hosting node.
1372
+ * @param edgeName the name of the outgoing JavaScript reference
1373
+ * @param edgeType optional parameter specifying the type of the outgoing
1374
+ * JavaScript reference
1375
+ * @returns the outgoing edge that meets the specification
1376
+ *
1377
+ * * **Examples**:
1378
+ * ```typescript
1379
+ * // find the internal reference to node's hidden class
1380
+ * const reference = node.getReference('map', 'hidden');
1381
+ * ```
1382
+ */
871
1383
  getReference: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
1384
+ /**
1385
+ * Given a JS reference's name and type, this API finds the outgoing JS
1386
+ * reference from the hosting node, and returns the JS heap object pointed to
1387
+ * by the outgoing JS reference.
1388
+ * @param edgeName the name of the outgoing JavaScript reference
1389
+ * @param edgeType optional parameter specifying the type of the outgoing
1390
+ * JavaScript reference
1391
+ * @returns the node pointed to by the outgoing reference that meets
1392
+ * the specification
1393
+ *
1394
+ * * **Examples**:
1395
+ * ```typescript
1396
+ * // find the node's hidden class
1397
+ * const hiddenClassNode = node.getReferenceNode('map', 'hidden');
1398
+ * // this is equivalent to
1399
+ * const hiddenClassNode2 = node.getReference('map', 'hidden')?.toNode;
1400
+ * ```
1401
+ */
872
1402
  getReferenceNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
1403
+ /**
1404
+ * Given a JS reference's name and type, this API finds an incoming JS
1405
+ * reference pointing to the hosting node.
1406
+ * @param edgeName the name of the incoming JavaScript reference
1407
+ * @param edgeType optional parameter specifying the type of the incoming
1408
+ * JavaScript reference
1409
+ * @returns the incoming edge that meets the specification
1410
+ *
1411
+ * * **Examples**:
1412
+ * ```typescript
1413
+ * // find one of the JS reference named "ref" pointing to node
1414
+ * const reference = node.getAnyReferrer('ref', 'property');
1415
+ * ```
1416
+ */
873
1417
  getAnyReferrer: (edgeName: string | number, edgeType?: string) => Nullable<IHeapEdge>;
1418
+ /**
1419
+ * Given a JS reference's name and type, this API finds one of the incoming JS
1420
+ * references pointing to the hosting node, and returns the JS heap object
1421
+ * containing the incoming reference.
1422
+ * @param edgeName the name of the incoming JavaScript reference
1423
+ * @param edgeType optional parameter specifying the type of the incoming
1424
+ * JavaScript reference
1425
+ * @returns the node containing the incoming JS reference that meets
1426
+ * the specification
1427
+ *
1428
+ * * **Examples**:
1429
+ * ```typescript
1430
+ * // find one of the JS heap object with a JS reference
1431
+ * // named "ref" pointing to node
1432
+ * const n1 = node.getAnyReferrerNode('ref', 'property');
1433
+ * // this is equivalent to
1434
+ * const n2 = node.getAnyReferrer('ref', 'property')?.fromNode;
1435
+ * ```
1436
+ */
874
1437
  getAnyReferrerNode: (edgeName: string | number, edgeType?: string) => Nullable<IHeapNode>;
1438
+ /**
1439
+ * Given a JS reference's name and type, this API finds all the incoming JS
1440
+ * reference pointing to the hosting node.
1441
+ * @param edgeName the name of the incoming JavaScript reference
1442
+ * @param edgeType optional parameter specifying the type of the incoming
1443
+ * JavaScript reference
1444
+ * @returns an array containing all the incoming edges that
1445
+ * meet the specification
1446
+ *
1447
+ * * **Examples**:
1448
+ * ```typescript
1449
+ * // find all of of the JS reference named "ref" pointing to node
1450
+ * const referrers = node.getReferrers('ref', 'property');
1451
+ * ```
1452
+ */
875
1453
  getReferrers: (edgeName: string | number, edgeType?: string) => IHeapEdge[];
1454
+ /**
1455
+ * Given a JS reference's name and type, this API finds all of the incoming JS
1456
+ * references pointing to the hosting node, and returns an array containing
1457
+ * the hosting node for each of the incoming JS references.
1458
+ * @param edgeName the name of the incoming JavaScript reference
1459
+ * @param edgeType optional parameter specifying the type of the incoming
1460
+ * JavaScript reference
1461
+ * @returns an array containing the hosting nodes, with each node corresponds
1462
+ * to each incoming JS reference that meets the specification
1463
+ *
1464
+ * * **Examples**:
1465
+ * ```typescript
1466
+ * // find all of the JS heap object with a JS reference
1467
+ * // named "ref" pointing to node
1468
+ * const nodes1 = node.getReferrerNodes('ref', 'property');
1469
+ * // this is equivalent to
1470
+ * const nodes2 = node.getReferrers('ref', 'property')
1471
+ * .map(edge => edge.fromNode);
1472
+ * ```
1473
+ */
876
1474
  getReferrerNodes: (edgeName: string | number, edgeType?: string) => IHeapNode[];
877
1475
  }
1476
+ /**
1477
+ * An `IHeapStringNode` instance represents a JS string in a heap snapshot.
1478
+ * A heap snapshot is generally a graph where graph nodes are JS heap objects
1479
+ * and graph edges are JS references among JS heap objects.
1480
+ *
1481
+ * @readonly it is not recommended to modify any `IHeapStringNode` instance
1482
+ *
1483
+ * * **Examples**: V8 or hermes heap snapshot can be parsed by the
1484
+ * {@link getHeapFromFile} API.
1485
+ *
1486
+ * ```typescript
1487
+ * import type {IHeapSnapshot, IHeapNode, IHeapStringNode} from '@memlab/core';
1488
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1489
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1490
+ *
1491
+ * (async function () {
1492
+ * const heapFile = dumpNodeHeapSnapshot();
1493
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1494
+ *
1495
+ * // iterate over each node (heap object)
1496
+ * heap.nodes.forEach((node: IHeapNode, i: number) => {
1497
+ * if (node.isString) {
1498
+ * const stringNode: IheapStringNode = node.toStringNode();
1499
+ * // get the string value
1500
+ * stringNode.stringValue;
1501
+ * }
1502
+ * });
1503
+ * })();
1504
+ * ```
1505
+ */
878
1506
  export interface IHeapStringNode extends IHeapNode {
1507
+ /**
1508
+ * get the string value of the JS string heap object associated with
1509
+ * this `IHeapStringNode` instance in heap
1510
+ */
879
1511
  stringValue: string;
880
1512
  }
1513
+ /**
1514
+ * A pseudo array containing all heap graph nodes (JS objects
1515
+ * in heap). A JS heap could contain millions of objects, so memlab uses
1516
+ * a pseudo array as the collection of all the heap nodes. The pseudo
1517
+ * array provides API to query and traverse all heap objects.
1518
+ *
1519
+ * @readonly modifying this pseudo array is not recommended
1520
+ *
1521
+ * * **Examples**:
1522
+ * ```typescript
1523
+ * import type {IHeapSnapshot, IHeapNodes} from '@memlab/core';
1524
+ * import {dumpNodeHeapSnapshot} from '@memlab/core';
1525
+ * import {getHeapFromFile} from '@memlab/heap-analysis';
1526
+ *
1527
+ * (async function () {
1528
+ * const heapFile = dumpNodeHeapSnapshot();
1529
+ * const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
1530
+ *
1531
+ * const nodes: IHeapNodes = heap.nodes;
1532
+ * nodes.length;
1533
+ * nodes.get(0);
1534
+ * nodes.forEach((node, i) => {
1535
+ * if (stopIteration) {
1536
+ * return false;
1537
+ * }
1538
+ * });
1539
+ * })();
1540
+ * ```
1541
+ */
881
1542
  export interface IHeapNodes {
1543
+ /**
1544
+ * The total number of nodes in heap graph (or JS objects in heap
1545
+ * snapshot).
1546
+ */
882
1547
  length: number;
883
- get(index: number): IHeapNode;
1548
+ /**
1549
+ * get an {@link IHeapNode} element at the specified index
1550
+ * @param index the index of an element in the pseudo array, the index ranges
1551
+ * from 0 to array length - 1. Notice that this is not the heap node id.
1552
+ * @returns When 0 <= `index` < array.length, this API returns the element
1553
+ * at the specified index, otherwise it returns `null`.
1554
+ */
1555
+ get(index: number): Nullable<IHeapNode>;
1556
+ /**
1557
+ * Iterate over all array elements and apply the callback
1558
+ * to each element in ascending order of element index.
1559
+ * @param callback the callback does not need to return any value, if
1560
+ * the callback returns `false` when iterating on element at index `i`,
1561
+ * then all elements after `i` won't be iterated.
1562
+ */
884
1563
  forEach(callback: (node: IHeapNode, index: number) => void | boolean): void;
1564
+ /** @internal */
885
1565
  forEachTraceable(callback: (node: IHeapNode, index: number) => void | boolean): void;
886
1566
  }
887
1567
  /** @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.5",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",