memorydetective 1.13.0 → 1.15.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 (57) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +8 -2
  3. package/USAGE.md +29 -0
  4. package/dist/index.js +36 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/parsers/schemaDiscovery.d.ts +88 -0
  7. package/dist/parsers/schemaDiscovery.js +144 -0
  8. package/dist/parsers/schemaDiscovery.js.map +1 -0
  9. package/dist/parsers/xctraceXml.d.ts +5 -0
  10. package/dist/parsers/xctraceXml.js +3 -0
  11. package/dist/parsers/xctraceXml.js.map +1 -1
  12. package/dist/tools/analyzeAllocations.d.ts +5 -1
  13. package/dist/tools/analyzeAllocations.js +17 -1
  14. package/dist/tools/analyzeAllocations.js.map +1 -1
  15. package/dist/tools/analyzeAnimationHitches.d.ts +5 -1
  16. package/dist/tools/analyzeAnimationHitches.js +17 -1
  17. package/dist/tools/analyzeAnimationHitches.js.map +1 -1
  18. package/dist/tools/analyzeAppLaunch.d.ts +3 -0
  19. package/dist/tools/analyzeAppLaunch.js +17 -1
  20. package/dist/tools/analyzeAppLaunch.js.map +1 -1
  21. package/dist/tools/analyzeEnergyImpact.d.ts +69 -0
  22. package/dist/tools/analyzeEnergyImpact.js +239 -0
  23. package/dist/tools/analyzeEnergyImpact.js.map +1 -0
  24. package/dist/tools/analyzeHangs.d.ts +63 -3
  25. package/dist/tools/analyzeHangs.js +143 -19
  26. package/dist/tools/analyzeHangs.js.map +1 -1
  27. package/dist/tools/analyzeLeakTimeline.d.ts +75 -0
  28. package/dist/tools/analyzeLeakTimeline.js +213 -0
  29. package/dist/tools/analyzeLeakTimeline.js.map +1 -0
  30. package/dist/tools/analyzeMemoryFootprint.d.ts +72 -0
  31. package/dist/tools/analyzeMemoryFootprint.js +234 -0
  32. package/dist/tools/analyzeMemoryFootprint.js.map +1 -0
  33. package/dist/tools/analyzeNetworkActivity.d.ts +99 -0
  34. package/dist/tools/analyzeNetworkActivity.js +312 -0
  35. package/dist/tools/analyzeNetworkActivity.js.map +1 -0
  36. package/dist/tools/analyzeTimeProfile.d.ts +10 -1
  37. package/dist/tools/analyzeTimeProfile.js +63 -8
  38. package/dist/tools/analyzeTimeProfile.js.map +1 -1
  39. package/dist/tools/countAlive.d.ts +35 -1
  40. package/dist/tools/countAlive.js +124 -29
  41. package/dist/tools/countAlive.js.map +1 -1
  42. package/dist/tools/inspectTrace.js +124 -18
  43. package/dist/tools/inspectTrace.js.map +1 -1
  44. package/dist/tools/recordTimeProfile.d.ts +83 -0
  45. package/dist/tools/recordTimeProfile.js +135 -0
  46. package/dist/tools/recordTimeProfile.js.map +1 -1
  47. package/dist/tools/replayScenario.d.ts +20 -4
  48. package/dist/tools/replayScenario.js +66 -0
  49. package/dist/tools/replayScenario.js.map +1 -1
  50. package/dist/tools/summarizeTrace.d.ts +6 -3
  51. package/dist/tools/summarizeTrace.js +49 -2
  52. package/dist/tools/summarizeTrace.js.map +1 -1
  53. package/dist/tools/verifyFix.d.ts +27 -0
  54. package/dist/tools/verifyFix.js +78 -4
  55. package/dist/tools/verifyFix.js.map +1 -1
  56. package/dist/types.d.ts +28 -0
  57. package/package.json +2 -2
