@memlab/core 1.1.21 → 1.1.23
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/dist/__tests__/lib/TestUtils.d.ts +13 -0
- package/dist/__tests__/lib/TestUtils.js +39 -0
- package/dist/lib/Config.d.ts +10 -1
- package/dist/lib/Config.js +21 -2
- package/dist/lib/Constant.js +1 -0
- package/dist/lib/FileManager.js +1 -1
- package/dist/lib/HeapAnalyzer.js +49 -27
- package/dist/lib/RunInfoUtils.d.ts +1 -0
- package/dist/lib/RunInfoUtils.js +8 -4
- package/dist/lib/SerializationHelper.d.ts +18 -0
- package/dist/lib/SerializationHelper.js +36 -0
- package/dist/lib/Serializer.d.ts +2 -6
- package/dist/lib/Serializer.js +12 -3
- package/dist/lib/TraceSampler.d.ts +36 -0
- package/dist/lib/TraceSampler.js +78 -0
- package/dist/lib/Types.d.ts +26 -3
- package/dist/lib/Utils.d.ts +8 -0
- package/dist/lib/Utils.js +74 -6
- package/dist/lib/charts/MemoryBarChart.js +2 -2
- package/dist/lib/heap-data/HeapStringNode.js +25 -16
- package/dist/lib/leak-filters/LeakFilterRuleList.js +2 -0
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.d.ts +0 -1
- package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.js +22 -9
- package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
- package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.js +4 -3
- package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.d.ts +20 -0
- package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.js +30 -0
- package/dist/lib/trace-filters/TraceFilterRuleList.js +2 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts +15 -0
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.js +44 -0
- package/dist/paths/TraceFinder.js +1 -1
- package/dist/trace-cluster/TraceBucket.d.ts +1 -1
- package/dist/trace-cluster/TraceBucket.js +54 -22
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import { IHeapSnapshot } from '../../lib/Types';
|
|
11
|
+
/** @internal */
|
|
12
|
+
export declare function getFullHeapFromFile(file: string): Promise<IHeapSnapshot>;
|
|
13
|
+
//# sourceMappingURL=TestUtils.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.getFullHeapFromFile = void 0;
|
|
22
|
+
const __1 = require("../..");
|
|
23
|
+
/** @internal */
|
|
24
|
+
function getFullHeapFromFile(file) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
return yield loadProcessedSnapshot({ file });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
exports.getFullHeapFromFile = getFullHeapFromFile;
|
|
30
|
+
function loadProcessedSnapshot(options = {}) {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
const opt = { buildNodeIdIndex: true, verbose: true };
|
|
33
|
+
const file = options.file || __1.utils.getSnapshotFilePathWithTabType(/.*/);
|
|
34
|
+
const snapshot = yield __1.utils.getSnapshotFromFile(file, opt);
|
|
35
|
+
__1.analysis.preparePathFinder(snapshot);
|
|
36
|
+
__1.info.flush();
|
|
37
|
+
return snapshot;
|
|
38
|
+
});
|
|
39
|
+
}
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ declare type ConfigOption = {
|
|
|
38
38
|
workDir?: string;
|
|
39
39
|
};
|
|
40
40
|
/** @internal */
|
|
41
|
+
export declare enum TraceObjectMode {
|
|
42
|
+
Default = 1,
|
|
43
|
+
SelectedJSObjects = 2
|
|
44
|
+
}
|
|
45
|
+
/** @internal */
|
|
41
46
|
export declare enum ErrorHandling {
|
|
42
47
|
Halt = 1,
|
|
43
48
|
Throw = 2
|
|
@@ -109,7 +114,7 @@ export declare class MemLabConfig {
|
|
|
109
114
|
qes: QuickExperiment[];
|
|
110
115
|
isOndemand: boolean;
|
|
111
116
|
useExternalSnapshot: boolean;
|
|
112
|
-
|
|
117
|
+
externalRunMetaTemplateFile: string;
|
|
113
118
|
externalSnapshotVisitOrderFile: string;
|
|
114
119
|
externalSnapshotDir: Nullable<string>;
|
|
115
120
|
externalSnapshotFilePaths: string[];
|
|
@@ -177,6 +182,7 @@ export declare class MemLabConfig {
|
|
|
177
182
|
nodeIgnoreSetInShape: Set<string>;
|
|
178
183
|
oversizeObjectAsLeak: boolean;
|
|
179
184
|
oversizeThreshold: number;
|
|
185
|
+
traceAllObjectsMode: TraceObjectMode;
|
|
180
186
|
clusterRetainedSizeThreshold: number;
|
|
181
187
|
externalLeakFilter?: Optional<ILeakFilter>;
|
|
182
188
|
monoRepoDir: string;
|
|
@@ -199,6 +205,9 @@ export declare class MemLabConfig {
|
|
|
199
205
|
interceptScript: boolean;
|
|
200
206
|
isAnalyzingMainThread: boolean;
|
|
201
207
|
targetWorkerTitle: Nullable<string>;
|
|
208
|
+
noReCluster: boolean;
|
|
209
|
+
maxSamplesForClustering: number;
|
|
210
|
+
filterTraceByName: Nullable<string>;
|
|
202
211
|
constructor(options?: ConfigOption);
|
|
203
212
|
private initInternalConfigs;
|
|
204
213
|
private init;
|
package/dist/lib/Config.js
CHANGED
|
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.MemLabConfig = exports.ErrorHandling = void 0;
|
|
38
|
+
exports.MemLabConfig = exports.ErrorHandling = exports.TraceObjectMode = void 0;
|
|
39
39
|
const path_1 = __importDefault(require("path"));
|
|
40
40
|
const RunningModes_1 = __importDefault(require("../modes/RunningModes"));
|
|
41
41
|
const Console_1 = __importDefault(require("./Console"));
|
|
@@ -57,6 +57,12 @@ const defaultViewport = {
|
|
|
57
57
|
deviceScaleFactor: 1,
|
|
58
58
|
};
|
|
59
59
|
/** @internal */
|
|
60
|
+
var TraceObjectMode;
|
|
61
|
+
(function (TraceObjectMode) {
|
|
62
|
+
TraceObjectMode[TraceObjectMode["Default"] = 1] = "Default";
|
|
63
|
+
TraceObjectMode[TraceObjectMode["SelectedJSObjects"] = 2] = "SelectedJSObjects";
|
|
64
|
+
})(TraceObjectMode = exports.TraceObjectMode || (exports.TraceObjectMode = {}));
|
|
65
|
+
/** @internal */
|
|
60
66
|
var ErrorHandling;
|
|
61
67
|
(function (ErrorHandling) {
|
|
62
68
|
ErrorHandling[ErrorHandling["Halt"] = 1] = "Halt";
|
|
@@ -176,12 +182,17 @@ class MemLabConfig {
|
|
|
176
182
|
// if true, split dataset into trunks
|
|
177
183
|
// with random order for sequential clustering
|
|
178
184
|
this.seqClusteringIsRandomChunks = false;
|
|
185
|
+
// maximum number of samples as input for leak trace clustering
|
|
186
|
+
this.maxSamplesForClustering = 5000;
|
|
179
187
|
// extra E2E run info (other than the fields defined in
|
|
180
188
|
// RunMetaInfo like app, interaction, browserInfo).
|
|
181
189
|
// Information saved in this map will be
|
|
182
190
|
// auto-serialized to run-meta.json when the file is saved
|
|
183
191
|
// and auto-deserialized from run-meta.json when the file is loaded
|
|
184
192
|
this.extraRunInfoMap = new Map();
|
|
193
|
+
// if specified via CLI options, this will filter leak traces by
|
|
194
|
+
// node and edge names in the leak trace
|
|
195
|
+
this.filterTraceByName = null;
|
|
185
196
|
}
|
|
186
197
|
// initialize configurable parameters
|
|
187
198
|
init(options = {}) {
|
|
@@ -209,6 +220,8 @@ class MemLabConfig {
|
|
|
209
220
|
this.skipGC = false;
|
|
210
221
|
// true if running in ContinuousTest
|
|
211
222
|
this.isContinuousTest = false;
|
|
223
|
+
// true if reclustering is turned off
|
|
224
|
+
this.noReCluster = false;
|
|
212
225
|
// true if running a local test
|
|
213
226
|
this.isTest = false;
|
|
214
227
|
// true if running in local puppeteer mode
|
|
@@ -308,7 +321,10 @@ class MemLabConfig {
|
|
|
308
321
|
// node names excluded from the trace finding
|
|
309
322
|
this.nodeNameBlockList = new Set(['system / PropertyCell']);
|
|
310
323
|
// edge names excluded from the trace finding
|
|
311
|
-
this.edgeNameBlockList = new Set([
|
|
324
|
+
this.edgeNameBlockList = new Set([
|
|
325
|
+
'feedback_cell',
|
|
326
|
+
'part of key -> value pair in ephemeron table',
|
|
327
|
+
]);
|
|
312
328
|
// node names less preferable in trace finding
|
|
313
329
|
this.nodeNameGreyList = new Set([
|
|
314
330
|
'InternalNode',
|
|
@@ -341,6 +357,9 @@ class MemLabConfig {
|
|
|
341
357
|
this.oversizeObjectAsLeak = false;
|
|
342
358
|
// if larger than this threshold, consider as memory leak
|
|
343
359
|
this.oversizeThreshold = 0;
|
|
360
|
+
// when specified default, this mode will trace/diff all objects
|
|
361
|
+
// you can specified other modes (e.g., selected JS objects only)
|
|
362
|
+
this.traceAllObjectsMode = TraceObjectMode.Default;
|
|
344
363
|
// only report leak clusters with aggregated retained size
|
|
345
364
|
// bigger than this threshold
|
|
346
365
|
this.clusterRetainedSizeThreshold = 0;
|
package/dist/lib/Constant.js
CHANGED
package/dist/lib/FileManager.js
CHANGED
|
@@ -427,7 +427,7 @@ class FileManager {
|
|
|
427
427
|
config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
|
|
428
428
|
config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
|
|
429
429
|
config.reportScreenshotFile = path_1.default.join(outDir, 'report.png');
|
|
430
|
-
config.
|
|
430
|
+
config.externalRunMetaTemplateFile = this.getRunMetaExternalTemplateFile();
|
|
431
431
|
config.externalSnapshotVisitOrderFile =
|
|
432
432
|
this.getSnapshotSequenceExternalTemplateFile();
|
|
433
433
|
joinAndProcessDir(options, this.getUniqueTraceClusterDir(options));
|
package/dist/lib/HeapAnalyzer.js
CHANGED
|
@@ -35,6 +35,7 @@ const TraceBucket_1 = __importDefault(require("../trace-cluster/TraceBucket"));
|
|
|
35
35
|
const LeakObjectFilter_1 = require("./leak-filters/LeakObjectFilter");
|
|
36
36
|
const MLTraceSimilarityStrategy_1 = __importDefault(require("../trace-cluster/strategies/MLTraceSimilarityStrategy"));
|
|
37
37
|
const LeakTraceFilter_1 = require("./trace-filters/LeakTraceFilter");
|
|
38
|
+
const TraceSampler_1 = __importDefault(require("./TraceSampler"));
|
|
38
39
|
class MemoryAnalyst {
|
|
39
40
|
checkLeak() {
|
|
40
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -48,13 +49,13 @@ class MemoryAnalyst {
|
|
|
48
49
|
const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({
|
|
49
50
|
workDir: controlWorkDir,
|
|
50
51
|
}));
|
|
51
|
-
const
|
|
52
|
-
workDir:
|
|
53
|
-
});
|
|
52
|
+
const treatmentSnapshotDirs = options.treatmentWorkDirs.map(treatmentWorkDir => FileManager_1.default.getCurDataDir({
|
|
53
|
+
workDir: treatmentWorkDir,
|
|
54
|
+
}));
|
|
54
55
|
// check control working dir
|
|
55
56
|
controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }));
|
|
56
57
|
// check treatment working dir
|
|
57
|
-
Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
|
|
58
|
+
treatmentSnapshotDirs.forEach(treatmentSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir }));
|
|
58
59
|
// display control and treatment memory
|
|
59
60
|
MemoryBarChart_1.default.plotMemoryBarChart(options);
|
|
60
61
|
return this.diffMemoryLeakTraces(options);
|
|
@@ -75,17 +76,26 @@ class MemoryAnalyst {
|
|
|
75
76
|
leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir }));
|
|
76
77
|
controlSnapshots.push(snapshotDiff.snapshot);
|
|
77
78
|
}
|
|
78
|
-
// diff snapshots and get treatment raw paths
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// diff snapshots from treatment dirs and get treatment raw paths array
|
|
80
|
+
const treatmentSnapshots = [];
|
|
81
|
+
const leakPathsFromTreatmentRuns = [];
|
|
82
|
+
let firstTreatmentSnapshotDiff = null;
|
|
83
|
+
for (const treatmentWorkDir of options.treatmentWorkDirs) {
|
|
84
|
+
const snapshotDiff = yield this.diffSnapshots({
|
|
85
|
+
loadAllSnapshots: true,
|
|
86
|
+
workDir: treatmentWorkDir,
|
|
87
|
+
});
|
|
88
|
+
if (firstTreatmentSnapshotDiff == null) {
|
|
89
|
+
firstTreatmentSnapshotDiff = snapshotDiff;
|
|
90
|
+
}
|
|
91
|
+
leakPathsFromTreatmentRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: treatmentWorkDir }));
|
|
92
|
+
treatmentSnapshots.push(snapshotDiff.snapshot);
|
|
93
|
+
}
|
|
85
94
|
const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length));
|
|
95
|
+
const treatmentPathCounts = JSON.stringify(leakPathsFromTreatmentRuns.map(leakPaths => leakPaths.length));
|
|
86
96
|
Console_1.default.topLevel(`${controlPathCounts} traces from control group`);
|
|
87
|
-
Console_1.default.topLevel(`${
|
|
88
|
-
const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots,
|
|
97
|
+
Console_1.default.topLevel(`${treatmentPathCounts} traces from treatment group`);
|
|
98
|
+
const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, leakPathsFromTreatmentRuns, treatmentSnapshots, Utils_1.default.aggregateDominatorMetrics, {
|
|
89
99
|
strategy: Config_1.default.isMLClustering
|
|
90
100
|
? new MLTraceSimilarityStrategy_1.default()
|
|
91
101
|
: undefined,
|
|
@@ -94,7 +104,10 @@ class MemoryAnalyst {
|
|
|
94
104
|
yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
|
|
95
105
|
// serialize JSON file with detailed leak trace information
|
|
96
106
|
const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path);
|
|
97
|
-
|
|
107
|
+
if (firstTreatmentSnapshotDiff == null) {
|
|
108
|
+
throw Utils_1.default.haltOrThrow('treatemnt snapshot diff result not found');
|
|
109
|
+
}
|
|
110
|
+
return LeakTraceDetailsLogger_1.default.logTraces(firstTreatmentSnapshotDiff.leakedHeapNodeIdSet, firstTreatmentSnapshotDiff.snapshot, firstTreatmentSnapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir);
|
|
98
111
|
});
|
|
99
112
|
}
|
|
100
113
|
// find all unique pattern of leaks
|
|
@@ -357,26 +370,35 @@ class MemoryAnalyst {
|
|
|
357
370
|
this.filterLeakedObjects(leakedNodeIds, snapshot);
|
|
358
371
|
const leakTraceFilter = new LeakTraceFilter_1.LeakTraceFilter();
|
|
359
372
|
const nodeIdInPaths = new Set();
|
|
360
|
-
const
|
|
361
|
-
let numOfLeakedObjects = 0;
|
|
362
|
-
let i = 0;
|
|
373
|
+
const samplePool = [];
|
|
363
374
|
// analysis for each node
|
|
364
375
|
Utils_1.default.applyToNodes(leakedNodeIds, snapshot, node => {
|
|
365
|
-
if (!Config_1.default.isContinuousTest && ++i % 11 === 0) {
|
|
366
|
-
Console_1.default.overwrite(`progress: ${i} / ${leakedNodeIds.size} @${node.id}`);
|
|
367
|
-
}
|
|
368
376
|
// BFS search for path from the leaked node to GC roots
|
|
369
377
|
const p = finder.getPathToGCRoots(snapshot, node);
|
|
370
378
|
if (p == null ||
|
|
371
379
|
!leakTraceFilter.filter(p, { config: Config_1.default, leakedNodeIds, snapshot })) {
|
|
372
380
|
return;
|
|
373
381
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
// filter leak trace based on CLI-specified node or edge names
|
|
383
|
+
if (!Utils_1.default.pathHasNodeOrEdgeWithName(p, Config_1.default.filterTraceByName)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// ignore if the leak trace is too long
|
|
387
|
+
if (Utils_1.default.getLeakTracePathLength(p) > 100) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
samplePool.push(p);
|
|
377
391
|
}, { reverse: true });
|
|
392
|
+
const sampler = new TraceSampler_1.default(samplePool.length);
|
|
393
|
+
const paths = samplePool.filter(p => {
|
|
394
|
+
if (sampler.sample()) {
|
|
395
|
+
this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
});
|
|
378
400
|
if (Config_1.default.verbose) {
|
|
379
|
-
Console_1.default.midLevel(
|
|
401
|
+
Console_1.default.midLevel(`Filter and select ${paths.length} leaked trace`);
|
|
380
402
|
}
|
|
381
403
|
return paths;
|
|
382
404
|
}
|
|
@@ -410,11 +432,11 @@ class MemoryAnalyst {
|
|
|
410
432
|
clusterHeapObjects(objectIds, snapshot) {
|
|
411
433
|
const finder = this.preparePathFinder(snapshot);
|
|
412
434
|
const paths = [];
|
|
413
|
-
|
|
435
|
+
const sampler = new TraceSampler_1.default(objectIds.size);
|
|
414
436
|
// analysis for each node
|
|
415
437
|
Utils_1.default.applyToNodes(objectIds, snapshot, node => {
|
|
416
|
-
if (
|
|
417
|
-
|
|
438
|
+
if (!sampler.sample()) {
|
|
439
|
+
return;
|
|
418
440
|
}
|
|
419
441
|
// BFS search for path from the leaked node to GC roots
|
|
420
442
|
const p = finder.getPathToGCRoots(snapshot, node);
|
|
@@ -11,6 +11,7 @@ import type { Nullable, Optional, RunMetaInfo } from './Types';
|
|
|
11
11
|
export declare class RunMetaInfoManager {
|
|
12
12
|
getRunMetaFilePath(options?: {
|
|
13
13
|
workDir?: Optional<string>;
|
|
14
|
+
readonly?: Optional<boolean>;
|
|
14
15
|
}): string;
|
|
15
16
|
saveRunMetaInfo(runMetaInfo: RunMetaInfo, options?: {
|
|
16
17
|
workDir?: Optional<string>;
|
package/dist/lib/RunInfoUtils.js
CHANGED
|
@@ -16,8 +16,10 @@ class RunMetaInfoManager {
|
|
|
16
16
|
if ((options === null || options === void 0 ? void 0 : options.workDir) != null) {
|
|
17
17
|
return FileManager_1.default.getRunMetaFile({ workDir: options.workDir });
|
|
18
18
|
}
|
|
19
|
-
if (Config_1.default.useExternalSnapshot) {
|
|
20
|
-
|
|
19
|
+
if ((options === null || options === void 0 ? void 0 : options.readonly) && Config_1.default.useExternalSnapshot) {
|
|
20
|
+
// only returns the template file if the
|
|
21
|
+
// run meta file is used for readonly purpose
|
|
22
|
+
return Config_1.default.externalRunMetaTemplateFile;
|
|
21
23
|
}
|
|
22
24
|
if (Config_1.default.runMetaFile != null) {
|
|
23
25
|
return Config_1.default.runMetaFile;
|
|
@@ -40,7 +42,8 @@ class RunMetaInfoManager {
|
|
|
40
42
|
return runMetaInfo;
|
|
41
43
|
}
|
|
42
44
|
loadRunMetaInfo(options) {
|
|
43
|
-
const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
|
|
45
|
+
const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
|
|
46
|
+
this.getRunMetaFilePath(Object.assign({ readonly: true }, options));
|
|
44
47
|
try {
|
|
45
48
|
return this.loadRunMetaInfoFromFile(file);
|
|
46
49
|
}
|
|
@@ -49,7 +52,8 @@ class RunMetaInfoManager {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
loadRunMetaInfoSilentFail(options) {
|
|
52
|
-
const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
|
|
55
|
+
const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
|
|
56
|
+
this.getRunMetaFilePath(Object.assign({ readonly: true }, options));
|
|
53
57
|
try {
|
|
54
58
|
return this.loadRunMetaInfoFromFile(file);
|
|
55
59
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { IHeapNode, IHeapSnapshot, ISerializationHelper, ISerializedInfo, JSONifyArgs, JSONifyOptions, Nullable } from './Types';
|
|
11
|
+
export declare class SerializationHelper implements ISerializationHelper {
|
|
12
|
+
protected snapshot: Nullable<IHeapSnapshot>;
|
|
13
|
+
setSnapshot(snapshot: IHeapSnapshot): void;
|
|
14
|
+
createOrMergeWrapper(info: ISerializedInfo, _node: IHeapNode, _args: JSONifyArgs, _options: JSONifyOptions): ISerializedInfo;
|
|
15
|
+
}
|
|
16
|
+
declare const _default: typeof SerializationHelper;
|
|
17
|
+
export default _default;
|
|
18
|
+
//# sourceMappingURL=SerializationHelper.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
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.SerializationHelper = void 0;
|
|
16
|
+
const InternalValueSetter_1 = require("./InternalValueSetter");
|
|
17
|
+
const Constant_1 = __importDefault(require("./Constant"));
|
|
18
|
+
class SerializationHelper {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.snapshot = null;
|
|
21
|
+
}
|
|
22
|
+
setSnapshot(snapshot) {
|
|
23
|
+
this.snapshot = snapshot;
|
|
24
|
+
}
|
|
25
|
+
createOrMergeWrapper(info,
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
_node,
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
_args,
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
31
|
+
_options) {
|
|
32
|
+
return info;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.SerializationHelper = SerializationHelper;
|
|
36
|
+
exports.default = (0, InternalValueSetter_1.setInternalValue)(SerializationHelper, __filename, Constant_1.default.internalDir);
|
package/dist/lib/Serializer.d.ts
CHANGED
|
@@ -7,12 +7,8 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
-
import { E2EStepInfo, HeapNodeIdSet, IHeapEdge, IHeapNode, IHeapSnapshot, ISerializedInfo, LeakTracePathItem, Nullable } from './Types';
|
|
11
|
-
declare
|
|
12
|
-
leakedIdSet?: Set<number>;
|
|
13
|
-
nodeIdsInSnapshots?: Array<Set<number>>;
|
|
14
|
-
};
|
|
15
|
-
declare function JSONifyPath(path: LeakTracePathItem, _snapshot: IHeapSnapshot, args: JSONifyArgs): Nullable<ISerializedInfo>;
|
|
10
|
+
import { E2EStepInfo, HeapNodeIdSet, IHeapEdge, IHeapNode, IHeapSnapshot, ISerializedInfo, JSONifyArgs, LeakTracePathItem, Nullable } from './Types';
|
|
11
|
+
declare function JSONifyPath(path: LeakTracePathItem, snapshot: IHeapSnapshot, args: JSONifyArgs): Nullable<ISerializedInfo>;
|
|
16
12
|
declare type SummarizeOptions = {
|
|
17
13
|
compact?: boolean;
|
|
18
14
|
color?: boolean;
|
package/dist/lib/Serializer.js
CHANGED
|
@@ -18,6 +18,7 @@ const Config_1 = __importDefault(require("./Config"));
|
|
|
18
18
|
const Utils_1 = __importDefault(require("./Utils"));
|
|
19
19
|
const Console_1 = __importDefault(require("./Console"));
|
|
20
20
|
const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
|
|
21
|
+
const SerializationHelper_1 = __importDefault(require("./SerializationHelper"));
|
|
21
22
|
const REGEXP_NAME_CLEANUP = /[[]\(\)]/g;
|
|
22
23
|
const EMPTY_JSONIFY_OPTIONS = {
|
|
23
24
|
fiberNodeReturnTrace: {},
|
|
@@ -374,7 +375,12 @@ function JSONifyNode(node, args, options) {
|
|
|
374
375
|
if (node.dominatorNode) {
|
|
375
376
|
info['dominator id (extra)'] = `@${node.dominatorNode.id}`;
|
|
376
377
|
}
|
|
377
|
-
|
|
378
|
+
// use serialization helper to wrap around
|
|
379
|
+
// the JSON node with additional tagging information
|
|
380
|
+
const { serializationHelper } = options;
|
|
381
|
+
return serializationHelper
|
|
382
|
+
? serializationHelper.createOrMergeWrapper(info, node, args, options)
|
|
383
|
+
: info;
|
|
378
384
|
}
|
|
379
385
|
function JSONifyTabsOrder() {
|
|
380
386
|
const file = Utils_1.default.getSnapshotSequenceFilePath();
|
|
@@ -383,7 +389,7 @@ function JSONifyTabsOrder() {
|
|
|
383
389
|
function shouldHighlight(node) {
|
|
384
390
|
return Utils_1.default.isDetachedDOMNode(node) || Utils_1.default.isDetachedFiberNode(node);
|
|
385
391
|
}
|
|
386
|
-
function JSONifyPath(path,
|
|
392
|
+
function JSONifyPath(path, snapshot, args) {
|
|
387
393
|
if (!path.node) {
|
|
388
394
|
return null;
|
|
389
395
|
}
|
|
@@ -393,6 +399,9 @@ function JSONifyPath(path, _snapshot, args) {
|
|
|
393
399
|
ret['$tabsOrder:' + JSONifyTabsOrder()] = '';
|
|
394
400
|
ret[`${idx++}: ${getNodeNameInJSON(path.node, args)}`] = JSONifyNode(path.node, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set() }));
|
|
395
401
|
let pathItem = path;
|
|
402
|
+
// initialize serialization helper
|
|
403
|
+
const serializationHelper = new SerializationHelper_1.default();
|
|
404
|
+
serializationHelper.setSnapshot(snapshot);
|
|
396
405
|
while (pathItem === null || pathItem === void 0 ? void 0 : pathItem.edge) {
|
|
397
406
|
const edge = pathItem.edge;
|
|
398
407
|
const nextNode = edge.toNode;
|
|
@@ -401,7 +410,7 @@ function JSONifyPath(path, _snapshot, args) {
|
|
|
401
410
|
nextNode.highlight = true;
|
|
402
411
|
}
|
|
403
412
|
const edgeRetainSize = pathItem.edgeRetainSize;
|
|
404
|
-
ret[`${idx++}: ${getEdgeNameInJSON(edge, edgeRetainSize)}${getNodeNameInJSON(nextNode, args)}`] = JSONifyNode(nextNode, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set() }));
|
|
413
|
+
ret[`${idx++}: ${getEdgeNameInJSON(edge, edgeRetainSize)}${getNodeNameInJSON(nextNode, args)}`] = JSONifyNode(nextNode, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set(), serializationHelper }));
|
|
405
414
|
pathItem = pathItem.next;
|
|
406
415
|
}
|
|
407
416
|
return ret;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
export declare type TraceSamplerOption = {
|
|
11
|
+
maxSample?: number;
|
|
12
|
+
};
|
|
13
|
+
export default class TraceSampler {
|
|
14
|
+
private maxCount;
|
|
15
|
+
private processed;
|
|
16
|
+
private selected;
|
|
17
|
+
private population;
|
|
18
|
+
constructor(n: number, options?: TraceSamplerOption);
|
|
19
|
+
init(n: number): void;
|
|
20
|
+
private calculateSampleRatio;
|
|
21
|
+
/**
|
|
22
|
+
* The caller decide to give up sampling this time.
|
|
23
|
+
* This `giveup` and the `sample` method in aggregation should be
|
|
24
|
+
* called `this.population` times.
|
|
25
|
+
*
|
|
26
|
+
* For example, if `giveup` is called n1 times,
|
|
27
|
+
* and `sample` is called n2 times, then n1 + n2 === this.population.
|
|
28
|
+
*/
|
|
29
|
+
giveup(): void;
|
|
30
|
+
/**
|
|
31
|
+
* This sample method should be called precisely this.population times.
|
|
32
|
+
* @returns true if this sample should be taken
|
|
33
|
+
*/
|
|
34
|
+
sample(): boolean;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=TraceSampler.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
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
|
+
const Config_1 = __importDefault(require("./Config"));
|
|
16
|
+
const Console_1 = __importDefault(require("./Console"));
|
|
17
|
+
const Utils_1 = __importDefault(require("./Utils"));
|
|
18
|
+
class TraceSampler {
|
|
19
|
+
constructor(n, options = {}) {
|
|
20
|
+
var _a;
|
|
21
|
+
// the max number of traces after sampling
|
|
22
|
+
this.maxCount = Config_1.default.maxSamplesForClustering;
|
|
23
|
+
this.processed = 0;
|
|
24
|
+
this.selected = 0;
|
|
25
|
+
this.population = -1;
|
|
26
|
+
this.maxCount = (_a = options.maxSample) !== null && _a !== void 0 ? _a : Config_1.default.maxSamplesForClustering;
|
|
27
|
+
this.init(n);
|
|
28
|
+
}
|
|
29
|
+
init(n) {
|
|
30
|
+
this.processed = 0;
|
|
31
|
+
this.selected = 0;
|
|
32
|
+
this.population = n;
|
|
33
|
+
this.calculateSampleRatio(n);
|
|
34
|
+
}
|
|
35
|
+
calculateSampleRatio(n) {
|
|
36
|
+
const sampleRatio = Math.min(1, this.maxCount / n);
|
|
37
|
+
if (sampleRatio < 1) {
|
|
38
|
+
Console_1.default.warning('Sampling trace due to a large number of traces:');
|
|
39
|
+
Console_1.default.lowLevel(` Number of Traces: ${n}`);
|
|
40
|
+
Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
|
|
41
|
+
}
|
|
42
|
+
return sampleRatio;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The caller decide to give up sampling this time.
|
|
46
|
+
* This `giveup` and the `sample` method in aggregation should be
|
|
47
|
+
* called `this.population` times.
|
|
48
|
+
*
|
|
49
|
+
* For example, if `giveup` is called n1 times,
|
|
50
|
+
* and `sample` is called n2 times, then n1 + n2 === this.population.
|
|
51
|
+
*/
|
|
52
|
+
giveup() {
|
|
53
|
+
++this.processed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* This sample method should be called precisely this.population times.
|
|
57
|
+
* @returns true if this sample should be taken
|
|
58
|
+
*/
|
|
59
|
+
sample() {
|
|
60
|
+
if (this.processed >= this.population) {
|
|
61
|
+
throw Utils_1.default.haltOrThrow(`processing ${this.processed + 1} samples but total population is ${this.population}`);
|
|
62
|
+
}
|
|
63
|
+
// use large number to mod here to avoid too much console I/O
|
|
64
|
+
if (!Config_1.default.isContinuousTest && this.processed % 771 === 0) {
|
|
65
|
+
const percent = Utils_1.default.getReadablePercent(this.processed / this.population);
|
|
66
|
+
Console_1.default.overwrite(`progress: ${this.processed} / ${this.population} (${percent})`);
|
|
67
|
+
}
|
|
68
|
+
const dynamicRatio = (this.maxCount - this.selected) / (this.population - this.processed);
|
|
69
|
+
// increase the counter indicating how many samples has been processed
|
|
70
|
+
++this.processed;
|
|
71
|
+
if (Math.random() <= dynamicRatio) {
|
|
72
|
+
++this.selected;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.default = TraceSampler;
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -806,8 +806,9 @@ export declare type TraceClusterMetaInfo = {
|
|
|
806
806
|
};
|
|
807
807
|
/** @internal */
|
|
808
808
|
export declare type ControlTreatmentClusterResult = {
|
|
809
|
-
|
|
809
|
+
controlLikelyOrOnlyClusters: TraceCluster[];
|
|
810
810
|
treatmentOnlyClusters: TraceCluster[];
|
|
811
|
+
treatmentLikelyClusters: TraceCluster[];
|
|
811
812
|
hybridClusters: Array<{
|
|
812
813
|
control: TraceCluster;
|
|
813
814
|
treatment: TraceCluster;
|
|
@@ -1930,12 +1931,12 @@ export interface IOveralLeakInfo extends Partial<IOveralHeapInfo> {
|
|
|
1930
1931
|
/** @internal */
|
|
1931
1932
|
export declare type DiffLeakOptions = {
|
|
1932
1933
|
controlWorkDirs: string[];
|
|
1933
|
-
|
|
1934
|
+
treatmentWorkDirs: string[];
|
|
1934
1935
|
};
|
|
1935
1936
|
/** @internal */
|
|
1936
1937
|
export declare type PlotMemoryOptions = {
|
|
1937
1938
|
controlWorkDirs?: string[];
|
|
1938
|
-
|
|
1939
|
+
treatmentWorkDirs?: string[];
|
|
1939
1940
|
workDir?: string;
|
|
1940
1941
|
} & IMemoryAnalystOptions;
|
|
1941
1942
|
/** @internal */
|
|
@@ -1971,4 +1972,26 @@ export interface IHeapConfig {
|
|
|
1971
1972
|
}
|
|
1972
1973
|
/** @internal */
|
|
1973
1974
|
export declare type ErrorWithMessage = Pick<Error, 'message'>;
|
|
1975
|
+
/** @internal */
|
|
1976
|
+
export declare type CommandOptionExample = string | {
|
|
1977
|
+
description?: string;
|
|
1978
|
+
cliOptionExample: string;
|
|
1979
|
+
};
|
|
1980
|
+
/** @internal */
|
|
1981
|
+
export declare type JSONifyArgs = {
|
|
1982
|
+
leakedIdSet?: Set<number>;
|
|
1983
|
+
nodeIdsInSnapshots?: Array<Set<number>>;
|
|
1984
|
+
};
|
|
1985
|
+
/** @internal */
|
|
1986
|
+
export interface ISerializationHelper {
|
|
1987
|
+
setSnapshot(snapshot: IHeapSnapshot): void;
|
|
1988
|
+
createOrMergeWrapper(info: ISerializedInfo, node: IHeapNode, args: JSONifyArgs, options: JSONifyOptions): ISerializedInfo;
|
|
1989
|
+
}
|
|
1990
|
+
/** @internal */
|
|
1991
|
+
export declare type JSONifyOptions = {
|
|
1992
|
+
fiberNodeReturnTrace: Record<number, string>;
|
|
1993
|
+
processedNodeId: Set<number>;
|
|
1994
|
+
forceJSONifyDepth?: number;
|
|
1995
|
+
serializationHelper?: ISerializationHelper;
|
|
1996
|
+
};
|
|
1974
1997
|
//# sourceMappingURL=Types.d.ts.map
|