@memlab/core 1.1.22 → 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 +1 -1
- package/dist/lib/Config.js +4 -1
- package/dist/lib/FileManager.js +1 -1
- package/dist/lib/HeapAnalyzer.js +26 -14
- 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/Types.d.ts +21 -3
- package/dist/lib/Utils.d.ts +2 -0
- package/dist/lib/Utils.js +34 -3
- package/dist/lib/charts/MemoryBarChart.js +2 -2
- 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/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/trace-cluster/TraceBucket.d.ts +1 -1
- package/dist/trace-cluster/TraceBucket.js +41 -11
- 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
|
@@ -114,7 +114,7 @@ export declare class MemLabConfig {
|
|
|
114
114
|
qes: QuickExperiment[];
|
|
115
115
|
isOndemand: boolean;
|
|
116
116
|
useExternalSnapshot: boolean;
|
|
117
|
-
|
|
117
|
+
externalRunMetaTemplateFile: string;
|
|
118
118
|
externalSnapshotVisitOrderFile: string;
|
|
119
119
|
externalSnapshotDir: Nullable<string>;
|
|
120
120
|
externalSnapshotFilePaths: string[];
|
package/dist/lib/Config.js
CHANGED
|
@@ -321,7 +321,10 @@ class MemLabConfig {
|
|
|
321
321
|
// node names excluded from the trace finding
|
|
322
322
|
this.nodeNameBlockList = new Set(['system / PropertyCell']);
|
|
323
323
|
// edge names excluded from the trace finding
|
|
324
|
-
this.edgeNameBlockList = new Set([
|
|
324
|
+
this.edgeNameBlockList = new Set([
|
|
325
|
+
'feedback_cell',
|
|
326
|
+
'part of key -> value pair in ephemeron table',
|
|
327
|
+
]);
|
|
325
328
|
// node names less preferable in trace finding
|
|
326
329
|
this.nodeNameGreyList = new Set([
|
|
327
330
|
'InternalNode',
|
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
|
@@ -49,13 +49,13 @@ class MemoryAnalyst {
|
|
|
49
49
|
const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({
|
|
50
50
|
workDir: controlWorkDir,
|
|
51
51
|
}));
|
|
52
|
-
const
|
|
53
|
-
workDir:
|
|
54
|
-
});
|
|
52
|
+
const treatmentSnapshotDirs = options.treatmentWorkDirs.map(treatmentWorkDir => FileManager_1.default.getCurDataDir({
|
|
53
|
+
workDir: treatmentWorkDir,
|
|
54
|
+
}));
|
|
55
55
|
// check control working dir
|
|
56
56
|
controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }));
|
|
57
57
|
// check treatment working dir
|
|
58
|
-
Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
|
|
58
|
+
treatmentSnapshotDirs.forEach(treatmentSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir }));
|
|
59
59
|
// display control and treatment memory
|
|
60
60
|
MemoryBarChart_1.default.plotMemoryBarChart(options);
|
|
61
61
|
return this.diffMemoryLeakTraces(options);
|
|
@@ -76,17 +76,26 @@ class MemoryAnalyst {
|
|
|
76
76
|
leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir }));
|
|
77
77
|
controlSnapshots.push(snapshotDiff.snapshot);
|
|
78
78
|
}
|
|
79
|
-
// diff snapshots and get treatment raw paths
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
}
|
|
86
94
|
const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length));
|
|
95
|
+
const treatmentPathCounts = JSON.stringify(leakPathsFromTreatmentRuns.map(leakPaths => leakPaths.length));
|
|
87
96
|
Console_1.default.topLevel(`${controlPathCounts} traces from control group`);
|
|
88
|
-
Console_1.default.topLevel(`${
|
|
89
|
-
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, {
|
|
90
99
|
strategy: Config_1.default.isMLClustering
|
|
91
100
|
? new MLTraceSimilarityStrategy_1.default()
|
|
92
101
|
: undefined,
|
|
@@ -95,7 +104,10 @@ class MemoryAnalyst {
|
|
|
95
104
|
yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
|
|
96
105
|
// serialize JSON file with detailed leak trace information
|
|
97
106
|
const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path);
|
|
98
|
-
|
|
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);
|
|
99
111
|
});
|
|
100
112
|
}
|
|
101
113
|
// find all unique pattern of leaks
|
|
@@ -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;
|
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 */
|
|
@@ -1976,4 +1977,21 @@ export declare type CommandOptionExample = string | {
|
|
|
1976
1977
|
description?: string;
|
|
1977
1978
|
cliOptionExample: string;
|
|
1978
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
|
+
};
|
|
1979
1997
|
//# sourceMappingURL=Types.d.ts.map
|
package/dist/lib/Utils.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ declare function isAlternateNode(node: IHeapNode): boolean;
|
|
|
49
49
|
declare function setIsRegularFiberNode(node: IHeapNode): void;
|
|
50
50
|
declare function isRegularFiberNode(node: IHeapNode): boolean;
|
|
51
51
|
declare function hasHostRoot(node: IHeapNode): boolean;
|
|
52
|
+
declare function markDetachedFiberNode(node: IHeapNode): boolean;
|
|
52
53
|
declare type IterateNodeCallback = (node: IHeapNode, snapshot: IHeapSnapshot) => boolean;
|
|
53
54
|
declare function filterNodesInPlace(idSet: Set<number>, snapshot: IHeapSnapshot, cb: IterateNodeCallback): void;
|
|
54
55
|
declare function applyToNodes(idSet: Set<number>, snapshot: IHeapSnapshot, cb: (node: IHeapNode, snapshot: IHeapSnapshot) => void, options?: AnyOptions): void;
|
|
@@ -229,6 +230,7 @@ declare const _default: {
|
|
|
229
230
|
mapToObject: typeof mapToObject;
|
|
230
231
|
markAllDetachedFiberNode: typeof markAllDetachedFiberNode;
|
|
231
232
|
markAlternateFiberNode: typeof markAlternateFiberNode;
|
|
233
|
+
markDetachedFiberNode: typeof markDetachedFiberNode;
|
|
232
234
|
memCache: Record<string, any>;
|
|
233
235
|
normalizeBaseUrl: typeof normalizeBaseUrl;
|
|
234
236
|
objectToMap: typeof objectToMap;
|
package/dist/lib/Utils.js
CHANGED
|
@@ -126,7 +126,7 @@ function isFiberNode(node) {
|
|
|
126
126
|
return name === 'FiberNode' || name === 'Detached FiberNode';
|
|
127
127
|
}
|
|
128
128
|
// quickly check the detachedness field
|
|
129
|
-
// need to call
|
|
129
|
+
// need to call markDetachedFiberNode(node) before this function
|
|
130
130
|
// does not traverse and check the existance of HostRoot
|
|
131
131
|
// NOTE: Doesn't work for FiberNode without detachedness field
|
|
132
132
|
function isDetachedFiberNode(node) {
|
|
@@ -497,6 +497,35 @@ function hasHostRoot(node) {
|
|
|
497
497
|
}
|
|
498
498
|
cur = getReactFiberNode(cur, 'return');
|
|
499
499
|
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
// The Fiber tree starts with a special type of Fiber node (HostRoot).
|
|
503
|
+
// return true if the node is a mounted Fiber node
|
|
504
|
+
function markDetachedFiberNode(node) {
|
|
505
|
+
if (node && node.is_detached) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
let cur = node;
|
|
509
|
+
const visitedIds = new Set();
|
|
510
|
+
const visitedNodes = new Set();
|
|
511
|
+
while (cur && isFiberNode(cur)) {
|
|
512
|
+
if (cur.id == null || visitedIds.has(cur.id)) {
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
visitedNodes.add(cur);
|
|
516
|
+
// if a Fiber node whose dominator is neither root nor
|
|
517
|
+
// another Fiber node, then consider it as detached Fiber node
|
|
518
|
+
if (cur.dominatorNode && cur.dominatorNode.id !== 1) {
|
|
519
|
+
if (!isFiberNode(cur.dominatorNode)) {
|
|
520
|
+
cur.markAsDetached();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
visitedIds.add(cur.id);
|
|
524
|
+
if (isHostRoot(cur)) {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
cur = getReactFiberNode(cur, 'return');
|
|
528
|
+
}
|
|
500
529
|
for (const visitedNode of visitedNodes) {
|
|
501
530
|
visitedNode.markAsDetached();
|
|
502
531
|
}
|
|
@@ -1533,8 +1562,9 @@ function dumpSnapshot(file, snapshot) {
|
|
|
1533
1562
|
function markAllDetachedFiberNode(snapshot) {
|
|
1534
1563
|
Console_1.default.overwrite('marking all detached Fiber nodes...');
|
|
1535
1564
|
snapshot.nodes.forEach(node => {
|
|
1536
|
-
|
|
1537
|
-
|
|
1565
|
+
if (isFiberNode(node)) {
|
|
1566
|
+
markDetachedFiberNode(node);
|
|
1567
|
+
}
|
|
1538
1568
|
});
|
|
1539
1569
|
}
|
|
1540
1570
|
function markAlternateFiberNode(snapshot) {
|
|
@@ -1891,6 +1921,7 @@ exports.default = {
|
|
|
1891
1921
|
mapToObject,
|
|
1892
1922
|
markAllDetachedFiberNode,
|
|
1893
1923
|
markAlternateFiberNode,
|
|
1924
|
+
markDetachedFiberNode,
|
|
1894
1925
|
memCache,
|
|
1895
1926
|
normalizeBaseUrl,
|
|
1896
1927
|
objectToMap,
|
|
@@ -97,7 +97,7 @@ class MemoryBarChart {
|
|
|
97
97
|
}
|
|
98
98
|
loadPlotData(options = {}) {
|
|
99
99
|
// plot data for a single run
|
|
100
|
-
if (!options.controlWorkDirs && !options.
|
|
100
|
+
if (!options.controlWorkDirs && !options.treatmentWorkDirs) {
|
|
101
101
|
return this.loadPlotDataFromWorkDir(options);
|
|
102
102
|
}
|
|
103
103
|
// plot data for control and test run
|
|
@@ -105,7 +105,7 @@ class MemoryBarChart {
|
|
|
105
105
|
workDir: options.controlWorkDirs && options.controlWorkDirs[0],
|
|
106
106
|
});
|
|
107
107
|
const testPlotData = this.loadPlotDataFromWorkDir({
|
|
108
|
-
workDir: options.
|
|
108
|
+
workDir: options.treatmentWorkDirs && options.treatmentWorkDirs[0],
|
|
109
109
|
});
|
|
110
110
|
// merge plot data
|
|
111
111
|
return this.mergePlotData([controlPlotData, testPlotData]);
|
|
@@ -21,6 +21,7 @@ const FilterOverSizedNodeAsLeak_rule_1 = require("./rules/FilterOverSizedNodeAsL
|
|
|
21
21
|
const FilterStackTraceFrame_rule_1 = require("./rules/FilterStackTraceFrame.rule");
|
|
22
22
|
const FilterTrivialNode_rule_1 = require("./rules/FilterTrivialNode.rule");
|
|
23
23
|
const FilterUnmountedFiberNode_rule_1 = require("./rules/FilterUnmountedFiberNode.rule");
|
|
24
|
+
const FilterXMLHTTPRequest_rule_1 = require("./rules/FilterXMLHTTPRequest.rule");
|
|
24
25
|
const list = [
|
|
25
26
|
new FilterByExternalFilter_rule_1.FilterByExternalFilterRule(),
|
|
26
27
|
new FilterTrivialNode_rule_1.FilterTrivialNodeRule(),
|
|
@@ -29,5 +30,6 @@ const list = [
|
|
|
29
30
|
new FilterUnmountedFiberNode_rule_1.FilterUnmountedFiberNodeRule(),
|
|
30
31
|
new FilterDetachedDOMElement_rule_1.FilterDetachedDOMElementRule(),
|
|
31
32
|
new FilterStackTraceFrame_rule_1.FilterStackTraceFrameRule(),
|
|
33
|
+
new FilterXMLHTTPRequest_rule_1.FilterXMLHTTPRequestRule(),
|
|
32
34
|
];
|
|
33
35
|
exports.default = (0, InternalValueSetter_1.setInternalValue)(list, __filename, Constant_1.default.internalDir);
|
|
@@ -15,6 +15,5 @@ import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class FilterDetachedDOMElementRule implements ILeakObjectFilterRule {
|
|
17
17
|
filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
|
|
18
|
-
protected checkDetachedFiberNode(config: MemLabConfig, node: IHeapNode): boolean;
|
|
19
18
|
}
|
|
20
19
|
//# sourceMappingURL=FilterDetachedDOMElement.rule.d.ts.map
|
|
@@ -23,18 +23,31 @@ class FilterDetachedDOMElementRule {
|
|
|
23
23
|
const isDetached = Utils_1.default.isDetachedDOMNode(node, {
|
|
24
24
|
ignoreInternalNode: true,
|
|
25
25
|
});
|
|
26
|
-
if (isDetached
|
|
26
|
+
if (isDetached &&
|
|
27
|
+
!isDominatedByEdgeName(node, 'stateNode') &&
|
|
28
|
+
!isDetachedDOMNodeDominatedByDehydratedMemoizedState(node)) {
|
|
27
29
|
return BaseLeakFilter_rule_1.LeakDecision.LEAK;
|
|
28
30
|
}
|
|
29
31
|
return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
30
32
|
}
|
|
31
|
-
checkDetachedFiberNode(config, node) {
|
|
32
|
-
if (!config.detectFiberNodeLeak ||
|
|
33
|
-
!Utils_1.default.isFiberNode(node) ||
|
|
34
|
-
Utils_1.default.hasHostRoot(node)) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
|
|
38
|
-
}
|
|
39
33
|
}
|
|
40
34
|
exports.FilterDetachedDOMElementRule = FilterDetachedDOMElementRule;
|
|
35
|
+
function isDominatedByEdgeName(node, edgeNameOrIndex) {
|
|
36
|
+
var _a;
|
|
37
|
+
const referrerNode = node.getAnyReferrerNode(edgeNameOrIndex);
|
|
38
|
+
if (referrerNode == null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id);
|
|
42
|
+
}
|
|
43
|
+
// check if the input is a detached DOM node dominated by a 'dehydrated'
|
|
44
|
+
// edge from a memoizedState. In this case, the node is not a memory leak
|
|
45
|
+
function isDetachedDOMNodeDominatedByDehydratedMemoizedState(node) {
|
|
46
|
+
var _a;
|
|
47
|
+
const referrerNode = node.getAnyReferrerNode('dehydrated', 'property');
|
|
48
|
+
if (referrerNode == null) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return (referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id) &&
|
|
52
|
+
isDominatedByEdgeName(referrerNode, 'memoizedState'));
|
|
53
|
+
}
|
|
@@ -26,9 +26,10 @@ class FilterUnmountedFiberNodeRule {
|
|
|
26
26
|
return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
27
27
|
}
|
|
28
28
|
checkDetachedFiberNode(config, node) {
|
|
29
|
-
if (!config.detectFiberNodeLeak ||
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (!config.detectFiberNodeLeak || !Utils_1.default.isFiberNode(node)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!Utils_1.default.isDetachedFiberNode(node)) {
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
35
|
return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { MemLabConfig } from '../../Config';
|
|
11
|
+
import type { IHeapNode } from '../../Types';
|
|
12
|
+
import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
|
|
13
|
+
/**
|
|
14
|
+
* mark XMLHTTPRequest with status ok as memory leaks
|
|
15
|
+
*/
|
|
16
|
+
export declare class FilterXMLHTTPRequestRule implements ILeakObjectFilterRule {
|
|
17
|
+
filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
|
|
18
|
+
protected checkFinishedXMLHTTPRequest(node: IHeapNode): boolean;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=FilterXMLHTTPRequest.rule.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FilterXMLHTTPRequestRule = void 0;
|
|
13
|
+
const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
|
|
14
|
+
/**
|
|
15
|
+
* mark XMLHTTPRequest with status ok as memory leaks
|
|
16
|
+
*/
|
|
17
|
+
class FilterXMLHTTPRequestRule {
|
|
18
|
+
filter(_config, node) {
|
|
19
|
+
return this.checkFinishedXMLHTTPRequest(node)
|
|
20
|
+
? BaseLeakFilter_rule_1.LeakDecision.LEAK
|
|
21
|
+
: BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
|
|
22
|
+
}
|
|
23
|
+
checkFinishedXMLHTTPRequest(node) {
|
|
24
|
+
if (node.name !== 'XMLHttpRequest' || node.type !== 'native') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return (node.findAnyReference((edge) => edge.toNode.name === '{"status":"ok"}') != null);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.FilterXMLHTTPRequestRule = FilterXMLHTTPRequestRule;
|
|
@@ -37,7 +37,7 @@ export default class NormalizedTrace {
|
|
|
37
37
|
private static buildTraceToPathMap;
|
|
38
38
|
private static pushLeakPathToCluster;
|
|
39
39
|
private static initEmptyCluster;
|
|
40
|
-
static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[],
|
|
40
|
+
static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], leakPathsFromTreatmentRuns: LeakTracePathItem[][], treatmentSnapshots: IHeapSnapshot[], aggregateDominatorMetrics: AggregateNodeCb, option?: {
|
|
41
41
|
strategy?: IClusterStrategy;
|
|
42
42
|
}): ControlTreatmentClusterResult;
|
|
43
43
|
static generateUnClassifiedClusters(paths: LeakTracePathItem[], snapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb): TraceCluster[];
|
|
@@ -310,43 +310,64 @@ class NormalizedTrace {
|
|
|
310
310
|
leakedNodeIds: new Set(),
|
|
311
311
|
};
|
|
312
312
|
}
|
|
313
|
-
static clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots,
|
|
313
|
+
static clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, leakPathsFromTreatmentRuns, treatmentSnapshots, aggregateDominatorMetrics, option = {}) {
|
|
314
314
|
const result = {
|
|
315
|
-
|
|
315
|
+
controlLikelyOrOnlyClusters: [],
|
|
316
316
|
treatmentOnlyClusters: [],
|
|
317
|
+
treatmentLikelyClusters: [],
|
|
317
318
|
hybridClusters: [],
|
|
318
319
|
};
|
|
319
320
|
Console_1.default.overwrite('Clustering leak traces');
|
|
320
321
|
const totalControlPaths = leakPathsFromControlRuns.reduce((count, leakPaths) => count + leakPaths.length, 0);
|
|
321
|
-
|
|
322
|
+
const totalTreatmentPaths = leakPathsFromTreatmentRuns.reduce((count, leakPaths) => count + leakPaths.length, 0);
|
|
323
|
+
if (totalControlPaths === 0 && totalTreatmentPaths === 0) {
|
|
322
324
|
Console_1.default.midLevel('No leaks found');
|
|
323
325
|
return result;
|
|
324
326
|
}
|
|
325
327
|
// sample paths if there are too many
|
|
326
328
|
const flattenedLeakPathsFromControlRuns = leakPathsFromControlRuns.reduce((arr, leakPaths) => [...arr, ...leakPaths], []);
|
|
327
329
|
const controlPaths = this.samplePaths(flattenedLeakPathsFromControlRuns);
|
|
328
|
-
|
|
330
|
+
const pathsForEachTreatmentGroup = leakPathsFromTreatmentRuns.map((treatmentPaths) => this.samplePaths(treatmentPaths));
|
|
329
331
|
// build control trace to control path map
|
|
330
332
|
const controlTraceToPathMap = NormalizedTrace.buildTraceToPathMap(controlPaths);
|
|
331
333
|
const controlTraces = Array.from(controlTraceToPathMap.keys());
|
|
332
|
-
// build treatment trace to treatment path
|
|
333
|
-
|
|
334
|
-
|
|
334
|
+
// build treatment trace to treatment path maps
|
|
335
|
+
// we need to know the mapping to each treatment group
|
|
336
|
+
// to figure out if a trace cluster contains traces from all treatment groups
|
|
337
|
+
const treatmentTraceToPathMaps = pathsForEachTreatmentGroup.map(treatmentPaths => NormalizedTrace.buildTraceToPathMap(treatmentPaths));
|
|
338
|
+
const treatmentTraceToPathMap = new Map();
|
|
339
|
+
const treatmentTraces = [];
|
|
340
|
+
for (const map of treatmentTraceToPathMaps) {
|
|
341
|
+
for (const [key, value] of map.entries()) {
|
|
342
|
+
treatmentTraceToPathMap.set(key, value);
|
|
343
|
+
treatmentTraces.push(key);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
335
346
|
// cluster traces from both the control group and the treatment group
|
|
336
347
|
const { allClusters } = NormalizedTrace.diffTraces([...controlTraces, ...treatmentTraces], [], option);
|
|
337
|
-
// pick one of the control heap snapshots
|
|
348
|
+
// pick one of the control and treatment heap snapshots
|
|
338
349
|
const controlSnapshot = controlSnapshots[0];
|
|
350
|
+
const treatmentSnapshot = treatmentSnapshots[0];
|
|
339
351
|
// construct TraceCluster from clustering result
|
|
340
352
|
allClusters.forEach((traces) => {
|
|
341
353
|
var _a, _b;
|
|
342
354
|
const controlCluster = NormalizedTrace.initEmptyCluster(controlSnapshot);
|
|
343
355
|
const treatmentCluster = NormalizedTrace.initEmptyCluster(treatmentSnapshot);
|
|
356
|
+
// a set containing each the treatment group that
|
|
357
|
+
// has at least one trace in this cluster
|
|
358
|
+
const treatmentSetWithClusterTrace = new Set();
|
|
344
359
|
for (const trace of traces) {
|
|
345
360
|
const normalizedTrace = trace;
|
|
346
361
|
if (controlTraceToPathMap.has(normalizedTrace)) {
|
|
347
362
|
NormalizedTrace.pushLeakPathToCluster(controlTraceToPathMap, normalizedTrace, controlCluster);
|
|
348
363
|
}
|
|
349
364
|
else {
|
|
365
|
+
for (let i = 0; i < treatmentTraceToPathMaps.length; ++i) {
|
|
366
|
+
if (treatmentTraceToPathMaps[i].has(normalizedTrace)) {
|
|
367
|
+
treatmentSetWithClusterTrace.add(i);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
350
371
|
NormalizedTrace.pushLeakPathToCluster(treatmentTraceToPathMap, normalizedTrace, treatmentCluster);
|
|
351
372
|
}
|
|
352
373
|
}
|
|
@@ -360,11 +381,19 @@ class NormalizedTrace {
|
|
|
360
381
|
if (treatmentClusterSize > 0) {
|
|
361
382
|
this.calculateClusterRetainedSize(treatmentCluster, treatmentSnapshot, aggregateDominatorMetrics);
|
|
362
383
|
}
|
|
363
|
-
if (controlClusterSize === 0
|
|
384
|
+
if (controlClusterSize === 0 &&
|
|
385
|
+
treatmentSetWithClusterTrace.size === leakPathsFromTreatmentRuns.length) {
|
|
386
|
+
// only when the leak cluster consists of traces from all treatment groups
|
|
364
387
|
result.treatmentOnlyClusters.push(treatmentCluster);
|
|
365
388
|
}
|
|
389
|
+
else if (controlClusterSize === 0) {
|
|
390
|
+
// when the leak cluster consists of traces from
|
|
391
|
+
// some but not all of treatment groups
|
|
392
|
+
result.treatmentLikelyClusters.push(treatmentCluster);
|
|
393
|
+
}
|
|
366
394
|
else if (treatmentClusterSize === 0) {
|
|
367
|
-
|
|
395
|
+
// when the leak cluster consists of traces from any of the control groups
|
|
396
|
+
result.controlLikelyOrOnlyClusters.push(controlCluster);
|
|
368
397
|
}
|
|
369
398
|
else {
|
|
370
399
|
result.hybridClusters.push({
|
|
@@ -374,7 +403,8 @@ class NormalizedTrace {
|
|
|
374
403
|
}
|
|
375
404
|
});
|
|
376
405
|
result.treatmentOnlyClusters.sort((c1, c2) => { var _a, _b; return ((_a = c2.retainedSize) !== null && _a !== void 0 ? _a : 0) - ((_b = c1.retainedSize) !== null && _b !== void 0 ? _b : 0); });
|
|
377
|
-
result.
|
|
406
|
+
result.treatmentLikelyClusters.sort((c1, c2) => { var _a, _b; return ((_a = c2.retainedSize) !== null && _a !== void 0 ? _a : 0) - ((_b = c1.retainedSize) !== null && _b !== void 0 ? _b : 0); });
|
|
407
|
+
result.controlLikelyOrOnlyClusters.sort((c1, c2) => { var _a, _b; return ((_a = c2.retainedSize) !== null && _a !== void 0 ? _a : 0) - ((_b = c1.retainedSize) !== null && _b !== void 0 ? _b : 0); });
|
|
378
408
|
result.hybridClusters.sort((g1, g2) => {
|
|
379
409
|
var _a, _b, _c, _d;
|
|
380
410
|
return ((_a = g2.control.retainedSize) !== null && _a !== void 0 ? _a : 0) +
|