@memlab/mcp-server 2.52.0 → 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.
Files changed (277) hide show
  1. package/README.md +22 -2
  2. package/dist/anonymized-snapshot.d.ts +75 -0
  3. package/dist/anonymized-snapshot.d.ts.map +1 -0
  4. package/dist/anonymized-snapshot.js +101 -0
  5. package/dist/anonymized-snapshot.js.map +1 -0
  6. package/dist/eval-hints.d.ts +41 -0
  7. package/dist/eval-hints.d.ts.map +1 -0
  8. package/dist/eval-hints.js +115 -0
  9. package/dist/eval-hints.js.map +1 -0
  10. package/dist/heap-budget.d.ts +32 -0
  11. package/dist/heap-budget.d.ts.map +1 -0
  12. package/dist/heap-budget.js +90 -0
  13. package/dist/heap-budget.js.map +1 -0
  14. package/dist/heap-shapes.d.ts +148 -0
  15. package/dist/heap-shapes.d.ts.map +1 -0
  16. package/dist/heap-shapes.js +193 -0
  17. package/dist/heap-shapes.js.map +1 -0
  18. package/dist/heap-state.d.ts +20 -0
  19. package/dist/heap-state.d.ts.map +1 -1
  20. package/dist/heap-state.js +8 -1
  21. package/dist/heap-state.js.map +1 -1
  22. package/dist/index.js +64 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/react-shapes.d.ts +42 -0
  25. package/dist/react-shapes.d.ts.map +1 -0
  26. package/dist/react-shapes.js +149 -0
  27. package/dist/react-shapes.js.map +1 -0
  28. package/dist/server-version.d.ts +19 -0
  29. package/dist/server-version.d.ts.map +1 -0
  30. package/dist/server-version.js +19 -0
  31. package/dist/server-version.js.map +1 -0
  32. package/dist/snapshot-borrow.d.ts +67 -0
  33. package/dist/snapshot-borrow.d.ts.map +1 -0
  34. package/dist/snapshot-borrow.js +113 -0
  35. package/dist/snapshot-borrow.js.map +1 -0
  36. package/dist/string-index.d.ts.map +1 -1
  37. package/dist/string-index.js +8 -0
  38. package/dist/string-index.js.map +1 -1
  39. package/dist/tools/analyze-run.d.ts.map +1 -1
  40. package/dist/tools/analyze-run.js +79 -18
  41. package/dist/tools/analyze-run.js.map +1 -1
  42. package/dist/tools/anonymize-snapshot.d.ts +12 -0
  43. package/dist/tools/anonymize-snapshot.d.ts.map +1 -0
  44. package/dist/tools/anonymize-snapshot.js +159 -0
  45. package/dist/tools/anonymize-snapshot.js.map +1 -0
  46. package/dist/tools/app-config.d.ts +12 -0
  47. package/dist/tools/app-config.d.ts.map +1 -0
  48. package/dist/tools/app-config.js +217 -0
  49. package/dist/tools/app-config.js.map +1 -0
  50. package/dist/tools/app-heap.d.ts +18 -0
  51. package/dist/tools/app-heap.d.ts.map +1 -1
  52. package/dist/tools/app-heap.js +12 -46
  53. package/dist/tools/app-heap.js.map +1 -1
  54. package/dist/tools/artifact-budget.d.ts +12 -0
  55. package/dist/tools/artifact-budget.d.ts.map +1 -0
  56. package/dist/tools/artifact-budget.js +219 -0
  57. package/dist/tools/artifact-budget.js.map +1 -0
  58. package/dist/tools/async-census.d.ts +12 -0
  59. package/dist/tools/async-census.d.ts.map +1 -0
  60. package/dist/tools/async-census.js +243 -0
  61. package/dist/tools/async-census.js.map +1 -0
  62. package/dist/tools/auto-investigate.d.ts.map +1 -1
  63. package/dist/tools/auto-investigate.js +54 -5
  64. package/dist/tools/auto-investigate.js.map +1 -1
  65. package/dist/tools/batch.d.ts.map +1 -1
  66. package/dist/tools/batch.js +33 -5
  67. package/dist/tools/batch.js.map +1 -1
  68. package/dist/tools/cache-analysis.d.ts +15 -0
  69. package/dist/tools/cache-analysis.d.ts.map +1 -1
  70. package/dist/tools/cache-analysis.js +85 -10
  71. package/dist/tools/cache-analysis.js.map +1 -1
  72. package/dist/tools/census-diff.d.ts +55 -0
  73. package/dist/tools/census-diff.d.ts.map +1 -0
  74. package/dist/tools/census-diff.js +228 -0
  75. package/dist/tools/census-diff.js.map +1 -0
  76. package/dist/tools/chain-walk.d.ts +12 -0
  77. package/dist/tools/chain-walk.d.ts.map +1 -0
  78. package/dist/tools/chain-walk.js +184 -0
  79. package/dist/tools/chain-walk.js.map +1 -0
  80. package/dist/tools/collection-diff.d.ts +44 -0
  81. package/dist/tools/collection-diff.d.ts.map +1 -0
  82. package/dist/tools/collection-diff.js +343 -0
  83. package/dist/tools/collection-diff.js.map +1 -0
  84. package/dist/tools/collection-trend.d.ts.map +1 -1
  85. package/dist/tools/collection-trend.js +2 -2
  86. package/dist/tools/collection-trend.js.map +1 -1
  87. package/dist/tools/detached-dom.d.ts +3 -0
  88. package/dist/tools/detached-dom.d.ts.map +1 -1
  89. package/dist/tools/detached-dom.js +78 -3
  90. package/dist/tools/detached-dom.js.map +1 -1
  91. package/dist/tools/dev-artifacts.d.ts.map +1 -1
  92. package/dist/tools/dev-artifacts.js +221 -3
  93. package/dist/tools/dev-artifacts.js.map +1 -1
  94. package/dist/tools/diff-snapshots.d.ts.map +1 -1
  95. package/dist/tools/diff-snapshots.js +18 -2
  96. package/dist/tools/diff-snapshots.js.map +1 -1
  97. package/dist/tools/dom-audit.d.ts +37 -0
  98. package/dist/tools/dom-audit.d.ts.map +1 -0
  99. package/dist/tools/dom-audit.js +247 -0
  100. package/dist/tools/dom-audit.js.map +1 -0
  101. package/dist/tools/duplicated-strings.d.ts.map +1 -1
  102. package/dist/tools/duplicated-strings.js +3 -2
  103. package/dist/tools/duplicated-strings.js.map +1 -1
  104. package/dist/tools/eval-across.d.ts +12 -0
  105. package/dist/tools/eval-across.d.ts.map +1 -0
  106. package/dist/tools/eval-across.js +135 -0
  107. package/dist/tools/eval-across.js.map +1 -0
  108. package/dist/tools/eval.d.ts +78 -0
  109. package/dist/tools/eval.d.ts.map +1 -1
  110. package/dist/tools/eval.js +2270 -701
  111. package/dist/tools/eval.js.map +1 -1
  112. package/dist/tools/event-listener-leaks.d.ts.map +1 -1
  113. package/dist/tools/event-listener-leaks.js +42 -17
  114. package/dist/tools/event-listener-leaks.js.map +1 -1
  115. package/dist/tools/event-registry.d.ts.map +1 -1
  116. package/dist/tools/event-registry.js +42 -8
  117. package/dist/tools/event-registry.js.map +1 -1
  118. package/dist/tools/explain-delta.d.ts.map +1 -1
  119. package/dist/tools/explain-delta.js +61 -10
  120. package/dist/tools/explain-delta.js.map +1 -1
  121. package/dist/tools/finding-index.d.ts +181 -0
  122. package/dist/tools/finding-index.d.ts.map +1 -1
  123. package/dist/tools/finding-index.js +343 -24
  124. package/dist/tools/finding-index.js.map +1 -1
  125. package/dist/tools/get-value.d.ts +16 -0
  126. package/dist/tools/get-value.d.ts.map +1 -1
  127. package/dist/tools/get-value.js +96 -2
  128. package/dist/tools/get-value.js.map +1 -1
  129. package/dist/tools/hypothesis.d.ts.map +1 -1
  130. package/dist/tools/hypothesis.js +117 -58
  131. package/dist/tools/hypothesis.js.map +1 -1
  132. package/dist/tools/id-space-audit.d.ts +12 -0
  133. package/dist/tools/id-space-audit.d.ts.map +1 -0
  134. package/dist/tools/id-space-audit.js +262 -0
  135. package/dist/tools/id-space-audit.js.map +1 -0
  136. package/dist/tools/identify.d.ts +12 -0
  137. package/dist/tools/identify.d.ts.map +1 -0
  138. package/dist/tools/identify.js +205 -0
  139. package/dist/tools/identify.js.map +1 -0
  140. package/dist/tools/identity-diff.d.ts +12 -0
  141. package/dist/tools/identity-diff.d.ts.map +1 -0
  142. package/dist/tools/identity-diff.js +188 -0
  143. package/dist/tools/identity-diff.js.map +1 -0
  144. package/dist/tools/intern-opportunities.d.ts.map +1 -1
  145. package/dist/tools/intern-opportunities.js +4 -3
  146. package/dist/tools/intern-opportunities.js.map +1 -1
  147. package/dist/tools/ladder-probe.d.ts +65 -0
  148. package/dist/tools/ladder-probe.d.ts.map +1 -0
  149. package/dist/tools/ladder-probe.js +445 -0
  150. package/dist/tools/ladder-probe.js.map +1 -0
  151. package/dist/tools/ladder.d.ts.map +1 -1
  152. package/dist/tools/ladder.js +5 -5
  153. package/dist/tools/ladder.js.map +1 -1
  154. package/dist/tools/leak-report.d.ts +23 -0
  155. package/dist/tools/leak-report.d.ts.map +1 -1
  156. package/dist/tools/leak-report.js +153 -11
  157. package/dist/tools/leak-report.js.map +1 -1
  158. package/dist/tools/load-snapshot.d.ts +11 -0
  159. package/dist/tools/load-snapshot.d.ts.map +1 -1
  160. package/dist/tools/load-snapshot.js +83 -10
  161. package/dist/tools/load-snapshot.js.map +1 -1
  162. package/dist/tools/metric.d.ts +13 -0
  163. package/dist/tools/metric.d.ts.map +1 -0
  164. package/dist/tools/metric.js +185 -0
  165. package/dist/tools/metric.js.map +1 -0
  166. package/dist/tools/module-attribution.d.ts +36 -0
  167. package/dist/tools/module-attribution.d.ts.map +1 -0
  168. package/dist/tools/module-attribution.js +235 -0
  169. package/dist/tools/module-attribution.js.map +1 -0
  170. package/dist/tools/next-measurement.d.ts +26 -0
  171. package/dist/tools/next-measurement.d.ts.map +1 -0
  172. package/dist/tools/next-measurement.js +149 -0
  173. package/dist/tools/next-measurement.js.map +1 -0
  174. package/dist/tools/object-cost-breakdown.d.ts.map +1 -1
  175. package/dist/tools/object-cost-breakdown.js +64 -0
  176. package/dist/tools/object-cost-breakdown.js.map +1 -1
  177. package/dist/tools/population-vs-owners.d.ts +12 -0
  178. package/dist/tools/population-vs-owners.d.ts.map +1 -0
  179. package/dist/tools/population-vs-owners.js +183 -0
  180. package/dist/tools/population-vs-owners.js.map +1 -0
  181. package/dist/tools/property-names.d.ts +29 -0
  182. package/dist/tools/property-names.d.ts.map +1 -0
  183. package/dist/tools/property-names.js +131 -0
  184. package/dist/tools/property-names.js.map +1 -0
  185. package/dist/tools/quick-diagnosis.d.ts.map +1 -1
  186. package/dist/tools/quick-diagnosis.js +7 -1
  187. package/dist/tools/quick-diagnosis.js.map +1 -1
  188. package/dist/tools/react-owners.d.ts +12 -0
  189. package/dist/tools/react-owners.d.ts.map +1 -0
  190. package/dist/tools/react-owners.js +167 -0
  191. package/dist/tools/react-owners.js.map +1 -0
  192. package/dist/tools/react-update-queues.d.ts +12 -0
  193. package/dist/tools/react-update-queues.d.ts.map +1 -0
  194. package/dist/tools/react-update-queues.js +173 -0
  195. package/dist/tools/react-update-queues.js.map +1 -0
  196. package/dist/tools/replicate.d.ts +49 -0
  197. package/dist/tools/replicate.d.ts.map +1 -0
  198. package/dist/tools/replicate.js +241 -0
  199. package/dist/tools/replicate.js.map +1 -0
  200. package/dist/tools/retainer-diff.d.ts +12 -0
  201. package/dist/tools/retainer-diff.d.ts.map +1 -0
  202. package/dist/tools/retainer-diff.js +168 -0
  203. package/dist/tools/retainer-diff.js.map +1 -0
  204. package/dist/tools/retainer-summary.d.ts +17 -0
  205. package/dist/tools/retainer-summary.d.ts.map +1 -1
  206. package/dist/tools/retainer-summary.js +85 -22
  207. package/dist/tools/retainer-summary.js.map +1 -1
  208. package/dist/tools/retainer-trace.d.ts.map +1 -1
  209. package/dist/tools/retainer-trace.js +11 -1
  210. package/dist/tools/retainer-trace.js.map +1 -1
  211. package/dist/tools/round-audit.d.ts +66 -0
  212. package/dist/tools/round-audit.d.ts.map +1 -0
  213. package/dist/tools/round-audit.js +258 -0
  214. package/dist/tools/round-audit.js.map +1 -0
  215. package/dist/tools/round-digest.d.ts +12 -0
  216. package/dist/tools/round-digest.d.ts.map +1 -0
  217. package/dist/tools/round-digest.js +190 -0
  218. package/dist/tools/round-digest.js.map +1 -0
  219. package/dist/tools/script-census.d.ts +12 -0
  220. package/dist/tools/script-census.d.ts.map +1 -0
  221. package/dist/tools/script-census.js +182 -0
  222. package/dist/tools/script-census.js.map +1 -0
  223. package/dist/tools/sequence-analysis.d.ts +4 -0
  224. package/dist/tools/sequence-analysis.d.ts.map +1 -1
  225. package/dist/tools/sequence-analysis.js +53 -9
  226. package/dist/tools/sequence-analysis.js.map +1 -1
  227. package/dist/tools/server-status.d.ts.map +1 -1
  228. package/dist/tools/server-status.js +4 -1
  229. package/dist/tools/server-status.js.map +1 -1
  230. package/dist/tools/settle-check.d.ts +12 -0
  231. package/dist/tools/settle-check.d.ts.map +1 -0
  232. package/dist/tools/settle-check.js +188 -0
  233. package/dist/tools/settle-check.js.map +1 -0
  234. package/dist/tools/sliced-strings.d.ts.map +1 -1
  235. package/dist/tools/sliced-strings.js +2 -1
  236. package/dist/tools/sliced-strings.js.map +1 -1
  237. package/dist/tools/snapshots.d.ts.map +1 -1
  238. package/dist/tools/snapshots.js +24 -6
  239. package/dist/tools/snapshots.js.map +1 -1
  240. package/dist/tools/sparse-elements.d.ts +12 -0
  241. package/dist/tools/sparse-elements.d.ts.map +1 -0
  242. package/dist/tools/sparse-elements.js +215 -0
  243. package/dist/tools/sparse-elements.js.map +1 -0
  244. package/dist/tools/stale-collections.d.ts +17 -0
  245. package/dist/tools/stale-collections.d.ts.map +1 -1
  246. package/dist/tools/stale-collections.js +28 -0
  247. package/dist/tools/stale-collections.js.map +1 -1
  248. package/dist/tools/string-patterns.d.ts.map +1 -1
  249. package/dist/tools/string-patterns.js +2 -1
  250. package/dist/tools/string-patterns.js.map +1 -1
  251. package/dist/tools/tools-index.d.ts.map +1 -1
  252. package/dist/tools/tools-index.js +155 -5
  253. package/dist/tools/tools-index.js.map +1 -1
  254. package/dist/tools/trace-all.d.ts +42 -0
  255. package/dist/tools/trace-all.d.ts.map +1 -0
  256. package/dist/tools/trace-all.js +263 -0
  257. package/dist/tools/trace-all.js.map +1 -0
  258. package/dist/tools/unit-cost.d.ts +12 -0
  259. package/dist/tools/unit-cost.d.ts.map +1 -0
  260. package/dist/tools/unit-cost.js +219 -0
  261. package/dist/tools/unit-cost.js.map +1 -0
  262. package/dist/tools/verify-fix.d.ts.map +1 -1
  263. package/dist/tools/verify-fix.js +11 -3
  264. package/dist/tools/verify-fix.js.map +1 -1
  265. package/dist/tools/weakref-census.d.ts +15 -0
  266. package/dist/tools/weakref-census.d.ts.map +1 -0
  267. package/dist/tools/weakref-census.js +147 -0
  268. package/dist/tools/weakref-census.js.map +1 -0
  269. package/dist/tools/what-if.d.ts +12 -0
  270. package/dist/tools/what-if.d.ts.map +1 -0
  271. package/dist/tools/what-if.js +285 -0
  272. package/dist/tools/what-if.js.map +1 -0
  273. package/dist/utils.d.ts +92 -2
  274. package/dist/utils.d.ts.map +1 -1
  275. package/dist/utils.js +172 -5
  276. package/dist/utils.js.map +1 -1
  277. package/package.json +3 -3
