memorydetective 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1
- package/README.md +7 -6
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/leaksDebugStacks.d.ts +67 -0
- package/dist/parsers/leaksDebugStacks.js +187 -0
- package/dist/parsers/leaksDebugStacks.js.map +1 -0
- package/dist/parsers/referenceTree.js +5 -0
- package/dist/parsers/referenceTree.js.map +1 -1
- package/dist/tools/analyzeHangs.d.ts +28 -0
- package/dist/tools/analyzeHangs.js +136 -9
- package/dist/tools/analyzeHangs.js.map +1 -1
- package/dist/tools/compareTracesByPattern.js +2 -0
- package/dist/tools/compareTracesByPattern.js.map +1 -1
- package/dist/tools/countAlive.d.ts +20 -4
- package/dist/tools/countAlive.js +91 -12
- package/dist/tools/countAlive.js.map +1 -1
- package/dist/tools/diffMemgraphs.d.ts +56 -3
- package/dist/tools/diffMemgraphs.js +99 -2
- package/dist/tools/diffMemgraphs.js.map +1 -1
- package/dist/tools/findRetainers.d.ts +15 -1
- package/dist/tools/findRetainers.js +51 -5
- package/dist/tools/findRetainers.js.map +1 -1
- package/dist/tools/inspectTrace.d.ts +72 -0
- package/dist/tools/inspectTrace.js +181 -0
- package/dist/tools/inspectTrace.js.map +1 -0
- package/dist/tools/verifyFix.d.ts +23 -0
- package/dist/tools/verifyFix.js +121 -1
- package/dist/tools/verifyFix.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type ReferenceTreeEntry } from "../parsers/referenceTree.js";
|
|
2
3
|
import type { LeaksReport } from "../types.js";
|
|
3
4
|
export declare const diffMemgraphsSchema: z.ZodObject<{
|
|
4
5
|
before: z.ZodString;
|
|
@@ -26,6 +27,15 @@ export interface CycleDiffEntry {
|
|
|
26
27
|
afterCount: number;
|
|
27
28
|
delta: number;
|
|
28
29
|
}
|
|
30
|
+
export interface ReferenceTreeDiffEntry {
|
|
31
|
+
className: string;
|
|
32
|
+
before: number;
|
|
33
|
+
after: number;
|
|
34
|
+
delta: number;
|
|
35
|
+
beforeBytes: number;
|
|
36
|
+
afterBytes: number;
|
|
37
|
+
bytesDelta: number;
|
|
38
|
+
}
|
|
29
39
|
export interface DiffMemgraphsResult {
|
|
30
40
|
ok: boolean;
|
|
31
41
|
before: {
|
|
@@ -39,16 +49,24 @@ export interface DiffMemgraphsResult {
|
|
|
39
49
|
totals: {
|
|
40
50
|
leakCountDelta: number;
|
|
41
51
|
bytesLeakedDelta: number;
|
|
52
|
+
/**
|
|
53
|
+
* Net instance-count delta from the reference-tree (heap-wide) view.
|
|
54
|
+
* Sum of all `referenceTreeChanges` deltas. Useful for "did the heap
|
|
55
|
+
* shrink overall?" without scrolling per-class. New in v1.11.
|
|
56
|
+
*/
|
|
57
|
+
referenceTreeInstanceDelta?: number;
|
|
58
|
+
/** Net bytes delta from the reference-tree view. New in v1.11. */
|
|
59
|
+
referenceTreeBytesDelta?: number;
|
|
42
60
|
};
|
|
43
61
|
classCounts: {
|
|
44
|
-
/** Classes whose count went up (potential new leaks). */
|
|
62
|
+
/** Classes whose count went up (potential new leaks). Cycle-based view. */
|
|
45
63
|
increased: Array<{
|
|
46
64
|
className: string;
|
|
47
65
|
before: number;
|
|
48
66
|
after: number;
|
|
49
67
|
delta: number;
|
|
50
68
|
}>;
|
|
51
|
-
/** Classes whose count went down (fixed leaks or just gone). */
|
|
69
|
+
/** Classes whose count went down (fixed leaks or just gone). Cycle-based view. */
|
|
52
70
|
decreased: Array<{
|
|
53
71
|
className: string;
|
|
54
72
|
before: number;
|
|
@@ -56,15 +74,50 @@ export interface DiffMemgraphsResult {
|
|
|
56
74
|
delta: number;
|
|
57
75
|
}>;
|
|
58
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Heap-wide class-count changes from the reference-tree pass. Populated
|
|
79
|
+
* even when `leakCount` is 0 in both snapshots (which is exactly when
|
|
80
|
+
* cycle-only `classCounts` returns empty and the user wants this view).
|
|
81
|
+
* Includes framework noise (NSMutableDictionary, CFString, etc.); see
|
|
82
|
+
* `actionableReferenceTreeChanges` for the filtered view. New in v1.11.
|
|
83
|
+
*/
|
|
84
|
+
referenceTreeChanges?: {
|
|
85
|
+
increased: ReferenceTreeDiffEntry[];
|
|
86
|
+
decreased: ReferenceTreeDiffEntry[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* `referenceTreeChanges` with framework noise filtered out via
|
|
90
|
+
* `isFrameworkNoise`. Surfaces AV / KVO / app-level classes for the
|
|
91
|
+
* verify-fix loop without scrolling past Foundation collection growth.
|
|
92
|
+
* New in v1.11.
|
|
93
|
+
*/
|
|
94
|
+
actionableReferenceTreeChanges?: {
|
|
95
|
+
increased: ReferenceTreeDiffEntry[];
|
|
96
|
+
decreased: ReferenceTreeDiffEntry[];
|
|
97
|
+
};
|
|
59
98
|
cycles: {
|
|
60
99
|
/** ROOT CYCLE signatures present only in `after`. */
|
|
61
100
|
newInAfter: CycleDiffEntry[];
|
|
62
101
|
/** Signatures present only in `before` (cycle disappeared). */
|
|
63
102
|
goneFromBefore: CycleDiffEntry[];
|
|
64
|
-
/** Signatures present in both
|
|
103
|
+
/** Signatures present in both; count change is what matters. */
|
|
65
104
|
persisted: CycleDiffEntry[];
|
|
66
105
|
};
|
|
67
106
|
}
|
|
68
107
|
/** Pure: compare two parsed reports and return a structured diff. */
|
|
69
108
|
export declare function diffReports(before: LeaksReport, after: LeaksReport, beforePath: string, afterPath: string): DiffMemgraphsResult;
|
|
109
|
+
/**
|
|
110
|
+
* Pure: diff two reference-tree entry lists by class name and return
|
|
111
|
+
* increased / decreased buckets. Each entry carries before/after counts,
|
|
112
|
+
* bytes, and delta values. Sorted: increased by delta desc, decreased by
|
|
113
|
+
* delta asc (most-negative first).
|
|
114
|
+
*
|
|
115
|
+
* Returns null when both inputs are empty. The async wrapper uses this
|
|
116
|
+
* absence to suppress the `referenceTreeChanges` field on the result so
|
|
117
|
+
* cycle-only callers see no change vs v1.10. New in v1.11.
|
|
118
|
+
*/
|
|
119
|
+
export declare function diffReferenceTrees(before: ReferenceTreeEntry[], after: ReferenceTreeEntry[]): {
|
|
120
|
+
increased: ReferenceTreeDiffEntry[];
|
|
121
|
+
decreased: ReferenceTreeDiffEntry[];
|
|
122
|
+
} | null;
|
|
70
123
|
export declare function diffMemgraphs(input: DiffMemgraphsInput): Promise<DiffMemgraphsResult>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../runtime/exec.js";
|
|
2
3
|
import { runLeaksAndParse } from "../runtime/leaks.js";
|
|
3
4
|
import { rootCyclesOnly } from "../parsers/leaksOutput.js";
|
|
5
|
+
import { parseReferenceTreeText, isFrameworkNoise, } from "../parsers/referenceTree.js";
|
|
4
6
|
import { countByClass } from "./countAlive.js";
|
|
5
7
|
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
6
8
|
export const diffMemgraphsSchema = z.object({
|
|
@@ -104,11 +106,106 @@ export function diffReports(before, after, beforePath, afterPath) {
|
|
|
104
106
|
cycles: { newInAfter, goneFromBefore, persisted },
|
|
105
107
|
};
|
|
106
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Pure: diff two reference-tree entry lists by class name and return
|
|
111
|
+
* increased / decreased buckets. Each entry carries before/after counts,
|
|
112
|
+
* bytes, and delta values. Sorted: increased by delta desc, decreased by
|
|
113
|
+
* delta asc (most-negative first).
|
|
114
|
+
*
|
|
115
|
+
* Returns null when both inputs are empty. The async wrapper uses this
|
|
116
|
+
* absence to suppress the `referenceTreeChanges` field on the result so
|
|
117
|
+
* cycle-only callers see no change vs v1.10. New in v1.11.
|
|
118
|
+
*/
|
|
119
|
+
export function diffReferenceTrees(before, after) {
|
|
120
|
+
if (before.length === 0 && after.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
const beforeByClass = new Map(before.map((e) => [e.className, e]));
|
|
123
|
+
const afterByClass = new Map(after.map((e) => [e.className, e]));
|
|
124
|
+
const allClasses = new Set([
|
|
125
|
+
...beforeByClass.keys(),
|
|
126
|
+
...afterByClass.keys(),
|
|
127
|
+
]);
|
|
128
|
+
const increased = [];
|
|
129
|
+
const decreased = [];
|
|
130
|
+
for (const cls of allClasses) {
|
|
131
|
+
const b = beforeByClass.get(cls);
|
|
132
|
+
const a = afterByClass.get(cls);
|
|
133
|
+
const beforeCount = b?.instanceCount ?? 0;
|
|
134
|
+
const afterCount = a?.instanceCount ?? 0;
|
|
135
|
+
const delta = afterCount - beforeCount;
|
|
136
|
+
if (delta === 0)
|
|
137
|
+
continue;
|
|
138
|
+
const beforeBytes = b?.totalBytes ?? 0;
|
|
139
|
+
const afterBytes = a?.totalBytes ?? 0;
|
|
140
|
+
const entry = {
|
|
141
|
+
className: cls,
|
|
142
|
+
before: beforeCount,
|
|
143
|
+
after: afterCount,
|
|
144
|
+
delta,
|
|
145
|
+
beforeBytes,
|
|
146
|
+
afterBytes,
|
|
147
|
+
bytesDelta: afterBytes - beforeBytes,
|
|
148
|
+
};
|
|
149
|
+
if (delta > 0)
|
|
150
|
+
increased.push(entry);
|
|
151
|
+
else
|
|
152
|
+
decreased.push(entry);
|
|
153
|
+
}
|
|
154
|
+
increased.sort((x, y) => y.delta - x.delta || y.bytesDelta - x.bytesDelta);
|
|
155
|
+
decreased.sort((x, y) => x.delta - y.delta || x.bytesDelta - y.bytesDelta);
|
|
156
|
+
return { increased, decreased };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Wide capture pool for the reference-tree pass. Mirrors analyzeMemgraph's
|
|
160
|
+
* 10x heuristic from v1.10: the actionable view filters out framework
|
|
161
|
+
* noise (NSMutableDictionary, CFString, libMainThreadChecker bss, etc.)
|
|
162
|
+
* so we need enough headroom for app-level classes ranked below the
|
|
163
|
+
* noise leaders to survive into the post-filter top.
|
|
164
|
+
*/
|
|
165
|
+
const REFERENCE_TREE_DIFF_TOPN = 1000;
|
|
166
|
+
/** Spawn `leaks --referenceTree --groupByType --noContent` against a
|
|
167
|
+
* `.memgraph` and return parsed entries. Failure is non-fatal: returns
|
|
168
|
+
* an empty array so the cycle-side diff still completes. */
|
|
169
|
+
async function captureReferenceTree(path) {
|
|
170
|
+
const result = await runCommand("leaks", ["--referenceTree", "--groupByType", "--noContent", path], { timeoutMs: 5 * 60_000 });
|
|
171
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
return parseReferenceTreeText(result.stdout, REFERENCE_TREE_DIFF_TOPN);
|
|
175
|
+
}
|
|
107
176
|
export async function diffMemgraphs(input) {
|
|
108
|
-
const [{ report: before, resolvedPath: bp }, { report: after, resolvedPath: ap }] = await Promise.all([
|
|
177
|
+
const [{ report: before, resolvedPath: bp }, { report: after, resolvedPath: ap }, beforeRefTree, afterRefTree,] = await Promise.all([
|
|
109
178
|
runLeaksAndParse(input.before),
|
|
110
179
|
runLeaksAndParse(input.after),
|
|
180
|
+
captureReferenceTree(input.before),
|
|
181
|
+
captureReferenceTree(input.after),
|
|
111
182
|
]);
|
|
112
|
-
|
|
183
|
+
const result = diffReports(before, after, bp, ap);
|
|
184
|
+
const referenceTreeChanges = diffReferenceTrees(beforeRefTree, afterRefTree);
|
|
185
|
+
if (referenceTreeChanges) {
|
|
186
|
+
result.referenceTreeChanges = referenceTreeChanges;
|
|
187
|
+
// Actionable view: same diff with framework noise filtered out. Same
|
|
188
|
+
// ordering preserved (no re-rank). Provides the verify-fix view for
|
|
189
|
+
// the notelet-shape case where AVPlayerItem 342 to 0 needs to surface
|
|
190
|
+
// above NSMutableDictionary 12k to 11k noise.
|
|
191
|
+
result.actionableReferenceTreeChanges = {
|
|
192
|
+
increased: referenceTreeChanges.increased.filter((e) => !isFrameworkNoise(e.className)),
|
|
193
|
+
decreased: referenceTreeChanges.decreased.filter((e) => !isFrameworkNoise(e.className)),
|
|
194
|
+
};
|
|
195
|
+
// Heap-wide totals so callers can branch on a single number.
|
|
196
|
+
let instanceDelta = 0;
|
|
197
|
+
let bytesDelta = 0;
|
|
198
|
+
for (const e of referenceTreeChanges.increased) {
|
|
199
|
+
instanceDelta += e.delta;
|
|
200
|
+
bytesDelta += e.bytesDelta;
|
|
201
|
+
}
|
|
202
|
+
for (const e of referenceTreeChanges.decreased) {
|
|
203
|
+
instanceDelta += e.delta;
|
|
204
|
+
bytesDelta += e.bytesDelta;
|
|
205
|
+
}
|
|
206
|
+
result.totals.referenceTreeInstanceDelta = instanceDelta;
|
|
207
|
+
result.totals.referenceTreeBytesDelta = bytesDelta;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
113
210
|
}
|
|
114
211
|
//# sourceMappingURL=diffMemgraphs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diffMemgraphs.js","sourceRoot":"","sources":["../../src/tools/diffMemgraphs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,iDAAiD,CAAC;IAC9D,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,mDAAmD,CAAC;IAChE,YAAY,EAAE,iBAAiB;CAChC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"diffMemgraphs.js","sourceRoot":"","sources":["../../src/tools/diffMemgraphs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type ReferenceTreeChain } from "../parsers/leaksDebugStacks.js";
|
|
2
3
|
import type { LeaksReport, NextCallSuggestion } from "../types.js";
|
|
3
4
|
export declare const findRetainersSchema: z.ZodObject<{
|
|
4
5
|
path: z.ZodString;
|
|
5
6
|
className: z.ZodString;
|
|
6
7
|
maxResults: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
includeReferenceTree: z.ZodDefault<z.ZodBoolean>;
|
|
7
9
|
}, "strip", z.ZodTypeAny, {
|
|
8
10
|
className: string;
|
|
9
11
|
path: string;
|
|
10
12
|
maxResults: number;
|
|
13
|
+
includeReferenceTree: boolean;
|
|
11
14
|
}, {
|
|
12
15
|
className: string;
|
|
13
16
|
path: string;
|
|
14
17
|
maxResults?: number | undefined;
|
|
18
|
+
includeReferenceTree?: boolean | undefined;
|
|
15
19
|
}>;
|
|
16
20
|
export type FindRetainersInput = z.infer<typeof findRetainersSchema>;
|
|
17
21
|
export interface RetainerChainEntry {
|
|
@@ -33,7 +37,17 @@ export interface FindRetainersResult {
|
|
|
33
37
|
className: string;
|
|
34
38
|
totalMatches: number;
|
|
35
39
|
retainers: RetainerChain[];
|
|
36
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* v1.12+. Populated when `includeReferenceTree: true` and `leaks
|
|
42
|
+
* --debug=stacks` returned data. Each chain aggregates instances that
|
|
43
|
+
* share the same allocation call stack (the 342 notelet AVPlayerItem
|
|
44
|
+
* instances collapse to 1 chain with `instanceCount: 342`). The
|
|
45
|
+
* `userFrame` field surfaces the deepest non-system frame, which is
|
|
46
|
+
* the line a developer would inspect (e.g. `MediaNoteItemVideoView.prepareVideo`
|
|
47
|
+
* for the notelet case).
|
|
48
|
+
*/
|
|
49
|
+
referenceTreeChains?: ReferenceTreeChain[];
|
|
50
|
+
/** Pipeline hint: once you know who retains the class, locate it in source. */
|
|
37
51
|
suggestedNextCalls?: NextCallSuggestion[];
|
|
38
52
|
}
|
|
39
53
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../runtime/exec.js";
|
|
2
3
|
import { runLeaksAndParse } from "../runtime/leaks.js";
|
|
4
|
+
import { parseLeaksDebugStacks, } from "../parsers/leaksDebugStacks.js";
|
|
3
5
|
import { suggestionGetDefinition } from "../runtime/suggestions.js";
|
|
4
6
|
export const findRetainersSchema = z.object({
|
|
5
7
|
path: z.string().min(1).describe("Absolute path to a `.memgraph` file."),
|
|
@@ -13,6 +15,10 @@ export const findRetainersSchema = z.object({
|
|
|
13
15
|
.positive()
|
|
14
16
|
.default(10)
|
|
15
17
|
.describe("Cap on how many retain chains to return (default 10)."),
|
|
18
|
+
includeReferenceTree: z
|
|
19
|
+
.boolean()
|
|
20
|
+
.default(false)
|
|
21
|
+
.describe("v1.12+. When true, also run `leaks --debug=stacks --debug='<className>$'` to surface per-instance allocation stacks aggregated by call-stack fingerprint. Required on memgraphs where `leakCount: 0` and the class is reachable from KVO/NotificationCenter/caches (abandoned-memory shape). Each chain returns the allocation call stack + the unique retainer classes + a representative instance address. **Note:** `leaks --debug=stacks` only emits blocks for instances whose allocation stack was recorded, which requires the target was launched with `MallocStackLogging=1`. Xcode's Memory Graph Debugger export does NOT enable MSL by default, so memgraphs captured that way may surface fewer chains than the total instance count from `analyzeMemgraph.abandonedMemorySuspects[]`. Default false preserves v1.11 behavior."),
|
|
16
22
|
});
|
|
17
23
|
/**
|
|
18
24
|
* Walk the cycle forest and collect every parent-path that ends in a node whose
|
|
@@ -46,19 +52,59 @@ export function findRetainersIn(report, needle, maxResults = 10) {
|
|
|
46
52
|
visit(root, []);
|
|
47
53
|
return { totalMatches: total, retainers: matches };
|
|
48
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Spawn `leaks --debug=stacks --debug='<ClassName>$'` and parse the stdout
|
|
57
|
+
* into per-stack-fingerprint aggregated chains. Failure is non-fatal:
|
|
58
|
+
* returns an empty array so the cycle-side path still completes.
|
|
59
|
+
*
|
|
60
|
+
* leaks(1) `--debug=` predicate rejects `^` (`cannot match the start of
|
|
61
|
+
* a class name`); only the `$` trailing anchor is supported. The
|
|
62
|
+
* resulting semantic is "ends with X", which matches AVPlayerItem
|
|
63
|
+
* exactly but also things like MyAVPlayerItem (rare; intentionally
|
|
64
|
+
* permissive over over-restrictive). Class-name regex metacharacters
|
|
65
|
+
* are escaped so substrings like "Player.Item" stay literal.
|
|
66
|
+
*/
|
|
67
|
+
async function captureReferenceTreeChains(path, className, maxResults) {
|
|
68
|
+
// Escape regex metacharacters in the user-supplied class name so a
|
|
69
|
+
// substring like "AVPlayerItem" stays literal under leaks's regex
|
|
70
|
+
// predicate. `^` and `$` aren't escaped (leaks treats them specially).
|
|
71
|
+
const escaped = className.replace(/[.*+?{}()|[\]\\]/g, "\\$&");
|
|
72
|
+
const predicate = `${escaped}$`;
|
|
73
|
+
const result = await runCommand("leaks", ["--debug=stacks", `--debug=${predicate}`, path], { timeoutMs: 5 * 60_000 });
|
|
74
|
+
if (result.code !== 0 && result.code !== 1)
|
|
75
|
+
return [];
|
|
76
|
+
const all = parseLeaksDebugStacks(result.stdout);
|
|
77
|
+
return all.slice(0, maxResults);
|
|
78
|
+
}
|
|
49
79
|
export async function findRetainers(input) {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
80
|
+
const wantReferenceTree = input.includeReferenceTree ?? false;
|
|
81
|
+
const maxResults = input.maxResults ?? 10;
|
|
82
|
+
const [{ report, resolvedPath }, referenceTreeChains,] = await Promise.all([
|
|
83
|
+
runLeaksAndParse(input.path),
|
|
84
|
+
wantReferenceTree
|
|
85
|
+
? captureReferenceTreeChains(input.path, input.className, maxResults)
|
|
86
|
+
: Promise.resolve([]),
|
|
87
|
+
]);
|
|
88
|
+
const { totalMatches, retainers } = findRetainersIn(report, input.className, maxResults);
|
|
89
|
+
// Update totalMatches to include reference-tree side if it found instances
|
|
90
|
+
// the cycle path missed. Instance counts aggregate across the per-stack
|
|
91
|
+
// chains; cycle matches count each path separately, so we don't double-add.
|
|
92
|
+
const referenceTreeInstanceTotal = referenceTreeChains.reduce((s, c) => s + c.instanceCount, 0);
|
|
93
|
+
const effectiveTotal = totalMatches > 0 ? totalMatches : referenceTreeInstanceTotal;
|
|
94
|
+
const suggestedNextCalls = effectiveTotal > 0
|
|
53
95
|
? [suggestionGetDefinition({ symbolName: input.className })]
|
|
54
96
|
: [];
|
|
55
|
-
|
|
97
|
+
const result = {
|
|
56
98
|
ok: true,
|
|
57
99
|
path: resolvedPath,
|
|
58
100
|
className: input.className,
|
|
59
|
-
totalMatches,
|
|
101
|
+
totalMatches: effectiveTotal,
|
|
60
102
|
retainers,
|
|
61
103
|
...(suggestedNextCalls.length > 0 ? { suggestedNextCalls } : {}),
|
|
62
104
|
};
|
|
105
|
+
if (referenceTreeChains.length > 0) {
|
|
106
|
+
result.referenceTreeChains = referenceTreeChains;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
63
109
|
}
|
|
64
110
|
//# sourceMappingURL=findRetainers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findRetainers.js","sourceRoot":"","sources":["../../src/tools/findRetainers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAGpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IACxE,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,4EAA4E,CAC7E;IACH,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,uDAAuD,CAAC;
|
|
1
|
+
{"version":3,"file":"findRetainers.js","sourceRoot":"","sources":["../../src/tools/findRetainers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `inspectTrace`: orientation tool for `.trace` bundles. Lists which
|
|
3
|
+
* schemas are present, how many rows each carries, the time range, the
|
|
4
|
+
* device model, the template name, and a pre-baked `suggestedNextCalls`
|
|
5
|
+
* pointing at the matching analyzer for each populated schema.
|
|
6
|
+
*
|
|
7
|
+
* The use case is "I have a .trace, what's worth looking at?". Without
|
|
8
|
+
* this tool the caller has to either pick an `analyze*` blindly or
|
|
9
|
+
* read the full xctrace export which is wasteful. The MCP-native
|
|
10
|
+
* agent loop benefits the most: discovery costs 1 call instead of 5.
|
|
11
|
+
*
|
|
12
|
+
* Implementation notes:
|
|
13
|
+
*
|
|
14
|
+
* - Single `xctrace export --xpath '/trace-toc/run'` invocation. Returns
|
|
15
|
+
* the run metadata + all table schemas in one shot. We parse just enough
|
|
16
|
+
* to enumerate schemas + count rows; we deliberately do NOT parse row
|
|
17
|
+
* contents (the downstream `analyze*` tools do that).
|
|
18
|
+
*
|
|
19
|
+
* - Time range: derived from the schema-level `start-time` / `end-time`
|
|
20
|
+
* attributes when present; falls back to the run's `<recorded-when>`
|
|
21
|
+
* timestamp string when not parseable.
|
|
22
|
+
*
|
|
23
|
+
* - The 5 known schemas (potential-hangs, animation-hitches, time-profile,
|
|
24
|
+
* allocations, app-launch) map 1:1 to the existing analyzers. Any
|
|
25
|
+
* schema NOT in that map is still surfaced in the `schemas[]` list (so
|
|
26
|
+
* the user sees it) but does not contribute a `suggestedNextCalls`
|
|
27
|
+
* entry — there's no analyzer to chain into.
|
|
28
|
+
*/
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import type { NextCallSuggestion } from "../types.js";
|
|
31
|
+
export declare const inspectTraceSchema: z.ZodObject<{
|
|
32
|
+
tracePath: z.ZodString;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
tracePath: string;
|
|
35
|
+
}, {
|
|
36
|
+
tracePath: string;
|
|
37
|
+
}>;
|
|
38
|
+
export type InspectTraceInput = z.infer<typeof inspectTraceSchema>;
|
|
39
|
+
export interface TraceSchemaSummary {
|
|
40
|
+
/** Schema name (e.g. "potential-hangs", "time-profile"). */
|
|
41
|
+
name: string;
|
|
42
|
+
/** Number of rows in this schema's table. 0 means the schema is present in the trace but carries no data. */
|
|
43
|
+
rowCount: number;
|
|
44
|
+
/** Engineering description when the schema declares one; absent otherwise. */
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface InspectTraceResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
tracePath: string;
|
|
50
|
+
/** All schemas present in the trace TOC, ranked by rowCount desc. */
|
|
51
|
+
schemas: TraceSchemaSummary[];
|
|
52
|
+
/** Convenience: schemaName -> rowCount, same data as `schemas[]` in object form. */
|
|
53
|
+
rowCounts: Record<string, number>;
|
|
54
|
+
/** Trace file size in bytes (the .trace bundle is a directory; reports the directory entry size, not recursive). */
|
|
55
|
+
fileSize?: number;
|
|
56
|
+
/** Device model name when the trace's run metadata exposes one. */
|
|
57
|
+
deviceModel?: string;
|
|
58
|
+
/** OS version when present. */
|
|
59
|
+
osVersion?: string;
|
|
60
|
+
/** Template name (e.g. "Time Profiler", "Allocations"). */
|
|
61
|
+
templateName?: string;
|
|
62
|
+
/** Recording timestamp string (raw, unparsed). */
|
|
63
|
+
recordedWhen?: string;
|
|
64
|
+
/** Plain-English orientation diagnosis. */
|
|
65
|
+
diagnosis: string;
|
|
66
|
+
/** Pipeline hints based on which analyzers have data to chain into. */
|
|
67
|
+
suggestedNextCalls: NextCallSuggestion[];
|
|
68
|
+
}
|
|
69
|
+
/** Pure: parse the trace-toc XML payload into an inspection result. */
|
|
70
|
+
export declare function parseTraceToc(xml: string, tracePath: string): Omit<InspectTraceResult, "ok" | "fileSize">;
|
|
71
|
+
export declare function inspectTrace(input: InspectTraceInput): Promise<InspectTraceResult>;
|
|
72
|
+
export declare function _basenameForTests(p: string): string;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `inspectTrace`: orientation tool for `.trace` bundles. Lists which
|
|
3
|
+
* schemas are present, how many rows each carries, the time range, the
|
|
4
|
+
* device model, the template name, and a pre-baked `suggestedNextCalls`
|
|
5
|
+
* pointing at the matching analyzer for each populated schema.
|
|
6
|
+
*
|
|
7
|
+
* The use case is "I have a .trace, what's worth looking at?". Without
|
|
8
|
+
* this tool the caller has to either pick an `analyze*` blindly or
|
|
9
|
+
* read the full xctrace export which is wasteful. The MCP-native
|
|
10
|
+
* agent loop benefits the most: discovery costs 1 call instead of 5.
|
|
11
|
+
*
|
|
12
|
+
* Implementation notes:
|
|
13
|
+
*
|
|
14
|
+
* - Single `xctrace export --xpath '/trace-toc/run'` invocation. Returns
|
|
15
|
+
* the run metadata + all table schemas in one shot. We parse just enough
|
|
16
|
+
* to enumerate schemas + count rows; we deliberately do NOT parse row
|
|
17
|
+
* contents (the downstream `analyze*` tools do that).
|
|
18
|
+
*
|
|
19
|
+
* - Time range: derived from the schema-level `start-time` / `end-time`
|
|
20
|
+
* attributes when present; falls back to the run's `<recorded-when>`
|
|
21
|
+
* timestamp string when not parseable.
|
|
22
|
+
*
|
|
23
|
+
* - The 5 known schemas (potential-hangs, animation-hitches, time-profile,
|
|
24
|
+
* allocations, app-launch) map 1:1 to the existing analyzers. Any
|
|
25
|
+
* schema NOT in that map is still surfaced in the `schemas[]` list (so
|
|
26
|
+
* the user sees it) but does not contribute a `suggestedNextCalls`
|
|
27
|
+
* entry — there's no analyzer to chain into.
|
|
28
|
+
*/
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { resolve as resolvePath, basename } from "node:path";
|
|
32
|
+
import { statSync } from "node:fs";
|
|
33
|
+
import { runCommand } from "../runtime/exec.js";
|
|
34
|
+
export const inspectTraceSchema = z.object({
|
|
35
|
+
tracePath: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.describe("Absolute path to a `.trace` bundle (output of `xcrun xctrace record` or Instruments)."),
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Map from xctrace schema name to the analyzer that consumes it. Drives
|
|
42
|
+
* `suggestedNextCalls[]` so the agent doesn't have to know the mapping.
|
|
43
|
+
*/
|
|
44
|
+
const SCHEMA_TO_ANALYZER = {
|
|
45
|
+
"potential-hangs": {
|
|
46
|
+
tool: "analyzeHangs",
|
|
47
|
+
description: "Parses the hangs table and returns Hang vs Microhang counts plus the longest events. Pair with `topFramesByHangStartNs` to classify main-thread violations.",
|
|
48
|
+
},
|
|
49
|
+
"animation-hitches": {
|
|
50
|
+
tool: "analyzeAnimationHitches",
|
|
51
|
+
description: "Parses the hitches table and reports by-type counts plus the count of user-perceptible (>100ms) hitches.",
|
|
52
|
+
},
|
|
53
|
+
"time-profile": {
|
|
54
|
+
tool: "analyzeTimeProfile",
|
|
55
|
+
description: "Returns top symbols by sample count. Reports a structured workaroundNotice when xctrace SIGSEGVs on heavy unsymbolicated traces.",
|
|
56
|
+
},
|
|
57
|
+
allocations: {
|
|
58
|
+
tool: "analyzeAllocations",
|
|
59
|
+
description: "Returns per-category aggregates (cumulative bytes, allocation count, transient/persistent/mixed lifecycle classification) plus top allocators.",
|
|
60
|
+
},
|
|
61
|
+
"app-launch": {
|
|
62
|
+
tool: "analyzeAppLaunch",
|
|
63
|
+
description: "Returns cold/warm launch type plus per-phase breakdown (process-creation, dyld-init, ObjC-init, AppDelegate, first-frame).",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
/** Pure: parse the trace-toc XML payload into an inspection result. */
|
|
67
|
+
export function parseTraceToc(xml, tracePath) {
|
|
68
|
+
const schemas = [];
|
|
69
|
+
// We use lightweight regex parsing here instead of fast-xml-parser
|
|
70
|
+
// because the TOC XML is simple, well-defined, and the existing
|
|
71
|
+
// parseXctraceXml is row-content focused (parses through schemas).
|
|
72
|
+
// Surfacing only schema metadata + row counts is faster and avoids
|
|
73
|
+
// pulling row payloads into memory for traces that have hundreds of
|
|
74
|
+
// thousands of rows.
|
|
75
|
+
// Match each <table schema="X"> block.
|
|
76
|
+
const tableRegex = /<table\b[^>]*\bschema="([^"]+)"[^>]*>([\s\S]*?)<\/table>/g;
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = tableRegex.exec(xml)) !== null) {
|
|
79
|
+
const name = match[1];
|
|
80
|
+
const body = match[2];
|
|
81
|
+
// Row count = number of <row> tags inside this table.
|
|
82
|
+
const rowMatches = body.match(/<row\b/g);
|
|
83
|
+
const rowCount = rowMatches ? rowMatches.length : 0;
|
|
84
|
+
// Description: schema may carry an `engineering-type` description.
|
|
85
|
+
const descMatch = /<engineering-description>([^<]+)<\/engineering-description>/.exec(body);
|
|
86
|
+
const description = descMatch?.[1]?.trim();
|
|
87
|
+
schemas.push({
|
|
88
|
+
name,
|
|
89
|
+
rowCount,
|
|
90
|
+
...(description ? { description } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
schemas.sort((a, b) => b.rowCount - a.rowCount);
|
|
94
|
+
const rowCounts = {};
|
|
95
|
+
for (const s of schemas)
|
|
96
|
+
rowCounts[s.name] = s.rowCount;
|
|
97
|
+
// Run-level metadata extracted from the TOC's <run> attributes / children.
|
|
98
|
+
const deviceMatch = /<device-model>([^<]+)<\/device-model>/.exec(xml);
|
|
99
|
+
const osMatch = /<os-version>([^<]+)<\/os-version>/.exec(xml);
|
|
100
|
+
const templateMatch = /<template-name>([^<]+)<\/template-name>/.exec(xml);
|
|
101
|
+
const recordedMatch = /<recorded-when>([^<]+)<\/recorded-when>/.exec(xml);
|
|
102
|
+
const suggestedNextCalls = [];
|
|
103
|
+
for (const s of schemas) {
|
|
104
|
+
if (s.rowCount === 0)
|
|
105
|
+
continue;
|
|
106
|
+
const mapping = SCHEMA_TO_ANALYZER[s.name];
|
|
107
|
+
if (!mapping)
|
|
108
|
+
continue;
|
|
109
|
+
suggestedNextCalls.push({
|
|
110
|
+
tool: mapping.tool,
|
|
111
|
+
args: { tracePath },
|
|
112
|
+
why: `${s.rowCount.toLocaleString()} rows in the ${s.name} schema. ${mapping.description}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
tracePath,
|
|
117
|
+
schemas,
|
|
118
|
+
rowCounts,
|
|
119
|
+
...(deviceMatch ? { deviceModel: deviceMatch[1].trim() } : {}),
|
|
120
|
+
...(osMatch ? { osVersion: osMatch[1].trim() } : {}),
|
|
121
|
+
...(templateMatch ? { templateName: templateMatch[1].trim() } : {}),
|
|
122
|
+
...(recordedMatch ? { recordedWhen: recordedMatch[1].trim() } : {}),
|
|
123
|
+
diagnosis: buildDiagnosis(schemas, templateMatch?.[1]?.trim()),
|
|
124
|
+
suggestedNextCalls,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function buildDiagnosis(schemas, templateName) {
|
|
128
|
+
if (schemas.length === 0) {
|
|
129
|
+
return "Trace TOC reports no schemas. The trace may be malformed, empty, or recorded with a custom template the parser does not recognize. Inspect via Instruments.app to confirm.";
|
|
130
|
+
}
|
|
131
|
+
const populated = schemas.filter((s) => s.rowCount > 0);
|
|
132
|
+
const topSchema = populated[0];
|
|
133
|
+
const parts = [];
|
|
134
|
+
if (templateName)
|
|
135
|
+
parts.push(`Template: \`${templateName}\`.`);
|
|
136
|
+
parts.push(`${schemas.length} schema${schemas.length === 1 ? "" : "s"} in the TOC, ${populated.length} with data.`);
|
|
137
|
+
if (topSchema) {
|
|
138
|
+
parts.push(`Heaviest: \`${topSchema.name}\` with ${topSchema.rowCount.toLocaleString()} rows.`);
|
|
139
|
+
}
|
|
140
|
+
if (populated.length === 0) {
|
|
141
|
+
parts.push("All tables are empty. Trace likely recorded zero events; double-check the recording duration and active template.");
|
|
142
|
+
}
|
|
143
|
+
return parts.join(" ");
|
|
144
|
+
}
|
|
145
|
+
export async function inspectTrace(input) {
|
|
146
|
+
const tracePath = resolvePath(input.tracePath);
|
|
147
|
+
if (!existsSync(tracePath)) {
|
|
148
|
+
throw new Error(`Trace bundle not found: ${tracePath}`);
|
|
149
|
+
}
|
|
150
|
+
let fileSize;
|
|
151
|
+
try {
|
|
152
|
+
fileSize = statSync(tracePath).size;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// The bundle is a directory on macOS; stat reports the directory inode
|
|
156
|
+
// size, which is fine for orientation. Failures are non-fatal.
|
|
157
|
+
}
|
|
158
|
+
const result = await runCommand("xcrun", ["xctrace", "export", "--input", tracePath, "--xpath", "/trace-toc/run"], { timeoutMs: 60_000 });
|
|
159
|
+
if (result.code !== 0) {
|
|
160
|
+
// Fallback: when the targeted xpath fails (older xctrace versions, or
|
|
161
|
+
// a trace with no /run node), try the broader /trace-toc xpath and
|
|
162
|
+
// parse whatever schemas surface.
|
|
163
|
+
const fallback = await runCommand("xcrun", ["xctrace", "export", "--input", tracePath, "--xpath", "/trace-toc"], { timeoutMs: 60_000 });
|
|
164
|
+
if (fallback.code !== 0) {
|
|
165
|
+
throw new Error(`xctrace export TOC failed (code ${result.code}): ${result.stderr || result.stdout || "<no output>"}`);
|
|
166
|
+
}
|
|
167
|
+
const parsed = parseTraceToc(fallback.stdout, tracePath);
|
|
168
|
+
return { ok: true, ...parsed, ...(fileSize != null ? { fileSize } : {}) };
|
|
169
|
+
}
|
|
170
|
+
const parsed = parseTraceToc(result.stdout, tracePath);
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
...parsed,
|
|
174
|
+
...(fileSize != null ? { fileSize } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Used by tests to verify the bundle name surfaces correctly.
|
|
178
|
+
export function _basenameForTests(p) {
|
|
179
|
+
return basename(p);
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=inspectTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspectTrace.js","sourceRoot":"","sources":["../../src/tools/inspectTrace.ts"],"names":[],"mappings":"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"}
|