@memlab/mcp-server 2.18.1 → 2.86.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 +30 -16
- package/bin/.eslintrc.js +26 -0
- package/bin/memlab-cli.js +313 -0
- package/dist/anonymized-snapshot.d.ts +75 -0
- package/dist/anonymized-snapshot.d.ts.map +1 -0
- package/dist/anonymized-snapshot.js +101 -0
- package/dist/anonymized-snapshot.js.map +1 -0
- package/dist/artifact-classes.d.ts +86 -0
- package/dist/artifact-classes.d.ts.map +1 -0
- package/dist/artifact-classes.js +261 -0
- package/dist/artifact-classes.js.map +1 -0
- package/dist/capture-mode.d.ts +60 -0
- package/dist/capture-mode.d.ts.map +1 -0
- package/dist/capture-mode.js +39 -0
- package/dist/capture-mode.js.map +1 -0
- package/dist/eval-hints.d.ts +41 -0
- package/dist/eval-hints.d.ts.map +1 -0
- package/dist/eval-hints.js +115 -0
- package/dist/eval-hints.js.map +1 -0
- package/dist/guardrail.d.ts.map +1 -1
- package/dist/guardrail.js +13 -0
- package/dist/guardrail.js.map +1 -1
- package/dist/heap-budget.d.ts +32 -0
- package/dist/heap-budget.d.ts.map +1 -0
- package/dist/heap-budget.js +90 -0
- package/dist/heap-budget.js.map +1 -0
- package/dist/heap-shapes.d.ts +148 -0
- package/dist/heap-shapes.d.ts.map +1 -0
- package/dist/heap-shapes.js +193 -0
- package/dist/heap-shapes.js.map +1 -0
- package/dist/heap-state.d.ts +109 -2
- package/dist/heap-state.d.ts.map +1 -1
- package/dist/heap-state.js +194 -3
- package/dist/heap-state.js.map +1 -1
- package/dist/index.js +102 -1
- package/dist/index.js.map +1 -1
- package/dist/progress.d.ts +25 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +46 -0
- package/dist/progress.js.map +1 -0
- package/dist/react-shapes.d.ts +42 -0
- package/dist/react-shapes.d.ts.map +1 -0
- package/dist/react-shapes.js +149 -0
- package/dist/react-shapes.js.map +1 -0
- package/dist/result-handles.d.ts +11 -0
- package/dist/result-handles.d.ts.map +1 -0
- package/dist/result-handles.js +77 -0
- package/dist/result-handles.js.map +1 -0
- package/dist/server-version.d.ts +19 -0
- package/dist/server-version.d.ts.map +1 -0
- package/dist/server-version.js +19 -0
- package/dist/server-version.js.map +1 -0
- package/dist/snapshot-borrow.d.ts +67 -0
- package/dist/snapshot-borrow.d.ts.map +1 -0
- package/dist/snapshot-borrow.js +113 -0
- package/dist/snapshot-borrow.js.map +1 -0
- package/dist/string-index.d.ts +55 -0
- package/dist/string-index.d.ts.map +1 -0
- package/dist/string-index.js +99 -0
- package/dist/string-index.js.map +1 -0
- package/dist/tool-registry.d.ts +42 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +20 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tools/analyze-run.d.ts +12 -0
- package/dist/tools/analyze-run.d.ts.map +1 -0
- package/dist/tools/analyze-run.js +404 -0
- package/dist/tools/analyze-run.js.map +1 -0
- package/dist/tools/anonymize-snapshot.d.ts +12 -0
- package/dist/tools/anonymize-snapshot.d.ts.map +1 -0
- package/dist/tools/anonymize-snapshot.js +159 -0
- package/dist/tools/anonymize-snapshot.js.map +1 -0
- package/dist/tools/app-config.d.ts +12 -0
- package/dist/tools/app-config.d.ts.map +1 -0
- package/dist/tools/app-config.js +217 -0
- package/dist/tools/app-config.js.map +1 -0
- package/dist/tools/app-heap.d.ts +30 -0
- package/dist/tools/app-heap.d.ts.map +1 -0
- package/dist/tools/app-heap.js +106 -0
- package/dist/tools/app-heap.js.map +1 -0
- package/dist/tools/artifact-budget.d.ts +12 -0
- package/dist/tools/artifact-budget.d.ts.map +1 -0
- package/dist/tools/artifact-budget.js +219 -0
- package/dist/tools/artifact-budget.js.map +1 -0
- package/dist/tools/async-census.d.ts +12 -0
- package/dist/tools/async-census.d.ts.map +1 -0
- package/dist/tools/async-census.js +243 -0
- package/dist/tools/async-census.js.map +1 -0
- package/dist/tools/auto-investigate.d.ts.map +1 -1
- package/dist/tools/auto-investigate.js +54 -5
- package/dist/tools/auto-investigate.js.map +1 -1
- package/dist/tools/batch.d.ts +12 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +196 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/cache-analysis.d.ts +15 -0
- package/dist/tools/cache-analysis.d.ts.map +1 -1
- package/dist/tools/cache-analysis.js +240 -14
- package/dist/tools/cache-analysis.js.map +1 -1
- package/dist/tools/census-diff.d.ts +55 -0
- package/dist/tools/census-diff.d.ts.map +1 -0
- package/dist/tools/census-diff.js +228 -0
- package/dist/tools/census-diff.js.map +1 -0
- package/dist/tools/chain-walk.d.ts +12 -0
- package/dist/tools/chain-walk.d.ts.map +1 -0
- package/dist/tools/chain-walk.js +184 -0
- package/dist/tools/chain-walk.js.map +1 -0
- package/dist/tools/class-histogram.d.ts.map +1 -1
- package/dist/tools/class-histogram.js +14 -3
- package/dist/tools/class-histogram.js.map +1 -1
- package/dist/tools/collection-diff.d.ts +44 -0
- package/dist/tools/collection-diff.d.ts.map +1 -0
- package/dist/tools/collection-diff.js +343 -0
- package/dist/tools/collection-diff.js.map +1 -0
- package/dist/tools/collection-trend.d.ts +55 -0
- package/dist/tools/collection-trend.d.ts.map +1 -0
- package/dist/tools/collection-trend.js +315 -0
- package/dist/tools/collection-trend.js.map +1 -0
- package/dist/tools/detached-dom.d.ts +5 -0
- package/dist/tools/detached-dom.d.ts.map +1 -1
- package/dist/tools/detached-dom.js +368 -22
- package/dist/tools/detached-dom.js.map +1 -1
- package/dist/tools/dev-artifacts.d.ts +62 -6
- package/dist/tools/dev-artifacts.d.ts.map +1 -1
- package/dist/tools/dev-artifacts.js +685 -24
- package/dist/tools/dev-artifacts.js.map +1 -1
- package/dist/tools/diff-snapshots.d.ts.map +1 -1
- package/dist/tools/diff-snapshots.js +18 -2
- package/dist/tools/diff-snapshots.js.map +1 -1
- package/dist/tools/dom-audit.d.ts +37 -0
- package/dist/tools/dom-audit.d.ts.map +1 -0
- package/dist/tools/dom-audit.js +247 -0
- package/dist/tools/dom-audit.js.map +1 -0
- package/dist/tools/dominator-attribution.d.ts +12 -0
- package/dist/tools/dominator-attribution.d.ts.map +1 -0
- package/dist/tools/dominator-attribution.js +189 -0
- package/dist/tools/dominator-attribution.js.map +1 -0
- package/dist/tools/dominator-chain.d.ts +12 -0
- package/dist/tools/dominator-chain.d.ts.map +1 -0
- package/dist/tools/dominator-chain.js +92 -0
- package/dist/tools/dominator-chain.js.map +1 -0
- package/dist/tools/duplicate-objects.d.ts +12 -0
- package/dist/tools/duplicate-objects.d.ts.map +1 -0
- package/dist/tools/duplicate-objects.js +172 -0
- package/dist/tools/duplicate-objects.js.map +1 -0
- package/dist/tools/duplicated-strings.d.ts.map +1 -1
- package/dist/tools/duplicated-strings.js +49 -40
- package/dist/tools/duplicated-strings.js.map +1 -1
- package/dist/tools/eval-across.d.ts +12 -0
- package/dist/tools/eval-across.d.ts.map +1 -0
- package/dist/tools/eval-across.js +135 -0
- package/dist/tools/eval-across.js.map +1 -0
- package/dist/tools/eval.d.ts +79 -0
- package/dist/tools/eval.d.ts.map +1 -1
- package/dist/tools/eval.js +2523 -197
- 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 +42 -17
- package/dist/tools/event-listener-leaks.js.map +1 -1
- package/dist/tools/event-registry.d.ts.map +1 -1
- package/dist/tools/event-registry.js +42 -8
- package/dist/tools/event-registry.js.map +1 -1
- package/dist/tools/explain-delta.d.ts +12 -0
- package/dist/tools/explain-delta.d.ts.map +1 -0
- package/dist/tools/explain-delta.js +221 -0
- package/dist/tools/explain-delta.js.map +1 -0
- package/dist/tools/find-nodes-by-class.d.ts.map +1 -1
- package/dist/tools/find-nodes-by-class.js +112 -13
- package/dist/tools/find-nodes-by-class.js.map +1 -1
- package/dist/tools/finding-index.d.ts +203 -0
- package/dist/tools/finding-index.d.ts.map +1 -0
- package/dist/tools/finding-index.js +566 -0
- package/dist/tools/finding-index.js.map +1 -0
- package/dist/tools/for-each.d.ts.map +1 -1
- package/dist/tools/for-each.js +15 -3
- package/dist/tools/for-each.js.map +1 -1
- package/dist/tools/get-referrers.d.ts.map +1 -1
- package/dist/tools/get-referrers.js +17 -0
- package/dist/tools/get-referrers.js.map +1 -1
- package/dist/tools/get-value.d.ts +16 -0
- package/dist/tools/get-value.d.ts.map +1 -1
- package/dist/tools/get-value.js +96 -2
- package/dist/tools/get-value.js.map +1 -1
- package/dist/tools/hunt-report.d.ts +12 -0
- package/dist/tools/hunt-report.d.ts.map +1 -0
- package/dist/tools/hunt-report.js +186 -0
- package/dist/tools/hunt-report.js.map +1 -0
- package/dist/tools/hypothesis.d.ts +12 -0
- package/dist/tools/hypothesis.d.ts.map +1 -0
- package/dist/tools/hypothesis.js +280 -0
- package/dist/tools/hypothesis.js.map +1 -0
- package/dist/tools/id-space-audit.d.ts +12 -0
- package/dist/tools/id-space-audit.d.ts.map +1 -0
- package/dist/tools/id-space-audit.js +262 -0
- package/dist/tools/id-space-audit.js.map +1 -0
- package/dist/tools/identify.d.ts +12 -0
- package/dist/tools/identify.d.ts.map +1 -0
- package/dist/tools/identify.js +205 -0
- package/dist/tools/identify.js.map +1 -0
- package/dist/tools/identity-diff.d.ts +12 -0
- package/dist/tools/identity-diff.d.ts.map +1 -0
- package/dist/tools/identity-diff.js +188 -0
- package/dist/tools/identity-diff.js.map +1 -0
- package/dist/tools/intern-opportunities.d.ts.map +1 -1
- package/dist/tools/intern-opportunities.js +116 -50
- package/dist/tools/intern-opportunities.js.map +1 -1
- package/dist/tools/ladder-probe.d.ts +65 -0
- package/dist/tools/ladder-probe.d.ts.map +1 -0
- package/dist/tools/ladder-probe.js +445 -0
- package/dist/tools/ladder-probe.js.map +1 -0
- package/dist/tools/ladder.d.ts +33 -0
- package/dist/tools/ladder.d.ts.map +1 -0
- package/dist/tools/ladder.js +266 -0
- package/dist/tools/ladder.js.map +1 -0
- package/dist/tools/largest-objects.d.ts.map +1 -1
- package/dist/tools/largest-objects.js +6 -2
- package/dist/tools/largest-objects.js.map +1 -1
- package/dist/tools/leak-report.d.ts +35 -0
- package/dist/tools/leak-report.d.ts.map +1 -0
- package/dist/tools/leak-report.js +398 -0
- package/dist/tools/leak-report.js.map +1 -0
- package/dist/tools/load-snapshot.d.ts +29 -0
- package/dist/tools/load-snapshot.d.ts.map +1 -1
- package/dist/tools/load-snapshot.js +294 -23
- package/dist/tools/load-snapshot.js.map +1 -1
- package/dist/tools/map-entries.d.ts +12 -0
- package/dist/tools/map-entries.d.ts.map +1 -0
- package/dist/tools/map-entries.js +139 -0
- package/dist/tools/map-entries.js.map +1 -0
- package/dist/tools/match-object.d.ts +12 -0
- package/dist/tools/match-object.d.ts.map +1 -0
- package/dist/tools/match-object.js +165 -0
- package/dist/tools/match-object.js.map +1 -0
- package/dist/tools/metric.d.ts +13 -0
- package/dist/tools/metric.d.ts.map +1 -0
- package/dist/tools/metric.js +185 -0
- package/dist/tools/metric.js.map +1 -0
- package/dist/tools/module-attribution.d.ts +36 -0
- package/dist/tools/module-attribution.d.ts.map +1 -0
- package/dist/tools/module-attribution.js +235 -0
- package/dist/tools/module-attribution.js.map +1 -0
- package/dist/tools/next-measurement.d.ts +26 -0
- package/dist/tools/next-measurement.d.ts.map +1 -0
- package/dist/tools/next-measurement.js +149 -0
- package/dist/tools/next-measurement.js.map +1 -0
- package/dist/tools/object-cost-breakdown.d.ts.map +1 -1
- package/dist/tools/object-cost-breakdown.js +64 -0
- package/dist/tools/object-cost-breakdown.js.map +1 -1
- package/dist/tools/object-shape.d.ts.map +1 -1
- package/dist/tools/object-shape.js +53 -5
- package/dist/tools/object-shape.js.map +1 -1
- package/dist/tools/population-diff.d.ts +57 -0
- package/dist/tools/population-diff.d.ts.map +1 -0
- package/dist/tools/population-diff.js +338 -0
- package/dist/tools/population-diff.js.map +1 -0
- package/dist/tools/population-vs-owners.d.ts +12 -0
- package/dist/tools/population-vs-owners.d.ts.map +1 -0
- package/dist/tools/population-vs-owners.js +183 -0
- package/dist/tools/population-vs-owners.js.map +1 -0
- package/dist/tools/property-names.d.ts +29 -0
- package/dist/tools/property-names.d.ts.map +1 -0
- package/dist/tools/property-names.js +131 -0
- package/dist/tools/property-names.js.map +1 -0
- package/dist/tools/quick-diagnosis.d.ts.map +1 -1
- package/dist/tools/quick-diagnosis.js +7 -1
- package/dist/tools/quick-diagnosis.js.map +1 -1
- package/dist/tools/react-owners.d.ts +12 -0
- package/dist/tools/react-owners.d.ts.map +1 -0
- package/dist/tools/react-owners.js +167 -0
- package/dist/tools/react-owners.js.map +1 -0
- package/dist/tools/react-update-queues.d.ts +12 -0
- package/dist/tools/react-update-queues.d.ts.map +1 -0
- package/dist/tools/react-update-queues.js +173 -0
- package/dist/tools/react-update-queues.js.map +1 -0
- package/dist/tools/replicate.d.ts +49 -0
- package/dist/tools/replicate.d.ts.map +1 -0
- package/dist/tools/replicate.js +241 -0
- package/dist/tools/replicate.js.map +1 -0
- package/dist/tools/retainer-diff.d.ts +12 -0
- package/dist/tools/retainer-diff.d.ts.map +1 -0
- package/dist/tools/retainer-diff.js +168 -0
- package/dist/tools/retainer-diff.js.map +1 -0
- package/dist/tools/retainer-layers.d.ts +23 -0
- package/dist/tools/retainer-layers.d.ts.map +1 -0
- package/dist/tools/retainer-layers.js +179 -0
- package/dist/tools/retainer-layers.js.map +1 -0
- package/dist/tools/retainer-summary.d.ts +18 -0
- package/dist/tools/retainer-summary.d.ts.map +1 -1
- package/dist/tools/retainer-summary.js +210 -51
- 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 +14 -2
- package/dist/tools/retainer-trace.js.map +1 -1
- package/dist/tools/round-audit.d.ts +66 -0
- package/dist/tools/round-audit.d.ts.map +1 -0
- package/dist/tools/round-audit.js +258 -0
- package/dist/tools/round-audit.js.map +1 -0
- package/dist/tools/round-digest.d.ts +12 -0
- package/dist/tools/round-digest.d.ts.map +1 -0
- package/dist/tools/round-digest.js +190 -0
- package/dist/tools/round-digest.js.map +1 -0
- package/dist/tools/script-census.d.ts +12 -0
- package/dist/tools/script-census.d.ts.map +1 -0
- package/dist/tools/script-census.js +182 -0
- package/dist/tools/script-census.js.map +1 -0
- package/dist/tools/search-nodes.d.ts.map +1 -1
- package/dist/tools/search-nodes.js +5 -1
- package/dist/tools/search-nodes.js.map +1 -1
- package/dist/tools/search-strings.d.ts.map +1 -1
- package/dist/tools/search-strings.js +53 -7
- package/dist/tools/search-strings.js.map +1 -1
- package/dist/tools/sequence-analysis.d.ts +53 -0
- package/dist/tools/sequence-analysis.d.ts.map +1 -1
- package/dist/tools/sequence-analysis.js +313 -89
- package/dist/tools/sequence-analysis.js.map +1 -1
- package/dist/tools/server-status.d.ts.map +1 -1
- package/dist/tools/server-status.js +4 -1
- package/dist/tools/server-status.js.map +1 -1
- package/dist/tools/settle-check.d.ts +12 -0
- package/dist/tools/settle-check.d.ts.map +1 -0
- package/dist/tools/settle-check.js +188 -0
- package/dist/tools/settle-check.js.map +1 -0
- package/dist/tools/shape-histogram.d.ts.map +1 -1
- package/dist/tools/shape-histogram.js +9 -2
- 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 +2 -1
- package/dist/tools/sliced-strings.js.map +1 -1
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +24 -6
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/sparse-elements.d.ts +12 -0
- package/dist/tools/sparse-elements.d.ts.map +1 -0
- package/dist/tools/sparse-elements.js +215 -0
- package/dist/tools/sparse-elements.js.map +1 -0
- package/dist/tools/stale-collections.d.ts +17 -0
- package/dist/tools/stale-collections.d.ts.map +1 -1
- package/dist/tools/stale-collections.js +28 -0
- package/dist/tools/stale-collections.js.map +1 -1
- package/dist/tools/string-patterns.d.ts.map +1 -1
- package/dist/tools/string-patterns.js +7 -5
- package/dist/tools/string-patterns.js.map +1 -1
- package/dist/tools/tools-index.d.ts +12 -0
- package/dist/tools/tools-index.d.ts.map +1 -0
- package/dist/tools/tools-index.js +492 -0
- package/dist/tools/tools-index.js.map +1 -0
- package/dist/tools/trace-all.d.ts +42 -0
- package/dist/tools/trace-all.d.ts.map +1 -0
- package/dist/tools/trace-all.js +263 -0
- package/dist/tools/trace-all.js.map +1 -0
- package/dist/tools/unit-cost.d.ts +12 -0
- package/dist/tools/unit-cost.d.ts.map +1 -0
- package/dist/tools/unit-cost.js +219 -0
- package/dist/tools/unit-cost.js.map +1 -0
- package/dist/tools/verify-fix.d.ts +23 -0
- package/dist/tools/verify-fix.d.ts.map +1 -0
- package/dist/tools/verify-fix.js +219 -0
- package/dist/tools/verify-fix.js.map +1 -0
- package/dist/tools/weakmap-entries.d.ts.map +1 -1
- package/dist/tools/weakmap-entries.js +6 -1
- package/dist/tools/weakmap-entries.js.map +1 -1
- package/dist/tools/weakref-census.d.ts +15 -0
- package/dist/tools/weakref-census.d.ts.map +1 -0
- package/dist/tools/weakref-census.js +147 -0
- package/dist/tools/weakref-census.js.map +1 -0
- package/dist/tools/what-if.d.ts +12 -0
- package/dist/tools/what-if.d.ts.map +1 -0
- package/dist/tools/what-if.js +285 -0
- package/dist/tools/what-if.js.map +1 -0
- package/dist/utils.d.ts +238 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +507 -15
- package/dist/utils.js.map +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,228 @@
|
|
|
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 memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { armScanBudgetFor, resolveRungs, scaledTimeoutMs, withSnapshotAt, } from '../snapshot-borrow.js';
|
|
12
|
+
import { abbreviateBlinkTypeName, errorResult, formatNumber, markdownTable, pathsHeader, toolResult, } from '../utils.js';
|
|
13
|
+
import { extractElementTag, isDetachedDOMNode, isPinned, } from './detached-dom.js';
|
|
14
|
+
/**
|
|
15
|
+
* A census is a per-CLASS population at one point in the ladder; a census DIFF
|
|
16
|
+
* is the same census taken at two points and compared row by row.
|
|
17
|
+
*
|
|
18
|
+
* This exists because the diff, not the totals, is the evidence. Two totals that
|
|
19
|
+
* match ("777 detached at both ends") are consistent with a leak that swapped
|
|
20
|
+
* one population for another of equal size, which is a real and observed shape.
|
|
21
|
+
* The per-class rows are what turn "no leak" from a guess into a finding — and
|
|
22
|
+
* the negative result is the common case, so the cheap version of it has to be
|
|
23
|
+
* one call rather than a hand-written thirty-line eval per round.
|
|
24
|
+
*/
|
|
25
|
+
const CALLBACK_PROPS = new Set(['callback', 'fn', 'handler', 'listener']);
|
|
26
|
+
const CONTEXT_PROPS = new Set(['context', 'ctx', 'this', 'target']);
|
|
27
|
+
function emptyCensus() {
|
|
28
|
+
return { counts: new Map(), total: 0 };
|
|
29
|
+
}
|
|
30
|
+
function bump(census, key) {
|
|
31
|
+
census.counts.set(key, (census.counts.get(key) ?? 0) + 1);
|
|
32
|
+
census.total += 1;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Detached DOM, bucketed by element tag.
|
|
36
|
+
*
|
|
37
|
+
* Only PINNED nodes are counted, matching `memlab_detached_dom`. Detached nodes
|
|
38
|
+
* with no retainer path are GC-eligible rather than leaked, and their count
|
|
39
|
+
* swings with whatever the collector happened to have done before the capture —
|
|
40
|
+
* including them makes every diff look noisy and hides the rows that moved.
|
|
41
|
+
*/
|
|
42
|
+
export function censusDetached(snapshot) {
|
|
43
|
+
const census = emptyCensus();
|
|
44
|
+
snapshot.nodes.forEach((node) => {
|
|
45
|
+
if (!isDetachedDOMNode(node) || !isPinned(node))
|
|
46
|
+
return;
|
|
47
|
+
bump(census, extractElementTag(node.name));
|
|
48
|
+
});
|
|
49
|
+
return census;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Listener records — objects carrying both a callback-ish and a context-ish
|
|
53
|
+
* property — bucketed by callback function name.
|
|
54
|
+
*
|
|
55
|
+
* Bucketing by CALLBACK rather than by host is deliberate. A listener leak shows
|
|
56
|
+
* up as the same handler registered N times; the host is usually a single
|
|
57
|
+
* long-lived emitter whose count never moves, so a host-keyed census reports
|
|
58
|
+
* "1 → 1" for exactly the leak it was run to find.
|
|
59
|
+
*/
|
|
60
|
+
export function censusListeners(snapshot) {
|
|
61
|
+
const census = emptyCensus();
|
|
62
|
+
snapshot.nodes.forEach((node) => {
|
|
63
|
+
if (node.type !== 'object' || node.id <= 3)
|
|
64
|
+
return;
|
|
65
|
+
let callbackName = null;
|
|
66
|
+
let hasContext = false;
|
|
67
|
+
for (const edge of node.references) {
|
|
68
|
+
const name = String(edge.name_or_index);
|
|
69
|
+
if (CALLBACK_PROPS.has(name) && edge.toNode.type === 'closure') {
|
|
70
|
+
callbackName =
|
|
71
|
+
edge.toNode.name.length > 0 ? edge.toNode.name : '(anonymous)';
|
|
72
|
+
}
|
|
73
|
+
else if (CONTEXT_PROPS.has(name)) {
|
|
74
|
+
hasContext = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (callbackName != null && hasContext) {
|
|
78
|
+
bump(census, callbackName);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return census;
|
|
82
|
+
}
|
|
83
|
+
const MAX_KEY_LEN = 60;
|
|
84
|
+
/**
|
|
85
|
+
* Keep a census row readable without losing which row it is.
|
|
86
|
+
*
|
|
87
|
+
* Blink's C++ template names (`blink::BasicHeapVector<...>`) run to hundreds of
|
|
88
|
+
* characters and a table of them costs more tokens than the counts it exists to
|
|
89
|
+
* show — one census eval blew the result cap on exactly this. Elide the type
|
|
90
|
+
* arguments first, since the head of the name is the identifying part, and only
|
|
91
|
+
* hard-truncate if it is still too long.
|
|
92
|
+
*/
|
|
93
|
+
export function clampKey(key) {
|
|
94
|
+
const abbreviated = abbreviateBlinkTypeName(key);
|
|
95
|
+
return abbreviated.length > MAX_KEY_LEN
|
|
96
|
+
? `${abbreviated.slice(0, MAX_KEY_LEN - 1)}\u2026`
|
|
97
|
+
: abbreviated;
|
|
98
|
+
}
|
|
99
|
+
export function diffCensus(baseline, target) {
|
|
100
|
+
const keys = new Set([...baseline.counts.keys(), ...target.counts.keys()]);
|
|
101
|
+
const rows = [];
|
|
102
|
+
for (const key of keys) {
|
|
103
|
+
const b = baseline.counts.get(key) ?? 0;
|
|
104
|
+
const t = target.counts.get(key) ?? 0;
|
|
105
|
+
rows.push({ key, baseline: b, target: t, delta: t - b });
|
|
106
|
+
}
|
|
107
|
+
// Largest movers first in both directions: a class that SHRANK by 200 is as
|
|
108
|
+
// informative as one that grew by 200, and sorting on the signed delta would
|
|
109
|
+
// bury it at the bottom.
|
|
110
|
+
rows.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta) || b.target - a.target);
|
|
111
|
+
return rows;
|
|
112
|
+
}
|
|
113
|
+
function renderKind(kind, baseline, target, topN) {
|
|
114
|
+
const rows = diffCensus(baseline, target);
|
|
115
|
+
const moved = rows.filter(r => r.delta !== 0);
|
|
116
|
+
const label = kind === 'detached' ? 'Detached DOM (pinned)' : 'Listener records';
|
|
117
|
+
const keyHeader = kind === 'detached' ? 'Element' : 'Callback';
|
|
118
|
+
const lines = [];
|
|
119
|
+
lines.push(`### ${label}`);
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push(`Totals: **${formatNumber(baseline.total)} → ${formatNumber(target.total)}** ` +
|
|
122
|
+
`(Δ ${target.total - baseline.total >= 0 ? '+' : ''}${formatNumber(target.total - baseline.total)}) ` +
|
|
123
|
+
`across ${formatNumber(rows.length)} distinct ${kind === 'detached' ? 'element type(s)' : 'callback(s)'}.`);
|
|
124
|
+
lines.push('');
|
|
125
|
+
if (moved.length === 0) {
|
|
126
|
+
lines.push(`**IDENTICAL** — every one of the ${formatNumber(rows.length)} rows is unchanged. ` +
|
|
127
|
+
'This is the strong form of a negative result: not merely equal totals, but no ' +
|
|
128
|
+
'population swapped for another of the same size.');
|
|
129
|
+
return lines;
|
|
130
|
+
}
|
|
131
|
+
lines.push(markdownTable([keyHeader, 'Baseline', 'Target', 'Δ'], moved
|
|
132
|
+
.slice(0, topN)
|
|
133
|
+
.map(r => [
|
|
134
|
+
clampKey(r.key),
|
|
135
|
+
formatNumber(r.baseline),
|
|
136
|
+
formatNumber(r.target),
|
|
137
|
+
`${r.delta >= 0 ? '+' : ''}${formatNumber(r.delta)}`,
|
|
138
|
+
]), new Set([1, 2, 3])));
|
|
139
|
+
if (moved.length > topN) {
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push(`_… ${formatNumber(moved.length - topN)} further changed row(s) omitted; raise \`top_n\` to see them._`);
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
lines.push(`${formatNumber(moved.length)} of ${formatNumber(rows.length)} rows changed; ` +
|
|
145
|
+
`${formatNumber(rows.length - moved.length)} identical.`);
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
148
|
+
export function registerCensusDiff(server) {
|
|
149
|
+
server.tool('memlab_census_diff', 'Take the detached-DOM and listener-record census at TWO rungs and diff them per class / per callback in one call. ' +
|
|
150
|
+
'This is the mandated baseline-vs-final comparison: run the census on BOTH ends and compare the per-class distribution, not just the two totals.\n\n' +
|
|
151
|
+
'Two totals that match are NOT a negative result — they are also consistent with one population being swapped for another of the same size, which is a real observed shape. ' +
|
|
152
|
+
'The per-row diff is what makes the negative claim: "777 nodes across 66 classes, zero change on every one" is a finding; "777 at both ends" is a guess.\n\n' +
|
|
153
|
+
'Replaces the ~30-line eval that otherwise gets rewritten every round (build {class -> count} on each rung, diff the maps by hand), and loads one graph at a time so it works on a ladder of 250-540 MB captures.', {
|
|
154
|
+
baseline: z
|
|
155
|
+
.string()
|
|
156
|
+
.describe('Path to the EARLY rung. Local path, manifold:// URL, or bare filename.'),
|
|
157
|
+
target: z
|
|
158
|
+
.string()
|
|
159
|
+
.describe('Path to the LATE rung, compared against the baseline.'),
|
|
160
|
+
kinds: z
|
|
161
|
+
.array(z.enum(['detached', 'listeners']))
|
|
162
|
+
.optional()
|
|
163
|
+
.default(['detached', 'listeners'])
|
|
164
|
+
.describe('Which censuses to take. Both by default, which is what the protocol asks for.'),
|
|
165
|
+
top_n: z
|
|
166
|
+
.number()
|
|
167
|
+
.int()
|
|
168
|
+
.min(1)
|
|
169
|
+
.optional()
|
|
170
|
+
.default(25)
|
|
171
|
+
.describe('Maximum CHANGED rows to print per kind. Unchanged rows are counted, never listed.'),
|
|
172
|
+
max_file_size_mb: z
|
|
173
|
+
.number()
|
|
174
|
+
.optional()
|
|
175
|
+
.describe('Per-file size ceiling, matching memlab_load_snapshot.'),
|
|
176
|
+
}, async ({ baseline, target, kinds, top_n, max_file_size_mb }) => {
|
|
177
|
+
try {
|
|
178
|
+
const { rungs, largestMB } = resolveRungs([baseline, target], max_file_size_mb);
|
|
179
|
+
const [baseRung, targetRung] = rungs;
|
|
180
|
+
// Both censuses are full-heap walks, so they run against the same 90s
|
|
181
|
+
// scan guardrail every other tool does. Scale it from the largest
|
|
182
|
+
// capture or a big ladder loses a rung to the guardrail rather than to
|
|
183
|
+
// anything about the heap.
|
|
184
|
+
const scanBudgetMs = scaledTimeoutMs(largestMB);
|
|
185
|
+
const take = (snapshot) => {
|
|
186
|
+
const out = {
|
|
187
|
+
detached: emptyCensus(),
|
|
188
|
+
listeners: emptyCensus(),
|
|
189
|
+
};
|
|
190
|
+
// One pass per kind, but only for the kinds asked for: the listener
|
|
191
|
+
// walk visits every object's edges and is the expensive half.
|
|
192
|
+
if (kinds.includes('detached'))
|
|
193
|
+
out.detached = censusDetached(snapshot);
|
|
194
|
+
if (kinds.includes('listeners'))
|
|
195
|
+
out.listeners = censusListeners(snapshot);
|
|
196
|
+
return out;
|
|
197
|
+
};
|
|
198
|
+
armScanBudgetFor(scanBudgetMs);
|
|
199
|
+
const baseCensus = await withSnapshotAt(baseRung.localPath, take);
|
|
200
|
+
armScanBudgetFor(scanBudgetMs);
|
|
201
|
+
const targetCensus = await withSnapshotAt(targetRung.localPath, take);
|
|
202
|
+
const lines = [];
|
|
203
|
+
lines.push('## Census diff');
|
|
204
|
+
lines.push('');
|
|
205
|
+
lines.push(`- baseline: \`${baseRung.label}\``);
|
|
206
|
+
lines.push(`- target: \`${targetRung.label}\``);
|
|
207
|
+
lines.push('');
|
|
208
|
+
let anyMoved = false;
|
|
209
|
+
for (const kind of kinds) {
|
|
210
|
+
const rows = diffCensus(baseCensus[kind], targetCensus[kind]);
|
|
211
|
+
if (rows.some(r => r.delta !== 0))
|
|
212
|
+
anyMoved = true;
|
|
213
|
+
lines.push(...renderKind(kind, baseCensus[kind], targetCensus[kind], top_n));
|
|
214
|
+
lines.push('');
|
|
215
|
+
}
|
|
216
|
+
lines.push(anyMoved
|
|
217
|
+
? '_A changed row is a population, not yet a leak. Trace a sample with `memlab_retainer_trace` and check ' +
|
|
218
|
+
'`memlab_dev_artifacts` before attributing it — React DEV and Fast Refresh populations grow across a ladder too._'
|
|
219
|
+
: '_Nothing moved in either census. Record this: an unchanged per-class distribution across the ladder is the ' +
|
|
220
|
+
'strongest cheap evidence that a suspected leak is not in the DOM or listener population at all._');
|
|
221
|
+
return toolResult(lines.join('\n'), pathsHeader(rungs.map(r => r.label)));
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
return errorResult(e instanceof Error ? e : new Error(String(e)));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=census-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"census-diff.js","sourceRoot":"","sources":["../../src/tools/census-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
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 memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
+
export declare function registerChainWalk(server: McpServer): void;
|
|
12
|
+
//# sourceMappingURL=chain-walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-walk.d.ts","sourceRoot":"","sources":["../../src/tools/chain-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAgCvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4NzD"}
|
|
@@ -0,0 +1,184 @@
|
|
|
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 memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { getSnapshot } from '../heap-state.js';
|
|
12
|
+
import { toolResult, formatBytes, formatNumber } from '../utils.js';
|
|
13
|
+
export function registerChainWalk(server) {
|
|
14
|
+
server.tool('memlab_chain_walk', 'Walk a repeated linked structure from a starting node and report how deep it goes and what it holds — for closure chains (`begin` -> `context` -> `.prev` -> ...), React update queues (`.next`), LRU lists (`.next`/`.prev`), and range/interval trees (`.l`/`.r`). ' +
|
|
15
|
+
'Exists because these get hand-written as evals, and the hand-written version reliably miscounts: it counts how many TIMES an object is captured rather than how many DISTINCT objects are captured, so a chain that pins the same element at several links reads as that many separate leaks. Measured case: a hand-rolled walk reported 6 distinct detached elements for a 6-link chain that pinned 2. ' +
|
|
16
|
+
'This returns distinct sets by construction and reports the occurrence count alongside only when the two differ. Cycles are detected and reported rather than walked forever. ' +
|
|
17
|
+
'Use it after a retainer trace shows the same edge name repeating — that repetition is the signal that a chain, not a single object, is the finding.', {
|
|
18
|
+
start_id: z
|
|
19
|
+
.number()
|
|
20
|
+
.describe('Node id to start from (the object holding the chain head).'),
|
|
21
|
+
head_edge: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Optional edge to step through first, from `start_id` to the first link (e.g. "begin"). Omit if `start_id` IS the first link.'),
|
|
25
|
+
next_edges: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.min(1)
|
|
28
|
+
.describe('Edge name(s) that advance one link. One name walks a list ("next", "prev", "p"); several walk a tree ("l", "r"), in which case every branch is followed and `depth` is the maximum depth reached.'),
|
|
29
|
+
capture_edges: z
|
|
30
|
+
.array(z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.default([])
|
|
33
|
+
.describe('Edges to record at each link — what the chain is holding onto. Omit to record every named property of each link, which is the right default when you do not yet know what it captures.'),
|
|
34
|
+
max_links: z
|
|
35
|
+
.number()
|
|
36
|
+
.optional()
|
|
37
|
+
.default(10000)
|
|
38
|
+
.describe('Safety bound on links visited (default 10000). A chain longer than this is itself the finding; the result says it was truncated.'),
|
|
39
|
+
show_links: z
|
|
40
|
+
.number()
|
|
41
|
+
.optional()
|
|
42
|
+
.default(3)
|
|
43
|
+
.describe('How many individual links to detail (default 3).'),
|
|
44
|
+
}, async ({ start_id, head_edge, next_edges, capture_edges, max_links, show_links, }) => {
|
|
45
|
+
const snapshot = getSnapshot();
|
|
46
|
+
if (!snapshot) {
|
|
47
|
+
return toolResult('Error: No heap snapshot loaded. Use memlab_load_snapshot first.');
|
|
48
|
+
}
|
|
49
|
+
const start = snapshot.getNodeById(start_id);
|
|
50
|
+
if (!start) {
|
|
51
|
+
return toolResult(`Error: node @${start_id} not found.`);
|
|
52
|
+
}
|
|
53
|
+
const edgeTarget = (node, name) => {
|
|
54
|
+
for (const e of node.references) {
|
|
55
|
+
if (String(e.name_or_index) === name)
|
|
56
|
+
return e.toNode;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
let head = start;
|
|
61
|
+
if (head_edge != null && head_edge !== '') {
|
|
62
|
+
head = edgeTarget(start, head_edge);
|
|
63
|
+
if (!head) {
|
|
64
|
+
return toolResult(`Error: @${start_id} has no edge \`${head_edge}\`. Check the edge name with memlab_object_shape({node_id: ${start_id}}).`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
const links = [];
|
|
69
|
+
// Distinct captured objects across the WHOLE chain — the number that is
|
|
70
|
+
// usually wanted and usually got wrong.
|
|
71
|
+
const distinctCaptured = new Map();
|
|
72
|
+
let occurrences = 0;
|
|
73
|
+
let cyclic = false;
|
|
74
|
+
let truncated = false;
|
|
75
|
+
let maxDepth = 0;
|
|
76
|
+
const stack = [
|
|
77
|
+
{ node: head, depth: 1 },
|
|
78
|
+
];
|
|
79
|
+
while (stack.length > 0) {
|
|
80
|
+
const entry = stack.pop();
|
|
81
|
+
if (!entry)
|
|
82
|
+
break;
|
|
83
|
+
const { node, depth } = entry;
|
|
84
|
+
if (seen.has(node.id)) {
|
|
85
|
+
cyclic = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (seen.size >= max_links) {
|
|
89
|
+
truncated = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
seen.add(node.id);
|
|
93
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
94
|
+
const captured = [];
|
|
95
|
+
for (const e of node.references) {
|
|
96
|
+
const edgeName = String(e.name_or_index);
|
|
97
|
+
if (next_edges.includes(edgeName))
|
|
98
|
+
continue;
|
|
99
|
+
const wanted = capture_edges.length > 0
|
|
100
|
+
? capture_edges.includes(edgeName)
|
|
101
|
+
: e.type === 'property' || e.type === 'context';
|
|
102
|
+
if (!wanted)
|
|
103
|
+
continue;
|
|
104
|
+
const t = e.toNode;
|
|
105
|
+
captured.push({
|
|
106
|
+
edge: edgeName,
|
|
107
|
+
id: t.id,
|
|
108
|
+
name: t.name,
|
|
109
|
+
detached: Boolean(t.is_detached),
|
|
110
|
+
});
|
|
111
|
+
occurrences++;
|
|
112
|
+
const prev = distinctCaptured.get(t.id);
|
|
113
|
+
if (prev) {
|
|
114
|
+
prev.occurrences++;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
distinctCaptured.set(t.id, {
|
|
118
|
+
name: t.name,
|
|
119
|
+
detached: Boolean(t.is_detached),
|
|
120
|
+
occurrences: 1,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (links.length < show_links) {
|
|
125
|
+
links.push({
|
|
126
|
+
depth,
|
|
127
|
+
nodeId: node.id,
|
|
128
|
+
name: node.name,
|
|
129
|
+
retained: node.retainedSize,
|
|
130
|
+
captured: captured.slice(0, 8),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
for (const edgeName of next_edges) {
|
|
134
|
+
const nxt = edgeTarget(node, edgeName);
|
|
135
|
+
if (nxt)
|
|
136
|
+
stack.push({ node: nxt, depth: depth + 1 });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const detachedDistinct = [...distinctCaptured.values()].filter(c => c.detached).length;
|
|
140
|
+
const out = [
|
|
141
|
+
`## Chain from @${start_id}${head_edge ? ` via \`.${head_edge}\`` : ''}`,
|
|
142
|
+
'',
|
|
143
|
+
`- **Links walked: ${formatNumber(seen.size)}** (max depth ${formatNumber(maxDepth)})`,
|
|
144
|
+
`- Distinct objects captured: **${formatNumber(distinctCaptured.size)}**` +
|
|
145
|
+
(occurrences !== distinctCaptured.size
|
|
146
|
+
? ` — from ${formatNumber(occurrences)} capture occurrences, so the same object is held at several links. Quote the DISTINCT count; the occurrence count is what a hand-written walk reports by mistake.`
|
|
147
|
+
: ''),
|
|
148
|
+
`- Distinct captured objects that are detached DOM: **${formatNumber(detachedDistinct)}**`,
|
|
149
|
+
];
|
|
150
|
+
if (cyclic) {
|
|
151
|
+
out.push('- ⚠ The chain revisits a node it already walked: it is cyclic, not a list. Depth is the number of distinct links, not a loop count.');
|
|
152
|
+
}
|
|
153
|
+
if (truncated) {
|
|
154
|
+
out.push(`- ⚠ Stopped at the ${formatNumber(max_links)}-link bound; the real chain is longer. That length is itself the finding.`);
|
|
155
|
+
}
|
|
156
|
+
if (seen.size === 1) {
|
|
157
|
+
out.push('', `> Only one link: nothing advanced through ${next_edges.map(e => `\`${e}\``).join(' / ')}. Either this is not a chain, or the advancing edge has a different name — check with memlab_object_shape({node_id: ${start_id}}).`);
|
|
158
|
+
}
|
|
159
|
+
if (links.length > 0) {
|
|
160
|
+
out.push('', '### First links', '');
|
|
161
|
+
for (const l of links) {
|
|
162
|
+
out.push(`**Link ${l.depth}** — @${l.nodeId} \`${l.name}\` (${formatBytes(l.retained)} retained)`);
|
|
163
|
+
if (l.captured.length === 0) {
|
|
164
|
+
out.push(' - captures nothing named');
|
|
165
|
+
}
|
|
166
|
+
for (const c of l.captured) {
|
|
167
|
+
out.push(` - \`${c.edge}\` → @${c.id} ${c.name}${c.detached ? ' **[DETACHED]**' : ''}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const top = [...distinctCaptured.entries()]
|
|
172
|
+
.sort((a, b) => b[1].occurrences - a[1].occurrences)
|
|
173
|
+
.slice(0, 8);
|
|
174
|
+
if (top.length > 0) {
|
|
175
|
+
out.push('', '### Distinct captured objects (most-held first)', '');
|
|
176
|
+
for (const [nodeId, info] of top) {
|
|
177
|
+
out.push(`- @${nodeId} \`${info.name}\`${info.detached ? ' **[DETACHED]**' : ''} — held at ${formatNumber(info.occurrences)} link(s)`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
out.push('', "_Depth alone is not a leak: a bounded structure has a depth too. What makes a chain a finding is that it grows per interaction and nothing removes links — check that with `memlab_ladder_probe` over a ladder, using this tool's link count as the metric._");
|
|
181
|
+
return toolResult(out.join('\n'));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=chain-walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-walk.js","sourceRoot":"","sources":["../../src/tools/chain-walk.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class-histogram.d.ts","sourceRoot":"","sources":["../../src/tools/class-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"class-histogram.d.ts","sourceRoot":"","sources":["../../src/tools/class-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAmBvE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4U9D"}
|
|
@@ -12,7 +12,7 @@ import memlabCore from '@memlab/core';
|
|
|
12
12
|
const { NumericSet } = memlabCore;
|
|
13
13
|
import { getSnapshot, getSnapshotMetadata } from '../heap-state.js';
|
|
14
14
|
import { checkAnalysisDeadline } from '../analysis-budget.js';
|
|
15
|
-
import { errorResult, toolResult, formatBytes, formatNumber, markdownTable, truncateNodeName, suggestionsSuppressed, boundedDominatorRetainedSize, } from '../utils.js';
|
|
15
|
+
import { errorResult, toolResult, formatBytes, formatNumber, markdownTable, truncateNodeName, suggestionsSuppressed, boundedDominatorRetainedSize, makeNamePatternTest, } from '../utils.js';
|
|
16
16
|
export function registerClassHistogram(server) {
|
|
17
17
|
server.tool('memlab_class_histogram', 'Show per-constructor/class instance count and total self size. Useful for identifying which types of objects dominate memory. ' +
|
|
18
18
|
'By DEFAULT (`include_retained_size:false`) it returns a counts + self-size histogram in a single O(N) pass with NO dominator walk — fast and safe on snapshots with millions of nodes; rows are sorted by self size. ' +
|
|
@@ -36,6 +36,10 @@ export function registerClassHistogram(server) {
|
|
|
36
36
|
.number()
|
|
37
37
|
.optional()
|
|
38
38
|
.describe('Minimum total self size in bytes to include.'),
|
|
39
|
+
name_pattern: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Filter class names by pattern before aggregating — a case-insensitive regular expression, or a plain substring when the pattern is not valid regex (e.g. "blink::UndoStep", "Detached", "^WA"). Answering "is class X present?" this way costs one row instead of a whole table.'),
|
|
39
43
|
node_type: z
|
|
40
44
|
.string()
|
|
41
45
|
.optional()
|
|
@@ -60,9 +64,12 @@ export function registerClassHistogram(server) {
|
|
|
60
64
|
.optional()
|
|
61
65
|
.default(false)
|
|
62
66
|
.describe('Omit "Suggested next steps" boilerplate from the output to save tokens.'),
|
|
63
|
-
}, async ({ limit, min_count, min_retained_size, min_self_size, node_type, include_retained_size, max_retained_classes, max_dominator_walk, suppress_suggestions, }) => {
|
|
67
|
+
}, async ({ limit, min_count, min_retained_size, min_self_size, name_pattern, node_type, include_retained_size, max_retained_classes, max_dominator_walk, suppress_suggestions, }) => {
|
|
64
68
|
try {
|
|
65
|
-
|
|
69
|
+
// Light-safe only without retained sizes: the retained path walks the
|
|
70
|
+
// dominator tree, which a light load never built.
|
|
71
|
+
const snapshot = getSnapshot({ allowLight: !include_retained_size });
|
|
72
|
+
const nameMatches = makeNamePatternTest(name_pattern);
|
|
66
73
|
const meta = getSnapshotMetadata();
|
|
67
74
|
// Flat strings and numbers are leaf nodes: they dominate nothing but
|
|
68
75
|
// themselves, so instances never overlap on the dominator tree and a
|
|
@@ -79,6 +86,8 @@ export function registerClassHistogram(server) {
|
|
|
79
86
|
snapshot.nodes.forEach(node => {
|
|
80
87
|
if (node_type && node.type !== node_type)
|
|
81
88
|
return;
|
|
89
|
+
if (!nameMatches(node.name))
|
|
90
|
+
return;
|
|
82
91
|
// Skip internal meta nodes
|
|
83
92
|
if (node.id <= 3)
|
|
84
93
|
return;
|
|
@@ -134,6 +143,8 @@ export function registerClassHistogram(server) {
|
|
|
134
143
|
idsByClass.set(key, new NumericSet());
|
|
135
144
|
if (idsByClass.size > 0) {
|
|
136
145
|
snapshot.nodes.forEach(node => {
|
|
146
|
+
if (!nameMatches(node.name))
|
|
147
|
+
return;
|
|
137
148
|
if (node.id <= 3)
|
|
138
149
|
return;
|
|
139
150
|
if (node_type && node.type !== node_type)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class-histogram.js","sourceRoot":"","sources":["../../src/tools/class-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,UAAU,MAAM,cAAc,CAAC;AAEtC,MAAM,EAAC,UAAU,EAAC,GAAG,UAAU,CAAC;AAChC,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAC,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAC,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,4BAA4B,
|
|
1
|
+
{"version":3,"file":"class-histogram.js","sourceRoot":"","sources":["../../src/tools/class-histogram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
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 memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import type { IHeapNode, IHeapSnapshot } from '@memlab/core';
|
|
11
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
/**
|
|
13
|
+
* A per-capture-stable name for one collection, in the SAME `<Owner>.<property>`
|
|
14
|
+
* form `memlab_collection_trend` takes as a locator.
|
|
15
|
+
*
|
|
16
|
+
* That shared shape is the point of this tool: discovery hands you a locator you
|
|
17
|
+
* can paste straight into the trend tool to confirm and to track a fix, instead
|
|
18
|
+
* of a node id that means nothing in the next capture.
|
|
19
|
+
*
|
|
20
|
+
* Node ids, array indices and V8 scope numbers are all per-capture, so none of
|
|
21
|
+
* them can appear in the signature. The walk climbs referrers (not the dominator
|
|
22
|
+
* tree — this runs on a LIGHT parse with no dominators) until it finds a class
|
|
23
|
+
* name worth printing, and falls back to `<context>` for a module-scope closure
|
|
24
|
+
* binding, which is where most module singletons live.
|
|
25
|
+
*/
|
|
26
|
+
export declare function collectionSignature(node: IHeapNode): string | null;
|
|
27
|
+
export interface CollectionStat {
|
|
28
|
+
instances: number;
|
|
29
|
+
entries: number;
|
|
30
|
+
maxEntries: number;
|
|
31
|
+
kinds: Set<string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Census every JS collection in one snapshot, keyed by signature.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately does NOT filter by size. The whole point is to catch the
|
|
37
|
+
* collection that is small now and unbounded later: one of the three
|
|
38
|
+
* never-drained telemetry stores this tool was written for held 2 entries at the
|
|
39
|
+
* baseline rung and 1,260 at the last one, so any size floor would have hidden
|
|
40
|
+
* it. Growth is the filter, and growth needs the ladder.
|
|
41
|
+
*/
|
|
42
|
+
export declare function censusCollections(snapshot: IHeapSnapshot): Map<string, CollectionStat>;
|
|
43
|
+
export declare function registerCollectionDiff(server: McpServer): void;
|
|
44
|
+
//# sourceMappingURL=collection-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-diff.d.ts","sourceRoot":"","sources":["../../src/tools/collection-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAY,SAAS,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AA6DvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAsDlE;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,GACtB,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAoB7B;AAkDD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2K9D"}
|