@@ -0,0 +1,213 @@
1
+ /**
2
+ * `analyzeLeakTimeline`: parses xctrace's leaks schema (the time-series
3
+ * instrument), distinct from `leaks(1)` CLI which is a snapshot. v1.15
4
+ * item E.
5
+ *
6
+ * The xctrace `leaks` instrument samples the heap periodically during
7
+ * recording and emits one row per leak event. Unlike leaks(1) (which
8
+ * gives you "what is leaked NOW") this gives you "when did the leak
9
+ * first appear, how did it grow, when did it peak?" Useful for fixing
10
+ * leaks that only fire under certain user flows.
11
+ *
12
+ * Output:
13
+ * - per-class first-seen-at timestamp
14
+ * - growth rate (instances over time)
15
+ * - peak instance count
16
+ * - aggregate event count
17
+ */
18
+ import { z } from "zod";
19
+ import { existsSync } from "node:fs";
20
+ import { resolve as resolvePath } from "node:path";
21
+ import { runCommand } from "../runtime/exec.js";
22
+ import { fetchDiscoveredSchemas } from "../parsers/schemaDiscovery.js";
23
+ import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
24
+ import { outputFormatField } from "../runtime/responseFormatter.js";
25
+ export const analyzeLeakTimelineSchema = z.object({
26
+ tracePath: z
27
+ .string()
28
+ .min(1)
29
+ .describe("Absolute path to a `.trace` bundle recorded with a Leaks template."),
30
+ topN: z
31
+ .number()
32
+ .int()
33
+ .positive()
34
+ .default(10)
35
+ .describe("Return the top N leaked classes ranked by peak instance count (default 10)."),
36
+ outputFormat: outputFormatField,
37
+ });
38
+ function pickNumber(row, keys) {
39
+ for (const k of keys) {
40
+ const v = asNumber(row[k]);
41
+ if (typeof v === "number" && Number.isFinite(v))
42
+ return v;
43
+ }
44
+ return undefined;
45
+ }
46
+ function pickString(row, keys) {
47
+ for (const k of keys) {
48
+ const v = asFormatted(row[k]);
49
+ if (v && v.trim().length > 0)
50
+ return v.trim();
51
+ }
52
+ return undefined;
53
+ }
54
+ /** Pure: turn the leaks XML into the timeline analysis. */
55
+ export function analyzeLeakTimelineFromXml(xml, tracePath, topN = 10) {
56
+ const tables = parseXctraceXml(xml);
57
+ // xctrace's Leaks instrument typically labels the schema "leaks" or
58
+ // "leak-events". Match conservatively against both.
59
+ const table = tables.find((t) => /^leaks?$/i.test(t.schema) || /leak-events?/i.test(t.schema));
60
+ if (!table) {
61
+ return {
62
+ ok: true,
63
+ tracePath,
64
+ totals: { rows: 0, classes: 0 },
65
+ topClasses: [],
66
+ diagnosis: "No leaks table found in the trace.",
67
+ status: "not_present",
68
+ supportStatus: [
69
+ {
70
+ kind: "leak-events",
71
+ status: "not_present",
72
+ reason: "Schema absent from the trace TOC.",
73
+ },
74
+ ],
75
+ };
76
+ }
77
+ const events = [];
78
+ for (const row of table.rows) {
79
+ const startNs = pickNumber(row, ["time", "event-time", "start", "sample-time"]) ?? 0;
80
+ const className = pickString(row, [
81
+ "class",
82
+ "class-name",
83
+ "type",
84
+ "type-name",
85
+ "leak-type",
86
+ ]) ?? "";
87
+ if (!className)
88
+ continue;
89
+ const cumulativeCount = pickNumber(row, [
90
+ "count",
91
+ "cumulative-count",
92
+ "instances",
93
+ ]);
94
+ const totalBytes = pickNumber(row, [
95
+ "bytes",
96
+ "size",
97
+ "total-bytes",
98
+ "leaked-bytes",
99
+ ]);
100
+ events.push({
101
+ startNs,
102
+ ...(asFormatted(row.time) ? { startFmt: asFormatted(row.time) } : {}),
103
+ className,
104
+ ...(cumulativeCount != null ? { cumulativeCount } : {}),
105
+ ...(totalBytes != null ? { totalBytes } : {}),
106
+ });
107
+ }
108
+ // Group events by class. Track first-seen-at, peakCount, peakBytes,
109
+ // event count per class.
110
+ const byClass = new Map();
111
+ for (const ev of events) {
112
+ const cur = byClass.get(ev.className) ??
113
+ {
114
+ className: ev.className,
115
+ firstSeenAtNs: ev.startNs,
116
+ firstSeenAtFmt: ev.startFmt,
117
+ peakCount: 0,
118
+ peakBytes: 0,
119
+ eventCount: 0,
120
+ };
121
+ if (ev.startNs < cur.firstSeenAtNs) {
122
+ cur.firstSeenAtNs = ev.startNs;
123
+ if (ev.startFmt)
124
+ cur.firstSeenAtFmt = ev.startFmt;
125
+ }
126
+ if (ev.cumulativeCount != null && ev.cumulativeCount > cur.peakCount) {
127
+ cur.peakCount = ev.cumulativeCount;
128
+ }
129
+ if (ev.totalBytes != null && ev.totalBytes > cur.peakBytes) {
130
+ cur.peakBytes = ev.totalBytes;
131
+ }
132
+ cur.eventCount += 1;
133
+ byClass.set(ev.className, cur);
134
+ }
135
+ const topClasses = Array.from(byClass.values())
136
+ .sort((a, b) => {
137
+ const peakDiff = b.peakCount - a.peakCount;
138
+ if (peakDiff !== 0)
139
+ return peakDiff;
140
+ return b.eventCount - a.eventCount;
141
+ })
142
+ .slice(0, topN);
143
+ const lastEventNs = events.reduce((max, e) => Math.max(max, e.startNs), 0);
144
+ return {
145
+ ok: true,
146
+ tracePath,
147
+ totals: {
148
+ rows: events.length,
149
+ classes: byClass.size,
150
+ ...(lastEventNs > 0 ? { lastEventNs } : {}),
151
+ },
152
+ topClasses,
153
+ diagnosis: buildDiagnosis(events.length, byClass.size, topClasses),
154
+ status: "available",
155
+ supportStatus: [
156
+ {
157
+ kind: "leak-events",
158
+ status: "available",
159
+ sourceSchemas: ["leaks"],
160
+ },
161
+ ],
162
+ };
163
+ }
164
+ function buildDiagnosis(rows, classes, topClasses) {
165
+ if (rows === 0) {
166
+ return "No leak events in the recording.";
167
+ }
168
+ const parts = [];
169
+ parts.push(`${rows} leak event${rows === 1 ? "" : "s"} across ${classes} class${classes === 1 ? "" : "es"}.`);
170
+ if (topClasses.length > 0) {
171
+ const top = topClasses[0];
172
+ parts.push(`Top leaked: \`${top.className}\` (peak ${top.peakCount} instances, first seen at ${(top.firstSeenAtNs / 1e9).toFixed(2)}s).`);
173
+ }
174
+ if (classes >= 5) {
175
+ parts.push("Multiple class signatures leaking. Suggests a shared cause (e.g. notification observer not removed) rather than a single one-off.");
176
+ }
177
+ return parts.join(" ");
178
+ }
179
+ export async function analyzeLeakTimeline(input) {
180
+ const tracePath = resolvePath(input.tracePath);
181
+ if (!existsSync(tracePath)) {
182
+ throw new Error(`Trace bundle not found: ${tracePath}`);
183
+ }
184
+ const { leaks: schemaName } = await fetchDiscoveredSchemas(runCommand, tracePath, ["leaks"]);
185
+ const result = await runCommand("xcrun", [
186
+ "xctrace",
187
+ "export",
188
+ "--input",
189
+ tracePath,
190
+ "--xpath",
191
+ `/trace-toc/run/data/table[@schema="${schemaName}"]`,
192
+ ], { timeoutMs: 5 * 60_000 });
193
+ if (result.code !== 0) {
194
+ return {
195
+ ok: true,
196
+ tracePath,
197
+ totals: { rows: 0, classes: 0 },
198
+ topClasses: [],
199
+ diagnosis: "Leaks schema not exportable from this trace (likely recorded with a non-Leaks template).",
200
+ status: "not_present",
201
+ supportStatus: [
202
+ {
203
+ kind: "leak-events",
204
+ status: "not_exportable",
205
+ reason: "xctrace export failed for the leaks schema family.",
206
+ sourceSchemas: [schemaName],
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ return analyzeLeakTimelineFromXml(result.stdout, tracePath, input.topN ?? 10);
212
+ }
213
+ //# sourceMappingURL=analyzeLeakTimeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzeLeakTimeline.js","sourceRoot":"","sources":["../../src/tools/analyzeLeakTimeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,GAEZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,oEAAoE,CACrE;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CACP,6EAA6E,CAC9E;IACH,YAAY,EAAE,iBAAiB;CAChC,CAAC,CAAC;AAgDH,SAAS,UAAU,CACjB,GAAiC,EACjC,IAAc;IAEd,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,UAAU,CACjB,GAAiC,EACjC,IAAc;IAEd,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,0BAA0B,CACxC,GAAW,EACX,SAAiB,EACjB,IAAI,GAAG,EAAE;IAET,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACpC,oEAAoE;IACpE,oDAAoD;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CACvB,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CACpE,CAAC;IACF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,IAAI;YACR,SAAS;YACT,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;YAC/B,UAAU,EAAE,EAAE;YACd,SAAS,EAAE,oCAAoC;YAC/C,MAAM,EAAE,aAAa;YACrB,aAAa,EAAE;gBACb;oBACE,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,aAAa;oBACrB,MAAM,EAAE,mCAAmC;iBAC5C;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC;QACrF,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,OAAO;YACP,YAAY;YACZ,MAAM;YACN,WAAW;YACX,WAAW;SACZ,CAAC,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,SAAS;YAAE,SAAS;QACzB,MAAM,eAAe,GAAG,UAAU,CAAC,GAAG,EAAE;YACtC,OAAO;YACP,kBAAkB;YAClB,WAAW;SACZ,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,OAAO;YACP,MAAM;YACN,aAAa;YACb,cAAc;SACf,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC;YACV,OAAO;YACP,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,SAAS;YACT,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,oEAAoE;IACpE,yBAAyB;IACzB,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAC;IACpD,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC;YACxB;gBACC,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,aAAa,EAAE,EAAE,CAAC,OAAO;gBACzB,cAAc,EAAE,EAAE,CAAC,QAAQ;gBAC3B,SAAS,EAAE,CAAC;gBACZ,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,CAAC;aACO,CAAC;QACzB,IAAI,EAAE,CAAC,OAAO,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;YACnC,GAAG,CAAC,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;YAC/B,IAAI,EAAE,CAAC,QAAQ;gBAAE,GAAG,CAAC,cAAc,GAAG,EAAE,CAAC,QAAQ,CAAC;QACpD,CAAC;QACD,IAAI,EAAE,CAAC,eAAe,IAAI,IAAI,IAAI,EAAE,CAAC,eAAe,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;YACrE,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,eAAe,CAAC;QACrC,CAAC;QACD,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;YAC3D,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,UAAU,CAAC;QAChC,CAAC;QACD,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;SAC5C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;QAC3C,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QACpC,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;IACrC,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAElB,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAE3E,OAAO;QACL,EAAE,EAAE,IAAI;QACR,SAAS;QACT,MAAM,EAAE;YACN,IAAI,EAAE,MAAM,CAAC,MAAM;YACnB,OAAO,EAAE,OAAO,CAAC,IAAI;YACrB,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C;QACD,UAAU;QACV,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC;QAClE,MAAM,EAAE,WAAW;QACnB,aAAa,EAAE;YACb;gBACE,IAAI,EAAE,aAAa;gBACnB,MAAM,EAAE,WAAW;gBACnB,aAAa,EAAE,CAAC,OAAO,CAAC;aACzB;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,IAAY,EACZ,OAAe,EACf,UAA8B;IAE9B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,kCAAkC,CAAC;IAC5C,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,GAAG,IAAI,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,OAAO,SAAS,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAClG,CAAC;IACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC1B,KAAK,CAAC,IAAI,CACR,iBAAiB,GAAG,CAAC,SAAS,YAAY,GAAG,CAAC,SAAS,6BAA6B,CAAC,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC9H,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACR,mIAAmI,CACpI,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAA+B;IAE/B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,sBAAsB,CACxD,UAAU,EACV,SAAS,EACT,CAAC,OAAO,CAAU,CACnB,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,OAAO,EACP;QACE,SAAS;QACT,QAAQ;QACR,SAAS;QACT,SAAS;QACT,SAAS;QACT,sCAAsC,UAAU,IAAI;KACrD,EACD,EAAE,SAAS,EAAE,CAAC,GAAG,MAAM,EAAE,CAC1B,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,EAAE,EAAE,IAAI;YACR,SAAS;YACT,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;YAC/B,UAAU,EAAE,EAAE;YACd,SAAS,EACP,0FAA0F;YAC5F,MAAM,EAAE,aAAa;YACrB,aAAa,EAAE;gBACb;oBACE,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,gBAAgB;oBACxB,MAAM,EAAE,oDAAoD;oBAC5D,aAAa,EAAE,CAAC,UAAU,CAAC;iBAC5B;aACF;SACF,CAAC;IACJ,CAAC;IACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `analyzeMemoryFootprint`: parses xctrace's memory-footprint schema. v1.15 item C.
3
+ *
4
+ * Distinct from analyzeAllocations: that tool surfaces category-level
5
+ * cumulative allocation bytes ("which classes are bloated?"). This tool
6
+ * surfaces process-level VM state ("how much RAM is the OS giving us
7
+ * right now, and where is it going?"): resident memory, dirty memory,
8
+ * VM regions, and the timeline of pressure events.
9
+ *
10
+ * The "why is my app getting OOM-killed?" investigation. iOS jetsam
11
+ * decisions are made based on dirty + footprint, not cumulative malloc.
12
+ */
13
+ import { z } from "zod";
14
+ import type { DataStatus, SupportStatus } from "../types.js";
15
+ export declare const analyzeMemoryFootprintSchema: z.ZodObject<{
16
+ tracePath: z.ZodString;
17
+ topN: z.ZodDefault<z.ZodNumber>;
18
+ outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ tracePath: string;
21
+ topN: number;
22
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
23
+ }, {
24
+ tracePath: string;
25
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
26
+ topN?: number | undefined;
27
+ }>;
28
+ export type AnalyzeMemoryFootprintInput = z.infer<typeof analyzeMemoryFootprintSchema>;
29
+ export interface MemoryFootprintSample {
30
+ startNs: number;
31
+ startFmt?: string;
32
+ /** Resident memory in bytes (RAM the process is using right now). */
33
+ residentBytes?: number;
34
+ /** Dirty memory in bytes (the OOM-kill discriminator on iOS). */
35
+ dirtyBytes?: number;
36
+ /** Compressed memory in bytes. */
37
+ compressedBytes?: number;
38
+ /** Virtual memory in bytes (address-space, not necessarily resident). */
39
+ virtualBytes?: number;
40
+ /** Optional sample-level label (e.g. "memory-warning", "background-event"). */
41
+ label?: string;
42
+ }
43
+ export interface AnalyzeMemoryFootprintResult {
44
+ ok: boolean;
45
+ tracePath: string;
46
+ totals: {
47
+ rows: number;
48
+ /** Peak resident bytes seen across all samples. */
49
+ peakResidentBytes: number;
50
+ /** Peak dirty bytes seen across all samples. */
51
+ peakDirtyBytes: number;
52
+ /** Average resident bytes across all samples. */
53
+ averageResidentBytes: number;
54
+ /** Time of peak resident as ns offset from recording start. */
55
+ peakResidentAtNs?: number;
56
+ };
57
+ /** Top N samples ranked by resident bytes desc. */
58
+ topByResident: MemoryFootprintSample[];
59
+ diagnosis: string;
60
+ /** @deprecated v1.14 item I. Use `supportStatus[]`. */
61
+ status: DataStatus;
62
+ /** v1.14+. Unified per-area status. */
63
+ supportStatus: SupportStatus[];
64
+ }
65
+ /**
66
+ * Format a byte count as human-friendly KB/MB. v1.15. Exported for
67
+ * the diagnosis text and downstream callers.
68
+ */
69
+ export declare function formatBytes(n: number | undefined): string;
70
+ /** Pure: turn the memory-footprint XML into the analyzed result. */
71
+ export declare function analyzeMemoryFootprintFromXml(xml: string, tracePath: string, topN?: number): AnalyzeMemoryFootprintResult;
72
+ export declare function analyzeMemoryFootprint(input: AnalyzeMemoryFootprintInput): Promise<AnalyzeMemoryFootprintResult>;
@@ -0,0 +1,234 @@
1
+ /**
2
+ * `analyzeMemoryFootprint`: parses xctrace's memory-footprint schema. v1.15 item C.
3
+ *
4
+ * Distinct from analyzeAllocations: that tool surfaces category-level
5
+ * cumulative allocation bytes ("which classes are bloated?"). This tool
6
+ * surfaces process-level VM state ("how much RAM is the OS giving us
7
+ * right now, and where is it going?"): resident memory, dirty memory,
8
+ * VM regions, and the timeline of pressure events.
9
+ *
10
+ * The "why is my app getting OOM-killed?" investigation. iOS jetsam
11
+ * decisions are made based on dirty + footprint, not cumulative malloc.
12
+ */
13
+ import { z } from "zod";
14
+ import { existsSync } from "node:fs";
15
+ import { resolve as resolvePath } from "node:path";
16
+ import { runCommand } from "../runtime/exec.js";
17
+ import { fetchDiscoveredSchemas } from "../parsers/schemaDiscovery.js";
18
+ import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
19
+ import { outputFormatField } from "../runtime/responseFormatter.js";
20
+ export const analyzeMemoryFootprintSchema = z.object({
21
+ tracePath: z
22
+ .string()
23
+ .min(1)
24
+ .describe("Absolute path to a `.trace` bundle recorded with an Allocations or System Trace template that includes the memory-footprint instrument."),
25
+ topN: z
26
+ .number()
27
+ .int()
28
+ .positive()
29
+ .default(10)
30
+ .describe("Return the top N memory snapshots ranked by resident bytes (default 10)."),
31
+ outputFormat: outputFormatField,
32
+ });
33
+ function pickNumber(row, keys) {
34
+ for (const k of keys) {
35
+ const v = asNumber(row[k]);
36
+ if (typeof v === "number" && Number.isFinite(v))
37
+ return v;
38
+ }
39
+ return undefined;
40
+ }
41
+ function pickString(row, keys) {
42
+ for (const k of keys) {
43
+ const v = asFormatted(row[k]);
44
+ if (v && v.trim().length > 0)
45
+ return v.trim();
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Format a byte count as human-friendly KB/MB. v1.15. Exported for
51
+ * the diagnosis text and downstream callers.
52
+ */
53
+ export function formatBytes(n) {
54
+ if (n == null || !Number.isFinite(n))
55
+ return "n/a";
56
+ if (n < 1024)
57
+ return `${n} B`;
58
+ if (n < 1024 * 1024)
59
+ return `${(n / 1024).toFixed(1)} KB`;
60
+ if (n < 1024 * 1024 * 1024)
61
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
62
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
63
+ }
64
+ /** Pure: turn the memory-footprint XML into the analyzed result. */
65
+ export function analyzeMemoryFootprintFromXml(xml, tracePath, topN = 10) {
66
+ const tables = parseXctraceXml(xml);
67
+ // The memory-footprint schema may appear under different names
68
+ // depending on the template (memory-footprint, resident-memory, etc).
69
+ // Match conservatively against the canonical name first, then any
70
+ // schema whose name matches the SCHEMA_FAMILIES.memory patterns
71
+ // already validated in schemaDiscovery.
72
+ const table = tables.find((t) => t.schema === "memory-footprint" ||
73
+ /memory-footprint/i.test(t.schema) ||
74
+ /resident-memory/i.test(t.schema));
75
+ if (!table) {
76
+ return {
77
+ ok: true,
78
+ tracePath,
79
+ totals: {
80
+ rows: 0,
81
+ peakResidentBytes: 0,
82
+ peakDirtyBytes: 0,
83
+ averageResidentBytes: 0,
84
+ },
85
+ topByResident: [],
86
+ diagnosis: "No memory-footprint table found in the trace.",
87
+ status: "not_present",
88
+ supportStatus: [
89
+ {
90
+ kind: "memory-footprint",
91
+ status: "not_present",
92
+ reason: "Schema absent from the trace TOC.",
93
+ },
94
+ ],
95
+ };
96
+ }
97
+ const samples = [];
98
+ for (const row of table.rows) {
99
+ const startNs = pickNumber(row, ["time", "sample-time", "event-time", "start"]) ?? 0;
100
+ const residentBytes = pickNumber(row, [
101
+ "resident",
102
+ "resident-bytes",
103
+ "resident-memory",
104
+ "phys",
105
+ "phys-footprint",
106
+ ]);
107
+ const dirtyBytes = pickNumber(row, [
108
+ "dirty",
109
+ "dirty-bytes",
110
+ "dirty-memory",
111
+ "private-dirty",
112
+ ]);
113
+ const compressedBytes = pickNumber(row, [
114
+ "compressed",
115
+ "compressed-bytes",
116
+ "compressed-memory",
117
+ ]);
118
+ const virtualBytes = pickNumber(row, [
119
+ "virtual",
120
+ "virtual-bytes",
121
+ "vm-size",
122
+ "vsize",
123
+ ]);
124
+ const label = pickString(row, ["label", "event", "event-type", "category"]);
125
+ samples.push({
126
+ startNs,
127
+ ...(asFormatted(row.time) ? { startFmt: asFormatted(row.time) } : {}),
128
+ ...(residentBytes != null ? { residentBytes } : {}),
129
+ ...(dirtyBytes != null ? { dirtyBytes } : {}),
130
+ ...(compressedBytes != null ? { compressedBytes } : {}),
131
+ ...(virtualBytes != null ? { virtualBytes } : {}),
132
+ ...(label ? { label } : {}),
133
+ });
134
+ }
135
+ const residents = samples
136
+ .map((s) => s.residentBytes)
137
+ .filter((v) => v != null);
138
+ const dirties = samples
139
+ .map((s) => s.dirtyBytes)
140
+ .filter((v) => v != null);
141
+ const peakResidentBytes = residents.length > 0 ? Math.max(...residents) : 0;
142
+ const peakDirtyBytes = dirties.length > 0 ? Math.max(...dirties) : 0;
143
+ const averageResidentBytes = residents.length > 0
144
+ ? residents.reduce((a, b) => a + b, 0) / residents.length
145
+ : 0;
146
+ const peakSample = samples.find((s) => s.residentBytes === peakResidentBytes);
147
+ const peakResidentAtNs = peakSample?.startNs;
148
+ const topByResident = [...samples]
149
+ .sort((a, b) => (b.residentBytes ?? 0) - (a.residentBytes ?? 0))
150
+ .slice(0, topN);
151
+ return {
152
+ ok: true,
153
+ tracePath,
154
+ totals: {
155
+ rows: samples.length,
156
+ peakResidentBytes,
157
+ peakDirtyBytes,
158
+ averageResidentBytes,
159
+ ...(peakResidentAtNs != null ? { peakResidentAtNs } : {}),
160
+ },
161
+ topByResident,
162
+ diagnosis: buildDiagnosis(samples.length, peakResidentBytes, peakDirtyBytes),
163
+ status: "available",
164
+ supportStatus: [
165
+ {
166
+ // The SupportStatusKind enum doesn't have memory-footprint yet;
167
+ // tagging as potential-hangs is wrong. We extend the enum in a
168
+ // follow-up; for now use a generic kind and put the real schema
169
+ // name in sourceSchemas so callers branch on that.
170
+ kind: "memory-footprint",
171
+ status: "available",
172
+ sourceSchemas: ["memory-footprint"],
173
+ },
174
+ ],
175
+ };
176
+ }
177
+ function buildDiagnosis(rows, peakResidentBytes, peakDirtyBytes) {
178
+ if (rows === 0) {
179
+ return "No memory-footprint samples in the recording.";
180
+ }
181
+ const parts = [];
182
+ parts.push(`${rows} memory snapshots.`);
183
+ if (peakResidentBytes > 0) {
184
+ parts.push(`Peak resident: ${formatBytes(peakResidentBytes)}.`);
185
+ }
186
+ if (peakDirtyBytes > 0) {
187
+ parts.push(`Peak dirty: ${formatBytes(peakDirtyBytes)}.`);
188
+ }
189
+ // Apple's jetsam thresholds vary by device class but ~200MB dirty is
190
+ // a reasonable "you're getting close to OOM" line for most apps.
191
+ if (peakDirtyBytes > 200 * 1024 * 1024) {
192
+ parts.push("Peak dirty memory above 200 MB. Approaching jetsam territory on smaller devices.");
193
+ }
194
+ return parts.join(" ");
195
+ }
196
+ export async function analyzeMemoryFootprint(input) {
197
+ const tracePath = resolvePath(input.tracePath);
198
+ if (!existsSync(tracePath)) {
199
+ throw new Error(`Trace bundle not found: ${tracePath}`);
200
+ }
201
+ const { memory: schemaName } = await fetchDiscoveredSchemas(runCommand, tracePath, ["memory"]);
202
+ const result = await runCommand("xcrun", [
203
+ "xctrace",
204
+ "export",
205
+ "--input",
206
+ tracePath,
207
+ "--xpath",
208
+ `/trace-toc/run/data/table[@schema="${schemaName}"]`,
209
+ ], { timeoutMs: 5 * 60_000 });
210
+ if (result.code !== 0) {
211
+ return {
212
+ ok: true,
213
+ tracePath,
214
+ totals: {
215
+ rows: 0,
216
+ peakResidentBytes: 0,
217
+ peakDirtyBytes: 0,
218
+ averageResidentBytes: 0,
219
+ },
220
+ topByResident: [],
221
+ diagnosis: "Memory-footprint schema not exportable from this trace (likely recorded with a non-Allocations / non-System-Trace template).",
222
+ status: "not_present",
223
+ supportStatus: [
224
+ {
225
+ kind: "potential-hangs",
226
+ status: "not_exportable",
227
+ reason: "xctrace export failed for the memory schema family.",
228
+ },
229
+ ],
230
+ };
231
+ }
232
+ return analyzeMemoryFootprintFromXml(result.stdout, tracePath, input.topN ?? 10);
233
+ }
234
+ //# sourceMappingURL=analyzeMemoryFootprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzeMemoryFootprint.js","sourceRoot":"","sources":["../../src/tools/analyzeMemoryFootprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `analyzeNetworkActivity`: parses xctrace's network-connections schema
3
+ * from a `.trace` recorded with the Network Profile template. v1.14 item A.
4
+ *
5
+ * "The network is slow" / "my SDK is chatty" / "slow launch because of
6
+ * one API call" are top-3 iOS perf complaints. Pre-v1.14 we had zero
7
+ * coverage of the network family. XcodeTraceMCP's regex map listed it
8
+ * as one of their five instrument families. This analyzer closes the
9
+ * gap with the same shape as analyzeHangs / analyzeAnimationHitches:
10
+ *
11
+ * - Bytes-in/out, duration, status-code, and URL/host extracted per
12
+ * connection.
13
+ * - Aggregates: total bytes, slowest response, average response, count
14
+ * per HTTP status bucket.
15
+ * - Top-N by duration (the "which calls blocked the user?" view) and
16
+ * top-N by bytes (the "which calls are bloating my budget?" view).
17
+ * - Per-host aggregates surfacing chatty SDKs without manually grouping.
18
+ *
19
+ * Resilient to column-name drift across xctrace versions: each field
20
+ * is looked up under multiple plausible mnemonics, falling back to the
21
+ * one that yields data.
22
+ */
23
+ import { z } from "zod";
24
+ import type { DataStatus, SupportStatus } from "../types.js";
25
+ export declare const analyzeNetworkActivitySchema: z.ZodObject<{
26
+ tracePath: z.ZodString;
27
+ topN: z.ZodDefault<z.ZodNumber>;
28
+ minBytes: z.ZodDefault<z.ZodNumber>;
29
+ outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ tracePath: string;
32
+ topN: number;
33
+ minBytes: number;
34
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
35
+ }, {
36
+ tracePath: string;
37
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
38
+ topN?: number | undefined;
39
+ minBytes?: number | undefined;
40
+ }>;
41
+ export type AnalyzeNetworkActivityInput = z.infer<typeof analyzeNetworkActivitySchema>;
42
+ export interface NetworkConnectionEntry {
43
+ /** Start timestamp in nanoseconds since recording start. */
44
+ startNs: number;
45
+ startFmt?: string;
46
+ /** Response/transaction duration in nanoseconds when available. */
47
+ durationNs?: number;
48
+ durationMs?: number;
49
+ durationFmt?: string;
50
+ /** URL or hostname (whichever the trace exposed). */
51
+ url?: string;
52
+ /** Host portion of the URL, when parseable. */
53
+ host?: string;
54
+ /** HTTP method (GET, POST, etc.) when present. */
55
+ method?: string;
56
+ /** HTTP response status code. */
57
+ statusCode?: number;
58
+ /** Bytes received from the server (response body + headers). */
59
+ bytesIn?: number;
60
+ /** Bytes sent to the server (request body + headers). */
61
+ bytesOut?: number;
62
+ }
63
+ export interface NetworkHostAggregate {
64
+ host: string;
65
+ count: number;
66
+ bytesIn: number;
67
+ bytesOut: number;
68
+ longestMs: number;
69
+ }
70
+ export interface AnalyzeNetworkActivityResult {
71
+ ok: boolean;
72
+ tracePath: string;
73
+ totals: {
74
+ rows: number;
75
+ totalBytesIn: number;
76
+ totalBytesOut: number;
77
+ longestMs: number;
78
+ averageMs: number;
79
+ /** Status-code bucket counts. Example: `{ "2xx": 47, "4xx": 3, "5xx": 1, "n/a": 12 }`. */
80
+ statusBuckets: Record<string, number>;
81
+ };
82
+ /** Top N connections ranked by `durationMs` desc. */
83
+ topByDuration: NetworkConnectionEntry[];
84
+ /** Top N connections ranked by `bytesIn + bytesOut` desc. */
85
+ topByBytes: NetworkConnectionEntry[];
86
+ /** Per-host aggregates, ranked by request count desc. */
87
+ byHost: NetworkHostAggregate[];
88
+ diagnosis: string;
89
+ /** @deprecated v1.14 item I. Use `supportStatus[]` instead. */
90
+ status: DataStatus;
91
+ /** v1.14+. Unified per-area status. See {@link SupportStatus}. */
92
+ supportStatus: SupportStatus[];
93
+ }
94
+ /** Extract a host string from a URL-or-host value. Falls back to the
95
+ * raw input when it does not look like a URL (already a host). */
96
+ export declare function extractHost(urlOrHost: string | undefined): string | undefined;
97
+ /** Pure: turn the network-connections XML into the analyzed result. */
98
+ export declare function analyzeNetworkActivityFromXml(xml: string, tracePath: string, topN?: number, minBytes?: number): AnalyzeNetworkActivityResult;
99
+ export declare function analyzeNetworkActivity(input: AnalyzeNetworkActivityInput): Promise<AnalyzeNetworkActivityResult>;