@memlab/mcp-server 2.18.1 → 2.52.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.
Files changed (186) hide show
  1. package/README.md +8 -14
  2. package/bin/.eslintrc.js +26 -0
  3. package/bin/memlab-cli.js +313 -0
  4. package/dist/artifact-classes.d.ts +86 -0
  5. package/dist/artifact-classes.d.ts.map +1 -0
  6. package/dist/artifact-classes.js +261 -0
  7. package/dist/artifact-classes.js.map +1 -0
  8. package/dist/capture-mode.d.ts +60 -0
  9. package/dist/capture-mode.d.ts.map +1 -0
  10. package/dist/capture-mode.js +39 -0
  11. package/dist/capture-mode.js.map +1 -0
  12. package/dist/guardrail.d.ts.map +1 -1
  13. package/dist/guardrail.js +13 -0
  14. package/dist/guardrail.js.map +1 -1
  15. package/dist/heap-state.d.ts +89 -2
  16. package/dist/heap-state.d.ts.map +1 -1
  17. package/dist/heap-state.js +186 -2
  18. package/dist/heap-state.js.map +1 -1
  19. package/dist/index.js +39 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/progress.d.ts +25 -0
  22. package/dist/progress.d.ts.map +1 -0
  23. package/dist/progress.js +46 -0
  24. package/dist/progress.js.map +1 -0
  25. package/dist/result-handles.d.ts +11 -0
  26. package/dist/result-handles.d.ts.map +1 -0
  27. package/dist/result-handles.js +77 -0
  28. package/dist/result-handles.js.map +1 -0
  29. package/dist/string-index.d.ts +55 -0
  30. package/dist/string-index.d.ts.map +1 -0
  31. package/dist/string-index.js +91 -0
  32. package/dist/string-index.js.map +1 -0
  33. package/dist/tool-registry.d.ts +42 -0
  34. package/dist/tool-registry.d.ts.map +1 -0
  35. package/dist/tool-registry.js +20 -0
  36. package/dist/tool-registry.js.map +1 -0
  37. package/dist/tools/analyze-run.d.ts +12 -0
  38. package/dist/tools/analyze-run.d.ts.map +1 -0
  39. package/dist/tools/analyze-run.js +343 -0
  40. package/dist/tools/analyze-run.js.map +1 -0
  41. package/dist/tools/app-heap.d.ts +12 -0
  42. package/dist/tools/app-heap.d.ts.map +1 -0
  43. package/dist/tools/app-heap.js +140 -0
  44. package/dist/tools/app-heap.js.map +1 -0
  45. package/dist/tools/batch.d.ts +12 -0
  46. package/dist/tools/batch.d.ts.map +1 -0
  47. package/dist/tools/batch.js +168 -0
  48. package/dist/tools/batch.js.map +1 -0
  49. package/dist/tools/cache-analysis.d.ts.map +1 -1
  50. package/dist/tools/cache-analysis.js +156 -5
  51. package/dist/tools/cache-analysis.js.map +1 -1
  52. package/dist/tools/class-histogram.d.ts.map +1 -1
  53. package/dist/tools/class-histogram.js +14 -3
  54. package/dist/tools/class-histogram.js.map +1 -1
  55. package/dist/tools/collection-trend.d.ts +55 -0
  56. package/dist/tools/collection-trend.d.ts.map +1 -0
  57. package/dist/tools/collection-trend.js +315 -0
  58. package/dist/tools/collection-trend.js.map +1 -0
  59. package/dist/tools/detached-dom.d.ts +2 -0
  60. package/dist/tools/detached-dom.d.ts.map +1 -1
  61. package/dist/tools/detached-dom.js +291 -20
  62. package/dist/tools/detached-dom.js.map +1 -1
  63. package/dist/tools/dev-artifacts.d.ts +62 -6
  64. package/dist/tools/dev-artifacts.d.ts.map +1 -1
  65. package/dist/tools/dev-artifacts.js +466 -23
  66. package/dist/tools/dev-artifacts.js.map +1 -1
  67. package/dist/tools/dominator-attribution.d.ts +12 -0
  68. package/dist/tools/dominator-attribution.d.ts.map +1 -0
  69. package/dist/tools/dominator-attribution.js +189 -0
  70. package/dist/tools/dominator-attribution.js.map +1 -0
  71. package/dist/tools/dominator-chain.d.ts +12 -0
  72. package/dist/tools/dominator-chain.d.ts.map +1 -0
  73. package/dist/tools/dominator-chain.js +92 -0
  74. package/dist/tools/dominator-chain.js.map +1 -0
  75. package/dist/tools/duplicate-objects.d.ts +12 -0
  76. package/dist/tools/duplicate-objects.d.ts.map +1 -0
  77. package/dist/tools/duplicate-objects.js +172 -0
  78. package/dist/tools/duplicate-objects.js.map +1 -0
  79. package/dist/tools/duplicated-strings.d.ts.map +1 -1
  80. package/dist/tools/duplicated-strings.js +46 -38
  81. package/dist/tools/duplicated-strings.js.map +1 -1
  82. package/dist/tools/eval.d.ts +1 -0
  83. package/dist/tools/eval.d.ts.map +1 -1
  84. package/dist/tools/eval.js +785 -28
  85. package/dist/tools/eval.js.map +1 -1
  86. package/dist/tools/explain-delta.d.ts +12 -0
  87. package/dist/tools/explain-delta.d.ts.map +1 -0
  88. package/dist/tools/explain-delta.js +170 -0
  89. package/dist/tools/explain-delta.js.map +1 -0
  90. package/dist/tools/find-nodes-by-class.d.ts.map +1 -1
  91. package/dist/tools/find-nodes-by-class.js +112 -13
  92. package/dist/tools/find-nodes-by-class.js.map +1 -1
  93. package/dist/tools/finding-index.d.ts +22 -0
  94. package/dist/tools/finding-index.d.ts.map +1 -0
  95. package/dist/tools/finding-index.js +247 -0
  96. package/dist/tools/finding-index.js.map +1 -0
  97. package/dist/tools/for-each.d.ts.map +1 -1
  98. package/dist/tools/for-each.js +15 -3
  99. package/dist/tools/for-each.js.map +1 -1
  100. package/dist/tools/get-referrers.d.ts.map +1 -1
  101. package/dist/tools/get-referrers.js +17 -0
  102. package/dist/tools/get-referrers.js.map +1 -1
  103. package/dist/tools/hunt-report.d.ts +12 -0
  104. package/dist/tools/hunt-report.d.ts.map +1 -0
  105. package/dist/tools/hunt-report.js +186 -0
  106. package/dist/tools/hunt-report.js.map +1 -0
  107. package/dist/tools/hypothesis.d.ts +12 -0
  108. package/dist/tools/hypothesis.d.ts.map +1 -0
  109. package/dist/tools/hypothesis.js +221 -0
  110. package/dist/tools/hypothesis.js.map +1 -0
  111. package/dist/tools/intern-opportunities.d.ts.map +1 -1
  112. package/dist/tools/intern-opportunities.js +112 -47
  113. package/dist/tools/intern-opportunities.js.map +1 -1
  114. package/dist/tools/ladder.d.ts +33 -0
  115. package/dist/tools/ladder.d.ts.map +1 -0
  116. package/dist/tools/ladder.js +266 -0
  117. package/dist/tools/ladder.js.map +1 -0
  118. package/dist/tools/largest-objects.d.ts.map +1 -1
  119. package/dist/tools/largest-objects.js +6 -2
  120. package/dist/tools/largest-objects.js.map +1 -1
  121. package/dist/tools/leak-report.d.ts +12 -0
  122. package/dist/tools/leak-report.d.ts.map +1 -0
  123. package/dist/tools/leak-report.js +256 -0
  124. package/dist/tools/leak-report.js.map +1 -0
  125. package/dist/tools/load-snapshot.d.ts +18 -0
  126. package/dist/tools/load-snapshot.d.ts.map +1 -1
  127. package/dist/tools/load-snapshot.js +211 -13
  128. package/dist/tools/load-snapshot.js.map +1 -1
  129. package/dist/tools/map-entries.d.ts +12 -0
  130. package/dist/tools/map-entries.d.ts.map +1 -0
  131. package/dist/tools/map-entries.js +139 -0
  132. package/dist/tools/map-entries.js.map +1 -0
  133. package/dist/tools/match-object.d.ts +12 -0
  134. package/dist/tools/match-object.d.ts.map +1 -0
  135. package/dist/tools/match-object.js +165 -0
  136. package/dist/tools/match-object.js.map +1 -0
  137. package/dist/tools/object-shape.d.ts.map +1 -1
  138. package/dist/tools/object-shape.js +53 -5
  139. package/dist/tools/object-shape.js.map +1 -1
  140. package/dist/tools/population-diff.d.ts +57 -0
  141. package/dist/tools/population-diff.d.ts.map +1 -0
  142. package/dist/tools/population-diff.js +338 -0
  143. package/dist/tools/population-diff.js.map +1 -0
  144. package/dist/tools/retainer-layers.d.ts +23 -0
  145. package/dist/tools/retainer-layers.d.ts.map +1 -0
  146. package/dist/tools/retainer-layers.js +179 -0
  147. package/dist/tools/retainer-layers.js.map +1 -0
  148. package/dist/tools/retainer-summary.d.ts +1 -0
  149. package/dist/tools/retainer-summary.d.ts.map +1 -1
  150. package/dist/tools/retainer-summary.js +128 -32
  151. package/dist/tools/retainer-summary.js.map +1 -1
  152. package/dist/tools/retainer-trace.d.ts.map +1 -1
  153. package/dist/tools/retainer-trace.js +4 -2
  154. package/dist/tools/retainer-trace.js.map +1 -1
  155. package/dist/tools/search-nodes.d.ts.map +1 -1
  156. package/dist/tools/search-nodes.js +5 -1
  157. package/dist/tools/search-nodes.js.map +1 -1
  158. package/dist/tools/search-strings.d.ts.map +1 -1
  159. package/dist/tools/search-strings.js +53 -7
  160. package/dist/tools/search-strings.js.map +1 -1
  161. package/dist/tools/sequence-analysis.d.ts +49 -0
  162. package/dist/tools/sequence-analysis.d.ts.map +1 -1
  163. package/dist/tools/sequence-analysis.js +266 -86
  164. package/dist/tools/sequence-analysis.js.map +1 -1
  165. package/dist/tools/shape-histogram.d.ts.map +1 -1
  166. package/dist/tools/shape-histogram.js +9 -2
  167. package/dist/tools/shape-histogram.js.map +1 -1
  168. package/dist/tools/string-patterns.d.ts.map +1 -1
  169. package/dist/tools/string-patterns.js +5 -4
  170. package/dist/tools/string-patterns.js.map +1 -1
  171. package/dist/tools/tools-index.d.ts +12 -0
  172. package/dist/tools/tools-index.d.ts.map +1 -0
  173. package/dist/tools/tools-index.js +342 -0
  174. package/dist/tools/tools-index.js.map +1 -0
  175. package/dist/tools/verify-fix.d.ts +23 -0
  176. package/dist/tools/verify-fix.d.ts.map +1 -0
  177. package/dist/tools/verify-fix.js +211 -0
  178. package/dist/tools/verify-fix.js.map +1 -0
  179. package/dist/tools/weakmap-entries.d.ts.map +1 -1
  180. package/dist/tools/weakmap-entries.js +6 -1
  181. package/dist/tools/weakmap-entries.js.map +1 -1
  182. package/dist/utils.d.ts +146 -1
  183. package/dist/utils.d.ts.map +1 -1
  184. package/dist/utils.js +335 -10
  185. package/dist/utils.js.map +1 -1
  186. package/package.json +5 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-attribution.d.ts","sourceRoot":"","sources":["../../src/tools/dominator-attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAuCvE,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwNpE"}
