@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 +3 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -1
- package/dist/lib/Config.d.ts +12 -8
- package/dist/lib/Config.js +15 -0
- package/dist/lib/Constant.js +2 -2
- package/dist/lib/HeapParser.js +1 -1
- package/dist/lib/PackageInfoLoader.d.ts +7 -0
- package/dist/lib/PackageInfoLoader.js +66 -0
- package/dist/lib/Serializer.js +4 -1
- package/dist/lib/Types.d.ts +701 -21
- package/dist/lib/Utils.js +2 -2
- package/dist/lib/heap-data/HeapNode.d.ts +1 -1
- package/dist/lib/heap-data/HeapNode.js +1 -1
- package/dist/lib/heap-data/HeapSnapshot.js +7 -1
- package/dist/paths/TraceFinder.js +4 -2
- package/dist/trace-cluster/TraceElement.d.ts +1 -1
- package/dist/trace-cluster/TraceElement.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This is the memlab core library. It contains V8/Hermes heap snapshot parser, core algorithms, leak trace clustering, utilities, and config.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
https://facebookincubator.github.io/memlab
|
|
5
|
+
## Online Resources
|
|
6
|
+
* [Official Website and Demo](https://facebookincubator.github.io/memlab)
|
|
7
|
+
* [Documentation](https://facebookincubator.github.io/memlab/docs/intro)
|
package/dist/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);
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/Config.js
CHANGED
|
@@ -54,6 +54,7 @@ class MemLabConfig {
|
|
|
54
54
|
this._deviceManualOverridden = false;
|
|
55
55
|
this._timerNodes = ['Pending activities'];
|
|
56
56
|
this._timerEdges = [];
|
|
57
|
+
this._isHeadfulBrowser = false;
|
|
57
58
|
this.targetApp = Constant_1.default.unset;
|
|
58
59
|
this.targetTab = Constant_1.default.unset;
|
|
59
60
|
this.analysisMode = Constant_1.default.unset;
|
|
@@ -66,6 +67,7 @@ class MemLabConfig {
|
|
|
66
67
|
this.specifiedEngine = false;
|
|
67
68
|
// set puppeteer configuration
|
|
68
69
|
this.puppeteerConfig = {
|
|
70
|
+
headless: !this._isHeadfulBrowser,
|
|
69
71
|
devtools: this.openDevtoolsConsole,
|
|
70
72
|
// IMPORTANT: test ContinuousTest before change this config
|
|
71
73
|
ignoreHTTPSErrors: true,
|
|
@@ -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
|
}
|
package/dist/lib/Constant.js
CHANGED
|
@@ -13,8 +13,8 @@ const InternalValueSetter_1 = require("./InternalValueSetter");
|
|
|
13
13
|
const constants = {
|
|
14
14
|
isFB: false,
|
|
15
15
|
isFRL: false,
|
|
16
|
-
defaultEngine: '
|
|
17
|
-
supportedEngines: ['
|
|
16
|
+
defaultEngine: 'V8',
|
|
17
|
+
supportedEngines: ['V8', 'hermes'],
|
|
18
18
|
supportedBrowsers: Object.create(null),
|
|
19
19
|
internalDir: 'fb-internal',
|
|
20
20
|
monoRepoDir: '',
|
package/dist/lib/HeapParser.js
CHANGED
|
@@ -102,7 +102,7 @@ function identifyAndSetEngine(snapshot) {
|
|
|
102
102
|
return; // skip if engine type is manually set
|
|
103
103
|
}
|
|
104
104
|
Console_1.default.overwrite('identifying snapshot engine...');
|
|
105
|
-
let engine = '
|
|
105
|
+
let engine = 'V8';
|
|
106
106
|
snapshot.nodes.forEach((node) => {
|
|
107
107
|
if (node.type === 'object' && node.name.startsWith('Object(')) {
|
|
108
108
|
engine = 'hermes';
|
|
@@ -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();
|
package/dist/lib/Serializer.js
CHANGED
|
@@ -182,6 +182,9 @@ function JSONifyFiberNodeReturnTrace(node, args, options) {
|
|
|
182
182
|
continue;
|
|
183
183
|
}
|
|
184
184
|
const parent = node.snapshot.nodes.get(index);
|
|
185
|
+
if (!parent) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
185
188
|
const parentInfo = getNodeNameInJSON(parent, args);
|
|
186
189
|
key = `${key}: --return (property)---> ${parentInfo}`;
|
|
187
190
|
const info = JSONifyFiberNodeShallow(parent);
|
|
@@ -593,7 +596,7 @@ function summarizeNode(node, options = {}) {
|
|
|
593
596
|
let nodeImpact = '';
|
|
594
597
|
if (nodeRetainSize) {
|
|
595
598
|
nodeImpact = options.color
|
|
596
|
-
? chalk_1.default.grey('[') + chalk_1.default.blue(nodeRetainSize) + chalk_1.default.grey(']')
|
|
599
|
+
? chalk_1.default.grey('[') + chalk_1.default.blue.bold(nodeRetainSize) + chalk_1.default.grey(']')
|
|
597
600
|
: `[${nodeRetainSize}]`;
|
|
598
601
|
}
|
|
599
602
|
const name = summarizeNodeName(node, options);
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
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
|
|
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
|
-
* {
|
|
254
|
-
* {
|
|
255
|
-
* {
|
|
256
|
-
* {
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
862
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
584
|
+
return node.findAnyReference((edge) => edge.name_or_index === edgeName &&
|
|
585
585
|
(type === undefined || edge.type === type));
|
|
586
586
|
}
|
|
587
587
|
function getEdgeStartsWithName(node, prefix) {
|
|
588
588
|
if (!node) {
|
|
589
589
|
return null;
|
|
590
590
|
}
|
|
591
|
-
return node.
|
|
591
|
+
return node.findAnyReference(edge => typeof edge.name_or_index === 'string' &&
|
|
592
592
|
edge.name_or_index.startsWith(prefix));
|
|
593
593
|
}
|
|
594
594
|
function isStringNode(node) {
|
|
@@ -31,7 +31,7 @@ export default class HeapNode implements IHeapNode {
|
|
|
31
31
|
get trace_node_id(): number;
|
|
32
32
|
get references(): HeapEdge[];
|
|
33
33
|
forEachReference(callback: EdgeIterationCallback): void;
|
|
34
|
-
|
|
34
|
+
findAnyReference(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
|
|
35
35
|
findAnyReferrer(predicate: Predicator<IHeapEdge>): Nullable<IHeapEdge>;
|
|
36
36
|
findReferrers(predicate: Predicator<IHeapEdge>): IHeapEdge[];
|
|
37
37
|
get referrers(): HeapEdge[];
|
|
@@ -82,6 +82,9 @@ class HeapSnapshot {
|
|
|
82
82
|
this.nodes = {
|
|
83
83
|
length: self._nodeCount,
|
|
84
84
|
get(idx) {
|
|
85
|
+
if (idx < 0 || idx >= self._nodeCount) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
85
88
|
return new HeapNode_1.default(self, idx);
|
|
86
89
|
},
|
|
87
90
|
forEach(cb) {
|
|
@@ -109,6 +112,9 @@ class HeapSnapshot {
|
|
|
109
112
|
this.edges = {
|
|
110
113
|
length: self._edgeCount,
|
|
111
114
|
get(idx) {
|
|
115
|
+
if (idx < 0 || idx >= self._edgeCount) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
112
118
|
return new HeapEdge_1.default(self, idx);
|
|
113
119
|
},
|
|
114
120
|
forEach(cb) {
|
|
@@ -180,7 +186,7 @@ class HeapSnapshot {
|
|
|
180
186
|
return false;
|
|
181
187
|
}
|
|
182
188
|
// check if the table has any weak reference to any object
|
|
183
|
-
const ref = table.
|
|
189
|
+
const ref = table.findAnyReference((edge) => edge.type === 'weak' && edge.toNode.name !== 'system / Oddball');
|
|
184
190
|
return ref != null;
|
|
185
191
|
}
|
|
186
192
|
getNodeById(id) {
|
|
@@ -159,7 +159,8 @@ class TraceFinder {
|
|
|
159
159
|
if (!Utils_1.default.isEssentialEdge(nodeIndex, edgeType, rootNodeIndex)) {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
const childNodeIndex = forwardEdges.get(edgeIndex)
|
|
162
|
+
const childNodeIndex = forwardEdges.get(edgeIndex)
|
|
163
|
+
.toNode.nodeIndex;
|
|
163
164
|
if (visited[childNodeIndex]) {
|
|
164
165
|
continue;
|
|
165
166
|
}
|
|
@@ -278,7 +279,8 @@ class TraceFinder {
|
|
|
278
279
|
if (!Utils_1.default.isEssentialEdge(ROOT_NODE_INDEX, edgeType, ROOT_NODE_INDEX)) {
|
|
279
280
|
continue;
|
|
280
281
|
}
|
|
281
|
-
const childNodeIndex = forwardEdges.get(edgeIndex).toNode
|
|
282
|
+
const childNodeIndex = forwardEdges.get(edgeIndex).toNode
|
|
283
|
+
.nodeIndex;
|
|
282
284
|
nodesWithOutdatedDominatorInfo[nodeIndex2PostOrderIndex[childNodeIndex]] = 1;
|
|
283
285
|
}
|
|
284
286
|
// now iterate through all nodes in the heap
|
|
@@ -34,7 +34,7 @@ export declare class NodeRecord implements IHeapNode {
|
|
|
34
34
|
get referrers(): IHeapEdge[];
|
|
35
35
|
toStringNode(): IHeapStringNode;
|
|
36
36
|
forEachReferrer(_callback: EdgeIterationCallback): void;
|
|
37
|
-
|
|
37
|
+
findAnyReference(): Nullable<IHeapEdge>;
|
|
38
38
|
findAnyReferrer(): Nullable<IHeapEdge>;
|
|
39
39
|
findReferrers(): IHeapEdge[];
|
|
40
40
|
set pathEdge(r: IHeapEdge);
|
|
@@ -70,8 +70,8 @@ class NodeRecord {
|
|
|
70
70
|
_callback) {
|
|
71
71
|
throw new Error('NodeRecord.forEachReferrer is not implemented');
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
throw new Error('NodeRecord.
|
|
73
|
+
findAnyReference() {
|
|
74
|
+
throw new Error('NodeRecord.findAnyReference is not implemented');
|
|
75
75
|
}
|
|
76
76
|
findAnyReferrer() {
|
|
77
77
|
throw new Error('NodeRecord.findAnyReferrer is not implemented');
|