package/README.md CHANGED
@@ -96,7 +96,11 @@ To auto-approve the read-only tools while keeping the two code-execution tools o
96
96
 
97
97
  ## How It Works
98
98
 
99
- The server holds a loaded `IHeapSnapshot` in memory across tool calls (loading is expensive for large heaps). Only one snapshot can be loaded at a time. All tools are read-only — they analyze the heap but never modify it.
99
+ The server holds loaded `IHeapSnapshot` graphs in memory across tool calls (loading is
100
+ expensive for large heaps). Several snapshots can be resident at once (`keep_previous: true`);
101
+ tools read the active one, which `memlab_snapshots` switches. When a load's projected working
102
+ set exceeds the free old-space budget, the least-recently-used resident snapshot is evicted
103
+ before the load is refused. All tools are read-only — they analyze the heap but never modify it.
100
104
 
101
105
  ## Getting a Heap Snapshot
102
106
 
@@ -120,11 +124,14 @@ console.log(`Heap snapshot written to ${snapshot}`);
120
124
  ### `memlab_load_snapshot`
121
125
 
122
126
  Load and parse a `.heapsnapshot` file. Builds indexes, computes the dominator tree, and calculates retained sizes. `file_path` may be a local absolute path, a `manifold://bucket/key` URL, or a bare snapshot filename (resolved against the `nest_server_nodejs_heap_snapshots` bucket and fetched via `manifold get`). Pass `keep_previous: true` to keep earlier snapshots resident for diffing/comparison (each gets a handle; manage with `memlab_snapshots`). `quiet` / `suppress_suggestions` set session-wide output controls to trim repeated boilerplate.