@@ -0,0 +1,189 @@
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 memlabCore from '@memlab/core';
11
+ import { z } from 'zod';
12
+ import { getSnapshot } from '../heap-state.js';
13
+ import { boundedDominatorRetainedSize, errorResult, formatBytes, formatNumber, markdownTable, toolResult, truncateNodeName, } from '../utils.js';
14
+ import { makeMembershipTest, parsePopulation } from './population-diff.js';
15
+ const { NumericSet } = memlabCore;
16
+ // Bound on the upward walk from a population member to a candidate, mirroring
17
+ // `boundedDominatorRetainedSize`. A member whose walk truncates is reported
18
+ // unattributed rather than guessed at.
19
+ const MAX_HOPS = 500;
20
+ export function registerDominatorAttribution(server) {
21
+ server.tool('memlab_dominator_attribution', 'Given several candidate retainers, measure how much of a population each one actually DOMINATES — i.e. what would really be freed by releasing it. Answers "which of these is the amplifier?" with a number instead of a plausible story. ' +
22
+ 'Each population member is walked up the dominator tree to the first candidate above it, so every member is attributed to exactly one candidate and the shares cannot double-count. Members that reach the root without passing any candidate are reported as an explicit unattributed remainder — those are held through paths none of the candidates control. ' +
23
+ 'When one candidate sits above another, a second column reports what the outer one dominates at any depth, so an ancestor is never printed as holding nothing merely because a nested candidate took its members. ' +
24
+ 'A candidate that comes back with ZERO is the useful result: it refutes a hypothesis that named it. Measured example: for 4,541 detached nodes the split was activeElement 3,675 / activeElementInst 0 / lastSelection 18 / 848 unattributed, which killed the "the React fiber is the amplifier" theory outright. ' +
25
+ 'Sizes are dominator-deduped (a nested member is not counted again under its ancestor), so the per-candidate figures are additive.', {
26
+ node_ids: z
27
+ .array(z.number())
28
+ .describe('Candidate retainer node ids, from the CURRENT snapshot (ids are per-capture). Typically the objects a hypothesis names — e.g. the ids behind the top rows of memlab_detached_dom group_by:"dominator", or the properties of one suspect object.'),
29
+ population: z
30
+ .string()
31
+ .optional()
32
+ .default('detached')
33
+ .describe('What to attribute: "detached" (default), "class:<ClassName>", "shape:prop1,prop2", or "all" (every node in the heap — expensive, and the totals become the whole heap).'),
34
+ limit: z
35
+ .number()
36
+ .optional()
37
+ .default(30)
38
+ .describe('Maximum candidate rows to print (default 30).'),
39
+ }, async ({ node_ids, population, limit }) => {
40
+ try {
41
+ if (node_ids.length === 0) {
42
+ return errorResult('node_ids is empty — pass the candidate retainers to compare.');
43
+ }
44
+ const snapshot = getSnapshot();
45
+ const candidates = new Map();
46
+ const missing = [];
47
+ for (const id of node_ids) {
48
+ const node = snapshot.getNodeById(id);
49
+ if (!node) {
50
+ missing.push(id);
51
+ continue;
52
+ }
53
+ candidates.set(id, {
54
+ id,
55
+ label: truncateNodeName(node.name, node.type, node.self_size, 48),
56
+ ownRetained: node.retainedSize,
57
+ memberCount: 0,
58
+ memberSelf: 0,
59
+ memberIds: [],
60
+ totalCount: 0,
61
+ cededCount: 0,
62
+ });
63
+ }
64
+ if (candidates.size === 0) {
65
+ return errorResult(`None of the ${node_ids.length} node id(s) exist in the current snapshot (ids are only valid within the snapshot they were read from): ${missing.join(', ')}.`);
66
+ }
67
+ const isMember = population.trim() === 'all'
68
+ ? (node) => node.id > 3
69
+ : makeMembershipTest(parsePopulation(population));
70
+ let total = 0;
71
+ let totalSelf = 0;
72
+ let unattributed = 0;
73
+ let unattributedSelf = 0;
74
+ let truncatedWalks = 0;
75
+ snapshot.nodes.forEach(node => {
76
+ if (!isMember(node))
77
+ return;
78
+ total++;
79
+ totalSelf += node.self_size;
80
+ // A candidate that is itself in the population is attributed to
81
+ // itself: it dominates itself, and excluding it would understate a
82
+ // container that IS part of what leaked.
83
+ let cur = candidates.has(node.id)
84
+ ? node
85
+ : (node.dominatorNode ?? null);
86
+ let hops = 0;
87
+ let hit;
88
+ // The walk does NOT stop at the first candidate. Stopping there
89
+ // reports an ancestor candidate as dominating nothing whenever a
90
+ // second candidate happens to sit between it and the members — which
91
+ // reads as "this object holds none of the leak" when the truth is the
92
+ // opposite. Nearest-wins still decides the exclusive attribution; the
93
+ // rest of the chain is recorded as containment.
94
+ while (cur) {
95
+ const c = candidates.get(cur.id);
96
+ if (c) {
97
+ c.totalCount++;
98
+ if (hit === undefined)
99
+ hit = c;
100
+ else
101
+ c.cededCount++;
102
+ }
103
+ if (hops++ >= MAX_HOPS) {
104
+ if (hit === undefined)
105
+ truncatedWalks++;
106
+ break;
107
+ }
108
+ const next = cur.dominatorNode ?? null;
109
+ if (!next || next.id === cur.id)
110
+ break;
111
+ cur = next;
112
+ }
113
+ if (hit) {
114
+ hit.memberCount++;
115
+ hit.memberSelf += node.self_size;
116
+ hit.memberIds.push(node.id);
117
+ }
118
+ else {
119
+ unattributed++;
120
+ unattributedSelf += node.self_size;
121
+ }
122
+ });
123
+ if (total === 0) {
124
+ return toolResult(`No nodes match population \`${population}\` in this snapshot — nothing to attribute.`);
125
+ }
126
+ const rows = [...candidates.values()].sort((a, b) => b.memberCount - a.memberCount || b.totalCount - a.totalCount);
127
+ const shown = rows.slice(0, limit);
128
+ const reclaimable = new Map();
129
+ for (const c of shown) {
130
+ reclaimable.set(c.id, c.memberIds.length > 0
131
+ ? boundedDominatorRetainedSize(new NumericSet(c.memberIds), snapshot)
132
+ : { retained: 0, exact: true });
133
+ }
134
+ const pct = (n) => total > 0 ? `${((n / total) * 100).toFixed(1)}%` : '-';
135
+ const lines = [
136
+ `## Dominator attribution of \`${population}\` across ${formatNumber(candidates.size)} candidate(s)`,
137
+ '',
138
+ `Population: ${formatNumber(total)} node(s), ${formatBytes(totalSelf)} self size.`,
139
+ '',
140
+ markdownTable([
141
+ 'Candidate',
142
+ 'Class',
143
+ 'Attributed',
144
+ '% of pop.',
145
+ 'Dominates (any depth)',
146
+ 'Reclaimable (deduped)',
147
+ 'Candidate retains',
148
+ ], shown.map(c => {
149
+ const r = reclaimable.get(c.id);
150
+ return [
151
+ `@${c.id}`,
152
+ c.label,
153
+ formatNumber(c.memberCount),
154
+ pct(c.memberCount),
155
+ c.cededCount > 0
156
+ ? `${formatNumber(c.totalCount)} (${formatNumber(c.cededCount)} to a nested candidate)`
157
+ : formatNumber(c.totalCount),
158
+ c.memberCount === 0
159
+ ? '—'
160
+ : `${formatBytes(r?.retained ?? 0)}${r?.exact === false ? ' (upper bound)' : ''}`,
161
+ formatBytes(c.ownRetained),
162
+ ];
163
+ }), new Set([2, 3, 4, 5, 6])),
164
+ '',
165
+ `- **Unattributed:** ${formatNumber(unattributed)} (${pct(unattributed)}), ${formatBytes(unattributedSelf)} self — reached the root without passing any candidate, so no candidate here frees them.`,
166
+ ];
167
+ if (rows.length > shown.length) {
168
+ lines.push(`- ${formatNumber(rows.length - shown.length)} further candidate(s) not shown (raise \`limit\`).`);
169
+ }
170
+ if (truncatedWalks > 0) {
171
+ lines.push(`- ⚠ ${formatNumber(truncatedWalks)} member(s) hit the ${MAX_HOPS}-hop walk cap and were counted as unattributed. Their true owner may be a candidate deeper up the chain; treat the unattributed figure as an upper bound.`);
172
+ }
173
+ const zero = shown.filter(c => c.totalCount === 0);
174
+ if (zero.length > 0) {
175
+ lines.push('', `⛔ **${formatNumber(zero.length)} candidate(s) dominate NOTHING in this population, at any depth** (${zero.map(c => `@${c.id} ${c.label}`).join(', ')}). Releasing them frees none of it — any hypothesis that names one of them as the amplifier is refuted by this measurement, not merely unsupported.`);
176
+ }
177
+ const nested = shown.filter(c => c.cededCount > 0);
178
+ if (nested.length > 0) {
179
+ lines.push('', `ℹ ${formatNumber(nested.length)} candidate(s) are ANCESTORS of another candidate (${nested.map(c => `@${c.id}`).join(', ')}), so members below the inner one are attributed there. Read their **Attributed** column as "what this object holds that the nested candidate does not" — a low value there with a high **Dominates** does not mean the object is innocent, it means the nested candidate is the tighter fix.`);
180
+ }
181
+ lines.push('', "_**Attributed** is by NEAREST candidate in the dominator tree, so each member counts once and the column sums to the population minus the unattributed remainder. **Dominates (any depth)** counts every member below the candidate and therefore double-counts across nested candidates — never sum it. **Reclaimable** is dominator-deduped over the attributed members — the bytes actually released — while **Candidate retains** is the candidate's entire retained size, which also covers everything it holds outside this population._");
182
+ return toolResult(lines.join('\n'));
183
+ }
184
+ catch (err) {
185
+ return errorResult(err);
186
+ }
187
+ });
188
+ }
189
+ //# sourceMappingURL=dominator-attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-attribution.js","sourceRoot":"","sources":["../../src/tools/dominator-attribution.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 registerDominatorChain(server: McpServer): void;
12
+ //# sourceMappingURL=dominator-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-chain.d.ts","sourceRoot":"","sources":["../../src/tools/dominator-chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAavE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAgG9D"}
@@ -0,0 +1,92 @@
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 { isNodeWorthInspecting, truncateNodeName, errorResult, toolResult, formatBytes, formatNumber, } from '../utils.js';
13
+ export function registerDominatorChain(server) {
14
+ server.tool('memlab_dominator_chain', 'Walk UPWARD through the immediate-dominator chain from a node to the GC root — the accountability chain of objects that each, if freed, would free the target. Every node on the chain exclusively dominates the target, so the nearest application-owned entry is the single owner to fix. This complements the DOWNWARD memlab_dominator_subtree / memlab_trace_dominators (what a node dominates) and the edge-based memlab_retainer_trace (a shortest reference path, which need not be a dominator). Mirrors the Chrome DevTools "Retained by / dominators" chain. Each step shows retained size and the fan-in (how much extra memory the next dominator is accountable for).', {
15
+ node_id: z
16
+ .number()
17
+ .describe('The numeric ID of the heap node to trace upward from'),
18
+ max_depth: z
19
+ .number()
20
+ .optional()
21
+ .default(30)
22
+ .describe('Maximum number of dominator hops to climb (default 30)'),
23
+ }, async ({ node_id, max_depth }) => {
24
+ try {
25
+ const snapshot = getSnapshot();
26
+ const targetNode = snapshot.getNodeById(node_id);
27
+ if (!targetNode) {
28
+ return errorResult(`Node with id ${node_id} not found`);
29
+ }
30
+ // Climb immediate dominators until the GC root (which dominates itself)
31
+ // or a depth/cycle limit is hit.
32
+ const chain = [targetNode];
33
+ const visited = new Set([targetNode.id]);
34
+ let current = targetNode;
35
+ let reachedRoot = false;
36
+ while (chain.length <= max_depth) {
37
+ const dom = current.dominatorNode;
38
+ if (!dom || dom.id === current.id) {
39
+ reachedRoot = true;
40
+ break;
41
+ }
42
+ if (visited.has(dom.id))
43
+ break;
44
+ visited.add(dom.id);
45
+ chain.push(dom);
46
+ current = dom;
47
+ }
48
+ // The nearest dominator ABOVE the target that is a real, inspectable
49
+ // app object — the accountable owner to fix.
50
+ const owner = chain
51
+ .slice(1)
52
+ .find(n => isNodeWorthInspecting(n) && n.name !== '(GC roots)');
53
+ const lines = [
54
+ `## Dominator chain for @${targetNode.id} (${truncateNodeName(targetNode.name, targetNode.type, targetNode.self_size, 60)})`,
55
+ '',
56
+ `Retained ${formatBytes(targetNode.retainedSize)}. Climbing ${chain.length - 1} immediate dominator(s) toward the GC root${reachedRoot ? '' : ` (stopped at max_depth ${max_depth})`}.`,
57
+ '',
58
+ ];
59
+ for (let i = 0; i < chain.length; i++) {
60
+ const n = chain[i];
61
+ const indent = ' '.repeat(i);
62
+ const name = truncateNodeName(n.name, n.type, n.self_size, 80);
63
+ // Fan-in: how much more the next dominator up is accountable for than
64
+ // this node — i.e. what else it holds besides this subtree.
65
+ let fanIn = '';
66
+ if (i + 1 < chain.length) {
67
+ const delta = chain[i + 1].retainedSize - n.retainedSize;
68
+ if (delta > 0)
69
+ fanIn = ` — dominator holds +${formatBytes(delta)} beyond this`;
70
+ }
71
+ const marker = i === 0 ? 'target' : `↑ dom ${i}`;
72
+ const ownerTag = owner && n.id === owner.id ? ' ← nearest app owner' : '';
73
+ lines.push(`${indent}[${marker}] @${n.id} ${name} (retained ${formatBytes(n.retainedSize)})${fanIn}${ownerTag}`);
74
+ }
75
+ lines.push('');
76
+ if (reachedRoot) {
77
+ lines.push('_(reached GC root — this is the full accountability chain)_');
78
+ }
79
+ else {
80
+ lines.push(`_(stopped at max_depth ${max_depth}; pass a larger max_depth to reach the root)_`);
81
+ }
82
+ if (owner) {
83
+ lines.push('', `**Nearest app owner:** @${owner.id} ${truncateNodeName(owner.name, owner.type, owner.self_size, 60)} — retains ${formatBytes(owner.retainedSize)} (${formatNumber(owner.retainedSize)} bytes). Fixing this object frees the target.`);
84
+ }
85
+ return toolResult(lines.join('\n'));
86
+ }
87
+ catch (err) {
88
+ return errorResult(err);
89
+ }
90
+ });
91
+ }
92
+ //# sourceMappingURL=dominator-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-chain.js","sourceRoot":"","sources":["../../src/tools/dominator-chain.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 registerDuplicateObjects(server: McpServer): void;
12
+ //# sourceMappingURL=duplicate-objects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-objects.d.ts","sourceRoot":"","sources":["../../src/tools/duplicate-objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAkCvE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6LhE"}
@@ -0,0 +1,172 @@
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 { formatBytes, formatNumber, markdownTable, errorResult, toolResult, suggestionsSuppressed, objectContentSignature, IDENTITY_PROPS, } from '../utils.js';
13
+ function readTypename(node) {
14
+ for (const edge of node.references) {
15
+ if (edge.type === 'property' &&
16
+ String(edge.name_or_index) === '__typename') {
17
+ const t = edge.toNode;
18
+ return t.isString ? (t.toStringNode()?.stringValue ?? null) : null;
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ export function registerDuplicateObjects(server) {
24
+ server.tool('memlab_duplicate_objects', 'Find structurally-identical DUPLICATE objects — instances that carry the same content repeated many times, which a class histogram or shape histogram cannot see (they group by class or by property-NAME set, not by VALUE). Groups object instances by a shallow content signature (sorted property names + each scalar value; object-valued props marked generically) and reports, per signature, the instance count and the reclaimable own-bytes of the redundant copies. ' +
25
+ 'This surfaces "materialize a canonical/default record once", "share a frozen default", or "intern the object" wins — e.g. thousands of normalized store records that are all the same all-default spec. ' +
26
+ 'Scope the scan with `class_name` (constructor, e.g. "Object") and/or `typename` (the `__typename` property value, e.g. "AdCreativeFeatureSpecAttachment") — for normalized GraphQL/Relay stores `typename` is usually what you want since every record is a generic `Object`. ' +
27
+ 'Per-instance identity fields (id, __id, __ref, key, clientMutationId) are EXCLUDED from the signature by default so records that differ only by their data-id still collapse together (override with `ignore_properties`). ' +
28
+ 'Caveat: numeric property VALUES are not recoverable from the heap-snapshot format, so a number field contributes only a generic marker — two objects differing only in a numeric field (SMI or double) hash to the same signature and are reported as duplicates. String, boolean, null and object-valued properties are compared (objects generically, so nested content is not).', {
29
+ class_name: z
30
+ .string()
31
+ .optional()
32
+ .describe('Restrict to objects with this constructor name (e.g. "Object"). Omit to scan every object-type node.'),
33
+ typename: z
34
+ .string()
35
+ .optional()
36
+ .describe('Restrict to objects whose `__typename` property equals this value (normalized GraphQL/Relay records). Combine with or instead of class_name.'),
37
+ min_count: z
38
+ .number()
39
+ .optional()
40
+ .default(50)
41
+ .describe('Only report signature groups with at least this many instances (default 50). Raise to focus on the most-duplicated content.'),
42
+ min_dup_ratio: z
43
+ .number()
44
+ .optional()
45
+ .describe('Only report signature groups whose instance count is at least this multiple of 1 distinct content (i.e. count ≥ min_dup_ratio). Optional extra filter on top of min_count.'),
46
+ limit: z
47
+ .number()
48
+ .optional()
49
+ .default(20)
50
+ .describe('Maximum number of signature groups to return (default 20).'),
51
+ max_string_len: z
52
+ .number()
53
+ .optional()
54
+ .default(40)
55
+ .describe('Cap on the length of string values folded into the signature (default 40). Two strings that differ only past this cap are treated as equal — lower it to be more lenient, raise it to be stricter.'),
56
+ ignore_properties: z
57
+ .array(z.string())
58
+ .optional()
59
+ .describe('Property names to EXCLUDE from the content signature — per-instance identity/unique fields (a data-id, a key) that would otherwise make every record look distinct and hide real duplicates. Defaults to id, __id, __ref, key, clientMutationId. Pass [] to include every property.'),
60
+ }, async ({ class_name, typename, min_count, min_dup_ratio, limit, max_string_len, ignore_properties, }) => {
61
+ try {
62
+ const snapshot = getSnapshot();
63
+ const ignoreProps = ignore_properties
64
+ ? new Set(ignore_properties)
65
+ : IDENTITY_PROPS;
66
+ const groups = new Map();
67
+ let scopeCount = 0;
68
+ let scopeSelf = 0;
69
+ snapshot.nodes.forEach((node) => {
70
+ if (node.type !== 'object')
71
+ return;
72
+ if (node.id <= 3)
73
+ return;
74
+ if (class_name && node.name !== class_name)
75
+ return;
76
+ if (typename && readTypename(node) !== typename)
77
+ return;
78
+ const sig = objectContentSignature(node, {
79
+ maxStringLen: max_string_len,
80
+ ignoreProps,
81
+ });
82
+ if (sig === '')
83
+ return; // no own properties — nothing to dedupe on
84
+ scopeCount++;
85
+ scopeSelf += node.self_size;
86
+ const g = groups.get(sig);
87
+ if (g) {
88
+ g.count++;
89
+ g.totalSelf += node.self_size;
90
+ }
91
+ else {
92
+ groups.set(sig, {
93
+ count: 1,
94
+ totalSelf: node.self_size,
95
+ exampleId: node.id,
96
+ });
97
+ }
98
+ });
99
+ if (scopeCount === 0) {
100
+ return toolResult('No matching objects found. Loosen `class_name` / `typename`, or check the value with `memlab_property_distribution(property:"__typename")` / `memlab_class_histogram`.');
101
+ }
102
+ const distinct = groups.size;
103
+ // Reclaimable = own-bytes of the redundant copies (all but one per
104
+ // signature) — what deduping/sharing a canonical instance would free.
105
+ // Shared child subtrees are NOT included (this is a lower bound on the
106
+ // structural win, an honest own-bytes figure).
107
+ let totalReclaimable = 0;
108
+ for (const g of groups.values()) {
109
+ totalReclaimable += g.totalSelf * ((g.count - 1) / g.count);
110
+ }
111
+ const ranked = [...groups.entries()]
112
+ .map(([sig, g]) => ({
113
+ sig,
114
+ ...g,
115
+ reclaimable: g.totalSelf * ((g.count - 1) / g.count),
116
+ }))
117
+ .filter(g => g.count >= min_count)
118
+ .filter(g => min_dup_ratio == null || g.count >= min_dup_ratio);
119
+ ranked.sort((a, b) => b.reclaimable - a.reclaimable);
120
+ const shown = ranked.slice(0, limit);
121
+ const scopeLabel = [
122
+ class_name ? `class \`${class_name}\`` : null,
123
+ typename ? `__typename \`${typename}\`` : null,
124
+ ]
125
+ .filter(Boolean)
126
+ .join(' + ') || 'all objects';
127
+ const lines = [
128
+ `## Duplicate objects — ${scopeLabel}`,
129
+ '',
130
+ `**${formatNumber(scopeCount)}** instances in scope collapse to **${formatNumber(distinct)}** distinct contents ` +
131
+ `(dup ratio **${(scopeCount / Math.max(1, distinct)).toFixed(1)}×**). ` +
132
+ `Reclaimable own-bytes if deduped to one-per-content: **~${formatBytes(totalReclaimable)}** ` +
133
+ `of ${formatBytes(scopeSelf)} total self size.`,
134
+ '',
135
+ ];
136
+ if (shown.length === 0) {
137
+ lines.push(`No single content is shared by ≥ ${min_count} instances` +
138
+ (min_dup_ratio != null ? ` (and ≥ ${min_dup_ratio}× dup)` : '') +
139
+ '. The instances are structurally varied (real data), not duplicates. Lower `min_count` to see smaller groups.');
140
+ return toolResult(lines.join('\n'));
141
+ }
142
+ const headers = [
143
+ 'Count',
144
+ 'Self each',
145
+ 'Reclaimable',
146
+ 'Example',
147
+ 'Signature',
148
+ ];
149
+ const rightCols = new Set([0, 1, 2]);
150
+ const rows = shown.map(g => {
151
+ const avg = g.totalSelf / g.count;
152
+ const sigShown = g.sig.length > 90 ? g.sig.slice(0, 90) + '…' : g.sig;
153
+ return [
154
+ formatNumber(g.count),
155
+ formatBytes(avg),
156
+ formatBytes(g.reclaimable),
157
+ `@${g.exampleId}`,
158
+ sigShown,
159
+ ];
160
+ });
161
+ lines.push(markdownTable(headers, rows, rightCols));
162
+ if (!suggestionsSuppressed()) {
163
+ lines.push('', '**Suggested next steps:**', `- Inspect a duplicate: \`memlab_object_shape(${shown[0].exampleId})\``, `- Trace why they are retained: \`memlab_retainer_trace(${shown[0].exampleId})\``, '- If these are normalized store records, the fix is usually at the resolver/normalizer: return a shared canonical instance (or null) for all-default content instead of materializing one per key.');
164
+ }
165
+ return toolResult(lines.join('\n'));
166
+ }
167
+ catch (err) {
168
+ return errorResult(err);
169
+ }
170
+ });
171
+ }
172
+ //# sourceMappingURL=duplicate-objects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-objects.js","sourceRoot":"","sources":["../../src/tools/duplicate-objects.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"duplicated-strings.d.ts","sourceRoot":"","sources":["../../src/tools/duplicated-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAUvE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAgOjE"}
1
+ {"version":3,"file":"duplicated-strings.d.ts","sourceRoot":"","sources":["../../src/tools/duplicated-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAavE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6OjE"}