@memlab/core 1.1.14 → 1.1.17
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 +6 -0
- package/dist/lib/Config.js +10 -1
- package/dist/lib/Console.d.ts +3 -0
- package/dist/lib/Console.js +81 -28
- package/dist/lib/FileManager.d.ts +4 -0
- package/dist/lib/FileManager.js +27 -0
- package/dist/lib/HeapAnalyzer.d.ts +5 -14
- package/dist/lib/HeapAnalyzer.js +9 -285
- package/dist/lib/Serializer.js +1 -1
- package/dist/lib/Types.d.ts +74 -7
- package/dist/lib/Utils.d.ts +7 -1
- package/dist/lib/Utils.js +58 -4
- package/dist/lib/heap-data/HeapNode.js +3 -4
- package/dist/lib/heap-data/HeapSnapshot.js +2 -2
- 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 +2 -2
|
@@ -257,10 +257,9 @@ class HeapNode {
|
|
|
257
257
|
get location() {
|
|
258
258
|
const heapSnapshot = this.heapSnapshot;
|
|
259
259
|
const locationIdx = heapSnapshot._nodeIdx2LocationIdx[this.idx];
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return new HeapLocation_1.default(heapSnapshot, locationIdx);
|
|
260
|
+
return locationIdx == null
|
|
261
|
+
? null
|
|
262
|
+
: new HeapLocation_1.default(heapSnapshot, locationIdx);
|
|
264
263
|
}
|
|
265
264
|
// search reference by edge name and edge type
|
|
266
265
|
getReference(edgeName, edgeType) {
|
|
@@ -206,8 +206,8 @@ class HeapSnapshot {
|
|
|
206
206
|
const locationFieldsCount = this._locationFieldsCount;
|
|
207
207
|
let locationIdx = 0;
|
|
208
208
|
while (locationIdx < this._locationCount) {
|
|
209
|
-
const
|
|
210
|
-
this._nodeIdx2LocationIdx[
|
|
209
|
+
const nodeIndex = locations[locationIdx * locationFieldsCount + this._locationObjectIndexOffset];
|
|
210
|
+
this._nodeIdx2LocationIdx[nodeIndex] = locationIdx;
|
|
211
211
|
++locationIdx;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "memlab core libraries",
|
|
6
6
|
"author": "Liang Gong <lgong@fb.com>",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build-pkg": "tsc",
|
|
61
61
|
"test-pkg": "jest .",
|
|
62
|
-
"publish-patch": "npm
|
|
62
|
+
"publish-patch": "npm publish",
|
|
63
63
|
"clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
|
|
64
64
|
},
|
|
65
65
|
"bugs": {
|