@memlab/core 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/lib/HeapAnonymizer.test.d.ts +11 -0
- package/dist/__tests__/lib/HeapAnonymizer.test.d.ts.map +1 -0
- package/dist/__tests__/lib/HeapAnonymizer.test.js +377 -0
- package/dist/__tests__/trace-cluster/MLTraceSimilarityStrategy.test.d.ts +11 -0
- package/dist/__tests__/trace-cluster/MLTraceSimilarityStrategy.test.d.ts.map +1 -0
- package/dist/__tests__/trace-cluster/MLTraceSimilarityStrategy.test.js +50 -0
- package/dist/__tests__/utils/Utils.test.js +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/lib/HeapAnonymizer.d.ts +362 -0
- package/dist/lib/HeapAnonymizer.d.ts.map +1 -0
- package/dist/lib/HeapAnonymizer.js +670 -0
- package/dist/lib/HeapSerializer.d.ts +125 -0
- package/dist/lib/HeapSerializer.d.ts.map +1 -0
- package/dist/lib/HeapSerializer.js +315 -0
- package/dist/lib/Types.d.ts.map +1 -1
- package/dist/lib/Utils.d.ts.map +1 -1
- package/dist/lib/Utils.js +12 -0
- package/dist/lib/leak-filters/BaseLeakFilter.rule.d.ts.map +1 -1
- package/dist/lib/leak-filters/rules/FilterUserTaggedLeaks.rule.d.ts.map +1 -1
- package/dist/lib/leak-filters/rules/FilterUserTaggedLeaks.rule.js +17 -2
- package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts.map +1 -1
- package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts.map +1 -1
- package/dist/paths/TraceFinder.d.ts +1 -3
- package/dist/paths/TraceFinder.d.ts.map +1 -1
- package/dist/paths/TraceFinder.js +205 -254
- package/dist/trace-cluster/strategies/MLTraceSimilarityStrategy.d.ts.map +1 -1
- package/dist/trace-cluster/strategies/MLTraceSimilarityStrategy.js +7 -2
- package/package.json +3 -3
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
* @format
|
|
9
9
|
* @oncall memory_lab
|
|
10
10
|
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.FilterUserTaggedLeaksRule = void 0;
|
|
13
16
|
const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
|
|
17
|
+
const Utils_1 = __importDefault(require("../../Utils"));
|
|
14
18
|
/**
|
|
15
19
|
* leaked objects that are tagged by user code
|
|
16
20
|
*/
|
|
@@ -42,11 +46,22 @@ class FilterUserTaggedLeaksRule extends BaseLeakFilter_rule_1.LeakObjectFilterRu
|
|
|
42
46
|
// heap: trackedItem.taggedObjects.table
|
|
43
47
|
const node = (_a = edge.toNode
|
|
44
48
|
.getReferenceNode('taggedObjects')) === null || _a === void 0 ? void 0 : _a.getReferenceNode('table', 'internal');
|
|
45
|
-
// traverse all
|
|
46
|
-
// trackedItem.taggedObjects.table
|
|
49
|
+
// traverse all entries in trackedItem.taggedObjects.table
|
|
47
50
|
node === null || node === void 0 ? void 0 : node.forEachReference((edge) => {
|
|
51
|
+
// V8 used to emit one weak edge per WeakSet key
|
|
48
52
|
if (edge.type === 'weak') {
|
|
49
53
|
this._taggedNodeIds.add(edge.toNode.id);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// newer V8 versions emit a single internal edge per entry, which
|
|
57
|
+
// points to the value; the key is only encoded in the edge name
|
|
58
|
+
// (e.g. `1 / part of key (Object @7) -> value (true @3) pair in
|
|
59
|
+
// WeakMap (table @9)`)
|
|
60
|
+
if (Utils_1.default.isWeakMapEdge(edge)) {
|
|
61
|
+
const keyNodeId = Utils_1.default.getWeakMapEdgeKeyId(edge);
|
|
62
|
+
if (keyNodeId > 0) {
|
|
63
|
+
this._taggedNodeIds.add(keyNodeId);
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
});
|
|
52
67
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterAttachedDOMToDetachedDOMTrace.rule.d.ts","sourceRoot":"","sources":["../../../../src/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAW,MAAM,aAAa,CAAC;AAK7D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,
|
|
1
|
+
{"version":3,"file":"FilterAttachedDOMToDetachedDOMTrace.rule.d.ts","sourceRoot":"","sources":["../../../../src/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAW,MAAM,aAAa,CAAC;AAK7D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,uCAAwC,YAAW,oBAAoB;IAClF,MAAM,CACJ,CAAC,EAAE,iBAAiB,EACpB,OAAO,GAAE,sBAA2B,GACnC,aAAa;CASjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterCppRootsToDetachedDOMTrace.rule.d.ts","sourceRoot":"","sources":["../../../../src/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAW,MAAM,aAAa,CAAC;AAK7D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,
|
|
1
|
+
{"version":3,"file":"FilterCppRootsToDetachedDOMTrace.rule.d.ts","sourceRoot":"","sources":["../../../../src/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAW,MAAM,aAAa,CAAC;AAK7D,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,oCAAqC,YAAW,oBAAoB;IAC/E,MAAM,CACJ,CAAC,EAAE,iBAAiB,EACpB,OAAO,GAAE,sBAA2B,GACnC,aAAa;CASjB"}
|
|
@@ -14,9 +14,7 @@ declare class TraceFinder {
|
|
|
14
14
|
}): [IHeapNode[], IHeapNode[]];
|
|
15
15
|
visitReachableNodesbyDFS(snapshot: IHeapSnapshot, nodeVisitor?: Optional<Predicator<IHeapNode>>, edgeVisitor?: Optional<Predicator<IHeapEdge>>): void;
|
|
16
16
|
flagReachableNodesFromWindow(snapshot: IHeapSnapshot, flags: Uint32Array, flag: number): void;
|
|
17
|
-
private
|
|
18
|
-
private calculateDominatorNodesFromPostOrder;
|
|
19
|
-
private calculateRetainedSizesFromDominatorNodes;
|
|
17
|
+
private computeDominatorsAndRetainedSizes;
|
|
20
18
|
shouldIgnoreEdgeInTraceFinding(edge: IHeapEdge): boolean;
|
|
21
19
|
shouldTraverseEdge(edge: IHeapEdge, snapshot: IHeapSnapshot, options?: AnyOptions): boolean;
|
|
22
20
|
private shouldTraverseNodeByInternalStandard;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TraceFinder.d.ts","sourceRoot":"","sources":["../../src/paths/TraceFinder.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,SAAS,
|
|
1
|
+
{"version":3,"file":"TraceFinder.d.ts","sourceRoot":"","sources":["../../src/paths/TraceFinder.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,SAAS,EACT,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,cAAc,CAAC;AAUtB,cAAM,WAAW;IACf,eAAe,CACb,QAAQ,EAAE,aAAa,EACvB,GAAG,GAAE;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAM,GAC/B,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC;IA0B7B,wBAAwB,CACtB,QAAQ,EAAE,aAAa,EACvB,WAAW,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAC7C,WAAW,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,GAC5C,IAAI;IA6CP,4BAA4B,CAC1B,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,GACX,IAAI;IAmEP,OAAO,CAAC,iCAAiC;IAgOzC,8BAA8B,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAqBxD,kBAAkB,CAChB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,UAAe,GACvB,OAAO;IAYV,OAAO,CAAC,oCAAoC;IAW5C,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAsB3C,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IA6B9C,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAK9C,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM;IAMnC,8BAA8B,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAmB7D,qBAAqB,CACnB,QAAQ,EAAE,aAAa,EACvB,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI;IA6EP,gBAAgB,CACd,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,GACxB,QAAQ,CAAC,iBAAiB,CAAC;CAkB/B;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -122,270 +122,226 @@ class TraceFinder {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
// Build the dominator tree and retained sizes using the Lengauer-Tarjan
|
|
126
|
+
// algorithm:
|
|
127
|
+
// Thomas Lengauer and Robert Endre Tarjan. 1979. A fast algorithm for
|
|
128
|
+
// finding dominators in a flowgraph. ACM Trans. Program. Lang. Syst. 1, 1
|
|
129
|
+
// (July 1979), 121-141. https://doi.org/10.1145/357062.357071
|
|
130
|
+
//
|
|
131
|
+
// This is the same near-linear, single-pass algorithm Chrome DevTools uses.
|
|
132
|
+
// It replaces the previous iterative Cooper-Harvey-Kennedy fix-point, which
|
|
133
|
+
// had two problems on real browser snapshots:
|
|
134
|
+
// 1. It could spin forever. The fix-point's two-finger intersect() walk
|
|
135
|
+
// assumes every step climbs toward the root, but the post-order that was
|
|
136
|
+
// assigned to orphan / weakly-reachable nodes (unreachable from the GC
|
|
137
|
+
// root) could violate that, producing a cycle in the provisional
|
|
138
|
+
// dominator pointers or a walk onto the out-of-bounds "empty" sentinel.
|
|
139
|
+
// The walk then never terminated -- one CPU pinned at 100% with a flat
|
|
140
|
+
// heap, stuck at "calculating dominators and retained sizes".
|
|
141
|
+
// 2. Even when it converged it needed O(graph-depth) sweeps.
|
|
142
|
+
//
|
|
143
|
+
// Lengauer-Tarjan avoids both: it assigns every node a valid DFS number,
|
|
144
|
+
// treats orphan and mutually-retaining "clique" nodes as retained by the
|
|
145
|
+
// root, and computes dominators in a single pass with no fix-point.
|
|
146
|
+
//
|
|
147
|
+
// Vertices are numbered 1..nodeCount (0 is the invalid/empty value the
|
|
148
|
+
// algorithm relies on); ordinal === vertex - 1 maps a vertex back to its heap
|
|
149
|
+
// node index.
|
|
150
|
+
computeDominatorsAndRetainedSizes(snapshot, flags) {
|
|
151
|
+
const nodes = snapshot.nodes;
|
|
152
|
+
const edges = snapshot.edges;
|
|
153
|
+
const nodeCount = nodes.length;
|
|
154
|
+
const flag = PAGE_OBJECT_FLAG;
|
|
155
|
+
// Offset of each node's first outgoing edge in the flat edge list.
|
|
132
156
|
const firstEdgeIndexes = new Uint32Array(nodeCount + 1);
|
|
133
|
-
firstEdgeIndexes[nodeCount] =
|
|
134
|
-
for (let
|
|
135
|
-
firstEdgeIndexes[
|
|
136
|
-
edgeIndex +=
|
|
157
|
+
firstEdgeIndexes[nodeCount] = edges.length;
|
|
158
|
+
for (let ordinal = 0, edgeIndex = 0; ordinal < nodeCount; ++ordinal) {
|
|
159
|
+
firstEdgeIndexes[ordinal] = edgeIndex;
|
|
160
|
+
edgeIndex += nodes.get(ordinal).edge_count;
|
|
137
161
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
// A single essential-edge predicate used for BOTH the forward DFS and the
|
|
163
|
+
// backward retainer scan, so the two passes agree on exactly which edges
|
|
164
|
+
// exist. (A disagreement between them -- the forward pass omitting a filter
|
|
165
|
+
// the backward pass applied -- is what corrupted the old dominator relation
|
|
166
|
+
// and caused the hang.) Mirrors Chrome DevTools' computeIsEssentialEdge:
|
|
167
|
+
// skip weak edges, non-root shortcut edges, self edges, and edges from a
|
|
168
|
+
// non-page-owned node into a page-owned node (otherwise the debugger's own
|
|
169
|
+
// references would perturb product-object dominators).
|
|
170
|
+
const isEssential = (fromOrdinal, edgeType, toOrdinal) => {
|
|
171
|
+
if (fromOrdinal === toOrdinal) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (!Utils_1.default.isEssentialEdge(fromOrdinal, edgeType, ROOT_NODE_INDEX)) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (fromOrdinal !== ROOT_NODE_INDEX &&
|
|
178
|
+
flags[toOrdinal] & flag &&
|
|
179
|
+
!(flags[fromOrdinal] & flag)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
// Lengauer-Tarjan working arrays, 1-indexed (index 0 == invalid).
|
|
185
|
+
const arrayLength = nodeCount + 1;
|
|
186
|
+
const parent = new Uint32Array(arrayLength);
|
|
187
|
+
const ancestor = new Uint32Array(arrayLength);
|
|
188
|
+
const vertex = new Uint32Array(arrayLength);
|
|
189
|
+
const label = new Uint32Array(arrayLength);
|
|
190
|
+
const semi = new Uint32Array(arrayLength);
|
|
191
|
+
const dom = new Uint32Array(arrayLength);
|
|
192
|
+
const bucket = new Array(arrayLength);
|
|
193
|
+
// Resumable per-node edge cursor for the iterative DFS.
|
|
194
|
+
const nextEdgeIndex = new Uint32Array(arrayLength);
|
|
195
|
+
let n = 0;
|
|
196
|
+
// Iterative DFS (a recursive version overflows the stack on large heaps).
|
|
197
|
+
const dfs = (root) => {
|
|
198
|
+
const rootOrdinal = root - 1;
|
|
199
|
+
nextEdgeIndex[rootOrdinal] = firstEdgeIndexes[rootOrdinal];
|
|
200
|
+
let v = root;
|
|
201
|
+
while (v !== 0) {
|
|
202
|
+
// Number v the first time it is reached.
|
|
203
|
+
if (semi[v] === 0) {
|
|
204
|
+
semi[v] = ++n;
|
|
205
|
+
vertex[n] = label[v] = v;
|
|
206
|
+
}
|
|
207
|
+
// The next node to visit is v's first unprocessed essential successor,
|
|
208
|
+
// else v's parent (backtrack).
|
|
209
|
+
let vNext = parent[v];
|
|
210
|
+
const vOrdinal = v - 1;
|
|
211
|
+
const edgesEnd = firstEdgeIndexes[vOrdinal + 1];
|
|
212
|
+
for (; nextEdgeIndex[vOrdinal] < edgesEnd; ++nextEdgeIndex[vOrdinal]) {
|
|
213
|
+
const edge = edges.get(nextEdgeIndex[vOrdinal]);
|
|
214
|
+
const wOrdinal = edge.toNode.nodeIndex;
|
|
215
|
+
if (!isEssential(vOrdinal, edge.type, wOrdinal)) {
|
|
168
216
|
continue;
|
|
169
217
|
}
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
continue;
|
|
218
|
+
const w = wOrdinal + 1;
|
|
219
|
+
if (semi[w] === 0) {
|
|
220
|
+
parent[w] = v;
|
|
221
|
+
nextEdgeIndex[wOrdinal] = firstEdgeIndexes[wOrdinal];
|
|
222
|
+
vNext = w;
|
|
223
|
+
break;
|
|
177
224
|
}
|
|
178
|
-
++stackTopIndex;
|
|
179
|
-
nodeStack[stackTopIndex] = childNodeIndex;
|
|
180
|
-
edgeStack[stackTopIndex] = firstEdgeIndexes[childNodeIndex];
|
|
181
|
-
visited[childNodeIndex] = 1;
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
// DFS is done, now build the post order based on the stack
|
|
185
|
-
nodeIndex2PostOrderIndex[nodeIndex] = postOrderIndex;
|
|
186
|
-
postOrderIndex2NodeIndex[postOrderIndex++] = nodeIndex;
|
|
187
|
-
--stackTopIndex;
|
|
188
225
|
}
|
|
226
|
+
v = vNext;
|
|
189
227
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
228
|
+
};
|
|
229
|
+
// eval/link with path compression, iterative to avoid deep recursion.
|
|
230
|
+
const compressionStack = new Uint32Array(arrayLength);
|
|
231
|
+
const compress = (node) => {
|
|
232
|
+
let v = node;
|
|
233
|
+
let stackPointer = 0;
|
|
234
|
+
while (ancestor[ancestor[v]] !== 0) {
|
|
235
|
+
compressionStack[++stackPointer] = v;
|
|
236
|
+
v = ancestor[v];
|
|
199
237
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// from the root), and make sure the root node has the
|
|
205
|
-
// last post order index
|
|
206
|
-
--postOrderIndex;
|
|
207
|
-
stackTopIndex = 0;
|
|
208
|
-
nodeStack[0] = rootNodeIndex;
|
|
209
|
-
// skip iterating the edges of the root node
|
|
210
|
-
edgeStack[0] = firstEdgeIndexes[rootNodeIndex + 1];
|
|
211
|
-
for (let nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
|
|
212
|
-
if (visited[nodeIndex] ||
|
|
213
|
-
!Utils_1.default.hasOnlyWeakReferrers(snapshot.nodes.get(nodeIndex))) {
|
|
214
|
-
continue;
|
|
238
|
+
while (stackPointer > 0) {
|
|
239
|
+
const w = compressionStack[stackPointer--];
|
|
240
|
+
if (semi[label[ancestor[w]]] < semi[label[w]]) {
|
|
241
|
+
label[w] = label[ancestor[w]];
|
|
215
242
|
}
|
|
216
|
-
|
|
217
|
-
// to traverse their subgraphs
|
|
218
|
-
++stackTopIndex;
|
|
219
|
-
nodeStack[stackTopIndex] = nodeIndex;
|
|
220
|
-
edgeStack[stackTopIndex] = firstEdgeIndexes[nodeIndex];
|
|
221
|
-
visited[nodeIndex] = nodeIndex;
|
|
243
|
+
ancestor[w] = ancestor[ancestor[w]];
|
|
222
244
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (postOrderIndex !== nodeCount) {
|
|
228
|
-
if (Config_1.default.verbose) {
|
|
229
|
-
Console_1.default.lowLevel(nodeCount - postOrderIndex + ' unreachable nodes in heap snapshot');
|
|
245
|
+
};
|
|
246
|
+
const evaluate = (v) => {
|
|
247
|
+
if (ancestor[v] === 0) {
|
|
248
|
+
return v;
|
|
230
249
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
compress(v);
|
|
251
|
+
return label[v];
|
|
252
|
+
};
|
|
253
|
+
const link = (v, w) => {
|
|
254
|
+
ancestor[w] = v;
|
|
255
|
+
};
|
|
256
|
+
const r = ROOT_NODE_INDEX + 1;
|
|
257
|
+
// Step 1: DFS from the root.
|
|
258
|
+
dfs(r);
|
|
259
|
+
// Step 2: nodes may remain unreachable from the root. First bring in
|
|
260
|
+
// orphans that have only weak retainers, DFS-ing from each.
|
|
261
|
+
if (n < nodeCount) {
|
|
262
|
+
for (let v = 1; v <= nodeCount; ++v) {
|
|
263
|
+
if (semi[v] === 0 &&
|
|
264
|
+
Utils_1.default.hasOnlyWeakReferrers(nodes.get(v - 1))) {
|
|
265
|
+
parent[v] = r;
|
|
266
|
+
dfs(v);
|
|
240
267
|
}
|
|
241
|
-
// give the orphan node a postorder index anyway
|
|
242
|
-
nodeIndex2PostOrderIndex[nodeIndex] = postOrderIndex;
|
|
243
|
-
postOrderIndex2NodeIndex[postOrderIndex++] = nodeIndex;
|
|
244
268
|
}
|
|
245
|
-
nodeIndex2PostOrderIndex[rootNodeIndex] = postOrderIndex;
|
|
246
|
-
postOrderIndex2NodeIndex[postOrderIndex++] = rootNodeIndex;
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
postOrderIndex2NodeIndex,
|
|
250
|
-
nodeIndex2PostOrderIndex,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
// The dominance algorithm is from:
|
|
254
|
-
// Keith D. Cooper and Timothy J. Harvey and Ken Kennedy
|
|
255
|
-
// "A Simple, Fast Dominance Algorithm"
|
|
256
|
-
calculateDominatorNodesFromPostOrder(nodes, edges, postOrderInfo, flags, snapshot) {
|
|
257
|
-
const { postOrderIndex2NodeIndex, nodeIndex2PostOrderIndex } = postOrderInfo;
|
|
258
|
-
const nodeCount = nodes.length;
|
|
259
|
-
const forwardEdges = edges;
|
|
260
|
-
const firstEdgeIndexes = new Uint32Array(nodeCount + 1);
|
|
261
|
-
firstEdgeIndexes[nodeCount] = forwardEdges.length;
|
|
262
|
-
for (let nodeIndex = 0, edgeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
|
|
263
|
-
firstEdgeIndexes[nodeIndex] = edgeIndex;
|
|
264
|
-
edgeIndex += nodes.get(nodeIndex).edge_count;
|
|
265
|
-
}
|
|
266
|
-
const flag = PAGE_OBJECT_FLAG;
|
|
267
|
-
const rootPostOrderedIndex = nodeCount - 1;
|
|
268
|
-
const emptySlot = nodeCount;
|
|
269
|
-
const dominators = new Uint32Array(nodeCount);
|
|
270
|
-
for (let i = 0; i < rootPostOrderedIndex; ++i) {
|
|
271
|
-
dominators[i] = emptySlot;
|
|
272
269
|
}
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
continue;
|
|
270
|
+
// Step 3: whatever is still unreachable is a clique of nodes retained only
|
|
271
|
+
// by one another. Attach each directly under the root and give it a DFS
|
|
272
|
+
// number so the algorithm is well-defined for every node.
|
|
273
|
+
if (n < nodeCount) {
|
|
274
|
+
for (let v = 1; v <= nodeCount; ++v) {
|
|
275
|
+
if (semi[v] === 0) {
|
|
276
|
+
parent[v] = r;
|
|
277
|
+
semi[v] = ++n;
|
|
278
|
+
vertex[n] = label[v] = v;
|
|
279
|
+
}
|
|
284
280
|
}
|
|
285
|
-
const childNodeIndex = forwardEdges.get(edgeIndex).toNode
|
|
286
|
-
.nodeIndex;
|
|
287
|
-
nodesWithOutdatedDominatorInfo[nodeIndex2PostOrderIndex[childNodeIndex]] =
|
|
288
|
-
1;
|
|
289
281
|
}
|
|
290
|
-
//
|
|
291
|
-
let
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
282
|
+
// Step 4: main loop, processing vertices in decreasing DFS number.
|
|
283
|
+
for (let i = n; i >= 2; --i) {
|
|
284
|
+
const w = vertex[i];
|
|
285
|
+
const wOrdinal = w - 1;
|
|
286
|
+
// Compute semidominator of w from its (essential) predecessors.
|
|
287
|
+
let isOrphanNode = true;
|
|
288
|
+
nodes.get(wOrdinal).forEachReferrer((edge) => {
|
|
289
|
+
const vOrdinal = edge.fromNode.nodeIndex;
|
|
290
|
+
if (!isEssential(vOrdinal, edge.type, wOrdinal)) {
|
|
291
|
+
return;
|
|
298
292
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
nodeIndex = postOrderIndex2NodeIndex[postOrderIndex];
|
|
306
|
-
const nodeFlag = flags[nodeIndex] & flag;
|
|
307
|
-
let newDominatorIndex = emptySlot;
|
|
308
|
-
let isOrphanNode = true;
|
|
309
|
-
const node = nodes.get(nodeIndex);
|
|
310
|
-
node.forEachReferrer((edge) => {
|
|
311
|
-
const referrerEdgeType = edge.type;
|
|
312
|
-
const referrerNodeIndex = edge.fromNode.nodeIndex;
|
|
313
|
-
if (!Utils_1.default.isEssentialEdge(referrerNodeIndex, referrerEdgeType, ROOT_NODE_INDEX)) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
isOrphanNode = false;
|
|
317
|
-
const referrerNodeFlag = flags[referrerNodeIndex] & flag;
|
|
318
|
-
// According to Chrome devtools, need to skip the edges from
|
|
319
|
-
// non-page-owned nodes to page-owned nodes (since debugger may
|
|
320
|
-
// also have references to heap objects)
|
|
321
|
-
if (referrerNodeIndex !== ROOT_NODE_INDEX &&
|
|
322
|
-
nodeFlag &&
|
|
323
|
-
!referrerNodeFlag) {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
if (!this.shouldTraverseEdge(edge, snapshot)) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
let referrerPostOrderIndex = nodeIndex2PostOrderIndex[referrerNodeIndex];
|
|
330
|
-
if (dominators[referrerPostOrderIndex] !== emptySlot) {
|
|
331
|
-
if (newDominatorIndex === emptySlot) {
|
|
332
|
-
newDominatorIndex = referrerPostOrderIndex;
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
while (referrerPostOrderIndex !== newDominatorIndex) {
|
|
336
|
-
while (referrerPostOrderIndex < newDominatorIndex) {
|
|
337
|
-
referrerPostOrderIndex = dominators[referrerPostOrderIndex];
|
|
338
|
-
}
|
|
339
|
-
while (newDominatorIndex < referrerPostOrderIndex) {
|
|
340
|
-
newDominatorIndex = dominators[newDominatorIndex];
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// no need to check any further if reaching the root node
|
|
345
|
-
if (newDominatorIndex === rootPostOrderedIndex) {
|
|
346
|
-
return { stop: true };
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
// set root node as the dominator of orphan nodes
|
|
351
|
-
if (isOrphanNode) {
|
|
352
|
-
newDominatorIndex = rootPostOrderedIndex;
|
|
293
|
+
isOrphanNode = false;
|
|
294
|
+
const u = evaluate(vOrdinal + 1);
|
|
295
|
+
if (semi[u] < semi[w]) {
|
|
296
|
+
semi[w] = semi[u];
|
|
353
297
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
298
|
+
});
|
|
299
|
+
// Treat an orphan as retained by the root; semi[r] is <= any other semi.
|
|
300
|
+
if (isOrphanNode) {
|
|
301
|
+
semi[w] = semi[r];
|
|
302
|
+
}
|
|
303
|
+
const semidominator = vertex[semi[w]];
|
|
304
|
+
if (bucket[semidominator] === undefined) {
|
|
305
|
+
bucket[semidominator] = new Set();
|
|
306
|
+
}
|
|
307
|
+
bucket[semidominator].add(w);
|
|
308
|
+
link(parent[w], w);
|
|
309
|
+
// Process the vertices in bucket(parent(w)).
|
|
310
|
+
const parentBucket = bucket[parent[w]];
|
|
311
|
+
if (parentBucket !== undefined) {
|
|
312
|
+
for (const v of parentBucket) {
|
|
313
|
+
const u = evaluate(v);
|
|
314
|
+
dom[v] = semi[u] < semi[v] ? u : parent[w];
|
|
363
315
|
}
|
|
316
|
+
parentBucket.clear();
|
|
364
317
|
}
|
|
365
318
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
319
|
+
// Step 5: fill in the immediate dominators not computed explicitly above.
|
|
320
|
+
// The root is treated as its own dominator, which also propagates the root
|
|
321
|
+
// as the dominator of any unreachable node.
|
|
322
|
+
dom[0] = dom[r] = r;
|
|
323
|
+
for (let i = 2; i <= n; ++i) {
|
|
324
|
+
const w = vertex[i];
|
|
325
|
+
if (dom[w] !== vertex[semi[w]]) {
|
|
326
|
+
dom[w] = dom[dom[w]];
|
|
327
|
+
}
|
|
371
328
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const { postOrderIndex2NodeIndex } = postOrderInfo;
|
|
376
|
-
const nodeCount = nodes.length;
|
|
329
|
+
// Convert to ordinal-indexed dominators and seed retained sizes with self
|
|
330
|
+
// sizes.
|
|
331
|
+
const dominatorOrdinals = new Uint32Array(nodeCount);
|
|
377
332
|
const retainedSizes = new Float64Array(nodeCount);
|
|
378
|
-
for (let
|
|
379
|
-
|
|
333
|
+
for (let ordinal = 0; ordinal < nodeCount; ++ordinal) {
|
|
334
|
+
dominatorOrdinals[ordinal] = dom[ordinal + 1] - 1;
|
|
335
|
+
retainedSizes[ordinal] = nodes.get(ordinal).self_size;
|
|
380
336
|
}
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
retainedSizes[
|
|
337
|
+
// Propagate retained sizes up the dominator tree in reverse DFS order, so
|
|
338
|
+
// each node is accumulated into its dominator before the dominator itself.
|
|
339
|
+
// vertex[1] is the root, so stop at i > 1.
|
|
340
|
+
for (let i = n; i > 1; --i) {
|
|
341
|
+
const ordinal = vertex[i] - 1;
|
|
342
|
+
retainedSizes[dominatorOrdinals[ordinal]] += retainedSizes[ordinal];
|
|
387
343
|
}
|
|
388
|
-
return retainedSizes;
|
|
344
|
+
return { dominatorOrdinals, retainedSizes };
|
|
389
345
|
}
|
|
390
346
|
shouldIgnoreEdgeInTraceFinding(edge) {
|
|
391
347
|
const fromNode = edge.fromNode;
|
|
@@ -471,24 +427,19 @@ class TraceFinder {
|
|
|
471
427
|
}
|
|
472
428
|
calculateAllNodesRetainedSizes(snapshot) {
|
|
473
429
|
Console_1.default.overwrite('calculating dominators and retained sizes .');
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
430
|
+
const nodes = snapshot.nodes;
|
|
431
|
+
// Flag nodes owned by the page (reachable from the window) so the dominator
|
|
432
|
+
// pass can ignore edges the debugger adds into page-owned objects.
|
|
433
|
+
const flags = new Uint32Array(nodes.length);
|
|
477
434
|
this.flagReachableNodesFromWindow(snapshot, flags, PAGE_OBJECT_FLAG);
|
|
478
|
-
Console_1.default.overwrite('calculating dominators and retained sizes ...');
|
|
479
|
-
const postOrderInfo = this.buildPostOrderIndex(snapshot, flags);
|
|
480
|
-
// step 2: build dominator relations
|
|
481
|
-
Console_1.default.overwrite('calculating dominators and retained sizes .');
|
|
482
|
-
const dominatorInfo = this.calculateDominatorNodesFromPostOrder(snapshot.nodes, snapshot.edges, postOrderInfo, flags, snapshot);
|
|
483
|
-
// step 3: calculate retained sizes
|
|
484
435
|
Console_1.default.overwrite('calculating dominators and retained sizes ..');
|
|
485
|
-
const retainedSizes = this.
|
|
486
|
-
//
|
|
436
|
+
const { dominatorOrdinals, retainedSizes } = this.computeDominatorsAndRetainedSizes(snapshot, flags);
|
|
437
|
+
// assign retained sizes and dominators to nodes
|
|
487
438
|
Console_1.default.overwrite('calculating dominators and retained sizes ...');
|
|
488
|
-
for (let
|
|
489
|
-
const node =
|
|
490
|
-
node.retainedSize = retainedSizes[
|
|
491
|
-
node.dominatorNode =
|
|
439
|
+
for (let ordinal = 0; ordinal < retainedSizes.length; ++ordinal) {
|
|
440
|
+
const node = nodes.get(ordinal);
|
|
441
|
+
node.retainedSize = retainedSizes[ordinal];
|
|
442
|
+
node.dominatorNode = nodes.get(dominatorOrdinals[ordinal]);
|
|
492
443
|
}
|
|
493
444
|
}
|
|
494
445
|
annotateShortestPaths(snapshot, excludeKeySet) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MLTraceSimilarityStrategy.d.ts","sourceRoot":"","sources":["../../../src/trace-cluster/strategies/MLTraceSimilarityStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAK5E,MAAM,CAAC,OAAO,OAAO,yBAA0B,YAAW,gBAAgB;IACjE,UAAU,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"MLTraceSimilarityStrategy.d.ts","sourceRoot":"","sources":["../../../src/trace-cluster/strategies/MLTraceSimilarityStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAK5E,MAAM,CAAC,OAAO,OAAO,yBAA0B,YAAW,gBAAgB;IACjE,UAAU,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,SAAS;IAkCxD,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM;CAkBrC"}
|
|
@@ -32,8 +32,13 @@ class MLTraceSimilarityStrategy {
|
|
|
32
32
|
if (!map.has(repTrace)) {
|
|
33
33
|
map.set(repTrace, [repTrace]);
|
|
34
34
|
}
|
|
35
|
-
//
|
|
36
|
-
|
|
35
|
+
// the representative trace is already seeded at index 0, and exactly one
|
|
36
|
+
// trace per cluster satisfies `result[i] === i`; without this guard that
|
|
37
|
+
// trace is added twice, inflating the cluster size by one
|
|
38
|
+
if (trace !== repTrace) {
|
|
39
|
+
// to please linter
|
|
40
|
+
(_a = map.get(repTrace)) === null || _a === void 0 ? void 0 : _a.push(trace);
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
43
|
return {
|
|
39
44
|
allClusters: Array.from(map.values()),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "memlab core libraries",
|
|
6
6
|
"author": "Liang Gong <lgong@meta.com>",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"chalk": "^4.0.0",
|
|
37
37
|
"fs-extra": "^4.0.2",
|
|
38
38
|
"minimist": "^1.2.8",
|
|
39
|
-
"puppeteer": "
|
|
40
|
-
"puppeteer-core": "
|
|
39
|
+
"puppeteer": "24.31.0",
|
|
40
|
+
"puppeteer-core": "24.31.0",
|
|
41
41
|
"string-width": "^4.2.0",
|
|
42
42
|
"util.promisify": "^1.1.1",
|
|
43
43
|
"xvfb": "^0.4.0"
|