127
+ Pass `light: true` for a count-only load that skips the dominator / retained-size /
128
+ shortest-path pass; tools needing retained sizes, dominators or retainer paths then return an
129
+ error naming the light-safe tools instead of reporting zeros.
123
130
 
124
131
  ```
125
132
  Input: { file_path: "snap.heapsnapshot" | "/abs/path" | "manifold://bucket/key",
126
133
  alias?: "before", keep_previous?: false, quiet?: false,
127
- suppress_suggestions?: false, max_file_size_mb?: 900 }
134
+ suppress_suggestions?: false, light?: false, max_file_size_mb?: 900 }
128
135
  Output: { status, file_path, node_count, edge_count, total_size, handle }
129
136
  ```
130
137
 
@@ -148,6 +155,19 @@ Input: { action?: "list"|"switch"|"unload", handle?: "before",
148
155
  Output: resident snapshots (active one marked), or switch/unload result
149
156
  ```
150
157
 
158
+ ### `memlab_batch`
159
+
160
+ Run several tools against ONE snapshot load and return all their outputs together, instead of
161
+ re-paying the multi-minute parse on every call. Steps run in order against the resident snapshot
162
+ and see each other's side effects. An unknown tool name is rejected before any step runs, and by
163
+ default a failing step is recorded and the batch continues.
164
+
165
+ ```
166
+ Input: { load?: { ...memlab_load_snapshot args }, steps: [{ tool, args? }],
167
+ stop_on_error?: false }
168
+ Output: a per-step output block for each step, headed by a run/failed step count
169
+ ```
170
+
151
171
  ### `memlab_property_distribution`
152
172
 
153
173
  For a class/shape and a property, report value cardinality plus the top-K most frequent values. The key tool for diagnosing cardinality explosions (OTel metric attributes, cache keys, per-record fields).
@@ -0,0 +1,75 @@
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 { IHeapSnapshot } from '@memlab/core';
11
+ /**
12
+ * Has this capture had its string content replaced — and if so, in a way that
13
+ * corrupts the duplication numbers, or not?
14
+ *
15
+ * Anonymisers replace string CONTENT while preserving LENGTH. Whether that
16
+ * matters to an analysis depends entirely on which kind you are looking at, so
17
+ * this reports three states rather than a boolean:
18
+ *
19
+ * - `uniform` — every character replaced by ONE filler character. Every
20
+ * distinct string of a given length collapses into the same value, so every
21
+ * string-duplication analysis measures the anonymiser instead of the app.
22
+ * Measured on one such capture: `memlab_quick_diagnosis` reported **"Total
23
+ * interning savings: 47.4 MB"** and `memlab_duplicated_strings` reported a
24
+ * single 5.2 KB string duplicated 939 times for 42.1 MB. Both were pure
25
+ * artifact. Nothing flagged it; it was caught only because the sample values
26
+ * rendered visibly as runs of `?`.
27
+ * - `stable` — replaced by a value-derived token, so distinctness survives
28
+ * along with length. Duplication and interning figures are as true as on the
29
+ * original capture, and warning about them would make readers discard
30
+ * correct findings. Only the content is unreadable.
31
+ * - `none` — an ordinary capture.
32
+ *
33
+ * Both tests are deliberately cheap and deliberately conservative: sample
34
+ * strings, and only classify when a large majority match. A normal heap has
35
+ * plenty of short repeated-character strings (" ", "----") and plenty of short
36
+ * lowercase words, so the length floor matters more than the ratio for either.
37
+ */
38
+ export type AnonymizationKind =
39
+ /** ordinary capture */
40
+ 'none'
41
+ /**
42
+ * every character replaced by one filler character. Length survives, so
43
+ * distinct values of equal length collapse into one and duplication figures
44
+ * become fiction.
45
+ */
46
+ | 'uniform'
47
+ /**
48
+ * replaced by a value-derived token. Length AND distinctness survive, so
49
+ * duplication figures stay true; only the content is gone.
50
+ */
51
+ | 'stable';
52
+ export interface AnonymizationCheck {
53
+ kind: AnonymizationKind;
54
+ /**
55
+ * true only for `uniform`, which is the kind that corrupts duplication
56
+ * numbers. Existing call sites read this to decide whether to warn, and a
57
+ * `stable` capture must NOT trip them — its numbers are trustworthy.
58
+ */
59
+ anonymized: boolean;
60
+ sampled: number;
61
+ singleCharRuns: number;
62
+ tokenRuns: number;
63
+ exampleChar?: string;
64
+ }
65
+ export declare function detectAnonymizedStrings(snapshot: IHeapSnapshot): AnonymizationCheck;
66
+ /** The banner every string-content tool prints when the capture is anonymised. */
67
+ export declare function anonymizedStringsBanner(check: AnonymizationCheck): string;
68
+ /**
69
+ * Prepend the anonymisation banner to a tool's output when it applies.
70
+ *
71
+ * Wraps the result rather than being left to each caller to remember, because
72
+ * "remember to check" is exactly the failure this exists to prevent.
73
+ */
74
+ export declare function withAnonymizedBanner(snapshot: IHeapSnapshot, text: string): string;
75
+ //# sourceMappingURL=anonymized-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymized-snapshot.d.ts","sourceRoot":"","sources":["../src/anonymized-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iBAAiB;AAC3B,uBAAuB;AACrB,MAAM;AACR;;;;GAIG;GACD,SAAS;AACX;;;GAGG;GACD,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,iBAAiB,CAAC;IACxB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,aAAa,GACtB,kBAAkB,CAqDpB;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAwBzE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,GACX,MAAM,CAIR"}
@@ -0,0 +1,101 @@
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
+ const MIN_LEN = 8;
11
+ const SAMPLE_TARGET = 400;
12
+ const RATIO = 0.8;
13
+ export function detectAnonymizedStrings(snapshot) {
14
+ let sampled = 0;
15
+ let singleCharRuns = 0;
16
+ let tokenRuns = 0;
17
+ let exampleChar;
18
+ let visited = 0;
19
+ snapshot.nodes.forEach(node => {
20
+ if (sampled >= SAMPLE_TARGET)
21
+ return;
22
+ if (node.type !== 'string')
23
+ return;
24
+ // Stride rather than take the first N: the first strings in a heap are
25
+ // V8 internals and interned literals, which are never anonymised and would
26
+ // make every capture look clean.
27
+ visited++;
28
+ if (visited % 7 !== 0)
29
+ return;
30
+ const value = node.name;
31
+ if (typeof value !== 'string' || value.length < MIN_LEN)
32
+ return;
33
+ sampled++;
34
+ const first = value[0];
35
+ let uniform = true;
36
+ for (let i = 1; i < value.length; i++) {
37
+ if (value[i] !== first) {
38
+ uniform = false;
39
+ break;
40
+ }
41
+ }
42
+ if (uniform) {
43
+ singleCharRuns++;
44
+ exampleChar ??= first;
45
+ return;
46
+ }
47
+ // `stable` fill is a run of lowercase letters and nothing else. Real string
48
+ // values of this length essentially always carry a space, digit, capital or
49
+ // punctuation mark somewhere, so a heap where most of them are bare
50
+ // lowercase runs has been rewritten.
51
+ if (/^[a-z]+$/.test(value)) {
52
+ tokenRuns++;
53
+ }
54
+ });
55
+ const uniformRatio = sampled >= 20 ? singleCharRuns / sampled : 0;
56
+ const tokenRatio = sampled >= 20 ? tokenRuns / sampled : 0;
57
+ const kind = uniformRatio >= RATIO ? 'uniform' : tokenRatio >= RATIO ? 'stable' : 'none';
58
+ return {
59
+ kind,
60
+ anonymized: kind === 'uniform',
61
+ sampled,
62
+ singleCharRuns,
63
+ tokenRuns,
64
+ exampleChar,
65
+ };
66
+ }
67
+ /** The banner every string-content tool prints when the capture is anonymised. */
68
+ export function anonymizedStringsBanner(check) {
69
+ if (check.kind === 'stable') {
70
+ // Deliberately NOT a warning about the numbers. `stable` anonymization
71
+ // preserves distinctness, so duplication and interning figures are exactly
72
+ // as true as on the original capture — saying otherwise here would make
73
+ // readers discard correct findings.
74
+ const pct = check.sampled
75
+ ? Math.round((check.tokenRuns / check.sampled) * 100)
76
+ : 0;
77
+ return (`> ℹ️ **This capture is ANONYMISED (value-stable) — ${pct}% of sampled string values are opaque tokens.** ` +
78
+ 'String CONTENT has been replaced, but lengths and distinctness were preserved, so counts, sizes, duplication and interning figures below are accurate. ' +
79
+ 'What you cannot do is read the strings: a sample value tells you nothing about the real data, so identify records by property shape rather than by value.');
80
+ }
81
+ const pct = check.sampled
82
+ ? Math.round((check.singleCharRuns / check.sampled) * 100)
83
+ : 0;
84
+ return (`> ⚠️ **This capture looks ANONYMISED — ${pct}% of sampled strings are a single character (\`${check.exampleChar ?? '?'}\`) repeated.** ` +
85
+ 'An anonymiser replaces string content but preserves length, so every distinct string of the same length collapses to the same value. ' +
86
+ '**Every duplication and interning figure below is manufactured by that collapse and does not exist in the real app.** ' +
87
+ 'Do not quote them, and do not open an interning task from them. Composition, populations, retainer paths and dominator-deduped sizes are unaffected and remain trustworthy.');
88
+ }
89
+ /**
90
+ * Prepend the anonymisation banner to a tool's output when it applies.
91
+ *
92
+ * Wraps the result rather than being left to each caller to remember, because
93
+ * "remember to check" is exactly the failure this exists to prevent.
94
+ */
95
+ export function withAnonymizedBanner(snapshot, text) {
96
+ const check = detectAnonymizedStrings(snapshot);
97
+ if (check.kind === 'none')
98
+ return text;
99
+ return `${anonymizedStringsBanner(check)}\n\n${text}`;
100
+ }
101
+ //# sourceMappingURL=anonymized-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymized-snapshot.js","sourceRoot":"","sources":["../src/anonymized-snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
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
+ /**
11
+ * Recognise an eval that is re-implementing a tool that already exists, and say
12
+ * so alongside the result.
13
+ *
14
+ * The discoverability problem this addresses is measured, not hypothetical: in
15
+ * one long session `memlab_eval_across`, `memlab_population_diff`,
16
+ * `memlab_event_registry`, `memlab_collection_trend` and `memlab_verify_fix`
17
+ * all existed, all would have saved work, and none was called — while the same
18
+ * questions were answered with hand-written evals, several times each. The
19
+ * operator had even read the warning about exactly this and still did not open
20
+ * the catalogue, because under time pressure writing the eval is the shorter
21
+ * path and nothing in that path mentions the alternative.
22
+ *
23
+ * So the hint is attached to the RESULT of the call that made the mistake,
24
+ * which is the one moment the caller is guaranteed to read. Two rules keep it
25
+ * from becoming noise:
26
+ *
27
+ * - **Never suppress or alter the result.** The eval ran; the hint is a
28
+ * footnote. A hint that blocked or replaced output would be worse than the
29
+ * duplication it is trying to prevent.
30
+ * - **Match on structure, not vocabulary.** A pattern fires only when the code
31
+ * shows the SHAPE of the built-in's job (a `{callback, context}` census, a
32
+ * per-class count keyed by snapshot, a detached-node distribution), not
33
+ * merely because it mentions a word.
34
+ */
35
+ export interface EvalHint {
36
+ tool: string;
37
+ why: string;
38
+ }
39
+ export declare function hintsForEval(code: string): EvalHint[];
40
+ export declare function formatEvalHints(hints: EvalHint[]): string | null;
41
+ //# sourceMappingURL=eval-hints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval-hints.d.ts","sourceRoot":"","sources":["../src/eval-hints.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAkGD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CASrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,IAAI,CAKhE"}
@@ -0,0 +1,115 @@
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
+ const RULES = [
11
+ {
12
+ tool: 'memlab_event_registry',
13
+ why: 'counts `{callback, context}` listener records by event and host directly',
14
+ // `callback` and `context` are two of the most common identifiers in
15
+ // JavaScript, so matching them alone fires on evals that have nothing to do
16
+ // with listeners. Require the SHAPE as well: both read as properties off
17
+ // the same record, and the eval is accumulating or iterating over them.
18
+ all: [
19
+ /[.[]['"]?callback\b|\bcallback\s*:/,
20
+ /[.[]['"]?context\b|\bcontext\s*:/,
21
+ /&&|\+\+|\+= ?1|\|\| ?0\) ?\+ ?1|\?\? ?0\) ?\+ ?1|\.push\(|\bfor\b/,
22
+ ],
23
+ // Already going through the census tool, or bucketing something else.
24
+ none: [/memlab_event_registry/],
25
+ },
26
+ {
27
+ tool: 'memlab_detached_dom',
28
+ why: 'reports the detached-node population and its per-class distribution, using the same pinned/GC-eligible split the other tools use',
29
+ all: [/is_detached|isRealDetached|Detached </],
30
+ none: [/memlab_detached_dom/],
31
+ },
32
+ {
33
+ tool: 'memlab_census_diff',
34
+ why: 'takes the detached and listener census on BOTH rungs and diffs them per class in one call — no manual map-building or unload/reload cycle',
35
+ all: [
36
+ /is_detached|isRealDetached|Detached </,
37
+ /\bbaseline\b|\bbefore\b|\bprev\b/,
38
+ ],
39
+ none: [/memlab_census_diff/],
40
+ },
41
+ {
42
+ tool: 'memlab_class_histogram',
43
+ why: 'produces the per-class count/size table without a hand-written walk',
44
+ // Deliberately not keyed to the identifier `node`: the callback parameter
45
+ // is as often `n`, and requiring the long spelling made the rule silently
46
+ // never fire on the shape it exists to catch.
47
+ all: [
48
+ /snapshot\.nodes\.forEach|for\s*\(\s*const\s+\w+\s+of\s+snapshot\.nodes/,
49
+ /\.name\b/,
50
+ /\+\+|\+= ?1|\|\| ?0\) ?\+ ?1|\?\? ?0\) ?\+ ?1/,
51
+ ],
52
+ none: [/memlab_class_histogram|helpers\.classCounts/],
53
+ },
54
+ {
55
+ tool: 'memlab_ladder_probe',
56
+ why: 'runs ONE numeric probe across an ordered ladder and returns the series, the per-cycle rate and r² — loading one rung at a time, so it works on captures too large to hold simultaneously',
57
+ all: [/helpers\.(save|load)\(|__rung|perRung|per_rung/],
58
+ none: [/memlab_ladder_probe/],
59
+ },
60
+ {
61
+ tool: 'memlab_duplicate_objects',
62
+ why: 'is a ready-made dedup report over object content signatures',
63
+ all: [/shapeSignature|objectContentSignature/, /\bMap\b|\bSet\b/],
64
+ none: [/memlab_duplicate_objects/],
65
+ },
66
+ {
67
+ // One canonical tool name per hint. The pair used to be crammed into this
68
+ // single field, which rendered as one backticked identifier containing a
69
+ // slash and spaces — not a name any caller or renderer can resolve. The
70
+ // sibling tool goes in `why`, as prose, where it belongs.
71
+ tool: 'memlab_map_entries',
72
+ why: 'enumerates collection entries without the backing-store walk (see `memlab_weakmap_entries` for WeakMaps)',
73
+ all: [/table|backing/i, /\bMap\b|\bWeakMap\b/],
74
+ none: [
75
+ /helpers\.mapEntries|helpers\.weakmapEntries/,
76
+ /memlab_map_entries|memlab_weakmap_entries/,
77
+ ],
78
+ },
79
+ {
80
+ tool: 'memlab_retainer_trace',
81
+ why: 'walks retainer paths with framework filtering and path collapsing already applied',
82
+ all: [
83
+ /\.referrers\b/,
84
+ /while\s*\(|for\s*\(/,
85
+ /pathEdge|dominatorNode|\.referrers\[0\]/,
86
+ ],
87
+ none: [/memlab_retainer_trace|helpers\.pathBetween/],
88
+ },
89
+ ];
90
+ /**
91
+ * At most this many hints per call. Beyond two the footnote stops being a
92
+ * pointer and starts being a second result the caller has to read.
93
+ */
94
+ const MAX_HINTS = 2;
95
+ export function hintsForEval(code) {
96
+ const hints = [];
97
+ for (const rule of RULES) {
98
+ if (hints.length >= MAX_HINTS)
99
+ break;
100
+ if (rule.none?.some(re => re.test(code)))
101
+ continue;
102
+ if (!rule.all.every(re => re.test(code)))
103
+ continue;
104
+ hints.push({ tool: rule.tool, why: rule.why });
105
+ }
106
+ return hints;
107
+ }
108
+ export function formatEvalHints(hints) {
109
+ if (hints.length === 0)
110
+ return null;
111
+ return hints
112
+ .map(h => `note: \`${h.tool}\` does this directly — it ${h.why}.`)
113
+ .join('\n');
114
+ }
115
+ //# sourceMappingURL=eval-hints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval-hints.js","sourceRoot":"","sources":["../src/eval-hints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
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 { IHeapSnapshot } from '@memlab/core';
11
+ import type { ArtifactKind } from './artifact-classes.js';
12
+ import type { DevOnlyTotals, DevRootCategory } from './tools/dev-artifacts.js';
13
+ export interface HeapBudget {
14
+ total: number;
15
+ app: number;
16
+ appNodes: number;
17
+ bundle: number;
18
+ code: number;
19
+ devOnly: number;
20
+ artifact: number;
21
+ /** Known-artifact self bytes split by family (warmup / ax / cdp-* / …). */
22
+ artifactByKind: Map<ArtifactKind, number>;
23
+ /** Dev-only totals split by root family, or null when no dev roots exist. */
24
+ devOnlyTotals: DevOnlyTotals | null;
25
+ }
26
+ export declare function computeHeapBudget(snapshot: IHeapSnapshot): HeapBudget;
27
+ export declare function devFamilyBytes(budget: HeapBudget): Array<{
28
+ family: DevRootCategory;
29
+ nodes: number;
30
+ selfBytes: number;
31
+ }>;
32
+ //# sourceMappingURL=heap-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heap-budget.d.ts","sourceRoot":"","sources":["../src/heap-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAY,aAAa,EAAC,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAMxD,OAAO,KAAK,EAAC,aAAa,EAAE,eAAe,EAAC,MAAM,0BAA0B,CAAC;AAmB7E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC1C,6EAA6E;IAC7E,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,UAAU,CA2DrE;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,GACjB,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAAC,CAapE"}
@@ -0,0 +1,90 @@
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 { classifyArtifact } from './artifact-classes.js';
11
+ import { collectDevRoots, computeReachableWithoutDevRoots, summarizeDevOnly, } from './tools/dev-artifacts.js';
12
+ /**
13
+ * The one bucket split of a heap into "app" and the several distinct reasons a
14
+ * byte might not be the app.
15
+ *
16
+ * Extracted so `memlab_app_heap` and `memlab_artifact_budget` cannot drift: they
17
+ * are two views of the same partition, and two implementations of "what counts
18
+ * as the app" that disagree by a few percent is worse than either one alone,
19
+ * because the disagreement is invisible in each tool's own output.
20
+ */
21
+ const CODE_TYPES = new Set(['code']);
22
+ // Bundle source text and the V8 structures that hold it. These exist because
23
+ // the app was *loaded*, not because it did anything.
24
+ const BUNDLE_NAME_RE = /ExternalStringData|InstructionStream|BytecodeArray|SharedFunctionInfo|ScopeInfo|UncompiledData|FeedbackVector|FeedbackMetadata|FeedbackCell|ClosureFeedbackCellArray|\(constant pool\)|ConstantPool/;
25
+ export function computeHeapBudget(snapshot) {
26
+ const devRoots = collectDevRoots(snapshot);
27
+ const reached = devRoots.byId.size > 0
28
+ ? computeReachableWithoutDevRoots(snapshot, devRoots)
29
+ : null;
30
+ let total = 0;
31
+ let code = 0;
32
+ let bundle = 0;
33
+ let devOnly = 0;
34
+ let artifact = 0;
35
+ let app = 0;
36
+ let appNodes = 0;
37
+ const artifactByKind = new Map();
38
+ const isDevOnly = (node) => reached != null && reached[node.nodeIndex] === 0;
39
+ snapshot.nodes.forEach((node) => {
40
+ if (node.id <= 3)
41
+ return;
42
+ const size = node.self_size;
43
+ total += size;
44
+ // Precedence matters: a node is counted once, in the most specific bucket
45
+ // that explains why it is NOT app memory.
46
+ if (isDevOnly(node)) {
47
+ devOnly += size;
48
+ return;
49
+ }
50
+ if (CODE_TYPES.has(node.type)) {
51
+ code += size;
52
+ return;
53
+ }
54
+ if (BUNDLE_NAME_RE.test(node.name)) {
55
+ bundle += size;
56
+ return;
57
+ }
58
+ const kind = classifyArtifact(node.name);
59
+ if (kind != null) {
60
+ artifact += size;
61
+ artifactByKind.set(kind, (artifactByKind.get(kind) ?? 0) + size);
62
+ return;
63
+ }
64
+ app += size;
65
+ appNodes++;
66
+ });
67
+ return {
68
+ total,
69
+ app,
70
+ appNodes,
71
+ bundle,
72
+ code,
73
+ devOnly,
74
+ artifact,
75
+ artifactByKind,
76
+ devOnlyTotals: reached != null ? summarizeDevOnly(snapshot, devRoots, reached) : null,
77
+ };
78
+ }
79
+ export function devFamilyBytes(budget) {
80
+ const out = [];
81
+ const byCategory = budget.devOnlyTotals?.byCategory;
82
+ if (byCategory == null)
83
+ return out;
84
+ for (const [family, v] of byCategory) {
85
+ out.push({ family, nodes: v.nodes, selfBytes: v.selfBytes });
86
+ }
87
+ out.sort((a, b) => b.selfBytes - a.selfBytes);
88
+ return out;
89
+ }
90
+ //# sourceMappingURL=heap-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heap-budget.js","sourceRoot":"","sources":["../src/heap-budget.ts"],"names":[],"mappings":"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"}