memorydetective 1.11.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 +21 -1
- package/README.md +4 -4
- 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/findRetainers.d.ts +15 -1
- package/dist/tools/findRetainers.js +51 -5
- package/dist/tools/findRetainers.js.map +1 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.12.0] - 2026-05-14
|
|
10
|
+
|
|
11
|
+
Four-phase patch completing the v1.10 retro §7.2 propagation (`countAlive`, `findRetainers`, `verifyFix` reference-tree integrations) and internalizing the v1.9 cross-schema correlation (`analyzeHangs` auto-classification). The agent chain `analyzeAbandonedMemory` → `findRetainers` → `countAlive` → `verifyFix` no longer falls off a cliff after the first call.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`countAlive` reference-tree integration.** New `includeReferenceTree: boolean` input flag (default false preserves v1.11 behavior). When true, spawns `leaks --referenceTree --groupByType --noContent` in parallel with the existing cycle pass, merges counts by class name, and surfaces a new `actionableCounts[]` field filtered via `isFrameworkNoise`. For `countAlive({ className: "AVPlayerItem", includeReferenceTree: true })` on the notelet pre-fix memgraph, returns `instanceCount: 685` (substring match captures both AVPlayerItem 342 + AVPlayerItemInternal 343); for the topN path, surfaces AVCMNotificationDispatcher / __NSObserver / AVPlayerItem instead of NSMutableDictionary / CFString noise. New per-class `byCycle` + `byReferenceTree` breakdown fields when the flag is on. 3 new schema validation tests.
|
|
16
|
+
|
|
17
|
+
- **`verifyFix` abandoned-memory verdict fallback.** v1.11 returned `overallVerdict: "PASS"` with empty `patternResolution[]` on the notelet pair (both `leakCount: 0`); v1.12 chains internally into `analyzeAbandonedMemory` when no cycle patterns fire on either side. New `verdictSource: "cycle-pattern" | "abandoned-memory"`, `freedClasses[]`, `regressionClasses[]` fields. Magnitude-dominance heuristic resolves verdict to PASS / PARTIAL / FAIL by comparing the sum of `|freed delta|` to `|growth delta|` at a 2x ratio. Notelet pair now returns `verdict: PASS` with diagnosis "Fix verified via abandoned-memory shrinkage: 10,386 instances freed dominates the residual 4,531-instance growth (typically Swift runtime / font cache / ObjC class table)." `isFrameworkNoise` extended to flag `N bytes into <X 0xADDR> [size]` heap-offset entries so the magnitude check doesn't get fooled by partial-allocation noise. 1 new reference-tree filter test.
|
|
18
|
+
|
|
19
|
+
- **`findRetainers` reference-tree chains.** New `includeReferenceTree: boolean` input flag (default false). When true, spawns `leaks --debug=stacks --debug='<className>$'` and parses the per-instance allocation stack output via a new `src/parsers/leaksDebugStacks.ts` parser. Instances sharing the same call-stack fingerprint aggregate into one chain with `instanceCount`. Each chain exposes `callStack[]` (ordered frames from dyld root to allocation site), `retainers[]` (unique retainer classes with aggregate counts), `exampleAddress` (one representative instance), and `userFrame` (the deepest non-system frame, surfacing the actual line a developer would inspect). New 11-test parser file. leaks's `--debug=` predicate restriction documented in the schema description: `^` is rejected by leaks, only trailing `$` works. MallocStackLogging caveat documented: Xcode-exported memgraphs may surface fewer chains than the total instance count because `leaks --debug=stacks` only emits blocks for instances whose allocation stack was recorded.
|
|
20
|
+
|
|
21
|
+
- **`analyzeHangs` cross-schema correlation.** v1.9 added `mainThreadViolations[]` enrichment via the caller-supplied `topFramesByHangStartNs` map; v1.12 internalizes it. New `includeStackClassification: boolean` input flag (default false preserves v1.9 behavior). When true, exports the `time-profile` schema in parallel with `potential-hangs`, correlates samples to hang windows by timestamp (sample in `[hang.startNs, hang.startNs + hang.durationNs]`), picks the dominant top frame per hang by aggregate weight, and runs `classifyHangFrame()` on it. New pure `correlateTimeProfileToHangs()` helper. Caller-supplied maps still take precedence over the auto-correlation. 8 new pure correlation tests covering empty inputs both sides, in/out-of-window, weight tiebreaker, backtrace fallback, multi-hang independence, default-weight handling.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `package.json` + `server.json`: 1.11.0 → 1.12.0.
|
|
26
|
+
- `compareTracesByPattern` call sites of `analyzeHangs` now pass `includeStackClassification: false` explicitly to satisfy the inferred `analyzeHangs` input type (cosmetic; no behavioral change).
|
|
27
|
+
|
|
9
28
|
## [1.11.0] - 2026-05-14
|
|
10
29
|
|
|
11
30
|
Three-fix patch driven by the v1.10 validation pass on the global npm binary. The v1.10 retro §7 surfaced gaps that v1.11 closes: CLI human-output ignored the new abandoned-memory data, `diffMemgraphs` had the same reference-tree blind spot v1.10 fixed only in `analyzeMemgraph` + `analyzeAbandonedMemory`, and there was no orientation tool for `.trace` bundles.
|
|
@@ -429,7 +448,8 @@ When called with no arguments it starts the MCP server over stdio.
|
|
|
429
448
|
- **`captureMemgraph`** does not work on physical iOS devices — `leaks(1)` only attaches to processes on the local Mac (which includes iOS simulators). Memory Graph capture from a physical device still requires Xcode.
|
|
430
449
|
- **`detectLeaksInXCUITest`** is flagged experimental: orchestration logic is implemented but not yet validated against a wide set of production XCUITest runs.
|
|
431
450
|
|
|
432
|
-
[Unreleased]: https://github.com/carloshpdoc/memorydetective/compare/v1.
|
|
451
|
+
[Unreleased]: https://github.com/carloshpdoc/memorydetective/compare/v1.12.0...HEAD
|
|
452
|
+
[1.12.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.11.0...v1.12.0
|
|
433
453
|
[1.11.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.10.0...v1.11.0
|
|
434
454
|
[1.10.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.9.0...v1.10.0
|
|
435
455
|
[1.9.0]: https://github.com/carloshpdoc/memorydetective/compare/v1.8.1...v1.9.0
|
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
- **MCP-native.** Plugs into Claude Code, Claude Desktop, Cursor, Cline, and any other MCP client. The agent drives the full investigate → classify → suggest-fix loop without you opening Instruments.
|
|
18
18
|
- **Honest about its limits.** No mocked outputs, no over-promises. Hangs analysis works clean from `xctrace`; sample-level Time Profile is parsed when `xctrace` symbolicates the trace and returns a structured workaround notice when it can't (the underlying `xctrace` SIGSEGV on heavy unsymbolicated traces is an Apple-side limitation we surface explicitly). Memory Graph capture works on Mac apps and iOS simulator; physical iOS devices still need Xcode.
|
|
19
19
|
|
|
20
|
-
> **What's new in v1.
|
|
20
|
+
> **What's new in v1.12** (2026-05-14): four-phase patch completing the v1.10 retro propagation across the remaining tools that had the reference-tree blind spot. (1) `countAlive({ includeReferenceTree: true })` now surfaces heap-wide instance counts (AVPlayerItem 685 = AVPlayerItem + AVPlayerItemInternal, via the substring match semantic) plus a noise-filtered `actionableCounts[]` view. (2) `verifyFix` chains internally into `analyzeAbandonedMemory` when no cycle patterns fire on either side; the notelet pair now returns `verdict: PASS` with `verdictSource: "abandoned-memory"` and a structured `freedClasses[]` list, instead of `verdict: undefined`. (3) `findRetainers({ includeReferenceTree: true })` parses `leaks --debug=stacks` and returns per-call-stack-fingerprint aggregated chains: the notelet AVPlayerItem case surfaces `MediaNoteItemVideoView.prepareVideo` as the allocation site + `_CFXNotificationRegistrarAddObject` (KVO global registry) as the retainer. (4) `analyzeHangs({ includeStackClassification: true })` now internally exports the time-profile schema alongside potential-hangs and auto-populates `mainThreadViolations[]` without the caller building a `topFramesByHangStartNs` map. 500 → 523 tests.
|
|
21
21
|
>
|
|
22
|
-
> **Also recent**: v1.
|
|
22
|
+
> **Also recent**: v1.11 added `inspectTrace`, `diffMemgraphs` reference-tree, and CLI abandoned-memory mini-table. v1.10 closed the notelet retro feedback loop with parser fixes + classifier guards + `verify-fix-table` output. v1.9 shipped `analyzeAbandonedMemory` + `detectLeaksInXCTest` + `cleanupTraces` + `mainThreadViolations` + security env flags. Full notes in [CHANGELOG](./CHANGELOG.md).
|
|
23
23
|
|
|
24
24
|
> **Heads up for macOS 26.x users:** Apple shipped a `task_for_pid` kernel regression on macOS 26.x that blocks `leaks --outputGraph`, `heap`, AND `xctrace --template Allocations` against iOS simulator processes regardless of `MallocStackLogging`. Even Xcode's "View Memory Graph Hierarchy" hits it unless `Malloc Stack Logging` is enabled in the scheme's Diagnostics tab. memorydetective surfaces this as a proactive `platformAdvisory` on the first capture-class tool call, plus a `workaroundNotice` with `issue: "macos-26-task-for-pid-broken"` if `leaks` is invoked. **The most reliable workaround today is to target an iOS 18 simulator runtime** (install via Xcode > Settings > Platforms > +iOS 18.x). Empirically validated in the [notelet investigation](https://github.com/carloshpdoc/memorydetective/blob/main/CHANGELOG.md#unreleased) 2026-05-12 where three independent CLI memory-introspection paths all failed before iOS 18 was identified as the working escape hatch. Set `MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1` to silence the notice once you have settled on a workaround.
|
|
25
25
|
|
|
@@ -490,7 +490,7 @@ The `tool` subcommand dispatches to any registered MCP tool by name, reading inp
|
|
|
490
490
|
git clone https://github.com/carloshpdoc/memorydetective
|
|
491
491
|
cd memorydetective
|
|
492
492
|
npm install
|
|
493
|
-
npm test #
|
|
493
|
+
npm test # 523 unit tests
|
|
494
494
|
npm run build # build → dist/
|
|
495
495
|
npm run dev # tsx, stdio mode (dev mode)
|
|
496
496
|
./scripts/demo.sh # full demo against a real .memgraph (set MEMGRAPH=path)
|
|
@@ -501,7 +501,7 @@ npm run dev # tsx, stdio mode (dev mode)
|
|
|
501
501
|
Contributions are welcome. Bug reports, feature requests, new cycle patterns, all of it.
|
|
502
502
|
|
|
503
503
|
- **Bugs / feature requests**: [open an issue](https://github.com/carloshpdoc/memorydetective/issues).
|
|
504
|
-
- **PRs**: fork → branch → `npm install` → make changes → `npm test` (
|
|
504
|
+
- **PRs**: fork → branch → `npm install` → make changes → `npm test` (523 tests must stay green) → open a PR with a concise description of what changed and why.
|
|
505
505
|
|
|
506
506
|
### Adding a cycle pattern to `classifyCycle`
|
|
507
507
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for `leaks --debug=stacks --debug='<ClassName>$'` output.
|
|
3
|
+
*
|
|
4
|
+
* This is the canonical way to get the allocation stack + retainer list for
|
|
5
|
+
* a specific class without it being part of a strict cycle. The notelet
|
|
6
|
+
* investigation's `342 to 0 AVPlayerItem` count came from running this
|
|
7
|
+
* command directly + grepping `_objc_rootAllocWithZone | wc -l`. v1.12
|
|
8
|
+
* automates the parse + aggregates by call-stack fingerprint so the
|
|
9
|
+
* response is a small structured list instead of 342 verbose blocks.
|
|
10
|
+
*
|
|
11
|
+
* Output shape (per SCANNING block):
|
|
12
|
+
*
|
|
13
|
+
* ```text
|
|
14
|
+
* SCANNING <AVPlayerItem 0xADDR> [size]
|
|
15
|
+
* Call stack: 0xADDR (dyld) start | 0xADDR (...) ??? | ...
|
|
16
|
+
* REFERENCES TO THIS: N STRONG: X CONSERVATIVE: Y WEAK UU etc: Z
|
|
17
|
+
* <retainer-class 0xADDR> [size] +offset: edge-name 0xADDR
|
|
18
|
+
* ...
|
|
19
|
+
* CONTENTS:
|
|
20
|
+
* +offset: field-name 0xADDR --> <target-class 0xADDR> [size]
|
|
21
|
+
* ...
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Multiple SCANNING blocks (one per instance) are aggregated by
|
|
25
|
+
* call-stack fingerprint. Identical stacks count as one chain with
|
|
26
|
+
* `instanceCount: N` instead of N duplicates.
|
|
27
|
+
*/
|
|
28
|
+
export interface AllocationFrame {
|
|
29
|
+
/** Hex address of the frame, e.g. "0x100e97da4". */
|
|
30
|
+
address: string;
|
|
31
|
+
/** Image/binary name in parentheses, e.g. "(dyld)" or "(NoteletDemo.debug.dylib)". */
|
|
32
|
+
image: string;
|
|
33
|
+
/** Symbol name when symbolicated, e.g. "_objc_rootAllocWithZone" or "MediaNoteItemVideoView.prepareVideo". `???` when stripped. */
|
|
34
|
+
symbol: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ReferenceTreeChain {
|
|
37
|
+
/** How many instances share this exact call-stack fingerprint. */
|
|
38
|
+
instanceCount: number;
|
|
39
|
+
/**
|
|
40
|
+
* Call-stack frames from outer (root, dyld start) to inner (allocation site).
|
|
41
|
+
* The leaks output emits them in dyld-first order; we preserve that.
|
|
42
|
+
*/
|
|
43
|
+
callStack: AllocationFrame[];
|
|
44
|
+
/** A representative instance address for the user to chain into via `leaks <addr>`. */
|
|
45
|
+
exampleAddress: string;
|
|
46
|
+
/** Unique retainer classes referenced from THIS instance with how often each appeared across the aggregation group. */
|
|
47
|
+
retainers: Array<{
|
|
48
|
+
className: string;
|
|
49
|
+
count: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** The "user-actionable" frame: the deepest frame whose image isn't system (dyld, libobjc, libsystem, libdispatch, SwiftUI core runtime). Surfaces the line a developer would inspect. */
|
|
52
|
+
userFrame?: AllocationFrame;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pure: parse a single `Call stack: ...` line into an ordered list of frames.
|
|
56
|
+
* Frames are pipe-separated; each frame is `<hex-addr> (<image>) <symbol>`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function parseCallStackLine(line: string): AllocationFrame[];
|
|
59
|
+
/**
|
|
60
|
+
* Heuristic: pick the user-actionable frame from a call stack. The deepest
|
|
61
|
+
* (closest to allocation) frame whose image is NOT a system runtime. For
|
|
62
|
+
* notelet, this resolves to `MediaNoteItemVideoView.prepareVideo` -- the
|
|
63
|
+
* line in the library that called `AVPlayerItem.init`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function pickUserFrame(frames: AllocationFrame[]): AllocationFrame | undefined;
|
|
66
|
+
/** Pure: parse the full `leaks --debug=stacks` output for a class. */
|
|
67
|
+
export declare function parseLeaksDebugStacks(output: string): ReferenceTreeChain[];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for `leaks --debug=stacks --debug='<ClassName>$'` output.
|
|
3
|
+
*
|
|
4
|
+
* This is the canonical way to get the allocation stack + retainer list for
|
|
5
|
+
* a specific class without it being part of a strict cycle. The notelet
|
|
6
|
+
* investigation's `342 to 0 AVPlayerItem` count came from running this
|
|
7
|
+
* command directly + grepping `_objc_rootAllocWithZone | wc -l`. v1.12
|
|
8
|
+
* automates the parse + aggregates by call-stack fingerprint so the
|
|
9
|
+
* response is a small structured list instead of 342 verbose blocks.
|
|
10
|
+
*
|
|
11
|
+
* Output shape (per SCANNING block):
|
|
12
|
+
*
|
|
13
|
+
* ```text
|
|
14
|
+
* SCANNING <AVPlayerItem 0xADDR> [size]
|
|
15
|
+
* Call stack: 0xADDR (dyld) start | 0xADDR (...) ??? | ...
|
|
16
|
+
* REFERENCES TO THIS: N STRONG: X CONSERVATIVE: Y WEAK UU etc: Z
|
|
17
|
+
* <retainer-class 0xADDR> [size] +offset: edge-name 0xADDR
|
|
18
|
+
* ...
|
|
19
|
+
* CONTENTS:
|
|
20
|
+
* +offset: field-name 0xADDR --> <target-class 0xADDR> [size]
|
|
21
|
+
* ...
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Multiple SCANNING blocks (one per instance) are aggregated by
|
|
25
|
+
* call-stack fingerprint. Identical stacks count as one chain with
|
|
26
|
+
* `instanceCount: N` instead of N duplicates.
|
|
27
|
+
*/
|
|
28
|
+
/** Image names we treat as "system runtime" when scanning for the user-actionable frame. */
|
|
29
|
+
const SYSTEM_IMAGES = new Set([
|
|
30
|
+
"dyld",
|
|
31
|
+
"dyld_sim",
|
|
32
|
+
"libobjc.A.dylib",
|
|
33
|
+
"libsystem_malloc.dylib",
|
|
34
|
+
"libsystem_pthread.dylib",
|
|
35
|
+
"libsystem_c.dylib",
|
|
36
|
+
"libdispatch.dylib",
|
|
37
|
+
"libswift_Concurrency.dylib",
|
|
38
|
+
"libswiftCore.dylib",
|
|
39
|
+
"libswiftFoundation.dylib",
|
|
40
|
+
"com.apple.Foundation",
|
|
41
|
+
"com.apple.CoreFoundation",
|
|
42
|
+
"com.apple.SwiftUI",
|
|
43
|
+
"com.apple.UIKitCore",
|
|
44
|
+
"com.apple.GraphicsServices",
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Pure: parse a single `Call stack: ...` line into an ordered list of frames.
|
|
48
|
+
* Frames are pipe-separated; each frame is `<hex-addr> (<image>) <symbol>`.
|
|
49
|
+
*/
|
|
50
|
+
export function parseCallStackLine(line) {
|
|
51
|
+
// Strip the leading "Call stack:" prefix and split by " | " separator.
|
|
52
|
+
const m = /Call stack:\s*(.*)$/.exec(line.trim());
|
|
53
|
+
if (!m)
|
|
54
|
+
return [];
|
|
55
|
+
const raw = m[1];
|
|
56
|
+
const frames = [];
|
|
57
|
+
for (const part of raw.split("|").map((s) => s.trim())) {
|
|
58
|
+
if (!part)
|
|
59
|
+
continue;
|
|
60
|
+
// `0xADDR (image) symbol`
|
|
61
|
+
const fm = /^(0x[0-9a-fA-F]+)\s*\(([^)]+)\)\s*(.*)$/.exec(part);
|
|
62
|
+
if (!fm)
|
|
63
|
+
continue;
|
|
64
|
+
frames.push({
|
|
65
|
+
address: fm[1],
|
|
66
|
+
image: fm[2],
|
|
67
|
+
symbol: fm[3].trim() || "???",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return frames;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Heuristic: pick the user-actionable frame from a call stack. The deepest
|
|
74
|
+
* (closest to allocation) frame whose image is NOT a system runtime. For
|
|
75
|
+
* notelet, this resolves to `MediaNoteItemVideoView.prepareVideo` -- the
|
|
76
|
+
* line in the library that called `AVPlayerItem.init`.
|
|
77
|
+
*/
|
|
78
|
+
export function pickUserFrame(frames) {
|
|
79
|
+
// Walk from innermost (end of array) to outermost (start) looking for
|
|
80
|
+
// the first non-system image. Skip frames whose symbol is `???`.
|
|
81
|
+
for (let i = frames.length - 1; i >= 0; i--) {
|
|
82
|
+
const f = frames[i];
|
|
83
|
+
if (SYSTEM_IMAGES.has(f.image))
|
|
84
|
+
continue;
|
|
85
|
+
if (f.symbol === "???")
|
|
86
|
+
continue;
|
|
87
|
+
return f;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Fingerprint a call stack for aggregation. Strips addresses (which differ
|
|
93
|
+
* per instance even when the call site is the same), keeps image + symbol.
|
|
94
|
+
*/
|
|
95
|
+
function fingerprintCallStack(frames) {
|
|
96
|
+
return frames.map((f) => `${f.image}::${f.symbol}`).join("|");
|
|
97
|
+
}
|
|
98
|
+
/** Pure: parse the full `leaks --debug=stacks` output for a class. */
|
|
99
|
+
export function parseLeaksDebugStacks(output) {
|
|
100
|
+
const blocks = output.split(/^SCANNING\s+/m).slice(1);
|
|
101
|
+
const buckets = new Map();
|
|
102
|
+
for (const block of blocks) {
|
|
103
|
+
// Block header: `<ClassName 0xADDR> [size]\n` followed by indented lines.
|
|
104
|
+
const headerMatch = /^<[^>]+\s+(0x[0-9a-fA-F]+)>\s*\[\d+\]/.exec(block);
|
|
105
|
+
if (!headerMatch)
|
|
106
|
+
continue;
|
|
107
|
+
const exampleAddress = headerMatch[1];
|
|
108
|
+
const lines = block.split(/\r?\n/);
|
|
109
|
+
let callStack = [];
|
|
110
|
+
const retainers = new Set();
|
|
111
|
+
let inRetainerSection = false;
|
|
112
|
+
let inContentsSection = false;
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (line.includes("Call stack:")) {
|
|
115
|
+
callStack = parseCallStackLine(line);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (/^REFERENCES TO THIS:/.test(line.trim())) {
|
|
119
|
+
inRetainerSection = true;
|
|
120
|
+
inContentsSection = false;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (/^CONTENTS:/.test(line.trim())) {
|
|
124
|
+
inRetainerSection = false;
|
|
125
|
+
inContentsSection = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (inRetainerSection) {
|
|
129
|
+
// Lines look like:
|
|
130
|
+
// ` <retainer-class 0xADDR> [size] +N: edge 0xADDR`
|
|
131
|
+
// ` <NSMutableSet (Storage) 0xADDR> [size] +N: __strong 0xADDR`
|
|
132
|
+
// The class name can contain spaces and parens (Foundation storage
|
|
133
|
+
// variants); capture everything between the leading `<` and the
|
|
134
|
+
// ` 0xADDR>` suffix. Greedy until the last space-then-hex pattern.
|
|
135
|
+
const rm = /<\s*(.+?)\s+0x[0-9a-fA-F]+\s*>/.exec(line);
|
|
136
|
+
if (rm) {
|
|
137
|
+
const name = rm[1].trim();
|
|
138
|
+
if (name.length > 0)
|
|
139
|
+
retainers.add(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// We don't currently capture CONTENTS (outgoing edges). Could be
|
|
143
|
+
// surfaced later as a separate field if useful.
|
|
144
|
+
void inContentsSection;
|
|
145
|
+
}
|
|
146
|
+
if (callStack.length === 0)
|
|
147
|
+
continue;
|
|
148
|
+
const fp = fingerprintCallStack(callStack);
|
|
149
|
+
const existing = buckets.get(fp);
|
|
150
|
+
if (existing) {
|
|
151
|
+
existing.instanceCount += 1;
|
|
152
|
+
for (const r of retainers) {
|
|
153
|
+
existing.retainerCounts.set(r, (existing.retainerCounts.get(r) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const retainerCounts = new Map();
|
|
158
|
+
for (const r of retainers)
|
|
159
|
+
retainerCounts.set(r, 1);
|
|
160
|
+
buckets.set(fp, {
|
|
161
|
+
instanceCount: 1,
|
|
162
|
+
callStack,
|
|
163
|
+
exampleAddress,
|
|
164
|
+
retainerCounts,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const chains = [];
|
|
169
|
+
for (const b of buckets.values()) {
|
|
170
|
+
const retainers = Array.from(b.retainerCounts.entries())
|
|
171
|
+
.map(([className, count]) => ({ className, count }))
|
|
172
|
+
.sort((x, y) => y.count - x.count);
|
|
173
|
+
const chain = {
|
|
174
|
+
instanceCount: b.instanceCount,
|
|
175
|
+
callStack: b.callStack,
|
|
176
|
+
exampleAddress: b.exampleAddress,
|
|
177
|
+
retainers,
|
|
178
|
+
};
|
|
179
|
+
const uf = pickUserFrame(b.callStack);
|
|
180
|
+
if (uf)
|
|
181
|
+
chain.userFrame = uf;
|
|
182
|
+
chains.push(chain);
|
|
183
|
+
}
|
|
184
|
+
chains.sort((a, b) => b.instanceCount - a.instanceCount);
|
|
185
|
+
return chains;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=leaksDebugStacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaksDebugStacks.js","sourceRoot":"","sources":["../../src/parsers/leaksDebugStacks.ts"],"names":[],"mappings":"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"}
|
|
@@ -267,6 +267,11 @@ export function isFrameworkNoise(className) {
|
|
|
267
267
|
// missed them, e.g. closures with no class name).
|
|
268
268
|
if (/^<[^>]*0x[0-9a-fA-F]+>/.test(className))
|
|
269
269
|
return true;
|
|
270
|
+
// "N bytes into <SomeClass 0xADDR> [size]" form. These are heap offsets,
|
|
271
|
+
// not class instances; they represent partial allocations and scale with
|
|
272
|
+
// app activity (Swift runtime growing, ObjC class table loading, etc.).
|
|
273
|
+
if (/^\d+ bytes into\b/.test(className))
|
|
274
|
+
return true;
|
|
270
275
|
// Foundation observer registry internals (these grow with KVO activity but
|
|
271
276
|
// are not the actionable site; the user's class is what to fix).
|
|
272
277
|
if (className === "CFXNotificationRegistrar")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referenceTree.js","sourceRoot":"","sources":["../../src/parsers/referenceTree.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"referenceTree.js","sourceRoot":"","sources":["../../src/parsers/referenceTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,11 +15,13 @@ export declare const analyzeHangsSchema: z.ZodObject<{
|
|
|
15
15
|
endMs: number;
|
|
16
16
|
}>>;
|
|
17
17
|
topFramesByHangStartNs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
18
|
+
includeStackClassification: z.ZodDefault<z.ZodBoolean>;
|
|
18
19
|
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
19
20
|
}, "strip", z.ZodTypeAny, {
|
|
20
21
|
tracePath: string;
|
|
21
22
|
topN: number;
|
|
22
23
|
minDurationMs: number;
|
|
24
|
+
includeStackClassification: boolean;
|
|
23
25
|
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
24
26
|
timeRangeMs?: {
|
|
25
27
|
startMs: number;
|
|
@@ -36,6 +38,7 @@ export declare const analyzeHangsSchema: z.ZodObject<{
|
|
|
36
38
|
endMs: number;
|
|
37
39
|
} | undefined;
|
|
38
40
|
topFramesByHangStartNs?: Record<string, string> | undefined;
|
|
41
|
+
includeStackClassification?: boolean | undefined;
|
|
39
42
|
}>;
|
|
40
43
|
export type AnalyzeHangsInput = z.infer<typeof analyzeHangsSchema>;
|
|
41
44
|
/**
|
|
@@ -114,4 +117,29 @@ export declare function analyzeHangsFromXml(xml: string, tracePath: string, topN
|
|
|
114
117
|
startMs: number;
|
|
115
118
|
endMs: number;
|
|
116
119
|
}, topFramesByHangStartNs?: Readonly<Record<string, string>>): AnalyzeHangsResult;
|
|
120
|
+
/**
|
|
121
|
+
* Pure: walk parsed time-profile rows + hang entries, correlate samples
|
|
122
|
+
* to hang windows by timestamp, return a `startNs -> topFrame` map.
|
|
123
|
+
*
|
|
124
|
+
* Algorithm: for each hang H with [startNs, startNs+durationNs], find all
|
|
125
|
+
* samples whose `weight` timestamp falls in that window. Per hang, pick
|
|
126
|
+
* the top frame by aggregate sample weight (or by sample count if weight
|
|
127
|
+
* is absent). The result map keys are stringified `startNs` values to
|
|
128
|
+
* match the existing `topFramesByHangStartNs` shape that v1.9 exposed.
|
|
129
|
+
*
|
|
130
|
+
* Returns an empty map when the time-profile rows are absent or none
|
|
131
|
+
* correlate. Failure modes degrade silently so the cycle-side path
|
|
132
|
+
* still completes.
|
|
133
|
+
*
|
|
134
|
+
* Exposed for testing.
|
|
135
|
+
*/
|
|
136
|
+
export declare function correlateTimeProfileToHangs(hangs: Array<{
|
|
137
|
+
startNs: number;
|
|
138
|
+
durationNs: number;
|
|
139
|
+
}>, timeProfileRows: Array<{
|
|
140
|
+
startNs: number;
|
|
141
|
+
weight?: number;
|
|
142
|
+
backtrace?: string;
|
|
143
|
+
topFrame?: string;
|
|
144
|
+
}>): Record<string, string>;
|
|
117
145
|
export declare function analyzeHangs(input: AnalyzeHangsInput): Promise<AnalyzeHangsResult>;
|
|
@@ -30,7 +30,11 @@ export const analyzeHangsSchema = z.object({
|
|
|
30
30
|
topFramesByHangStartNs: z
|
|
31
31
|
.record(z.string(), z.string())
|
|
32
32
|
.optional()
|
|
33
|
-
.describe("Optional supplemental map from a hang's `startNs` (as a string) to the top frame seen during that hang. When provided, each matching hang in `top[]` is enriched with `mainThreadViolations[]` that catalog the kind of work happening on the main thread (sync-io, db-lock, network, lock-contention). Typical pipeline: call `analyzeTimeProfile` separately on the same `.trace`, correlate samples to hang windows by timestamp, then re-call `analyzeHangs` with the resulting map. Omit to skip the enrichment."),
|
|
33
|
+
.describe("Optional supplemental map from a hang's `startNs` (as a string) to the top frame seen during that hang. When provided, each matching hang in `top[]` is enriched with `mainThreadViolations[]` that catalog the kind of work happening on the main thread (sync-io, db-lock, network, lock-contention). Typical pipeline: call `analyzeTimeProfile` separately on the same `.trace`, correlate samples to hang windows by timestamp, then re-call `analyzeHangs` with the resulting map. Omit to skip the enrichment. SUPERSEDED in v1.12 by `includeStackClassification: true`, which builds this map internally."),
|
|
34
|
+
includeStackClassification: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.default(false)
|
|
37
|
+
.describe("v1.12+. When true, analyzeHangs internally exports the `time-profile` schema in parallel with `potential-hangs`, correlates samples to hang windows by timestamp, picks the dominant top frame per hang, and runs `classifyHangFrame` on it. The `mainThreadViolations[]` field on each top hang is populated automatically. Replaces the v1.9 caller-built `topFramesByHangStartNs` map: most callers should set this flag instead of building the map manually. Adds a second xctrace export call, run in parallel with the hangs export so wall-clock is unchanged when the trace export succeeds. Falls back gracefully (empty violations, no error) when the time-profile schema is absent or xctrace SIGSEGVs on it."),
|
|
34
38
|
outputFormat: outputFormatField,
|
|
35
39
|
});
|
|
36
40
|
const MAIN_THREAD_VIOLATION_SIGNATURES = [
|
|
@@ -206,22 +210,145 @@ function buildHangDiagnosis(rows, hangs, microhangs, longestMs, averageMs) {
|
|
|
206
210
|
}
|
|
207
211
|
return parts.join(" ");
|
|
208
212
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Pure: walk parsed time-profile rows + hang entries, correlate samples
|
|
215
|
+
* to hang windows by timestamp, return a `startNs -> topFrame` map.
|
|
216
|
+
*
|
|
217
|
+
* Algorithm: for each hang H with [startNs, startNs+durationNs], find all
|
|
218
|
+
* samples whose `weight` timestamp falls in that window. Per hang, pick
|
|
219
|
+
* the top frame by aggregate sample weight (or by sample count if weight
|
|
220
|
+
* is absent). The result map keys are stringified `startNs` values to
|
|
221
|
+
* match the existing `topFramesByHangStartNs` shape that v1.9 exposed.
|
|
222
|
+
*
|
|
223
|
+
* Returns an empty map when the time-profile rows are absent or none
|
|
224
|
+
* correlate. Failure modes degrade silently so the cycle-side path
|
|
225
|
+
* still completes.
|
|
226
|
+
*
|
|
227
|
+
* Exposed for testing.
|
|
228
|
+
*/
|
|
229
|
+
export function correlateTimeProfileToHangs(hangs, timeProfileRows) {
|
|
230
|
+
const result = {};
|
|
231
|
+
if (hangs.length === 0 || timeProfileRows.length === 0)
|
|
232
|
+
return result;
|
|
233
|
+
for (const hang of hangs) {
|
|
234
|
+
const windowEnd = hang.startNs + hang.durationNs;
|
|
235
|
+
// Per-frame aggregate score within this hang's window.
|
|
236
|
+
const scores = new Map();
|
|
237
|
+
for (const sample of timeProfileRows) {
|
|
238
|
+
if (sample.startNs < hang.startNs || sample.startNs > windowEnd)
|
|
239
|
+
continue;
|
|
240
|
+
const frame = sample.topFrame ??
|
|
241
|
+
// First non-empty line of backtrace, when topFrame isn't pre-parsed.
|
|
242
|
+
sample.backtrace?.split(/\r?\n/).find((l) => l.trim().length > 0) ??
|
|
243
|
+
"";
|
|
244
|
+
if (!frame)
|
|
245
|
+
continue;
|
|
246
|
+
const weight = sample.weight ?? 1;
|
|
247
|
+
scores.set(frame, (scores.get(frame) ?? 0) + weight);
|
|
248
|
+
}
|
|
249
|
+
if (scores.size === 0)
|
|
250
|
+
continue;
|
|
251
|
+
// Pick the frame with the highest aggregate score.
|
|
252
|
+
let topFrame = "";
|
|
253
|
+
let topScore = -Infinity;
|
|
254
|
+
for (const [frame, score] of scores) {
|
|
255
|
+
if (score > topScore) {
|
|
256
|
+
topScore = score;
|
|
257
|
+
topFrame = frame;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (topFrame)
|
|
261
|
+
result[hangFrameMapKey(hang.startNs)] = topFrame;
|
|
213
262
|
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Spawn `xctrace export` for the `time-profile` schema. Non-fatal on
|
|
267
|
+
* failure: returns an empty array so the caller can degrade gracefully.
|
|
268
|
+
* Returns parsed rows with at minimum `startNs` + a `topFrame` string.
|
|
269
|
+
*/
|
|
270
|
+
async function captureTimeProfileRows(tracePath) {
|
|
214
271
|
const result = await runCommand("xcrun", [
|
|
215
272
|
"xctrace",
|
|
216
273
|
"export",
|
|
217
274
|
"--input",
|
|
218
275
|
tracePath,
|
|
219
276
|
"--xpath",
|
|
220
|
-
'/trace-toc/run/data/table[@schema="
|
|
277
|
+
'/trace-toc/run/data/table[@schema="time-profile"]',
|
|
221
278
|
], { timeoutMs: 5 * 60_000 });
|
|
222
|
-
if (result.code !== 0)
|
|
223
|
-
|
|
279
|
+
if (result.code !== 0)
|
|
280
|
+
return [];
|
|
281
|
+
try {
|
|
282
|
+
const tables = parseXctraceXml(result.stdout);
|
|
283
|
+
const tp = tables.find((t) => t.schema === "time-profile");
|
|
284
|
+
if (!tp)
|
|
285
|
+
return [];
|
|
286
|
+
const rows = [];
|
|
287
|
+
for (const row of tp.rows) {
|
|
288
|
+
const startNs = asNumber(row.start);
|
|
289
|
+
if (startNs == null)
|
|
290
|
+
continue;
|
|
291
|
+
const weight = asNumber(row.weight) ?? undefined;
|
|
292
|
+
// The "top frame" field name varies (`backtrace`, `top-frame`, etc.).
|
|
293
|
+
// Pick the first non-empty stringified candidate.
|
|
294
|
+
const candidates = [
|
|
295
|
+
asFormatted(row["top-frame"]),
|
|
296
|
+
asFormatted(row.backtrace),
|
|
297
|
+
asFormatted(row.symbol),
|
|
298
|
+
asFormatted(row["leaf-symbol"]),
|
|
299
|
+
];
|
|
300
|
+
const topFrame = candidates.find((v) => typeof v === "string" && v.length > 0);
|
|
301
|
+
rows.push({ startNs, weight, topFrame });
|
|
302
|
+
}
|
|
303
|
+
return rows;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
export async function analyzeHangs(input) {
|
|
310
|
+
const tracePath = resolvePath(input.tracePath);
|
|
311
|
+
if (!existsSync(tracePath)) {
|
|
312
|
+
throw new Error(`Trace bundle not found: ${tracePath}`);
|
|
313
|
+
}
|
|
314
|
+
const wantStackClassification = input.includeStackClassification ?? false;
|
|
315
|
+
const [hangsResult, timeProfileRows] = await Promise.all([
|
|
316
|
+
runCommand("xcrun", [
|
|
317
|
+
"xctrace",
|
|
318
|
+
"export",
|
|
319
|
+
"--input",
|
|
320
|
+
tracePath,
|
|
321
|
+
"--xpath",
|
|
322
|
+
'/trace-toc/run/data/table[@schema="potential-hangs"]',
|
|
323
|
+
], { timeoutMs: 5 * 60_000 }),
|
|
324
|
+
wantStackClassification
|
|
325
|
+
? captureTimeProfileRows(tracePath)
|
|
326
|
+
: Promise.resolve([]),
|
|
327
|
+
]);
|
|
328
|
+
if (hangsResult.code !== 0) {
|
|
329
|
+
throw new Error(`xctrace export failed (code ${hangsResult.code}): ${hangsResult.stderr || hangsResult.stdout}`);
|
|
330
|
+
}
|
|
331
|
+
// Build the supplemental top-frames map. Caller-supplied map takes
|
|
332
|
+
// precedence over the v1.12 auto-correlation so users who pre-built a
|
|
333
|
+
// map can override the heuristic. The auto path runs only when the
|
|
334
|
+
// user didn't supply a map AND opted into stack classification.
|
|
335
|
+
let topFramesMap = input.topFramesByHangStartNs;
|
|
336
|
+
if (!topFramesMap && wantStackClassification && timeProfileRows.length > 0) {
|
|
337
|
+
// Parse the hangs table once to drive correlation; analyzeHangsFromXml
|
|
338
|
+
// re-parses internally so we get a clean separation between the
|
|
339
|
+
// correlation step and the final render.
|
|
340
|
+
const tables = parseXctraceXml(hangsResult.stdout);
|
|
341
|
+
const hangsTable = tables.find((t) => t.schema === "potential-hangs");
|
|
342
|
+
if (hangsTable) {
|
|
343
|
+
const hangsForCorrelation = [];
|
|
344
|
+
for (const row of hangsTable.rows) {
|
|
345
|
+
const startNs = asNumber(row.start) ?? 0;
|
|
346
|
+
const durationNs = asNumber(row.duration) ?? 0;
|
|
347
|
+
hangsForCorrelation.push({ startNs, durationNs });
|
|
348
|
+
}
|
|
349
|
+
topFramesMap = correlateTimeProfileToHangs(hangsForCorrelation, timeProfileRows);
|
|
350
|
+
}
|
|
224
351
|
}
|
|
225
|
-
return analyzeHangsFromXml(
|
|
352
|
+
return analyzeHangsFromXml(hangsResult.stdout, tracePath, input.topN ?? 10, input.minDurationMs ?? 0, input.timeRangeMs, topFramesMap);
|
|
226
353
|
}
|
|
227
354
|
//# sourceMappingURL=analyzeHangs.js.map
|