memorydetective 1.14.0 → 1.16.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.
@@ -0,0 +1,75 @@
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 type { DataStatus, SupportStatus } from "../types.js";
20
+ export declare const analyzeLeakTimelineSchema: z.ZodObject<{
21
+ tracePath: z.ZodString;
22
+ topN: z.ZodDefault<z.ZodNumber>;
23
+ outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
24
+ }, "strip", z.ZodTypeAny, {
25
+ tracePath: string;
26
+ topN: number;
27
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
28
+ }, {
29
+ tracePath: string;
30
+ outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
31
+ topN?: number | undefined;
32
+ }>;
33
+ export type AnalyzeLeakTimelineInput = z.infer<typeof analyzeLeakTimelineSchema>;
34
+ export interface LeakEvent {
35
+ startNs: number;
36
+ startFmt?: string;
37
+ className: string;
38
+ /** Cumulative count of this class at this point in the recording. */
39
+ cumulativeCount?: number;
40
+ /** Bytes leaked at this point when xctrace exposes a size column. */
41
+ totalBytes?: number;
42
+ }
43
+ export interface LeakClassSummary {
44
+ className: string;
45
+ /** Timestamp the class first appeared as a leak. */
46
+ firstSeenAtNs: number;
47
+ firstSeenAtFmt?: string;
48
+ /** Highest cumulativeCount observed across all events. */
49
+ peakCount: number;
50
+ /** Highest totalBytes observed across all events. */
51
+ peakBytes: number;
52
+ /** Total events emitted for this class. */
53
+ eventCount: number;
54
+ }
55
+ export interface AnalyzeLeakTimelineResult {
56
+ ok: boolean;
57
+ tracePath: string;
58
+ totals: {
59
+ rows: number;
60
+ /** Distinct class count across the timeline. */
61
+ classes: number;
62
+ /** Latest timestamp seen, useful as a "leaks were still growing" check. */
63
+ lastEventNs?: number;
64
+ };
65
+ /** Top N classes ranked by peakCount desc. */
66
+ topClasses: LeakClassSummary[];
67
+ diagnosis: string;
68
+ /** @deprecated v1.14 item I. Use `supportStatus[]`. */
69
+ status: DataStatus;
70
+ /** v1.14+. Unified per-area status. */
71
+ supportStatus: SupportStatus[];
72
+ }
73
+ /** Pure: turn the leaks XML into the timeline analysis. */
74
+ export declare function analyzeLeakTimelineFromXml(xml: string, tracePath: string, topN?: number): AnalyzeLeakTimelineResult;
75
+ export declare function analyzeLeakTimeline(input: AnalyzeLeakTimelineInput): Promise<AnalyzeLeakTimelineResult>;
@@ -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":"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"}
@@ -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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"}
@@ -66,6 +66,18 @@ const SCHEMA_TO_ANALYZER = {
66
66
  tool: "analyzeNetworkActivity",
67
67
  description: "Per-request URL / host / method / status / response time / bytes. Top-N by duration and by bytes plus per-host aggregates.",
68
68
  },
69
+ "memory-footprint": {
70
+ tool: "analyzeMemoryFootprint",
71
+ description: "Peak resident / dirty / virtual bytes plus per-sample timeline. The OOM-kill discriminator on iOS.",
72
+ },
73
+ "energy-impact": {
74
+ tool: "analyzeEnergyImpact",
75
+ description: "Per-sample bucket (idle / passive / active / high), aggregate wakeups, active ratio, top samples by energy cost.",
76
+ },
77
+ leaks: {
78
+ tool: "analyzeLeakTimeline",
79
+ description: "Time series of leak events: per-class first-seen-at, peak count, peak bytes, event count. Answers 'when did the leak appear?'.",
80
+ },
69
81
  };
70
82
  /** Pure: parse the trace-toc XML payload into an inspection result. */
71
83
  export function parseTraceToc(xml, tracePath) {