@memlab/core 1.1.14 → 1.1.16
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/index.d.ts +4 -0
- package/dist/index.js +6 -1
- package/dist/lib/Config.d.ts +4 -0
- package/dist/lib/Config.js +10 -1
- package/dist/lib/Serializer.js +1 -1
- package/dist/lib/Utils.js +9 -3
- package/dist/trace-cluster/ClusterUtils.d.ts +15 -1
- package/dist/trace-cluster/ClusterUtils.js +42 -0
- package/dist/trace-cluster/MultiIterationSeqClustering.d.ts +21 -0
- package/dist/trace-cluster/MultiIterationSeqClustering.js +112 -0
- package/dist/trace-cluster/TraceBucket.js +3 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -50,5 +50,9 @@ export * from './lib/PackageInfoLoader';
|
|
|
50
50
|
/** @internal */
|
|
51
51
|
export { default as SequentialClustering } from './trace-cluster/SequentialClustering';
|
|
52
52
|
/** @internal */
|
|
53
|
+
export { default as MultiIterationSeqClustering } from './trace-cluster/MultiIterationSeqClustering';
|
|
54
|
+
/** @internal */
|
|
53
55
|
export { default as TraceFinder } from './paths/TraceFinder';
|
|
56
|
+
/** @internal */
|
|
57
|
+
export * from './trace-cluster/ClusterUtils';
|
|
54
58
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.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.TraceFinder = exports.SequentialClustering = 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;
|
|
38
|
+
exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = 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
39
|
const path_1 = __importDefault(require("path"));
|
|
40
40
|
const PackageInfoLoader_1 = require("./lib/PackageInfoLoader");
|
|
41
41
|
/** @internal */
|
|
@@ -101,5 +101,10 @@ __exportStar(require("./lib/PackageInfoLoader"), exports);
|
|
|
101
101
|
var SequentialClustering_1 = require("./trace-cluster/SequentialClustering");
|
|
102
102
|
Object.defineProperty(exports, "SequentialClustering", { enumerable: true, get: function () { return __importDefault(SequentialClustering_1).default; } });
|
|
103
103
|
/** @internal */
|
|
104
|
+
var MultiIterationSeqClustering_1 = require("./trace-cluster/MultiIterationSeqClustering");
|
|
105
|
+
Object.defineProperty(exports, "MultiIterationSeqClustering", { enumerable: true, get: function () { return __importDefault(MultiIterationSeqClustering_1).default; } });
|
|
106
|
+
/** @internal */
|
|
104
107
|
var TraceFinder_1 = require("./paths/TraceFinder");
|
|
105
108
|
Object.defineProperty(exports, "TraceFinder", { enumerable: true, get: function () { return __importDefault(TraceFinder_1).default; } });
|
|
109
|
+
/** @internal */
|
|
110
|
+
__exportStar(require("./trace-cluster/ClusterUtils"), exports);
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -188,9 +188,13 @@ export declare class MemLabConfig {
|
|
|
188
188
|
mlClusteringLinkageMaxDistance: number;
|
|
189
189
|
mlMaxDF: number;
|
|
190
190
|
isSequentialClustering: boolean;
|
|
191
|
+
isMultiIterationSeqClustering: boolean;
|
|
191
192
|
seqClusteringSplitCount: number;
|
|
193
|
+
multiIterSeqClusteringIteration: number;
|
|
194
|
+
multiIterSeqClusteringSampleSize: number;
|
|
192
195
|
seqClusteringIsRandomChunks: boolean;
|
|
193
196
|
instrumentJS: boolean;
|
|
197
|
+
interceptScript: boolean;
|
|
194
198
|
constructor(options?: ConfigOption);
|
|
195
199
|
private initInternalConfigs;
|
|
196
200
|
private init;
|
package/dist/lib/Config.js
CHANGED
|
@@ -116,7 +116,9 @@ class MemLabConfig {
|
|
|
116
116
|
this.snapshotHasDetachedness = false;
|
|
117
117
|
// by default running in regular mode
|
|
118
118
|
this.runningMode = RunningModes_1.default.get('regular', this);
|
|
119
|
-
// intercept and
|
|
119
|
+
// intercept and log JavaScript Code in browser
|
|
120
|
+
this.interceptScript = false;
|
|
121
|
+
// rewrite JavaScript Code in browser
|
|
120
122
|
this.instrumentJS = false;
|
|
121
123
|
// external heap snapshot paths, if enabled
|
|
122
124
|
this.externalSnapshotFilePaths = [];
|
|
@@ -139,8 +141,15 @@ class MemLabConfig {
|
|
|
139
141
|
this.mlMaxDF = 1;
|
|
140
142
|
// if true, evaluating results with sequential clustering
|
|
141
143
|
this.isSequentialClustering = false;
|
|
144
|
+
// if true, evaluating results with sequential
|
|
145
|
+
// clustering with multiple iterations
|
|
146
|
+
this.isMultiIterationSeqClustering = false;
|
|
142
147
|
// split the sample leak traces into 4 smaller ones by default.
|
|
143
148
|
this.seqClusteringSplitCount = 4;
|
|
149
|
+
// the number of iterations for multi-iteration sequential clustering
|
|
150
|
+
this.multiIterSeqClusteringIteration = 1;
|
|
151
|
+
// the number of trace samples to retain from each cluster
|
|
152
|
+
this.multiIterSeqClusteringSampleSize = Infinity;
|
|
144
153
|
// if true, split dataset into trunks
|
|
145
154
|
// with random order for sequential clustering
|
|
146
155
|
this.seqClusteringIsRandomChunks = false;
|
package/dist/lib/Serializer.js
CHANGED
|
@@ -274,7 +274,7 @@ function JSONifyCode(node, args, options) {
|
|
|
274
274
|
}
|
|
275
275
|
function JSONifyContext(node, args, options) {
|
|
276
276
|
const info = Object.create(null);
|
|
277
|
-
const key = 'variables in scope (used by nested closures)';
|
|
277
|
+
const key = 'variables in defining scope (used by nested closures)';
|
|
278
278
|
const closure_vars = (info[key] = Object.create(null));
|
|
279
279
|
iterateSelectedEdges(node, (edge) => {
|
|
280
280
|
const key = filterJSONPropName(edge.name_or_index);
|
package/dist/lib/Utils.js
CHANGED
|
@@ -540,7 +540,10 @@ function getScenarioName(scenario) {
|
|
|
540
540
|
if (!scenario.name) {
|
|
541
541
|
return Constant_1.default.namePrefixForScenarioFromFile;
|
|
542
542
|
}
|
|
543
|
-
|
|
543
|
+
if (Constant_1.default.namePrefixForScenarioFromFile.length > 0) {
|
|
544
|
+
return Constant_1.default.namePrefixForScenarioFromFile + '-' + scenario.name();
|
|
545
|
+
}
|
|
546
|
+
return scenario.name();
|
|
544
547
|
}
|
|
545
548
|
function handleSnapshotError(e) {
|
|
546
549
|
haltOrThrow(e, {
|
|
@@ -821,8 +824,11 @@ function extractHTMLElementNodeInfo(node) {
|
|
|
821
824
|
return `${node.name} ${extractFiberNodeInfo(reactFiberEdge.toNode)}`;
|
|
822
825
|
}
|
|
823
826
|
function hasOnlyWeakReferrers(node) {
|
|
824
|
-
const referrer = node.findAnyReferrer(
|
|
825
|
-
|
|
827
|
+
const referrer = node.findAnyReferrer(
|
|
828
|
+
// shortcut references are added by JS engine
|
|
829
|
+
// GC won't consider shortcut as a retaining edge
|
|
830
|
+
(edge) => edge.type !== 'weak' && edge.type !== 'shortcut');
|
|
831
|
+
return referrer == null;
|
|
826
832
|
}
|
|
827
833
|
function getRunMetaFilePath() {
|
|
828
834
|
return Config_1.default.useExternalSnapshot
|
|
@@ -7,8 +7,22 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
|
+
import type { LeakTrace } from '../lib/Types';
|
|
10
11
|
declare const _default: {
|
|
11
|
-
isSimilarTrace: (t1:
|
|
12
|
+
isSimilarTrace: (t1: LeakTrace, t2: LeakTrace) => boolean;
|
|
12
13
|
};
|
|
13
14
|
export default _default;
|
|
15
|
+
/**
|
|
16
|
+
* const elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
17
|
+
* randomChunks(elements, 3) -> [[4, 8, 3], [9, 5, 1], [2, 6, 7, 10]]
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare const randomChunks: <T>(items: T[], n: number) => T[][];
|
|
21
|
+
/**
|
|
22
|
+
* chunks(elements, 3) -> [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare const chunks: <T>(items: T[], n: number) => T[][];
|
|
26
|
+
/** @internal*/
|
|
27
|
+
export declare const lastNodeFromTrace: (trace: LeakTrace) => import("../lib/Types").LeakTraceElement;
|
|
14
28
|
//# sourceMappingURL=ClusterUtils.d.ts.map
|
|
@@ -12,6 +12,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.lastNodeFromTrace = exports.chunks = exports.randomChunks = void 0;
|
|
15
16
|
const ClusterUtilsHelper_1 = __importDefault(require("./ClusterUtilsHelper"));
|
|
16
17
|
const ClusteringHeuristics_1 = __importDefault(require("./ClusteringHeuristics"));
|
|
17
18
|
exports.default = ClusterUtilsHelper_1.default.initialize(ClusteringHeuristics_1.default);
|
|
19
|
+
/**
|
|
20
|
+
* const elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
21
|
+
* randomChunks(elements, 3) -> [[4, 8, 3], [9, 5, 1], [2, 6, 7, 10]]
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
const randomChunks = (items, n) => {
|
|
25
|
+
const array = [...items];
|
|
26
|
+
const size = Math.floor(array.length / n);
|
|
27
|
+
const chunks = [];
|
|
28
|
+
for (let i = 0; i < n - 1; i++) {
|
|
29
|
+
const chunk = [];
|
|
30
|
+
for (let j = 0; j < size; j++) {
|
|
31
|
+
const idx = Math.floor(Math.random() * array.length);
|
|
32
|
+
chunk[j] = array[idx];
|
|
33
|
+
array.splice(idx, 1);
|
|
34
|
+
}
|
|
35
|
+
chunks.push(chunk);
|
|
36
|
+
}
|
|
37
|
+
chunks.push(array);
|
|
38
|
+
return chunks;
|
|
39
|
+
};
|
|
40
|
+
exports.randomChunks = randomChunks;
|
|
41
|
+
/**
|
|
42
|
+
* chunks(elements, 3) -> [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
const chunks = (items, n) => {
|
|
46
|
+
const array = [...items];
|
|
47
|
+
const size = Math.floor(array.length / n);
|
|
48
|
+
const chunks = [];
|
|
49
|
+
for (let i = 0; i < n - 1; i++) {
|
|
50
|
+
const chunk = array.splice(0, size);
|
|
51
|
+
chunks.push(chunk);
|
|
52
|
+
}
|
|
53
|
+
chunks.push(array);
|
|
54
|
+
return chunks;
|
|
55
|
+
};
|
|
56
|
+
exports.chunks = chunks;
|
|
57
|
+
/** @internal*/
|
|
58
|
+
const lastNodeFromTrace = (trace) => trace[trace.length - 1];
|
|
59
|
+
exports.lastNodeFromTrace = lastNodeFromTrace;
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { LeakTrace } from '../lib/Types';
|
|
11
|
+
declare type ClusterOption = {
|
|
12
|
+
numberOfIteration?: number;
|
|
13
|
+
numberOfTraceToRetainInCluster?: number;
|
|
14
|
+
};
|
|
15
|
+
export default class MultiIterationSeqClustering {
|
|
16
|
+
private traceSimilarity;
|
|
17
|
+
constructor();
|
|
18
|
+
cluster(newLeakTraces: LeakTrace[], options?: ClusterOption): LeakTrace[][];
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=MultiIterationSeqClustering.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
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 ClusterUtils_1 = require("./ClusterUtils");
|
|
16
|
+
const TraceSimilarityStrategy_1 = __importDefault(require("./strategies/TraceSimilarityStrategy"));
|
|
17
|
+
const MLTraceSimilarityStrategy_1 = __importDefault(require("./strategies/MLTraceSimilarityStrategy"));
|
|
18
|
+
const Config_1 = __importDefault(require("../lib/Config"));
|
|
19
|
+
const Utils_1 = __importDefault(require("../lib/Utils"));
|
|
20
|
+
const Console_1 = __importDefault(require("../lib/Console"));
|
|
21
|
+
class MultiIterationSeqClustering {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.traceSimilarity = Config_1.default.isMLClustering
|
|
24
|
+
? new MLTraceSimilarityStrategy_1.default()
|
|
25
|
+
: new TraceSimilarityStrategy_1.default();
|
|
26
|
+
}
|
|
27
|
+
cluster(newLeakTraces, options = {}) {
|
|
28
|
+
const maxNumOfSampleTraceInCluster = positiveIntOrDefaultValue(options.numberOfTraceToRetainInCluster, Infinity);
|
|
29
|
+
const numIteration = positiveIntOrDefaultValue(options.numberOfIteration, 1);
|
|
30
|
+
if (Config_1.default.verbose) {
|
|
31
|
+
Console_1.default.lowLevel(`maxNumOfSampleTraceInCluster: ${maxNumOfSampleTraceInCluster}`);
|
|
32
|
+
Console_1.default.lowLevel(`numIteration: ${numIteration}`);
|
|
33
|
+
}
|
|
34
|
+
// build trace and id mapping
|
|
35
|
+
const traceId2RepTraceId = new Map();
|
|
36
|
+
const traceId2Trace = new Map();
|
|
37
|
+
for (const trace of newLeakTraces) {
|
|
38
|
+
traceId2Trace.set(traceId(trace), trace);
|
|
39
|
+
}
|
|
40
|
+
// split all traces into several batches
|
|
41
|
+
const splitFn = Config_1.default.seqClusteringIsRandomChunks ? ClusterUtils_1.randomChunks : ClusterUtils_1.chunks;
|
|
42
|
+
const traceGroups = splitFn(newLeakTraces, numIteration);
|
|
43
|
+
let clusteredTraceSamples = [];
|
|
44
|
+
for (let iter = 0; iter < numIteration; ++iter) {
|
|
45
|
+
Console_1.default.overwrite(`Iteration: ${iter + 1}`);
|
|
46
|
+
// mix the current traces to cluster with all clustered trace samples
|
|
47
|
+
const curTraceGroup = traceGroups[iter].concat(clusteredTraceSamples);
|
|
48
|
+
// cluster the trace group
|
|
49
|
+
const { allClusters: clusters } = this.traceSimilarity.diffTraces(curTraceGroup, []);
|
|
50
|
+
// assign trace id to representative trace id
|
|
51
|
+
updateTraceId2RepTraceIdMap(clusters, traceId2RepTraceId);
|
|
52
|
+
// sample each group
|
|
53
|
+
for (let i = 0; i < clusters.length; ++i) {
|
|
54
|
+
clusters[i] = clusters[i].slice(0, maxNumOfSampleTraceInCluster);
|
|
55
|
+
}
|
|
56
|
+
// update samples
|
|
57
|
+
clusteredTraceSamples = clusters.reduce((acc, cluster) => acc.concat(cluster), []);
|
|
58
|
+
}
|
|
59
|
+
// rebuild full clusters based on the mappings
|
|
60
|
+
const repTraceId2Cluster = new Map();
|
|
61
|
+
for (const id of traceId2RepTraceId.keys()) {
|
|
62
|
+
const repTraceId = traceId2RepTraceId.get(id);
|
|
63
|
+
if (!repTraceId2Cluster.has(repTraceId)) {
|
|
64
|
+
repTraceId2Cluster.set(repTraceId, []);
|
|
65
|
+
}
|
|
66
|
+
const cluster = repTraceId2Cluster.get(repTraceId);
|
|
67
|
+
if (cluster.length === 0) {
|
|
68
|
+
const repTrace = traceId2Trace.get(repTraceId);
|
|
69
|
+
cluster.push(repTrace);
|
|
70
|
+
}
|
|
71
|
+
if (id !== repTraceId) {
|
|
72
|
+
const trace = traceId2Trace.get(id);
|
|
73
|
+
cluster.push(trace);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return Array.from(repTraceId2Cluster.values());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.default = MultiIterationSeqClustering;
|
|
80
|
+
function traceId(trace) {
|
|
81
|
+
const lastNode = (0, ClusterUtils_1.lastNodeFromTrace)(trace);
|
|
82
|
+
if (lastNode.id == null) {
|
|
83
|
+
throw Utils_1.default.haltOrThrow('last node id missing');
|
|
84
|
+
}
|
|
85
|
+
return lastNode.id;
|
|
86
|
+
}
|
|
87
|
+
function updateTraceId2RepTraceIdMap(clusters, traceId2RepTraceId) {
|
|
88
|
+
for (const cluster of clusters) {
|
|
89
|
+
const repTrace = cluster[0];
|
|
90
|
+
for (const trace of cluster) {
|
|
91
|
+
traceId2RepTraceId.set(traceId(trace), traceId(repTrace));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// update trace id to representative trace id closure
|
|
95
|
+
for (const id of traceId2RepTraceId.keys()) {
|
|
96
|
+
const queue = [];
|
|
97
|
+
let cur = id;
|
|
98
|
+
let repTraceId = traceId2RepTraceId.get(cur);
|
|
99
|
+
while (repTraceId !== cur) {
|
|
100
|
+
queue.push(cur);
|
|
101
|
+
cur = repTraceId;
|
|
102
|
+
repTraceId = traceId2RepTraceId.get(cur);
|
|
103
|
+
}
|
|
104
|
+
for (const idInQueue of queue) {
|
|
105
|
+
traceId2RepTraceId.set(idInQueue, repTraceId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return traceId2RepTraceId;
|
|
109
|
+
}
|
|
110
|
+
function positiveIntOrDefaultValue(v, d) {
|
|
111
|
+
return typeof v !== 'number' || v <= 0 ? d : v;
|
|
112
|
+
}
|
|
@@ -21,6 +21,7 @@ const TraceElement_1 = require("./TraceElement");
|
|
|
21
21
|
const TraceSimilarityStrategy_1 = __importDefault(require("./strategies/TraceSimilarityStrategy"));
|
|
22
22
|
const TraceAsClusterStrategy_1 = __importDefault(require("./strategies/TraceAsClusterStrategy"));
|
|
23
23
|
const MLTraceSimilarityStrategy_1 = __importDefault(require("./strategies/MLTraceSimilarityStrategy"));
|
|
24
|
+
const ClusterUtils_1 = require("./ClusterUtils");
|
|
24
25
|
// sync up with html/intern/js/webspeed/memlab/lib/LeakCluster.js
|
|
25
26
|
class NormalizedTrace {
|
|
26
27
|
constructor(p = null, snapshot = null) {
|
|
@@ -166,10 +167,9 @@ class NormalizedTrace {
|
|
|
166
167
|
return NormalizedTrace.clusteredLeakTracesToRecord(allClusters);
|
|
167
168
|
}
|
|
168
169
|
static clusteredLeakTracesToRecord(allClusters) {
|
|
169
|
-
const lastNodeFromTrace = (trace) => trace[trace.length - 1];
|
|
170
170
|
const labaledLeakTraces = allClusters.reduce((acc, bucket) => {
|
|
171
|
-
const lastNodeFromFirstTrace = lastNodeFromTrace(bucket[0]);
|
|
172
|
-
bucket.map(lastNodeFromTrace).forEach(lastNodeInTrace => {
|
|
171
|
+
const lastNodeFromFirstTrace = (0, ClusterUtils_1.lastNodeFromTrace)(bucket[0]);
|
|
172
|
+
bucket.map(ClusterUtils_1.lastNodeFromTrace).forEach(lastNodeInTrace => {
|
|
173
173
|
if (lastNodeInTrace.id == null || lastNodeFromFirstTrace.id == null) {
|
|
174
174
|
throw new Error('node id not found in last node of the leak trace');
|
|
175
175
|
}
|