@memlab/mcp-server 2.1.1 → 2.3.0
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/README.md +73 -3
- package/dist/analysis-budget.d.ts +71 -0
- package/dist/analysis-budget.d.ts.map +1 -0
- package/dist/analysis-budget.js +118 -0
- package/dist/analysis-budget.js.map +1 -0
- package/dist/guardrail.d.ts +24 -0
- package/dist/guardrail.d.ts.map +1 -0
- package/dist/guardrail.js +65 -0
- package/dist/guardrail.js.map +1 -0
- package/dist/heap-state.d.ts +46 -1
- package/dist/heap-state.d.ts.map +1 -1
- package/dist/heap-state.js +142 -10
- package/dist/heap-state.js.map +1 -1
- package/dist/index.js +36 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/array-group-by.d.ts +12 -0
- package/dist/tools/array-group-by.d.ts.map +1 -0
- package/dist/tools/array-group-by.js +191 -0
- package/dist/tools/array-group-by.js.map +1 -0
- package/dist/tools/auto-investigate.d.ts.map +1 -1
- package/dist/tools/auto-investigate.js +999 -37
- package/dist/tools/auto-investigate.js.map +1 -1
- package/dist/tools/cache-analysis.d.ts.map +1 -1
- package/dist/tools/cache-analysis.js +438 -21
- package/dist/tools/cache-analysis.js.map +1 -1
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +261 -1
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/class-histogram.d.ts.map +1 -1
- package/dist/tools/class-histogram.js +142 -33
- package/dist/tools/class-histogram.js.map +1 -1
- package/dist/tools/closure-inspection.d.ts.map +1 -1
- package/dist/tools/closure-inspection.js +238 -25
- package/dist/tools/closure-inspection.js.map +1 -1
- package/dist/tools/detached-dom.d.ts.map +1 -1
- package/dist/tools/detached-dom.js +25 -3
- package/dist/tools/detached-dom.js.map +1 -1
- package/dist/tools/dev-artifacts.d.ts +33 -0
- package/dist/tools/dev-artifacts.d.ts.map +1 -0
- package/dist/tools/dev-artifacts.js +165 -0
- package/dist/tools/dev-artifacts.js.map +1 -0
- package/dist/tools/diff-snapshots.d.ts.map +1 -1
- package/dist/tools/diff-snapshots.js +113 -18
- package/dist/tools/diff-snapshots.js.map +1 -1
- package/dist/tools/duplicated-strings.d.ts.map +1 -1
- package/dist/tools/duplicated-strings.js +74 -12
- package/dist/tools/duplicated-strings.js.map +1 -1
- package/dist/tools/eval.d.ts.map +1 -1
- package/dist/tools/eval.js +189 -10
- package/dist/tools/eval.js.map +1 -1
- package/dist/tools/event-listener-leaks.d.ts.map +1 -1
- package/dist/tools/event-listener-leaks.js +235 -13
- package/dist/tools/event-listener-leaks.js.map +1 -1
- package/dist/tools/event-registry.d.ts +12 -0
- package/dist/tools/event-registry.d.ts.map +1 -0
- package/dist/tools/event-registry.js +183 -0
- package/dist/tools/event-registry.js.map +1 -0
- package/dist/tools/find-by-property.d.ts.map +1 -1
- package/dist/tools/find-by-property.js +113 -31
- package/dist/tools/find-by-property.js.map +1 -1
- package/dist/tools/find-by-shape.d.ts +12 -0
- package/dist/tools/find-by-shape.d.ts.map +1 -0
- package/dist/tools/find-by-shape.js +178 -0
- package/dist/tools/find-by-shape.js.map +1 -0
- package/dist/tools/find-nodes-by-class.d.ts.map +1 -1
- package/dist/tools/find-nodes-by-class.js +50 -6
- package/dist/tools/find-nodes-by-class.js.map +1 -1
- package/dist/tools/for-each.d.ts.map +1 -1
- package/dist/tools/for-each.js +1 -1
- package/dist/tools/for-each.js.map +1 -1
- package/dist/tools/get-node.d.ts.map +1 -1
- package/dist/tools/get-node.js +6 -0
- package/dist/tools/get-node.js.map +1 -1
- package/dist/tools/get-string.d.ts +12 -0
- package/dist/tools/get-string.d.ts.map +1 -0
- package/dist/tools/get-string.js +223 -0
- package/dist/tools/get-string.js.map +1 -0
- package/dist/tools/get-value.d.ts +12 -0
- package/dist/tools/get-value.d.ts.map +1 -0
- package/dist/tools/get-value.js +152 -0
- package/dist/tools/get-value.js.map +1 -0
- package/dist/tools/global-variables.d.ts.map +1 -1
- package/dist/tools/global-variables.js +54 -34
- package/dist/tools/global-variables.js.map +1 -1
- package/dist/tools/growth-signals.d.ts +12 -0
- package/dist/tools/growth-signals.d.ts.map +1 -0
- package/dist/tools/growth-signals.js +211 -0
- package/dist/tools/growth-signals.js.map +1 -0
- package/dist/tools/intern-opportunities.d.ts +12 -0
- package/dist/tools/intern-opportunities.d.ts.map +1 -0
- package/dist/tools/intern-opportunities.js +289 -0
- package/dist/tools/intern-opportunities.js.map +1 -0
- package/dist/tools/load-snapshot.d.ts +10 -0
- package/dist/tools/load-snapshot.d.ts.map +1 -1
- package/dist/tools/load-snapshot.js +152 -15
- package/dist/tools/load-snapshot.js.map +1 -1
- package/dist/tools/object-cost-breakdown.d.ts +12 -0
- package/dist/tools/object-cost-breakdown.d.ts.map +1 -0
- package/dist/tools/object-cost-breakdown.js +439 -0
- package/dist/tools/object-cost-breakdown.js.map +1 -0
- package/dist/tools/object-shape.d.ts.map +1 -1
- package/dist/tools/object-shape.js +95 -2
- package/dist/tools/object-shape.js.map +1 -1
- package/dist/tools/pinch-points.d.ts.map +1 -1
- package/dist/tools/pinch-points.js +9 -9
- package/dist/tools/pinch-points.js.map +1 -1
- package/dist/tools/property-distribution.d.ts +12 -0
- package/dist/tools/property-distribution.d.ts.map +1 -0
- package/dist/tools/property-distribution.js +168 -0
- package/dist/tools/property-distribution.js.map +1 -0
- package/dist/tools/quick-diagnosis.d.ts.map +1 -1
- package/dist/tools/quick-diagnosis.js +56 -19
- package/dist/tools/quick-diagnosis.js.map +1 -1
- package/dist/tools/retainer-summary.d.ts.map +1 -1
- package/dist/tools/retainer-summary.js +90 -5
- package/dist/tools/retainer-summary.js.map +1 -1
- package/dist/tools/retainer-trace.d.ts.map +1 -1
- package/dist/tools/retainer-trace.js +74 -22
- package/dist/tools/retainer-trace.js.map +1 -1
- package/dist/tools/search-nodes.d.ts.map +1 -1
- package/dist/tools/search-nodes.js +10 -3
- package/dist/tools/search-nodes.js.map +1 -1
- package/dist/tools/search-strings.d.ts +12 -0
- package/dist/tools/search-strings.d.ts.map +1 -0
- package/dist/tools/search-strings.js +182 -0
- package/dist/tools/search-strings.js.map +1 -0
- package/dist/tools/sequence-analysis.d.ts +12 -0
- package/dist/tools/sequence-analysis.d.ts.map +1 -0
- package/dist/tools/sequence-analysis.js +234 -0
- package/dist/tools/sequence-analysis.js.map +1 -0
- package/dist/tools/server-status.d.ts +12 -0
- package/dist/tools/server-status.d.ts.map +1 -0
- package/dist/tools/server-status.js +41 -0
- package/dist/tools/server-status.js.map +1 -0
- package/dist/tools/shape-histogram.d.ts.map +1 -1
- package/dist/tools/shape-histogram.js +61 -16
- package/dist/tools/shape-histogram.js.map +1 -1
- package/dist/tools/sliced-strings.d.ts.map +1 -1
- package/dist/tools/sliced-strings.js +16 -11
- package/dist/tools/sliced-strings.js.map +1 -1
- package/dist/tools/snapshot-summary.d.ts.map +1 -1
- package/dist/tools/snapshot-summary.js +55 -13
- package/dist/tools/snapshot-summary.js.map +1 -1
- package/dist/tools/snapshots.d.ts +12 -0
- package/dist/tools/snapshots.d.ts.map +1 -0
- package/dist/tools/snapshots.js +93 -0
- package/dist/tools/snapshots.js.map +1 -0
- package/dist/tools/stale-collections.d.ts.map +1 -1
- package/dist/tools/stale-collections.js +248 -7
- package/dist/tools/stale-collections.js.map +1 -1
- package/dist/tools/trace-dominators.d.ts.map +1 -1
- package/dist/tools/trace-dominators.js +28 -1
- package/dist/tools/trace-dominators.js.map +1 -1
- package/dist/tools/weakmap-entries.d.ts +12 -0
- package/dist/tools/weakmap-entries.d.ts.map +1 -0
- package/dist/tools/weakmap-entries.js +156 -0
- package/dist/tools/weakmap-entries.js.map +1 -0
- package/dist/utils.d.ts +81 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +303 -35
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
package/dist/tools/eval.js
CHANGED
|
@@ -119,7 +119,8 @@ export function registerEval(server) {
|
|
|
119
119
|
'`.toNode`, `.fromNode`.\n' +
|
|
120
120
|
'**Iterating all nodes:** `snapshot.nodes.forEach(node => { ... })` — NOT for-of.\n' +
|
|
121
121
|
'**Get node by ID:** `snapshot.getNodeById(id)` returns IHeapNode or null.\n' +
|
|
122
|
-
'**String values:** `node.toStringNode()?.stringValue` for string nodes.\n
|
|
122
|
+
'**String values:** `node.toStringNode()?.stringValue` for string nodes.\n' +
|
|
123
|
+
'**Caveat — retained_size is unreliable here:** inside eval, `node.retained_size`/`.retainedSize` can read back ~0 for every node on some loads. Node counts, property/edge walks, and string values ARE trustworthy. For authoritative retained sizes call `helpers.retainedSize(id)` / `helpers.retainedSizes([ids])` (they re-resolve the node on the real snapshot), or use the dedicated tools (`memlab_largest_objects`, `memlab_class_histogram`, `memlab_pinch_points`, `memlab_object_shape`).\n\n' +
|
|
123
124
|
'**Example — inspect Map entries:**\n' +
|
|
124
125
|
'```\nconst map = snapshot.getNodeById(12345);\nconst entries = [];\n' +
|
|
125
126
|
'for (const edge of map.references) {\n' +
|
|
@@ -127,23 +128,38 @@ export function registerEval(server) {
|
|
|
127
128
|
' for (const te of edge.toNode.references) {\n' +
|
|
128
129
|
' entries.push({name: te.toNode.name, type: te.toNode.type});\n' +
|
|
129
130
|
' }\n }\n}\nresult = entries.slice(0, 10);\n```', {
|
|
131
|
+
mode: z
|
|
132
|
+
.enum(['eval', 'describe_env'])
|
|
133
|
+
.optional()
|
|
134
|
+
.default('eval')
|
|
135
|
+
.describe('"eval" (default) runs `code`. "describe_env" ignores `code` and returns the in-scope globals, the IHeapNode/IHeapEdge API, and the required calling conventions (`result =`, `.forEach`) so you can self-correct before running.'),
|
|
130
136
|
code: z
|
|
131
137
|
.string()
|
|
138
|
+
.optional()
|
|
132
139
|
.describe('JavaScript code to execute. Must assign the output to a `result` variable. ' +
|
|
133
140
|
'Available globals: snapshot (IHeapSnapshot — use .nodes.forEach(), .getNodeById(), .edges.forEach()), ' +
|
|
134
141
|
'utils (@memlab/core utils with aggregateDominatorMetrics, isFiberNode, isDetachedDOMNode), ' +
|
|
135
142
|
'helpers ({ serializeNodeSummary, serializeNodeDetail, formatBytes, formatNumber, ' +
|
|
136
|
-
'markdownTable, isNodeWorthInspecting, filterLargestObjects, queryNodes
|
|
143
|
+
'markdownTable, isNodeWorthInspecting, filterLargestObjects, queryNodes, ' +
|
|
144
|
+
'groupReferrersByEdge(nodeId), groupArrayElementsByProperty(arrayNodeId, propName), ' +
|
|
145
|
+
'isOrphaned(nodeId, ownershipEdgeNames[]), countUniqueTargets(arrayNodeId, propName), ' +
|
|
146
|
+
'retainedSize(id), retainedSizes(ids[]) }), ' +
|
|
137
147
|
'and standard JS built-ins. ' +
|
|
138
148
|
'Node traversal: use node.references (outgoing) and node.referrers (incoming) with for-of. ' +
|
|
139
149
|
'Edge properties: .name_or_index, .type, .toNode, .fromNode.'),
|
|
140
150
|
timeout_ms: z
|
|
141
151
|
.number()
|
|
142
152
|
.optional()
|
|
143
|
-
.default(
|
|
144
|
-
.describe('Execution timeout in milliseconds (default
|
|
145
|
-
}, async ({ code, timeout_ms }) => {
|
|
153
|
+
.default(60000)
|
|
154
|
+
.describe('Execution timeout in milliseconds (default 60000). Full-snapshot scans on large heaps may need 120000+.'),
|
|
155
|
+
}, async ({ mode, code, timeout_ms }) => {
|
|
146
156
|
try {
|
|
157
|
+
if (mode === 'describe_env') {
|
|
158
|
+
return toolResult(describeEnv());
|
|
159
|
+
}
|
|
160
|
+
if (code == null || code.trim() === '') {
|
|
161
|
+
return errorResult(new Error('No code provided. Pass `code`, or use mode:"describe_env" to see the available globals and conventions.'));
|
|
162
|
+
}
|
|
147
163
|
const snapshot = getSnapshot();
|
|
148
164
|
const consoleOutput = [];
|
|
149
165
|
const capturedConsole = {
|
|
@@ -152,6 +168,107 @@ export function registerEval(server) {
|
|
|
152
168
|
error: (...args) => consoleOutput.push('[error] ' + args.map(String).join(' ')),
|
|
153
169
|
info: (...args) => consoleOutput.push('[info] ' + args.map(String).join(' ')),
|
|
154
170
|
};
|
|
171
|
+
const groupReferrersByEdge = (nodeId) => {
|
|
172
|
+
const target = snapshot.getNodeById(nodeId);
|
|
173
|
+
if (!target)
|
|
174
|
+
return {};
|
|
175
|
+
const groups = {};
|
|
176
|
+
for (const edge of target.referrers) {
|
|
177
|
+
const eName = String(edge.name_or_index);
|
|
178
|
+
const from = edge.fromNode;
|
|
179
|
+
if (!groups[eName])
|
|
180
|
+
groups[eName] = [];
|
|
181
|
+
if (groups[eName].length < 10) {
|
|
182
|
+
groups[eName].push({
|
|
183
|
+
fromName: from.name,
|
|
184
|
+
fromType: from.type,
|
|
185
|
+
fromId: from.id,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return groups;
|
|
190
|
+
};
|
|
191
|
+
const groupArrayElementsByProperty = (arrayNodeId, propertyName) => {
|
|
192
|
+
const arrNode = snapshot.getNodeById(arrayNodeId);
|
|
193
|
+
if (!arrNode)
|
|
194
|
+
return { error: 'Node not found' };
|
|
195
|
+
const groups = {};
|
|
196
|
+
let missing = 0;
|
|
197
|
+
let total = 0;
|
|
198
|
+
for (const edge of arrNode.references) {
|
|
199
|
+
if (edge.type !== 'element')
|
|
200
|
+
continue;
|
|
201
|
+
const elem = edge.toNode;
|
|
202
|
+
if (elem.id <= 3)
|
|
203
|
+
continue;
|
|
204
|
+
total++;
|
|
205
|
+
let found = false;
|
|
206
|
+
for (const propEdge of elem.references) {
|
|
207
|
+
if (String(propEdge.name_or_index) === propertyName) {
|
|
208
|
+
const target = propEdge.toNode;
|
|
209
|
+
const key = target.name;
|
|
210
|
+
if (!groups[key])
|
|
211
|
+
groups[key] = { count: 0, exampleId: target.id };
|
|
212
|
+
groups[key].count++;
|
|
213
|
+
found = true;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!found)
|
|
218
|
+
missing++;
|
|
219
|
+
}
|
|
220
|
+
return { groups, total, missing };
|
|
221
|
+
};
|
|
222
|
+
const isOrphaned = (nodeId, ownershipEdgeNames) => {
|
|
223
|
+
const target = snapshot.getNodeById(nodeId);
|
|
224
|
+
if (!target)
|
|
225
|
+
return false;
|
|
226
|
+
const ownerSet = new Set(ownershipEdgeNames);
|
|
227
|
+
for (const edge of target.referrers) {
|
|
228
|
+
if (ownerSet.has(String(edge.name_or_index)))
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
};
|
|
233
|
+
// Authoritative retained sizes (Feedback round 3 §3b). Reading
|
|
234
|
+
// `.retainedSize` off proxied/iterated nodes inside eval can come back
|
|
235
|
+
// ~0; these helpers look the node up fresh on the real snapshot (the
|
|
236
|
+
// same path the dedicated tools use) so custom analyses can rank by
|
|
237
|
+
// retained size.
|
|
238
|
+
const retainedSize = (id) => {
|
|
239
|
+
const n = snapshot.getNodeById(id);
|
|
240
|
+
return n ? n.retainedSize : 0;
|
|
241
|
+
};
|
|
242
|
+
const retainedSizes = (ids) => {
|
|
243
|
+
const out = {};
|
|
244
|
+
for (const id of ids) {
|
|
245
|
+
const n = snapshot.getNodeById(id);
|
|
246
|
+
out[id] = n ? n.retainedSize : 0;
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
};
|
|
250
|
+
const countUniqueTargets = (arrayNodeId, propertyName) => {
|
|
251
|
+
const arrNode = snapshot.getNodeById(arrayNodeId);
|
|
252
|
+
if (!arrNode)
|
|
253
|
+
return { error: 'Node not found' };
|
|
254
|
+
const uniqueIds = new Set();
|
|
255
|
+
let total = 0;
|
|
256
|
+
for (const edge of arrNode.references) {
|
|
257
|
+
if (edge.type !== 'element')
|
|
258
|
+
continue;
|
|
259
|
+
const elem = edge.toNode;
|
|
260
|
+
if (elem.id <= 3)
|
|
261
|
+
continue;
|
|
262
|
+
total++;
|
|
263
|
+
for (const propEdge of elem.references) {
|
|
264
|
+
if (String(propEdge.name_or_index) === propertyName) {
|
|
265
|
+
uniqueIds.add(propEdge.toNode.id);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { uniqueCount: uniqueIds.size, totalElements: total };
|
|
271
|
+
};
|
|
155
272
|
const helpers = {
|
|
156
273
|
serializeNodeSummary,
|
|
157
274
|
serializeNodeDetail,
|
|
@@ -161,6 +278,12 @@ export function registerEval(server) {
|
|
|
161
278
|
isNodeWorthInspecting,
|
|
162
279
|
filterLargestObjects,
|
|
163
280
|
queryNodes,
|
|
281
|
+
groupReferrersByEdge,
|
|
282
|
+
groupArrayElementsByProperty,
|
|
283
|
+
isOrphaned,
|
|
284
|
+
countUniqueTargets,
|
|
285
|
+
retainedSize,
|
|
286
|
+
retainedSizes,
|
|
164
287
|
};
|
|
165
288
|
const sandbox = {
|
|
166
289
|
snapshot: wrapSnapshot(snapshot),
|
|
@@ -197,6 +320,15 @@ export function registerEval(server) {
|
|
|
197
320
|
const context = vm.createContext(sandbox);
|
|
198
321
|
const script = new vm.Script(code, { filename: 'memlab_eval' });
|
|
199
322
|
script.runInContext(context, { timeout: timeout_ms });
|
|
323
|
+
// Actionable hint when nothing was assigned to `result` (the #1 user
|
|
324
|
+
// error — code that `return`s a value or runs a value-returning IIFE
|
|
325
|
+
// never populates `result`, so output is silently "undefined").
|
|
326
|
+
if (sandbox.result === undefined && consoleOutput.length === 0) {
|
|
327
|
+
return toolResult('Your code ran without error but never assigned to `result`, so there is nothing to return.\n' +
|
|
328
|
+
'Assign the value you want back to `result` (do NOT use `return` at the top level), e.g.:\n' +
|
|
329
|
+
' `result = someValue;`\n' +
|
|
330
|
+
'Use mode:"describe_env" to see the full calling convention.');
|
|
331
|
+
}
|
|
200
332
|
let output;
|
|
201
333
|
try {
|
|
202
334
|
output = JSON.stringify(sandbox.result, null, 2) ?? 'undefined';
|
|
@@ -212,12 +344,59 @@ export function registerEval(server) {
|
|
|
212
344
|
return toolResult(output);
|
|
213
345
|
}
|
|
214
346
|
catch (err) {
|
|
215
|
-
|
|
216
|
-
err.message.includes('Script execution timed out')) {
|
|
217
|
-
return errorResult(new Error(`Execution timed out after ${timeout_ms}ms`));
|
|
218
|
-
}
|
|
219
|
-
return errorResult(err);
|
|
347
|
+
return errorResult(new Error(actionableEvalError(err, code)));
|
|
220
348
|
}
|
|
221
349
|
});
|
|
222
350
|
}
|
|
351
|
+
// Map the opaque VM errors that the documented calling-convention mistakes
|
|
352
|
+
// produce into actionable guidance (Feedback §3).
|
|
353
|
+
function actionableEvalError(err, code) {
|
|
354
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
355
|
+
if (msg.includes('Script execution timed out')) {
|
|
356
|
+
return `Execution timed out. Increase timeout_ms, or narrow the scan (filter earlier, use a dedicated tool like memlab_find_by_property/memlab_property_distribution instead of a full snapshot.nodes walk).`;
|
|
357
|
+
}
|
|
358
|
+
if (msg.includes('Illegal return statement')) {
|
|
359
|
+
return `Illegal return statement: you cannot \`return\` at the top level here. Assign the value to \`result\` instead — e.g. \`result = ...;\`. (Use mode:"describe_env" for the convention.)`;
|
|
360
|
+
}
|
|
361
|
+
if (msg.includes('is not iterable')) {
|
|
362
|
+
return `${msg}\nHint: \`snapshot.nodes\` is not a for-of iterable. Use \`snapshot.nodes.forEach(node => { ... })\` (and \`snapshot.edges.forEach(...)\`). \`node.references\`/\`node.referrers\` ARE for-of iterable.`;
|
|
363
|
+
}
|
|
364
|
+
if (code &&
|
|
365
|
+
/\bfor\s*\(\s*(const|let|var)\b.*\bof\b.*\bsnapshot\.nodes\b/.test(code)) {
|
|
366
|
+
return `${msg}\nHint: iterate all nodes with \`snapshot.nodes.forEach(node => { ... })\`, not \`for...of\`.`;
|
|
367
|
+
}
|
|
368
|
+
return msg;
|
|
369
|
+
}
|
|
370
|
+
function describeEnv() {
|
|
371
|
+
return [
|
|
372
|
+
'# memlab_eval environment',
|
|
373
|
+
'',
|
|
374
|
+
'## Calling conventions (REQUIRED)',
|
|
375
|
+
'- Assign your output to `result` — do NOT use `return` at the top level (that throws "Illegal return statement").',
|
|
376
|
+
'- Iterate all nodes with `snapshot.nodes.forEach(node => { ... })` and all edges with `snapshot.edges.forEach(...)`. `snapshot.nodes` is NOT a for-of iterable.',
|
|
377
|
+
'- `node.references` (outgoing) and `node.referrers` (incoming) ARE for-of iterable.',
|
|
378
|
+
'',
|
|
379
|
+
'## In-scope globals',
|
|
380
|
+
'- `snapshot` — IHeapSnapshot: `.nodes.forEach(cb)`, `.edges.forEach(cb)`, `.getNodeById(id)`.',
|
|
381
|
+
'- `utils` — @memlab/core utils (e.g. `aggregateDominatorMetrics`, `isFiberNode`, `isDetachedDOMNode`).',
|
|
382
|
+
'- `helpers` — `serializeNodeSummary`, `serializeNodeDetail`, `formatBytes`, `formatNumber`, `markdownTable`, `isNodeWorthInspecting`, `filterLargestObjects`, `queryNodes`, `groupReferrersByEdge(nodeId)`, `groupArrayElementsByProperty(arrayNodeId, prop)`, `isOrphaned(nodeId, ownerEdges[])`, `countUniqueTargets(arrayNodeId, prop)`, `retainedSize(id)`, `retainedSizes(ids[])`.',
|
|
383
|
+
'- Standard JS built-ins (Array, Object, Map, Set, JSON, Math, RegExp, …). No require/process/fs/network.',
|
|
384
|
+
'',
|
|
385
|
+
'## IHeapNode API',
|
|
386
|
+
'`.id`, `.name`, `.type`, `.self_size`, `.retainedSize` (alias `.retained_size`), `.edge_count`, `.is_detached`, `.numOfReferrers` (alias `.referrer_count`), `.isString`, `.toStringNode()?.stringValue`, `.hasPathEdge`, `.pathEdge`, `.dominatorNode`, `.location` (`script_id`/`line`/`column`).',
|
|
387
|
+
'',
|
|
388
|
+
'## Caveat: retained_size',
|
|
389
|
+
'Inside eval, `.retainedSize`/`.retained_size` can read back ~0 for every node on some loads. Counts, property/edge walks, and string values are reliable. For authoritative retained sizes call `helpers.retainedSize(id)` or `helpers.retainedSizes([ids])` (they re-resolve the node on the real snapshot, so you can rank custom analyses by retained size), or use `memlab_largest_objects`, `memlab_class_histogram`, `memlab_pinch_points`, or `memlab_object_shape`.',
|
|
390
|
+
'',
|
|
391
|
+
'## IHeapEdge API',
|
|
392
|
+
'`.name_or_index`, `.type` (property/element/context/internal/hidden/shortcut), `.toNode`, `.fromNode`.',
|
|
393
|
+
'',
|
|
394
|
+
'## Runnable example',
|
|
395
|
+
'```',
|
|
396
|
+
'const counts = {};',
|
|
397
|
+
'snapshot.nodes.forEach(node => { counts[node.type] = (counts[node.type] || 0) + 1; });',
|
|
398
|
+
'result = counts;',
|
|
399
|
+
'```',
|
|
400
|
+
].join('\n');
|
|
401
|
+
}
|
|
223
402
|
//# sourceMappingURL=eval.js.map
|
package/dist/tools/eval.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../../src/tools/eval.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../../src/tools/eval.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-listener-leaks.d.ts","sourceRoot":"","sources":["../../src/tools/event-listener-leaks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"event-listener-leaks.d.ts","sourceRoot":"","sources":["../../src/tools/event-listener-leaks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AA+NvE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkZlE"}
|
|
@@ -102,14 +102,63 @@ function countZombies(listeners, snapshot) {
|
|
|
102
102
|
}
|
|
103
103
|
return zombies;
|
|
104
104
|
}
|
|
105
|
+
function analyzeContextDistribution(listeners, snapshot) {
|
|
106
|
+
const callbackIds = new Set();
|
|
107
|
+
const contextIds = new Set();
|
|
108
|
+
const contextShapeMap = new Map();
|
|
109
|
+
let orphanedCount = 0;
|
|
110
|
+
for (const l of listeners) {
|
|
111
|
+
callbackIds.add(l.callbackId);
|
|
112
|
+
if (l.contextId != null) {
|
|
113
|
+
contextIds.add(l.contextId);
|
|
114
|
+
const ctxNode = snapshot.getNodeById(l.contextId);
|
|
115
|
+
if (ctxNode) {
|
|
116
|
+
const shapeName = ctxNode.name;
|
|
117
|
+
const existing = contextShapeMap.get(shapeName);
|
|
118
|
+
if (existing) {
|
|
119
|
+
existing.count++;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
contextShapeMap.set(shapeName, { count: 1, exampleId: ctxNode.id });
|
|
123
|
+
}
|
|
124
|
+
if (ctxNode.numOfReferrers <= 2) {
|
|
125
|
+
orphanedCount++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const contextShapes = [...contextShapeMap.entries()]
|
|
131
|
+
.map(([shapeName, info]) => ({
|
|
132
|
+
shapeName,
|
|
133
|
+
count: info.count,
|
|
134
|
+
exampleId: info.exampleId,
|
|
135
|
+
}))
|
|
136
|
+
.sort((a, b) => b.count - a.count)
|
|
137
|
+
.slice(0, 10);
|
|
138
|
+
return {
|
|
139
|
+
uniqueCallbacks: callbackIds.size,
|
|
140
|
+
uniqueContexts: contextIds.size,
|
|
141
|
+
totalListeners: listeners.length,
|
|
142
|
+
contextShapes,
|
|
143
|
+
orphanedContextCount: orphanedCount,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// A duplicate is the SAME callback function instance bound to the SAME context
|
|
147
|
+
// instance, registered more than once. We key by node ids (callbackId +
|
|
148
|
+
// contextId), NOT by name/shape — keying by name counted distinct closures that
|
|
149
|
+
// merely share a name across different hosts as "duplicates", which produced
|
|
150
|
+
// wildly inflated counts (e.g. 99.95%) on per-model registries that are really
|
|
151
|
+
// structural, one-listener-per-model (Feedback §8).
|
|
105
152
|
function countDuplicateCallbacks(listeners) {
|
|
106
|
-
const
|
|
153
|
+
const pairs = new Map();
|
|
107
154
|
for (const l of listeners) {
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
if (l.callbackId <= 0)
|
|
156
|
+
continue;
|
|
157
|
+
const key = `${l.callbackId}\0${l.contextId ?? 0}`;
|
|
158
|
+
pairs.set(key, (pairs.get(key) ?? 0) + 1);
|
|
110
159
|
}
|
|
111
160
|
let duplicates = 0;
|
|
112
|
-
for (const count of
|
|
161
|
+
for (const count of pairs.values()) {
|
|
113
162
|
if (count > 1)
|
|
114
163
|
duplicates += count - 1;
|
|
115
164
|
}
|
|
@@ -117,11 +166,14 @@ function countDuplicateCallbacks(listeners) {
|
|
|
117
166
|
}
|
|
118
167
|
export function registerEventListenerLeaks(server) {
|
|
119
168
|
server.tool('memlab_event_listener_leaks', 'Detect EventEmitter-style listener accumulation — the #1 cause of memory leaks in ' +
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
'
|
|
169
|
+
'event-driven apps. Scans for: (1) objects with _events/_listeners properties (Backbone/Node.js), ' +
|
|
170
|
+
'and (2) accumulations of {callback, context}-shaped objects (custom event systems like ' +
|
|
171
|
+
'WAWebEventEmitter, Signal, etc.). Detects arrays with >N entries (accumulation), entries ' +
|
|
172
|
+
'where the context/ctx object has no other referrers (zombie listeners), and multiple entries ' +
|
|
173
|
+
'with the same callback (mount/unmount leaks). ' +
|
|
174
|
+
'Use extra_event_properties to detect app-specific event container property names ' +
|
|
175
|
+
'(e.g., "#eventMap" for private class fields). ' +
|
|
176
|
+
'⚠ Full-heap scan — slow on very large heaps (millions of nodes).', {
|
|
125
177
|
min_listeners: z
|
|
126
178
|
.number()
|
|
127
179
|
.optional()
|
|
@@ -137,10 +189,35 @@ export function registerEventListenerLeaks(server) {
|
|
|
137
189
|
.optional()
|
|
138
190
|
.default(true)
|
|
139
191
|
.describe('Check for zombie listeners whose context objects have no other referrers (default true). Can be slow on very large snapshots.'),
|
|
140
|
-
|
|
192
|
+
extra_event_properties: z
|
|
193
|
+
.array(z.string())
|
|
194
|
+
.optional()
|
|
195
|
+
.describe('Additional property names to scan as event containers (e.g., ["#eventMap", "#listeningTo"] for private class fields). ' +
|
|
196
|
+
'These are checked in addition to the built-in patterns (_events, _listeners, eventMap, etc.).'),
|
|
197
|
+
detect_shapes: z
|
|
198
|
+
.boolean()
|
|
199
|
+
.optional()
|
|
200
|
+
.default(true)
|
|
201
|
+
.describe('Also detect accumulations of {callback, context}-shaped objects regardless of parent property naming (default true). ' +
|
|
202
|
+
"Catches custom event systems that don't use standard _events/_listeners patterns."),
|
|
203
|
+
analyze_contexts: z
|
|
204
|
+
.boolean()
|
|
205
|
+
.optional()
|
|
206
|
+
.default(true)
|
|
207
|
+
.describe('For each accumulation, analyze the distribution of unique callbacks vs contexts, ' +
|
|
208
|
+
'group context objects by shape/class, and detect orphaned contexts (default true). ' +
|
|
209
|
+
'This is the key signal for identifying listener-based memory leaks where many ' +
|
|
210
|
+
'orphaned objects register on the same event.'),
|
|
211
|
+
}, async ({ min_listeners, limit, check_zombies, extra_event_properties, detect_shapes, analyze_contexts, }) => {
|
|
141
212
|
try {
|
|
142
213
|
const snapshot = getSnapshot();
|
|
143
214
|
const accumulations = [];
|
|
215
|
+
const effectiveEventProps = new Set(EVENT_PROPERTY_NAMES);
|
|
216
|
+
if (extra_event_properties) {
|
|
217
|
+
for (const p of extra_event_properties) {
|
|
218
|
+
effectiveEventProps.add(p);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
144
221
|
snapshot.nodes.forEach(node => {
|
|
145
222
|
if (node.id <= 3)
|
|
146
223
|
return;
|
|
@@ -148,7 +225,7 @@ export function registerEventListenerLeaks(server) {
|
|
|
148
225
|
return;
|
|
149
226
|
for (const edge of node.references) {
|
|
150
227
|
const eName = String(edge.name_or_index);
|
|
151
|
-
if (!
|
|
228
|
+
if (!effectiveEventProps.has(eName))
|
|
152
229
|
continue;
|
|
153
230
|
if (edge.toNode.id <= 3)
|
|
154
231
|
continue;
|
|
@@ -161,6 +238,9 @@ export function registerEventListenerLeaks(server) {
|
|
|
161
238
|
? countZombies(allListeners, snapshot)
|
|
162
239
|
: 0;
|
|
163
240
|
const duplicateCallbackCount = countDuplicateCallbacks(allListeners);
|
|
241
|
+
const contextDist = analyze_contexts
|
|
242
|
+
? analyzeContextDistribution(allListeners, snapshot)
|
|
243
|
+
: undefined;
|
|
164
244
|
const entry = {
|
|
165
245
|
hostNodeId: node.id,
|
|
166
246
|
hostName: node.name,
|
|
@@ -179,6 +259,7 @@ export function registerEventListenerLeaks(server) {
|
|
|
179
259
|
zombieCount,
|
|
180
260
|
duplicateCallbackCount,
|
|
181
261
|
totalRetainedSize: node.retainedSize,
|
|
262
|
+
contextDistribution: contextDist,
|
|
182
263
|
};
|
|
183
264
|
let inserted = false;
|
|
184
265
|
for (let i = 0; i < accumulations.length; i++) {
|
|
@@ -195,14 +276,129 @@ export function registerEventListenerLeaks(server) {
|
|
|
195
276
|
break;
|
|
196
277
|
}
|
|
197
278
|
});
|
|
279
|
+
// Shape-based detection: find accumulations of {callback, context}-shaped objects
|
|
280
|
+
if (detect_shapes) {
|
|
281
|
+
const CALLBACK_PROPS = new Set([
|
|
282
|
+
'callback',
|
|
283
|
+
'fn',
|
|
284
|
+
'handler',
|
|
285
|
+
'listener',
|
|
286
|
+
]);
|
|
287
|
+
const CONTEXT_PROPS = new Set([
|
|
288
|
+
'context',
|
|
289
|
+
'ctx',
|
|
290
|
+
'this',
|
|
291
|
+
'target',
|
|
292
|
+
'scope',
|
|
293
|
+
]);
|
|
294
|
+
const shapeMap = new Map();
|
|
295
|
+
snapshot.nodes.forEach(node => {
|
|
296
|
+
if (node.id <= 3)
|
|
297
|
+
return;
|
|
298
|
+
if (node.type !== 'object' || node.name !== 'Object')
|
|
299
|
+
return;
|
|
300
|
+
const propNames = [];
|
|
301
|
+
let hasCallback = false;
|
|
302
|
+
let hasContext = false;
|
|
303
|
+
let callbackId = 0;
|
|
304
|
+
let callbackName = '';
|
|
305
|
+
let contextId = null;
|
|
306
|
+
let contextName = null;
|
|
307
|
+
for (const edge of node.references) {
|
|
308
|
+
if (edge.type !== 'property')
|
|
309
|
+
continue;
|
|
310
|
+
const pName = String(edge.name_or_index);
|
|
311
|
+
propNames.push(pName);
|
|
312
|
+
if (CALLBACK_PROPS.has(pName)) {
|
|
313
|
+
hasCallback = true;
|
|
314
|
+
callbackId = edge.toNode.id;
|
|
315
|
+
callbackName = edge.toNode.name;
|
|
316
|
+
}
|
|
317
|
+
if (CONTEXT_PROPS.has(pName)) {
|
|
318
|
+
hasContext = true;
|
|
319
|
+
contextId = edge.toNode.id;
|
|
320
|
+
contextName = edge.toNode.name;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (!hasCallback || !hasContext)
|
|
324
|
+
return;
|
|
325
|
+
if (propNames.length === 0 || propNames.length > 10)
|
|
326
|
+
return;
|
|
327
|
+
propNames.sort();
|
|
328
|
+
const key = propNames.join(',');
|
|
329
|
+
const existing = shapeMap.get(key);
|
|
330
|
+
if (existing) {
|
|
331
|
+
existing.count++;
|
|
332
|
+
existing.totalRetained += node.retainedSize;
|
|
333
|
+
existing.allListeners.push({
|
|
334
|
+
callbackId,
|
|
335
|
+
callbackName,
|
|
336
|
+
contextId,
|
|
337
|
+
contextName,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
shapeMap.set(key, {
|
|
342
|
+
count: 1,
|
|
343
|
+
totalRetained: node.retainedSize,
|
|
344
|
+
properties: propNames,
|
|
345
|
+
exampleId: node.id,
|
|
346
|
+
allListeners: [
|
|
347
|
+
{ callbackId, callbackName, contextId, contextName },
|
|
348
|
+
],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
for (const [, shape] of shapeMap) {
|
|
353
|
+
if (shape.count < min_listeners)
|
|
354
|
+
continue;
|
|
355
|
+
const contextDist = analyze_contexts
|
|
356
|
+
? analyzeContextDistribution(shape.allListeners, snapshot)
|
|
357
|
+
: undefined;
|
|
358
|
+
const entry = {
|
|
359
|
+
hostNodeId: shape.exampleId,
|
|
360
|
+
hostName: `{${shape.properties.join(', ')}}`,
|
|
361
|
+
hostType: 'shape',
|
|
362
|
+
eventPropertyName: `(${shape.properties.join(', ')})`,
|
|
363
|
+
eventContainerId: shape.exampleId,
|
|
364
|
+
totalListeners: shape.count,
|
|
365
|
+
eventBreakdown: [
|
|
366
|
+
{
|
|
367
|
+
eventName: '(shape-based)',
|
|
368
|
+
listenerCount: shape.count,
|
|
369
|
+
sampleListeners: shape.allListeners.slice(0, 3),
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
zombieCount: 0,
|
|
373
|
+
duplicateCallbackCount: countDuplicateCallbacks(shape.allListeners),
|
|
374
|
+
totalRetainedSize: shape.totalRetained,
|
|
375
|
+
contextDistribution: contextDist,
|
|
376
|
+
};
|
|
377
|
+
let inserted = false;
|
|
378
|
+
for (let i = 0; i < accumulations.length; i++) {
|
|
379
|
+
if (entry.totalListeners > accumulations[i].totalListeners) {
|
|
380
|
+
accumulations.splice(i, 0, entry);
|
|
381
|
+
inserted = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (!inserted)
|
|
386
|
+
accumulations.push(entry);
|
|
387
|
+
if (accumulations.length > limit)
|
|
388
|
+
accumulations.length = limit;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
198
391
|
if (accumulations.length === 0) {
|
|
199
392
|
return toolResult(`No event listener accumulation found with >= ${min_listeners} listeners. ` +
|
|
200
|
-
`Try lowering min_listeners or check \`memlab_find_by_property\` with property_name="
|
|
393
|
+
`Try lowering min_listeners, or check \`memlab_find_by_property\` with property_name="callback", ` +
|
|
394
|
+
`or use \`memlab_shape_histogram\` with sort_by="count" to find high-count shapes.`);
|
|
201
395
|
}
|
|
202
396
|
const lines = [
|
|
203
397
|
`## Event Listener Leak Detection`,
|
|
204
398
|
`Found ${accumulations.length} object(s) with potential listener accumulation`,
|
|
205
399
|
'',
|
|
400
|
+
'_"Duplicates" = the same callback instance bound to the same context, registered more than once (by node id, not by name/shape). One listener per distinct model/host is structural, not a leak._',
|
|
401
|
+
'',
|
|
206
402
|
];
|
|
207
403
|
const headers = [
|
|
208
404
|
'Host',
|
|
@@ -233,7 +429,33 @@ export function registerEventListenerLeaks(server) {
|
|
|
233
429
|
lines.push('');
|
|
234
430
|
}
|
|
235
431
|
if (a.duplicateCallbackCount > 0) {
|
|
236
|
-
lines.push(`**${formatNumber(a.duplicateCallbackCount)} duplicate callback(s)** — same callback registered
|
|
432
|
+
lines.push(`**${formatNumber(a.duplicateCallbackCount)} duplicate callback(s)** — the same callback *instance* bound to the same context registered more than once (classic mount/unmount leak). One listener per distinct model is structural, not a duplicate.`);
|
|
433
|
+
lines.push('');
|
|
434
|
+
}
|
|
435
|
+
if (a.contextDistribution) {
|
|
436
|
+
const cd = a.contextDistribution;
|
|
437
|
+
lines.push('**Context distribution:**');
|
|
438
|
+
lines.push(`- ${formatNumber(cd.uniqueCallbacks)} unique callback(s) across ${formatNumber(cd.totalListeners)} listeners`);
|
|
439
|
+
lines.push(`- ${formatNumber(cd.uniqueContexts)} unique context object(s) across ${formatNumber(cd.totalListeners)} listeners`);
|
|
440
|
+
if (cd.orphanedContextCount > 0) {
|
|
441
|
+
const orphanPct = cd.uniqueContexts > 0
|
|
442
|
+
? ` (${((cd.orphanedContextCount / cd.uniqueContexts) * 100).toFixed(0)}%)`
|
|
443
|
+
: '';
|
|
444
|
+
lines.push(`- **${formatNumber(cd.orphanedContextCount)} orphaned context(s)**${orphanPct} — only retained by listener registrations`);
|
|
445
|
+
}
|
|
446
|
+
if (cd.contextShapes.length > 0) {
|
|
447
|
+
lines.push('- Context shapes:');
|
|
448
|
+
for (const cs of cd.contextShapes) {
|
|
449
|
+
lines.push(` - ${formatNumber(cs.count)}× \`${cs.shapeName}\` (example: @${cs.exampleId})`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (cd.uniqueCallbacks <= 3 &&
|
|
453
|
+
cd.uniqueContexts > 10 &&
|
|
454
|
+
cd.orphanedContextCount > cd.uniqueContexts * 0.5) {
|
|
455
|
+
lines.push('');
|
|
456
|
+
lines.push(`**⚠ LEAK PATTERN:** Few callbacks (${cd.uniqueCallbacks}) but many orphaned contexts (${formatNumber(cd.orphanedContextCount)}) — ` +
|
|
457
|
+
`classic listener-based memory leak where orphaned objects register on shared models but are never unregistered.`);
|
|
458
|
+
}
|
|
237
459
|
lines.push('');
|
|
238
460
|
}
|
|
239
461
|
lines.push('Event breakdown:');
|