memorydetective 1.12.0 → 1.14.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 +65 -1
- package/README.md +15 -5
- package/USAGE.md +29 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/schemaDiscovery.d.ts +88 -0
- package/dist/parsers/schemaDiscovery.js +144 -0
- package/dist/parsers/schemaDiscovery.js.map +1 -0
- package/dist/parsers/xctraceXml.d.ts +5 -0
- package/dist/parsers/xctraceXml.js +3 -0
- package/dist/parsers/xctraceXml.js.map +1 -1
- package/dist/runtime/prompts.js +49 -0
- package/dist/runtime/prompts.js.map +1 -1
- package/dist/tools/analyzeAllocations.d.ts +5 -1
- package/dist/tools/analyzeAllocations.js +17 -1
- package/dist/tools/analyzeAllocations.js.map +1 -1
- package/dist/tools/analyzeAnimationHitches.d.ts +5 -1
- package/dist/tools/analyzeAnimationHitches.js +17 -1
- package/dist/tools/analyzeAnimationHitches.js.map +1 -1
- package/dist/tools/analyzeAppLaunch.d.ts +3 -0
- package/dist/tools/analyzeAppLaunch.js +17 -1
- package/dist/tools/analyzeAppLaunch.js.map +1 -1
- package/dist/tools/analyzeHangs.d.ts +63 -3
- package/dist/tools/analyzeHangs.js +143 -19
- package/dist/tools/analyzeHangs.js.map +1 -1
- package/dist/tools/analyzeNetworkActivity.d.ts +99 -0
- package/dist/tools/analyzeNetworkActivity.js +312 -0
- package/dist/tools/analyzeNetworkActivity.js.map +1 -0
- package/dist/tools/analyzeTimeProfile.d.ts +10 -1
- package/dist/tools/analyzeTimeProfile.js +63 -8
- package/dist/tools/analyzeTimeProfile.js.map +1 -1
- package/dist/tools/captureScenarioState.d.ts +2 -2
- package/dist/tools/countAlive.d.ts +35 -1
- package/dist/tools/countAlive.js +124 -29
- package/dist/tools/countAlive.js.map +1 -1
- package/dist/tools/inspectTrace.js +112 -18
- package/dist/tools/inspectTrace.js.map +1 -1
- package/dist/tools/recordTimeProfile.d.ts +83 -0
- package/dist/tools/recordTimeProfile.js +135 -0
- package/dist/tools/recordTimeProfile.js.map +1 -1
- package/dist/tools/replayScenario.d.ts +4 -4
- package/dist/tools/summarizeTrace.d.ts +147 -0
- package/dist/tools/summarizeTrace.js +424 -0
- package/dist/tools/summarizeTrace.js.map +1 -0
- package/dist/tools/verifyFix.d.ts +27 -0
- package/dist/tools/verifyFix.js +78 -4
- package/dist/tools/verifyFix.js.map +1 -1
- package/dist/types.d.ts +28 -0
- package/package.json +2 -2
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `summarizeTrace`: the trace-to-summary-card-in-one-call feature.
|
|
3
|
+
*
|
|
4
|
+
* Today an agent handed a `.trace` chains inspectTrace + up to 5
|
|
5
|
+
* analyzers + reasons over tens of KB of JSON. That's 6 round-trips
|
|
6
|
+
* and ~$0.10-0.20 in tokens, most of which get thrown away after the
|
|
7
|
+
* agent identifies the one user-visible finding.
|
|
8
|
+
*
|
|
9
|
+
* summarizeTrace does it all in one call:
|
|
10
|
+
*
|
|
11
|
+
* 1. Inspects the TOC via inspectTrace (reuses the v1.11 path).
|
|
12
|
+
* 2. For each populated known schema, runs the matching analyzer in
|
|
13
|
+
* parallel with smart defaults tuned for "what would a user care
|
|
14
|
+
* about?" (Apple's 100ms hitch threshold, top-10 hangs, top-15
|
|
15
|
+
* time-profile symbols, etc).
|
|
16
|
+
* 3. Cross-correlates findings (v1.13 Phase 2): hangs overlapping
|
|
17
|
+
* with hitches, allocation spikes preceding hangs, etc.
|
|
18
|
+
* 4. Produces a structured result PLUS a pre-rendered compact
|
|
19
|
+
* markdown card (< 10 KB target) suitable for direct presentation
|
|
20
|
+
* to the user without further reasoning.
|
|
21
|
+
*
|
|
22
|
+
* Strategic positioning: this is memorydetective's "synthesis over
|
|
23
|
+
* raw-query" play vs trace-MCPs that go deep on single-schema access.
|
|
24
|
+
* See `~/Desktop/internal/v1.9-notelet-retro-market.md` §4.5 for the
|
|
25
|
+
* full framing.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { existsSync } from "node:fs";
|
|
29
|
+
import { resolve as resolvePath, basename } from "node:path";
|
|
30
|
+
import { inspectTrace } from "./inspectTrace.js";
|
|
31
|
+
import { analyzeHangs, } from "./analyzeHangs.js";
|
|
32
|
+
import { analyzeAnimationHitches, } from "./analyzeAnimationHitches.js";
|
|
33
|
+
import { analyzeTimeProfile, } from "./analyzeTimeProfile.js";
|
|
34
|
+
import { analyzeAllocations, } from "./analyzeAllocations.js";
|
|
35
|
+
import { analyzeAppLaunch, } from "./analyzeAppLaunch.js";
|
|
36
|
+
export const summarizeTraceSchema = z.object({
|
|
37
|
+
tracePath: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1)
|
|
40
|
+
.describe("Absolute path to a `.trace` bundle (output of `xcrun xctrace record` or Instruments)."),
|
|
41
|
+
focus: z
|
|
42
|
+
.enum(["hangs", "hitches", "allocations", "launch", "all"])
|
|
43
|
+
.default("all")
|
|
44
|
+
.describe("When set to a specific area, the summary card emphasizes that area and downplays others. Useful for piping into more focused agent loops. Default `all`."),
|
|
45
|
+
verbose: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.default(false)
|
|
48
|
+
.describe("When true, the markdown card includes the full top-N per area (15+ rows per section) instead of the default 5. Trade-off: card grows from <10 KB to potentially 30+ KB."),
|
|
49
|
+
});
|
|
50
|
+
const DEFAULT_HITCH_THRESHOLD_MS = 100; // Apple's user-perceptible threshold.
|
|
51
|
+
const DEFAULT_HANG_MIN_MS = 100;
|
|
52
|
+
const DEFAULT_TOP_N_HANGS = 10;
|
|
53
|
+
const DEFAULT_TOP_N_HITCHES = 10;
|
|
54
|
+
const DEFAULT_TOP_N_TIME_PROFILE = 15;
|
|
55
|
+
const DEFAULT_TOP_N_ALLOCATIONS = 10;
|
|
56
|
+
/**
|
|
57
|
+
* Build a per-area summary by running an analyzer with smart defaults
|
|
58
|
+
* and wrapping the outcome (success / schema-absent / failed) into a
|
|
59
|
+
* status-tagged struct. The schema-absent branch reads the inspectTrace
|
|
60
|
+
* `rowCounts` so we don't spawn xctrace for empty schemas.
|
|
61
|
+
*/
|
|
62
|
+
async function buildAreaSummary(schemaName, inspection, runner, schemaAbsentDiagnosis) {
|
|
63
|
+
const rowCount = inspection.rowCounts[schemaName] ?? 0;
|
|
64
|
+
if (rowCount === 0) {
|
|
65
|
+
return {
|
|
66
|
+
status: "schema-absent",
|
|
67
|
+
diagnosis: schemaAbsentDiagnosis,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const result = await runner();
|
|
72
|
+
return {
|
|
73
|
+
status: "ok",
|
|
74
|
+
diagnosis: `${rowCount.toLocaleString()} rows analyzed.`,
|
|
75
|
+
result,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
status: "failed",
|
|
81
|
+
diagnosis: `Analyzer failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Pure: detect hangs whose window overlaps with animation hitches.
|
|
87
|
+
* When a user sees a hang AND a hitch in the same time window,
|
|
88
|
+
* they almost certainly perceived the impact (the main-thread block
|
|
89
|
+
* delayed render commits, dropping frames).
|
|
90
|
+
*
|
|
91
|
+
* The overlap check is symmetric: a hitch can fall within a hang's
|
|
92
|
+
* window OR a hang can fall within a hitch's window. Both directions
|
|
93
|
+
* are treated equally.
|
|
94
|
+
*
|
|
95
|
+
* Confidence:
|
|
96
|
+
*
|
|
97
|
+
* - `high`: both events >= 250ms AND the overlap span >= 100ms.
|
|
98
|
+
* - `medium`: at least one event >= 250ms.
|
|
99
|
+
* - `low`: neither event >= 250ms but the windows touch.
|
|
100
|
+
*
|
|
101
|
+
* Results are sorted by `atSec` ascending so the markdown card lists
|
|
102
|
+
* correlations in trace order.
|
|
103
|
+
*/
|
|
104
|
+
export function correlateHangsAndHitches(hangs, hitches) {
|
|
105
|
+
const results = [];
|
|
106
|
+
for (const hang of hangs) {
|
|
107
|
+
const hangEnd = hang.startNs + hang.durationNs;
|
|
108
|
+
for (const hitch of hitches) {
|
|
109
|
+
const hitchEnd = hitch.startNs + hitch.durationNs;
|
|
110
|
+
// Half-open interval overlap: max(starts) < min(ends).
|
|
111
|
+
const overlapStart = Math.max(hang.startNs, hitch.startNs);
|
|
112
|
+
const overlapEnd = Math.min(hangEnd, hitchEnd);
|
|
113
|
+
if (overlapEnd <= overlapStart)
|
|
114
|
+
continue;
|
|
115
|
+
const overlapMs = (overlapEnd - overlapStart) / 1e6;
|
|
116
|
+
const atSec = Math.min(hang.startNs, hitch.startNs) / 1e9;
|
|
117
|
+
let confidence;
|
|
118
|
+
if (hang.durationMs >= 250 &&
|
|
119
|
+
hitch.durationMs >= 250 &&
|
|
120
|
+
overlapMs >= 100) {
|
|
121
|
+
confidence = "high";
|
|
122
|
+
}
|
|
123
|
+
else if (hang.durationMs >= 250 || hitch.durationMs >= 250) {
|
|
124
|
+
confidence = "medium";
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
confidence = "low";
|
|
128
|
+
}
|
|
129
|
+
const hitchKind = hitch.hitchType ? `${hitch.hitchType} ` : "";
|
|
130
|
+
const narrative = `Hang at t=${(hang.startNs / 1e9).toFixed(2)}s (${hang.durationMs.toFixed(0)}ms) overlaps with ${hitchKind}hitch at t=${(hitch.startNs / 1e9).toFixed(2)}s (${hitch.durationMs.toFixed(0)}ms). Main-thread block likely caused the dropped frames.`;
|
|
131
|
+
results.push({ kind: "hangs+hitches", confidence, narrative, atSec });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
results.sort((a, b) => a.atSec - b.atSec);
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Pure: build all cross-area correlations from per-area summaries.
|
|
139
|
+
* Currently only `hangs+hitches` produces entries; allocation-based
|
|
140
|
+
* correlations need per-timestamp allocation data the existing
|
|
141
|
+
* analyzeAllocations doesn't expose (v1.14+ candidate).
|
|
142
|
+
*/
|
|
143
|
+
export function buildCorrelations(areas) {
|
|
144
|
+
const hangs = areas.hangs.result?.top ?? [];
|
|
145
|
+
const hitches = areas.hitches.result?.top ?? [];
|
|
146
|
+
if (hangs.length === 0 || hitches.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
return correlateHangsAndHitches(hangs, hitches);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Pure: produce the one-or-two-sentence headline that goes at the top
|
|
152
|
+
* of the markdown card. Picks the most user-visible finding across
|
|
153
|
+
* all areas. Order of priority: longest hang above 250ms > worst
|
|
154
|
+
* launch phase > worst hitch > largest allocation spike.
|
|
155
|
+
*/
|
|
156
|
+
export function buildHeadline(areas) {
|
|
157
|
+
const hang = areas.hangs.result?.top?.[0];
|
|
158
|
+
if (hang && hang.durationMs >= 250) {
|
|
159
|
+
const violation = hang.mainThreadViolations?.[0];
|
|
160
|
+
const causedBy = violation
|
|
161
|
+
? ` (caused by \`${violation.topFrame}\` -> ${violation.kind})`
|
|
162
|
+
: "";
|
|
163
|
+
return `${hang.durationMs.toFixed(0)}ms hang at t=${(hang.startNs / 1e9).toFixed(2)}s${causedBy}. Likely user-visible freeze.`;
|
|
164
|
+
}
|
|
165
|
+
const launch = areas.appLaunch.result;
|
|
166
|
+
if (launch && launch.totalLaunchMs > 1000) {
|
|
167
|
+
return `Launch took ${launch.totalLaunchMs.toFixed(0)}ms (${launch.launchType} launch). Above the 1s user-visible threshold.`;
|
|
168
|
+
}
|
|
169
|
+
const hitchesPerceptible = areas.hitches.result?.totals?.perceptible ?? 0;
|
|
170
|
+
if (hitchesPerceptible > 0) {
|
|
171
|
+
return `${hitchesPerceptible} animation hitch${hitchesPerceptible === 1 ? "" : "es"} above the 100ms user-perceptible threshold. Investigate render-server commits and main-thread work during scroll.`;
|
|
172
|
+
}
|
|
173
|
+
if (hang) {
|
|
174
|
+
return `${hang.durationMs.toFixed(0)}ms hang at t=${(hang.startNs / 1e9).toFixed(2)}s. Below the 250ms user-visible threshold but still worth investigating.`;
|
|
175
|
+
}
|
|
176
|
+
return "No user-perceptible perf events detected in the analyzed schemas.";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Pure: assemble the compact markdown summary card. Designed to be
|
|
180
|
+
* <10 KB at default settings. The structured `areas` field carries
|
|
181
|
+
* the full data for callers who need it; this is the human view.
|
|
182
|
+
*/
|
|
183
|
+
export function buildMarkdownCard(result, verbose) {
|
|
184
|
+
const sections = [];
|
|
185
|
+
const inspection = result.inspection;
|
|
186
|
+
const traceName = basename(result.tracePath);
|
|
187
|
+
sections.push(`# Trace summary: ${traceName}`);
|
|
188
|
+
sections.push("");
|
|
189
|
+
const meta = [];
|
|
190
|
+
if (inspection.deviceModel)
|
|
191
|
+
meta.push(inspection.deviceModel);
|
|
192
|
+
if (inspection.osVersion)
|
|
193
|
+
meta.push(inspection.osVersion);
|
|
194
|
+
if (inspection.templateName)
|
|
195
|
+
meta.push(`Template: \`${inspection.templateName}\``);
|
|
196
|
+
if (meta.length > 0)
|
|
197
|
+
sections.push(`**${meta.join(" · ")}**`);
|
|
198
|
+
sections.push("");
|
|
199
|
+
sections.push(`> **Headline:** ${result.headline}`);
|
|
200
|
+
sections.push("");
|
|
201
|
+
const topNHangs = verbose ? DEFAULT_TOP_N_HANGS : 5;
|
|
202
|
+
const topNHitches = verbose ? DEFAULT_TOP_N_HITCHES : 5;
|
|
203
|
+
const topNAllocations = verbose ? DEFAULT_TOP_N_ALLOCATIONS : 5;
|
|
204
|
+
const topNTimeProfile = verbose ? DEFAULT_TOP_N_TIME_PROFILE : 5;
|
|
205
|
+
// Hangs section.
|
|
206
|
+
if (result.areas.hangs.status === "ok" && result.areas.hangs.result) {
|
|
207
|
+
const h = result.areas.hangs.result;
|
|
208
|
+
const totalHangs = h.totals?.hangs ?? 0;
|
|
209
|
+
const totalMicrohangs = h.totals?.microhangs ?? 0;
|
|
210
|
+
const userVisible = (h.top ?? []).filter((e) => e.durationMs >= 250).length;
|
|
211
|
+
sections.push(`## Hangs (${totalHangs}, ${userVisible} user-visible, ${totalMicrohangs} microhang${totalMicrohangs === 1 ? "" : "s"})`);
|
|
212
|
+
sections.push("");
|
|
213
|
+
if ((h.top ?? []).length > 0) {
|
|
214
|
+
for (const entry of (h.top ?? []).slice(0, topNHangs)) {
|
|
215
|
+
const at = (entry.startNs / 1e9).toFixed(2);
|
|
216
|
+
const violation = entry.mainThreadViolations?.[0];
|
|
217
|
+
const classification = violation
|
|
218
|
+
? ` → ${violation.kind} (\`${violation.topFrame}\`)`
|
|
219
|
+
: "";
|
|
220
|
+
sections.push(`- ${entry.durationMs.toFixed(0)}ms at t=${at}s${classification}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
sections.push("_No hang events above the minimum threshold._");
|
|
225
|
+
}
|
|
226
|
+
sections.push("");
|
|
227
|
+
}
|
|
228
|
+
else if (result.areas.hangs.status === "schema-absent") {
|
|
229
|
+
// Suppressed when no hangs data; reduces card clutter.
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
sections.push(`## Hangs`);
|
|
233
|
+
sections.push("");
|
|
234
|
+
sections.push(`_${result.areas.hangs.diagnosis}_`);
|
|
235
|
+
sections.push("");
|
|
236
|
+
}
|
|
237
|
+
// Animation hitches section.
|
|
238
|
+
if (result.areas.hitches.status === "ok" && result.areas.hitches.result) {
|
|
239
|
+
const h = result.areas.hitches.result;
|
|
240
|
+
const totalHitches = h.totals?.rows ?? 0;
|
|
241
|
+
const perceptible = h.totals?.perceptible ?? 0;
|
|
242
|
+
sections.push(`## Animation hitches (${totalHitches}, ${perceptible} above 100ms)`);
|
|
243
|
+
sections.push("");
|
|
244
|
+
if ((h.top ?? []).length > 0) {
|
|
245
|
+
sections.push("| At | Duration | Type |");
|
|
246
|
+
sections.push("|---|---:|---|");
|
|
247
|
+
for (const entry of (h.top ?? []).slice(0, topNHitches)) {
|
|
248
|
+
const at = `t=${(entry.startNs / 1e9).toFixed(2)}s`;
|
|
249
|
+
sections.push(`| ${at} | ${entry.durationMs.toFixed(0)}ms | ${entry.hitchType || "—"} |`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
sections.push("");
|
|
253
|
+
}
|
|
254
|
+
else if (result.areas.hitches.status === "failed") {
|
|
255
|
+
sections.push(`## Animation hitches`);
|
|
256
|
+
sections.push("");
|
|
257
|
+
sections.push(`_${result.areas.hitches.diagnosis}_`);
|
|
258
|
+
sections.push("");
|
|
259
|
+
}
|
|
260
|
+
// Time profile section. analyzeTimeProfile may surface a workaround
|
|
261
|
+
// notice (xctrace SIGSEGV) via `notice`; we surface it inline so the
|
|
262
|
+
// summary card flags the partial-data situation.
|
|
263
|
+
if (result.areas.timeProfile.status === "ok" &&
|
|
264
|
+
result.areas.timeProfile.result) {
|
|
265
|
+
const tp = result.areas.timeProfile.result;
|
|
266
|
+
sections.push(`## Time profile (${tp.totalSamples.toLocaleString()} samples, top ${topNTimeProfile} symbols)`);
|
|
267
|
+
sections.push("");
|
|
268
|
+
if (tp.notice) {
|
|
269
|
+
sections.push(`> _${tp.notice}_`);
|
|
270
|
+
sections.push("");
|
|
271
|
+
}
|
|
272
|
+
const symbols = tp.topSymbols ?? [];
|
|
273
|
+
if (symbols.length > 0) {
|
|
274
|
+
for (const s of symbols.slice(0, topNTimeProfile)) {
|
|
275
|
+
const pct = tp.totalSamples > 0
|
|
276
|
+
? `${((s.samples / tp.totalSamples) * 100).toFixed(1)}%`
|
|
277
|
+
: `${s.samples} samples`;
|
|
278
|
+
sections.push(`- ${pct} \`${s.symbol || "???"}\``);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
sections.push("_No symbols above the noise threshold._");
|
|
283
|
+
}
|
|
284
|
+
sections.push("");
|
|
285
|
+
}
|
|
286
|
+
else if (result.areas.timeProfile.status === "failed") {
|
|
287
|
+
sections.push(`## Time profile`);
|
|
288
|
+
sections.push("");
|
|
289
|
+
sections.push(`_${result.areas.timeProfile.diagnosis}_`);
|
|
290
|
+
sections.push("");
|
|
291
|
+
}
|
|
292
|
+
// Allocations section.
|
|
293
|
+
if (result.areas.allocations.status === "ok" &&
|
|
294
|
+
result.areas.allocations.result) {
|
|
295
|
+
const a = result.areas.allocations.result;
|
|
296
|
+
const cumulativeBytes = a.totals.cumulativeBytes;
|
|
297
|
+
const persistentBytes = a.totals.persistentBytes;
|
|
298
|
+
sections.push(`## Allocations (${(cumulativeBytes / 1024 / 1024).toFixed(1)} MB cumulative, ${(persistentBytes / 1024 / 1024).toFixed(1)} MB persistent)`);
|
|
299
|
+
sections.push("");
|
|
300
|
+
const top = a.topByBytes ?? [];
|
|
301
|
+
if (top.length > 0) {
|
|
302
|
+
sections.push("| Category | Lifecycle | Bytes (cumulative) | Count |");
|
|
303
|
+
sections.push("|---|---|---:|---:|");
|
|
304
|
+
for (const entry of top.slice(0, topNAllocations)) {
|
|
305
|
+
const mb = (entry.cumulativeBytes / 1024 / 1024).toFixed(2);
|
|
306
|
+
sections.push(`| \`${entry.category}\` | ${entry.lifecycle} | ${mb} MB | ${entry.cumulativeCount.toLocaleString()} |`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
sections.push("");
|
|
310
|
+
}
|
|
311
|
+
// App launch section.
|
|
312
|
+
if (result.areas.appLaunch.status === "ok" &&
|
|
313
|
+
result.areas.appLaunch.result) {
|
|
314
|
+
const al = result.areas.appLaunch.result;
|
|
315
|
+
sections.push(`## App launch (${al.launchType}, ${al.totalLaunchMs.toFixed(0)}ms total)`);
|
|
316
|
+
sections.push("");
|
|
317
|
+
if ((al.phases ?? []).length > 0) {
|
|
318
|
+
sections.push("| Phase | Duration | % of total |");
|
|
319
|
+
sections.push("|---|---:|---:|");
|
|
320
|
+
for (const p of al.phases ?? []) {
|
|
321
|
+
sections.push(`| ${p.label || p.phase} | ${p.durationMs.toFixed(0)}ms | ${p.percentOfTotal.toFixed(1)}% |`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
sections.push("");
|
|
325
|
+
}
|
|
326
|
+
// Cross-correlations (v1.13 Phase 2). High and medium go straight
|
|
327
|
+
// into the card; low-confidence entries collapsed into a single
|
|
328
|
+
// "plus N more" line to keep the card compact.
|
|
329
|
+
const correlations = result.correlations ?? [];
|
|
330
|
+
if (correlations.length > 0) {
|
|
331
|
+
const highMedium = correlations.filter((c) => c.confidence === "high" || c.confidence === "medium");
|
|
332
|
+
const low = correlations.filter((c) => c.confidence === "low");
|
|
333
|
+
if (highMedium.length > 0 || verbose) {
|
|
334
|
+
sections.push("## Cross-correlations");
|
|
335
|
+
sections.push("");
|
|
336
|
+
const visible = verbose ? correlations : highMedium;
|
|
337
|
+
for (const c of visible) {
|
|
338
|
+
const confidenceBadge = c.confidence === "high"
|
|
339
|
+
? "**HIGH**"
|
|
340
|
+
: c.confidence === "medium"
|
|
341
|
+
? "MEDIUM"
|
|
342
|
+
: "low";
|
|
343
|
+
sections.push(`- (${confidenceBadge}) ${c.narrative}`);
|
|
344
|
+
}
|
|
345
|
+
if (!verbose && low.length > 0) {
|
|
346
|
+
sections.push(`- _${low.length} low-confidence overlap${low.length === 1 ? "" : "s"} omitted; pass \`verbose: true\` to see them._`);
|
|
347
|
+
}
|
|
348
|
+
sections.push("");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Suggested next calls from inspectTrace (carries them already).
|
|
352
|
+
if (inspection.suggestedNextCalls.length > 0) {
|
|
353
|
+
sections.push("## Suggested next calls");
|
|
354
|
+
sections.push("");
|
|
355
|
+
for (const call of inspection.suggestedNextCalls.slice(0, 5)) {
|
|
356
|
+
sections.push(`- \`${call.tool}\` — ${call.why}`);
|
|
357
|
+
}
|
|
358
|
+
sections.push("");
|
|
359
|
+
}
|
|
360
|
+
return sections.join("\n").trim();
|
|
361
|
+
}
|
|
362
|
+
export async function summarizeTrace(input) {
|
|
363
|
+
const tracePath = resolvePath(input.tracePath);
|
|
364
|
+
if (!existsSync(tracePath)) {
|
|
365
|
+
throw new Error(`Trace bundle not found: ${tracePath}`);
|
|
366
|
+
}
|
|
367
|
+
const verbose = input.verbose ?? false;
|
|
368
|
+
// Step 1: TOC.
|
|
369
|
+
const inspection = await inspectTrace({ tracePath });
|
|
370
|
+
// Step 2: chain analyzers in parallel. Each branch is fault-tolerant
|
|
371
|
+
// via buildAreaSummary so one failure doesn't tank the whole summary.
|
|
372
|
+
const [hangs, hitches, timeProfile, allocations, appLaunch] = await Promise.all([
|
|
373
|
+
buildAreaSummary("potential-hangs", inspection, () => analyzeHangs({
|
|
374
|
+
tracePath,
|
|
375
|
+
topN: DEFAULT_TOP_N_HANGS,
|
|
376
|
+
minDurationMs: DEFAULT_HANG_MIN_MS,
|
|
377
|
+
includeStackClassification: true,
|
|
378
|
+
}), "potential-hangs schema absent from this trace."),
|
|
379
|
+
buildAreaSummary("animation-hitches", inspection, () => analyzeAnimationHitches({
|
|
380
|
+
tracePath,
|
|
381
|
+
topN: DEFAULT_TOP_N_HITCHES,
|
|
382
|
+
minDurationMs: DEFAULT_HITCH_THRESHOLD_MS,
|
|
383
|
+
}), "animation-hitches schema absent from this trace."),
|
|
384
|
+
buildAreaSummary("time-profile", inspection, () => analyzeTimeProfile({
|
|
385
|
+
tracePath,
|
|
386
|
+
topN: DEFAULT_TOP_N_TIME_PROFILE,
|
|
387
|
+
}), "time-profile schema absent from this trace."),
|
|
388
|
+
buildAreaSummary("allocations", inspection, () => analyzeAllocations({
|
|
389
|
+
tracePath,
|
|
390
|
+
topN: DEFAULT_TOP_N_ALLOCATIONS,
|
|
391
|
+
minBytes: 0,
|
|
392
|
+
}), "allocations schema absent from this trace."),
|
|
393
|
+
buildAreaSummary("app-launch", inspection, () => analyzeAppLaunch({ tracePath }), "app-launch schema absent from this trace."),
|
|
394
|
+
]);
|
|
395
|
+
const areas = {
|
|
396
|
+
hangs,
|
|
397
|
+
hitches,
|
|
398
|
+
timeProfile,
|
|
399
|
+
allocations,
|
|
400
|
+
appLaunch,
|
|
401
|
+
};
|
|
402
|
+
const correlations = buildCorrelations(areas);
|
|
403
|
+
const headline = buildHeadline(areas);
|
|
404
|
+
const base = {
|
|
405
|
+
ok: true,
|
|
406
|
+
tracePath,
|
|
407
|
+
inspection,
|
|
408
|
+
areas,
|
|
409
|
+
correlations,
|
|
410
|
+
headline,
|
|
411
|
+
};
|
|
412
|
+
const markdown = buildMarkdownCard(base, verbose);
|
|
413
|
+
return { ...base, markdown };
|
|
414
|
+
}
|
|
415
|
+
// Helper for tests: ensure the keys in `areas` stay aligned with the
|
|
416
|
+
// implementation. Imported by the test file.
|
|
417
|
+
export const SUMMARIZE_AREA_KEYS = [
|
|
418
|
+
"hangs",
|
|
419
|
+
"hitches",
|
|
420
|
+
"timeProfile",
|
|
421
|
+
"allocations",
|
|
422
|
+
"appLaunch",
|
|
423
|
+
];
|
|
424
|
+
//# sourceMappingURL=summarizeTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizeTrace.js","sourceRoot":"","sources":["../../src/tools/summarizeTrace.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,21 +14,40 @@ import type { LeaksReport, NextCallSuggestion } from "../types.js";
|
|
|
14
14
|
* Designed for CI gating: a build script can check `overallVerdict` and
|
|
15
15
|
* fail the merge if the pattern resolved by the PR has regressed.
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* v1.14 item M. Default list of classes that legitimately stay alive
|
|
19
|
+
* across before/after snapshots in normal iOS app activity. Sourced from
|
|
20
|
+
* DebugSwift's `Performance.LeakDetector.swift` `_ignoredViewControllerClassNames`,
|
|
21
|
+
* `_ignoredViewClassNames`, and `_ignoredWindowClassNames` curated lists
|
|
22
|
+
* (FLEXTool fork lineage via Janneman84/LeakedViewControllerDetector).
|
|
23
|
+
*
|
|
24
|
+
* When these appear in `regressionClasses[]` we surface them under
|
|
25
|
+
* `expectedAlive[]` for transparency but don't let them flip the verdict
|
|
26
|
+
* to FAIL. Users can extend via `expectedAliveClasses` input or disable
|
|
27
|
+
* with `disableDefaultWhitelist: true` for strict matching.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_EXPECTED_ALIVE_CLASSES: readonly string[];
|
|
17
30
|
export declare const verifyFixSchema: z.ZodObject<{
|
|
18
31
|
before: z.ZodString;
|
|
19
32
|
after: z.ZodString;
|
|
20
33
|
expectedPatternId: z.ZodOptional<z.ZodString>;
|
|
34
|
+
expectedAliveClasses: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
35
|
+
disableDefaultWhitelist: z.ZodDefault<z.ZodBoolean>;
|
|
21
36
|
verbosity: z.ZodDefault<z.ZodEnum<["compact", "normal", "full"]>>;
|
|
22
37
|
}, "strip", z.ZodTypeAny, {
|
|
23
38
|
verbosity: "compact" | "normal" | "full";
|
|
24
39
|
after: string;
|
|
25
40
|
before: string;
|
|
41
|
+
disableDefaultWhitelist: boolean;
|
|
26
42
|
expectedPatternId?: string | undefined;
|
|
43
|
+
expectedAliveClasses?: string[] | undefined;
|
|
27
44
|
}, {
|
|
28
45
|
after: string;
|
|
29
46
|
before: string;
|
|
30
47
|
verbosity?: "compact" | "normal" | "full" | undefined;
|
|
31
48
|
expectedPatternId?: string | undefined;
|
|
49
|
+
expectedAliveClasses?: string[] | undefined;
|
|
50
|
+
disableDefaultWhitelist?: boolean | undefined;
|
|
32
51
|
}>;
|
|
33
52
|
export type VerifyFixInput = z.infer<typeof verifyFixSchema>;
|
|
34
53
|
export interface PatternResolution {
|
|
@@ -93,6 +112,14 @@ export interface VerifyFixResult {
|
|
|
93
112
|
* entries of `analyzeAbandonedMemory.actionableGrowth[]`.
|
|
94
113
|
*/
|
|
95
114
|
regressionClasses?: AbandonedMemoryEntry[];
|
|
115
|
+
/**
|
|
116
|
+
* v1.14+. Class names from the effective whitelist
|
|
117
|
+
* (DEFAULT_EXPECTED_ALIVE_CLASSES + user-supplied) that DID appear in
|
|
118
|
+
* the raw regression set before filtering. Surfaced for transparency:
|
|
119
|
+
* the agent can see which "regressions" were intentionally ignored.
|
|
120
|
+
* Empty array when nothing was filtered. Absent when no fallback ran.
|
|
121
|
+
*/
|
|
122
|
+
expectedAlive?: string[];
|
|
96
123
|
}
|
|
97
124
|
/** Pure function: compute verifyFix result from two parsed reports. */
|
|
98
125
|
export declare function verifyFromReports(beforeReport: LeaksReport, afterReport: LeaksReport, beforePath: string, afterPath: string, input: VerifyFixInput): VerifyFixResult;
|
package/dist/tools/verifyFix.js
CHANGED
|
@@ -17,6 +17,34 @@ import { analyzeAbandonedMemory, } from "./analyzeAbandonedMemory.js";
|
|
|
17
17
|
* Designed for CI gating: a build script can check `overallVerdict` and
|
|
18
18
|
* fail the merge if the pattern resolved by the PR has regressed.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* v1.14 item M. Default list of classes that legitimately stay alive
|
|
22
|
+
* across before/after snapshots in normal iOS app activity. Sourced from
|
|
23
|
+
* DebugSwift's `Performance.LeakDetector.swift` `_ignoredViewControllerClassNames`,
|
|
24
|
+
* `_ignoredViewClassNames`, and `_ignoredWindowClassNames` curated lists
|
|
25
|
+
* (FLEXTool fork lineage via Janneman84/LeakedViewControllerDetector).
|
|
26
|
+
*
|
|
27
|
+
* When these appear in `regressionClasses[]` we surface them under
|
|
28
|
+
* `expectedAlive[]` for transparency but don't let them flip the verdict
|
|
29
|
+
* to FAIL. Users can extend via `expectedAliveClasses` input or disable
|
|
30
|
+
* with `disableDefaultWhitelist: true` for strict matching.
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_EXPECTED_ALIVE_CLASSES = [
|
|
33
|
+
// ViewControllers Apple keeps alive for system functions.
|
|
34
|
+
"UICompatibilityInputViewController",
|
|
35
|
+
"_SFAppPasswordSavingViewController",
|
|
36
|
+
"UIKeyboardHiddenViewController_Save",
|
|
37
|
+
"_UIAlertControllerTextFieldViewController",
|
|
38
|
+
"UISystemInputAssistantViewController",
|
|
39
|
+
"UIPredictionViewController",
|
|
40
|
+
// Internal views with persistent backing.
|
|
41
|
+
"PLTileContainerView",
|
|
42
|
+
"CAMPreviewView",
|
|
43
|
+
"_UIPointerInteractionAssistantEffectContainerView",
|
|
44
|
+
// Windows the OS retains.
|
|
45
|
+
"UIRemoteKeyboardWindow",
|
|
46
|
+
"UITextEffectsWindow",
|
|
47
|
+
];
|
|
20
48
|
export const verifyFixSchema = z.object({
|
|
21
49
|
before: z
|
|
22
50
|
.string()
|
|
@@ -30,6 +58,14 @@ export const verifyFixSchema = z.object({
|
|
|
30
58
|
.string()
|
|
31
59
|
.optional()
|
|
32
60
|
.describe("If provided, the verdict is gated on whether this specific patternId disappeared from `after`. Defaults to checking every classified pattern."),
|
|
61
|
+
expectedAliveClasses: z
|
|
62
|
+
.array(z.string().min(1))
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("v1.14+. Class names (substrings) that legitimately stay alive across the before/after snapshots. Singletons, framework registrars, persistent caches. When a class in this list appears in regressionClasses[], it is moved to expectedAlive[] and does not flip the verdict to FAIL. Merged with the curated default list (DebugSwift's ignoredViewControllerClassNames + ignoredViewClassNames + ignoredWindowClassNames) unless `disableDefaultWhitelist: true`."),
|
|
65
|
+
disableDefaultWhitelist: z
|
|
66
|
+
.boolean()
|
|
67
|
+
.default(false)
|
|
68
|
+
.describe("v1.14+. When true, the curated DEFAULT_EXPECTED_ALIVE_CLASSES list is NOT applied. Only the user-supplied expectedAliveClasses (if any) is used. Useful for strict regression mode in tests where every alive class should be evaluated."),
|
|
33
69
|
verbosity: z
|
|
34
70
|
.enum(["compact", "normal", "full"])
|
|
35
71
|
.default("compact"),
|
|
@@ -227,7 +263,30 @@ const ABANDONED_MEMORY_TOPN = 100;
|
|
|
227
263
|
* Returns null when even the abandoned-memory path can't run (paths
|
|
228
264
|
* inaccessible, etc.); the caller falls back to the cycle-pattern result.
|
|
229
265
|
*/
|
|
230
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Build the effective expected-alive whitelist by merging the curated
|
|
268
|
+
* default list (unless disabled) with the user-supplied set. Returns a
|
|
269
|
+
* lowercase Set for case-insensitive substring matching. v1.14.
|
|
270
|
+
*/
|
|
271
|
+
function buildExpectedAliveSet(userSupplied, disableDefault) {
|
|
272
|
+
const base = disableDefault ? [] : DEFAULT_EXPECTED_ALIVE_CLASSES;
|
|
273
|
+
const all = [...base, ...(userSupplied ?? [])];
|
|
274
|
+
return new Set(all.map((s) => s.toLowerCase()));
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Returns true when the className matches any entry in the whitelist
|
|
278
|
+
* (substring match, case-insensitive). v1.14.
|
|
279
|
+
*/
|
|
280
|
+
function isExpectedAlive(className, whitelist) {
|
|
281
|
+
if (whitelist.size === 0)
|
|
282
|
+
return false;
|
|
283
|
+
const lc = className.toLowerCase();
|
|
284
|
+
for (const w of whitelist)
|
|
285
|
+
if (lc.includes(w))
|
|
286
|
+
return true;
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
async function buildAbandonedMemoryFallback(beforePath, afterPath, expectedAliveWhitelist) {
|
|
231
290
|
let amResult;
|
|
232
291
|
try {
|
|
233
292
|
amResult = await analyzeAbandonedMemory({
|
|
@@ -240,7 +299,20 @@ async function buildAbandonedMemoryFallback(beforePath, afterPath) {
|
|
|
240
299
|
return null;
|
|
241
300
|
}
|
|
242
301
|
const freedClasses = (amResult.actionableShrinkage ?? []).filter((e) => Math.abs(e.delta) >= ACTIONABLE_DELTA_THRESHOLD);
|
|
243
|
-
|
|
302
|
+
// Partition raw regression set into "really regressed" vs "in the
|
|
303
|
+
// expected-alive whitelist" so the verdict ignores the latter while
|
|
304
|
+
// surfacing them on the response for transparency.
|
|
305
|
+
const rawRegressions = (amResult.actionableGrowth ?? []).filter((e) => Math.abs(e.delta) >= ACTIONABLE_DELTA_THRESHOLD);
|
|
306
|
+
const regressionClasses = [];
|
|
307
|
+
const expectedAlive = [];
|
|
308
|
+
for (const entry of rawRegressions) {
|
|
309
|
+
if (isExpectedAlive(entry.className, expectedAliveWhitelist)) {
|
|
310
|
+
expectedAlive.push(entry.className);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
regressionClasses.push(entry);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
244
316
|
// Magnitude check: a fix that frees thousands of instances and incidentally
|
|
245
317
|
// grows a hundred Swift Metadata / pthread_mutex_t / ObjC class table
|
|
246
318
|
// entries (these scale with app activity, not user code) should be PASS,
|
|
@@ -292,7 +364,7 @@ async function buildAbandonedMemoryFallback(beforePath, afterPath) {
|
|
|
292
364
|
diagnosis =
|
|
293
365
|
"No actionable class-count changes between snapshots (|delta| < 10). Either both snapshots are clean or the workflow did not exercise the targeted code.";
|
|
294
366
|
}
|
|
295
|
-
return { verdict, freedClasses, regressionClasses, diagnosis };
|
|
367
|
+
return { verdict, freedClasses, regressionClasses, expectedAlive, diagnosis };
|
|
296
368
|
}
|
|
297
369
|
export async function verifyFix(input) {
|
|
298
370
|
const [{ report: beforeReport, resolvedPath: bp }, { report: afterReport, resolvedPath: ap },] = await Promise.all([
|
|
@@ -306,11 +378,13 @@ export async function verifyFix(input) {
|
|
|
306
378
|
// cycle-pattern path takes precedence when patterns DO fire, so this
|
|
307
379
|
// is a strict fallback.
|
|
308
380
|
if (result.patternResolution.length === 0) {
|
|
309
|
-
const
|
|
381
|
+
const whitelist = buildExpectedAliveSet(input.expectedAliveClasses, input.disableDefaultWhitelist ?? false);
|
|
382
|
+
const fallback = await buildAbandonedMemoryFallback(input.before, input.after, whitelist);
|
|
310
383
|
if (fallback) {
|
|
311
384
|
result.verdictSource = "abandoned-memory";
|
|
312
385
|
result.freedClasses = fallback.freedClasses;
|
|
313
386
|
result.regressionClasses = fallback.regressionClasses;
|
|
387
|
+
result.expectedAlive = fallback.expectedAlive;
|
|
314
388
|
result.overallVerdict = fallback.verdict;
|
|
315
389
|
result.diagnosis = fallback.diagnosis;
|
|
316
390
|
}
|