memorydetective 1.8.1 → 1.10.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 +75 -1
- package/README.md +84 -15
- package/USAGE.md +27 -4
- package/dist/cli.js +106 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.js +42 -22
- package/dist/index.js.map +1 -1
- package/dist/parsers/referenceTree.d.ts +111 -0
- package/dist/parsers/referenceTree.js +328 -0
- package/dist/parsers/referenceTree.js.map +1 -0
- package/dist/runtime/axe.js +6 -1
- package/dist/runtime/axe.js.map +1 -1
- package/dist/runtime/exec.d.ts +30 -0
- package/dist/runtime/exec.js +30 -3
- package/dist/runtime/exec.js.map +1 -1
- package/dist/runtime/fixTemplates.js +67 -0
- package/dist/runtime/fixTemplates.js.map +1 -1
- package/dist/runtime/leakReport.d.ts +62 -0
- package/dist/runtime/leakReport.js +138 -0
- package/dist/runtime/leakReport.js.map +1 -0
- package/dist/runtime/platformCheck.d.ts +54 -0
- package/dist/runtime/platformCheck.js +94 -0
- package/dist/runtime/platformCheck.js.map +1 -0
- package/dist/runtime/redact.d.ts +66 -0
- package/dist/runtime/redact.js +146 -0
- package/dist/runtime/redact.js.map +1 -0
- package/dist/runtime/responseFormatter.d.ts +78 -0
- package/dist/runtime/responseFormatter.js +307 -0
- package/dist/runtime/responseFormatter.js.map +1 -0
- package/dist/runtime/securityFlags.d.ts +74 -0
- package/dist/runtime/securityFlags.js +90 -0
- package/dist/runtime/securityFlags.js.map +1 -0
- package/dist/runtime/staticAnalysisHints.js +14 -1
- package/dist/runtime/staticAnalysisHints.js.map +1 -1
- package/dist/templates/leak-report.html +39 -0
- package/dist/templates/templates/leak-report.html +39 -0
- package/dist/tools/analyzeAbandonedMemory.d.ts +162 -0
- package/dist/tools/analyzeAbandonedMemory.js +325 -0
- package/dist/tools/analyzeAbandonedMemory.js.map +1 -0
- package/dist/tools/analyzeAllocations.d.ts +11 -2
- package/dist/tools/analyzeAllocations.js +4 -0
- package/dist/tools/analyzeAllocations.js.map +1 -1
- package/dist/tools/analyzeAnimationHitches.d.ts +32 -2
- package/dist/tools/analyzeAnimationHitches.js +25 -4
- package/dist/tools/analyzeAnimationHitches.js.map +1 -1
- package/dist/tools/analyzeAppLaunch.d.ts +3 -0
- package/dist/tools/analyzeAppLaunch.js +2 -0
- package/dist/tools/analyzeAppLaunch.js.map +1 -1
- package/dist/tools/analyzeHangs.d.ts +78 -2
- package/dist/tools/analyzeHangs.js +117 -4
- package/dist/tools/analyzeHangs.js.map +1 -1
- package/dist/tools/analyzeMemgraph.d.ts +40 -1
- package/dist/tools/analyzeMemgraph.js +66 -2
- package/dist/tools/analyzeMemgraph.js.map +1 -1
- package/dist/tools/analyzeTimeProfile.d.ts +11 -1
- package/dist/tools/analyzeTimeProfile.js +5 -0
- package/dist/tools/analyzeTimeProfile.js.map +1 -1
- package/dist/tools/bootAndLaunchForLeakInvestigation.d.ts +18 -9
- package/dist/tools/bootAndLaunchForLeakInvestigation.js +27 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.js.map +1 -1
- package/dist/tools/captureMemgraph.d.ts +22 -4
- package/dist/tools/captureMemgraph.js +42 -9
- package/dist/tools/captureMemgraph.js.map +1 -1
- package/dist/tools/captureScenarioState.d.ts +12 -4
- package/dist/tools/captureScenarioState.js +4 -0
- package/dist/tools/captureScenarioState.js.map +1 -1
- package/dist/tools/classifyCycle.js +77 -0
- package/dist/tools/classifyCycle.js.map +1 -1
- package/dist/tools/cleanupTraces.d.ts +87 -0
- package/dist/tools/cleanupTraces.js +232 -0
- package/dist/tools/cleanupTraces.js.map +1 -0
- package/dist/tools/compareTracesByPattern.d.ts +2 -2
- package/dist/tools/detectLeaksInXCTest.d.ts +116 -0
- package/dist/tools/detectLeaksInXCTest.js +311 -0
- package/dist/tools/detectLeaksInXCTest.js.map +1 -0
- package/dist/tools/detectLeaksInXCUITest.d.ts +8 -3
- package/dist/tools/detectLeaksInXCUITest.js +30 -4
- package/dist/tools/detectLeaksInXCUITest.js.map +1 -1
- package/dist/tools/diffMemgraphs.d.ts +5 -2
- package/dist/tools/diffMemgraphs.js +2 -0
- package/dist/tools/diffMemgraphs.js.map +1 -1
- package/dist/tools/findCycles.d.ts +1 -1
- package/dist/tools/recordTimeProfile.d.ts +29 -9
- package/dist/tools/recordTimeProfile.js +70 -7
- package/dist/tools/recordTimeProfile.js.map +1 -1
- package/dist/tools/renderCycleGraph.d.ts +1 -1
- package/dist/tools/verifyFix.d.ts +2 -2
- package/dist/types.d.ts +24 -1
- package/package.json +3 -3
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { runCommand } from "../runtime/exec.js";
|
|
5
5
|
import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
|
|
6
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
6
7
|
export const analyzeAnimationHitchesSchema = z.object({
|
|
7
8
|
tracePath: z
|
|
8
9
|
.string()
|
|
@@ -18,11 +19,19 @@ export const analyzeAnimationHitchesSchema = z.object({
|
|
|
18
19
|
.number()
|
|
19
20
|
.nonnegative()
|
|
20
21
|
.default(0)
|
|
21
|
-
.describe("Filter out hitches shorter than this duration in milliseconds. Apple categorizes hitches >100ms as user-perceptible
|
|
22
|
+
.describe("Filter out hitches shorter than this duration in milliseconds. Apple categorizes hitches >100ms as user-perceptible, pass 100 to focus on those."),
|
|
23
|
+
timeRangeMs: z
|
|
24
|
+
.object({
|
|
25
|
+
startMs: z.number().nonnegative(),
|
|
26
|
+
endMs: z.number().nonnegative(),
|
|
27
|
+
})
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Optional time-window filter. Only hitches whose `startNs` falls within `[startMs, endMs]` (milliseconds since recording start) are included. Use this to answer 'what hitches happened during this 5-second user-visible jank window?' without re-recording."),
|
|
30
|
+
outputFormat: outputFormatField,
|
|
22
31
|
});
|
|
23
32
|
const PERCEPTIBLE_MS = 100;
|
|
24
33
|
/** Pure: turn parsed XML into the analyzed result. */
|
|
25
|
-
export function analyzeAnimationHitchesFromXml(xml, tracePath, topN = 10, minDurationMs = 0) {
|
|
34
|
+
export function analyzeAnimationHitchesFromXml(xml, tracePath, topN = 10, minDurationMs = 0, timeRangeMs) {
|
|
26
35
|
const tables = parseXctraceXml(xml);
|
|
27
36
|
const table = tables.find((t) => t.schema === "animation-hitches");
|
|
28
37
|
if (!table) {
|
|
@@ -39,6 +48,7 @@ export function analyzeAnimationHitchesFromXml(xml, tracePath, topN = 10, minDur
|
|
|
39
48
|
byType: {},
|
|
40
49
|
top: [],
|
|
41
50
|
diagnosis: "No animation-hitches table found in the trace.",
|
|
51
|
+
status: "not_present",
|
|
42
52
|
};
|
|
43
53
|
}
|
|
44
54
|
const all = [];
|
|
@@ -57,7 +67,17 @@ export function analyzeAnimationHitchesFromXml(xml, tracePath, topN = 10, minDur
|
|
|
57
67
|
source: asFormatted(row.source),
|
|
58
68
|
});
|
|
59
69
|
}
|
|
60
|
-
const filtered = all.filter((e) =>
|
|
70
|
+
const filtered = all.filter((e) => {
|
|
71
|
+
if (e.durationMs < minDurationMs)
|
|
72
|
+
return false;
|
|
73
|
+
if (timeRangeMs) {
|
|
74
|
+
const startMs = e.startNs / 1_000_000;
|
|
75
|
+
if (startMs < timeRangeMs.startMs || startMs > timeRangeMs.endMs) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
61
81
|
const byType = {};
|
|
62
82
|
for (const e of filtered) {
|
|
63
83
|
byType[e.hitchType] = (byType[e.hitchType] ?? 0) + 1;
|
|
@@ -83,6 +103,7 @@ export function analyzeAnimationHitchesFromXml(xml, tracePath, topN = 10, minDur
|
|
|
83
103
|
byType,
|
|
84
104
|
top,
|
|
85
105
|
diagnosis,
|
|
106
|
+
status: "available",
|
|
86
107
|
};
|
|
87
108
|
}
|
|
88
109
|
function buildDiagnosis(rows, perceptible, longestMs, averageMs) {
|
|
@@ -116,6 +137,6 @@ export async function analyzeAnimationHitches(input) {
|
|
|
116
137
|
if (result.code !== 0) {
|
|
117
138
|
throw new Error(`xctrace export failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
118
139
|
}
|
|
119
|
-
return analyzeAnimationHitchesFromXml(result.stdout, tracePath, input.topN ?? 10, input.minDurationMs ?? 0);
|
|
140
|
+
return analyzeAnimationHitchesFromXml(result.stdout, tracePath, input.topN ?? 10, input.minDurationMs ?? 0, input.timeRangeMs);
|
|
120
141
|
}
|
|
121
142
|
//# sourceMappingURL=analyzeAnimationHitches.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeAnimationHitches.js","sourceRoot":"","sources":["../../src/tools/analyzeAnimationHitches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAElC,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,6JAA6J,CAC9J;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,gEAAgE,CAAC;IAC7E,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,WAAW,EAAE;SACb,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CACP,
|
|
1
|
+
{"version":3,"file":"analyzeAnimationHitches.js","sourceRoot":"","sources":["../../src/tools/analyzeAnimationHitches.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const analyzeAppLaunchSchema: z.ZodObject<{
|
|
3
3
|
tracePath: z.ZodString;
|
|
4
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
4
5
|
}, "strip", z.ZodTypeAny, {
|
|
5
6
|
tracePath: string;
|
|
7
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
6
8
|
}, {
|
|
7
9
|
tracePath: string;
|
|
10
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
8
11
|
}>;
|
|
9
12
|
export type AnalyzeAppLaunchInput = z.infer<typeof analyzeAppLaunchSchema>;
|
|
10
13
|
export interface PhaseEntry {
|
|
@@ -3,11 +3,13 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { runCommand } from "../runtime/exec.js";
|
|
5
5
|
import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
|
|
6
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
6
7
|
export const analyzeAppLaunchSchema = z.object({
|
|
7
8
|
tracePath: z
|
|
8
9
|
.string()
|
|
9
10
|
.min(1)
|
|
10
11
|
.describe("Absolute path to a `.trace` bundle recorded with the App Launch template (`xcrun xctrace record --template 'App Launch' --launch <bundleId>`)."),
|
|
12
|
+
outputFormat: outputFormatField,
|
|
11
13
|
});
|
|
12
14
|
/** Phases Apple breaks app launch into, ordered by occurrence. */
|
|
13
15
|
const PHASE_ORDER = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeAppLaunch.js","sourceRoot":"","sources":["../../src/tools/analyzeAppLaunch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,GACZ,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzeAppLaunch.js","sourceRoot":"","sources":["../../src/tools/analyzeAppLaunch.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,82 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { DataStatus } from "../types.js";
|
|
2
3
|
export declare const analyzeHangsSchema: z.ZodObject<{
|
|
3
4
|
tracePath: z.ZodString;
|
|
4
5
|
topN: z.ZodDefault<z.ZodNumber>;
|
|
5
6
|
minDurationMs: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
timeRangeMs: z.ZodOptional<z.ZodObject<{
|
|
8
|
+
startMs: z.ZodNumber;
|
|
9
|
+
endMs: z.ZodNumber;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
startMs: number;
|
|
12
|
+
endMs: number;
|
|
13
|
+
}, {
|
|
14
|
+
startMs: number;
|
|
15
|
+
endMs: number;
|
|
16
|
+
}>>;
|
|
17
|
+
topFramesByHangStartNs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
18
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
6
19
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
topN: number;
|
|
8
20
|
tracePath: string;
|
|
21
|
+
topN: number;
|
|
9
22
|
minDurationMs: number;
|
|
23
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
24
|
+
timeRangeMs?: {
|
|
25
|
+
startMs: number;
|
|
26
|
+
endMs: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
topFramesByHangStartNs?: Record<string, string> | undefined;
|
|
10
29
|
}, {
|
|
11
30
|
tracePath: string;
|
|
31
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
12
32
|
topN?: number | undefined;
|
|
13
33
|
minDurationMs?: number | undefined;
|
|
34
|
+
timeRangeMs?: {
|
|
35
|
+
startMs: number;
|
|
36
|
+
endMs: number;
|
|
37
|
+
} | undefined;
|
|
38
|
+
topFramesByHangStartNs?: Record<string, string> | undefined;
|
|
14
39
|
}>;
|
|
15
40
|
export type AnalyzeHangsInput = z.infer<typeof analyzeHangsSchema>;
|
|
41
|
+
/**
|
|
42
|
+
* Catalog of main-thread-violation signatures. Each entry classifies a
|
|
43
|
+
* top-frame symbol pattern into one of four kinds that map onto the most
|
|
44
|
+
* common iOS user-perceived freezes:
|
|
45
|
+
*
|
|
46
|
+
* - `sync-io`: a blocking POSIX read/write or Foundation file API the
|
|
47
|
+
* runtime cannot async away from the main queue.
|
|
48
|
+
* - `db-lock`: SQLite mutex acquisition (the underlying primitive for
|
|
49
|
+
* Core Data, GRDB, and most Swift ORMs).
|
|
50
|
+
* - `network`: a blocking Network.framework/NSURLConnection sync call.
|
|
51
|
+
* - `lock-contention`: pthread/os_unfair_lock acquisition on the main
|
|
52
|
+
* thread, which serializes us against another thread.
|
|
53
|
+
*
|
|
54
|
+
* The matchers are case-sensitive substring checks. They deliberately
|
|
55
|
+
* stay close to the symbol name DebugSwift's Thread Checker flags so the
|
|
56
|
+
* coverage gap between the on-device tool and the offline catalog stays
|
|
57
|
+
* small. Adding new symbols later is a one-line append.
|
|
58
|
+
*/
|
|
59
|
+
export type MainThreadViolationKind = "sync-io" | "db-lock" | "network" | "lock-contention";
|
|
60
|
+
export interface MainThreadViolation {
|
|
61
|
+
kind: MainThreadViolationKind;
|
|
62
|
+
topFrame: string;
|
|
63
|
+
samples: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Pure: classify a top-frame symbol into a `MainThreadViolation`. Returns
|
|
67
|
+
* `null` when nothing in the catalog matches. The `samples` count comes
|
|
68
|
+
* from the caller; with only a top-frame string available we set it to 1.
|
|
69
|
+
*
|
|
70
|
+
* Multiple signatures can match a single frame (e.g. a sync I/O call that
|
|
71
|
+
* also holds an unfair lock). We return the FIRST match in catalog order,
|
|
72
|
+
* which puts more user-actionable categories ahead of generic locks.
|
|
73
|
+
*/
|
|
74
|
+
export declare function classifyHangFrame(topFrame: string, samples?: number): MainThreadViolation | null;
|
|
75
|
+
/** Stable key used to correlate the supplemental `topFramesByHangStartNs`
|
|
76
|
+
* map. Hang startNs values are nanoseconds (integers when xctrace exports
|
|
77
|
+
* them cleanly), so the key is just `String(startNs)`. Centralized so
|
|
78
|
+
* callers building the map use the same convention. */
|
|
79
|
+
export declare function hangFrameMapKey(startNs: number): string;
|
|
16
80
|
export interface HangEntry {
|
|
17
81
|
startNs: number;
|
|
18
82
|
startFmt: string;
|
|
@@ -20,6 +84,10 @@ export interface HangEntry {
|
|
|
20
84
|
durationMs: number;
|
|
21
85
|
durationFmt: string;
|
|
22
86
|
hangType: string;
|
|
87
|
+
/** Main-thread violations detected from the supplemental top-frame map.
|
|
88
|
+
* Empty array when the caller provided a frame but no signature matched;
|
|
89
|
+
* undefined when no frame was provided for this hang at all. */
|
|
90
|
+
mainThreadViolations?: MainThreadViolation[];
|
|
23
91
|
}
|
|
24
92
|
export interface AnalyzeHangsResult {
|
|
25
93
|
ok: boolean;
|
|
@@ -35,7 +103,15 @@ export interface AnalyzeHangsResult {
|
|
|
35
103
|
/** Filtered + sorted hangs, capped to topN. */
|
|
36
104
|
top: HangEntry[];
|
|
37
105
|
diagnosis: string;
|
|
106
|
+
/**
|
|
107
|
+
* Disambiguates empty arrays into "no data in the trace" vs "trace could
|
|
108
|
+
* not be exported" vs "data was exported partially". See {@link DataStatus}.
|
|
109
|
+
*/
|
|
110
|
+
status: DataStatus;
|
|
38
111
|
}
|
|
39
112
|
/** Pure: turn parsed XML rows into our analyzed result. */
|
|
40
|
-
export declare function analyzeHangsFromXml(xml: string, tracePath: string, topN?: number, minDurationMs?: number
|
|
113
|
+
export declare function analyzeHangsFromXml(xml: string, tracePath: string, topN?: number, minDurationMs?: number, timeRangeMs?: {
|
|
114
|
+
startMs: number;
|
|
115
|
+
endMs: number;
|
|
116
|
+
}, topFramesByHangStartNs?: Readonly<Record<string, string>>): AnalyzeHangsResult;
|
|
41
117
|
export declare function analyzeHangs(input: AnalyzeHangsInput): Promise<AnalyzeHangsResult>;
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { runCommand } from "../runtime/exec.js";
|
|
5
5
|
import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
|
|
6
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
6
7
|
export const analyzeHangsSchema = z.object({
|
|
7
8
|
tracePath: z
|
|
8
9
|
.string()
|
|
@@ -18,10 +19,97 @@ export const analyzeHangsSchema = z.object({
|
|
|
18
19
|
.number()
|
|
19
20
|
.nonnegative()
|
|
20
21
|
.default(0)
|
|
21
|
-
.describe("Filter out hangs shorter than this duration in milliseconds (default 0
|
|
22
|
+
.describe("Filter out hangs shorter than this duration in milliseconds (default 0, include all). Use 250 to focus on 'real' hangs only."),
|
|
23
|
+
timeRangeMs: z
|
|
24
|
+
.object({
|
|
25
|
+
startMs: z.number().nonnegative(),
|
|
26
|
+
endMs: z.number().nonnegative(),
|
|
27
|
+
})
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Optional time-window filter. Only hangs whose `startNs` falls within `[startMs, endMs]` (milliseconds since recording start) are included. Use this to answer 'what hangs happened between t=2s and t=7s?' without re-recording."),
|
|
30
|
+
topFramesByHangStartNs: z
|
|
31
|
+
.record(z.string(), z.string())
|
|
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."),
|
|
34
|
+
outputFormat: outputFormatField,
|
|
22
35
|
});
|
|
36
|
+
const MAIN_THREAD_VIOLATION_SIGNATURES = [
|
|
37
|
+
// sync-io: POSIX read/write and Foundation/NSData blocking APIs.
|
|
38
|
+
// Foundation often calls through to the libsystem symbols below, but
|
|
39
|
+
// dSYM symbolication can land on either, so match both.
|
|
40
|
+
{
|
|
41
|
+
kind: "sync-io",
|
|
42
|
+
matches: (f) => /\b(read|pread|readv|write|pwrite|writev|fsync|fdatasync|aio_read|aio_write)\b/.test(f) ||
|
|
43
|
+
f.includes("Data initWithContentsOfFile") ||
|
|
44
|
+
f.includes("NSData _initWithContentsOfURL") ||
|
|
45
|
+
f.includes("FileHandle readDataOfLength") ||
|
|
46
|
+
f.includes("FileManager createFileAtPath") ||
|
|
47
|
+
f.includes("FileManager removeItem"),
|
|
48
|
+
},
|
|
49
|
+
// db-lock: SQLite mutex acquisition. Triggers under Core Data, GRDB,
|
|
50
|
+
// SQLite.swift, FMDB - any client that funnels through libsqlite3.
|
|
51
|
+
{
|
|
52
|
+
kind: "db-lock",
|
|
53
|
+
matches: (f) => f.includes("sqlite3_step") ||
|
|
54
|
+
f.includes("sqlite3_prepare") ||
|
|
55
|
+
f.includes("sqlite3_mutex_enter") ||
|
|
56
|
+
f.includes("sqlite3LockAndPrepare") ||
|
|
57
|
+
f.includes("pagerSharedLock") ||
|
|
58
|
+
f.includes("NSPersistentStoreCoordinator lock") ||
|
|
59
|
+
f.includes("NSManagedObjectContext save"),
|
|
60
|
+
},
|
|
61
|
+
// network: blocking Network.framework / legacy NSURLConnection sync
|
|
62
|
+
// call, or +[NSURLConnection sendSynchronousRequest:returningResponse:].
|
|
63
|
+
{
|
|
64
|
+
kind: "network",
|
|
65
|
+
matches: (f) => f.includes("sendSynchronousRequest") ||
|
|
66
|
+
f.includes("NSURLConnection sendSynchronousRequest") ||
|
|
67
|
+
f.includes("URLSession dataTaskWithRequest") ||
|
|
68
|
+
// CFNetwork sync path.
|
|
69
|
+
f.includes("CFReadStreamRead") ||
|
|
70
|
+
// Network.framework sync wait.
|
|
71
|
+
/\bnw_connection_(start|wait)\b/.test(f),
|
|
72
|
+
},
|
|
73
|
+
// lock-contention: pthread / os_unfair_lock acquisition on the main
|
|
74
|
+
// thread that blocks waiting for another thread.
|
|
75
|
+
{
|
|
76
|
+
kind: "lock-contention",
|
|
77
|
+
matches: (f) => f.includes("pthread_mutex_lock") ||
|
|
78
|
+
f.includes("pthread_rwlock_wrlock") ||
|
|
79
|
+
f.includes("pthread_rwlock_rdlock") ||
|
|
80
|
+
f.includes("os_unfair_lock_lock") ||
|
|
81
|
+
f.includes("dispatch_semaphore_wait") ||
|
|
82
|
+
f.includes("dispatch_sync") ||
|
|
83
|
+
f.includes("NSConditionLock lockWhenCondition") ||
|
|
84
|
+
f.includes("NSLock lock"),
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Pure: classify a top-frame symbol into a `MainThreadViolation`. Returns
|
|
89
|
+
* `null` when nothing in the catalog matches. The `samples` count comes
|
|
90
|
+
* from the caller; with only a top-frame string available we set it to 1.
|
|
91
|
+
*
|
|
92
|
+
* Multiple signatures can match a single frame (e.g. a sync I/O call that
|
|
93
|
+
* also holds an unfair lock). We return the FIRST match in catalog order,
|
|
94
|
+
* which puts more user-actionable categories ahead of generic locks.
|
|
95
|
+
*/
|
|
96
|
+
export function classifyHangFrame(topFrame, samples = 1) {
|
|
97
|
+
for (const sig of MAIN_THREAD_VIOLATION_SIGNATURES) {
|
|
98
|
+
if (sig.matches(topFrame)) {
|
|
99
|
+
return { kind: sig.kind, topFrame, samples };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/** Stable key used to correlate the supplemental `topFramesByHangStartNs`
|
|
105
|
+
* map. Hang startNs values are nanoseconds (integers when xctrace exports
|
|
106
|
+
* them cleanly), so the key is just `String(startNs)`. Centralized so
|
|
107
|
+
* callers building the map use the same convention. */
|
|
108
|
+
export function hangFrameMapKey(startNs) {
|
|
109
|
+
return String(startNs);
|
|
110
|
+
}
|
|
23
111
|
/** Pure: turn parsed XML rows into our analyzed result. */
|
|
24
|
-
export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0) {
|
|
112
|
+
export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0, timeRangeMs, topFramesByHangStartNs) {
|
|
25
113
|
const tables = parseXctraceXml(xml);
|
|
26
114
|
const hangsTable = tables.find((t) => t.schema === "potential-hangs");
|
|
27
115
|
if (!hangsTable) {
|
|
@@ -38,6 +126,7 @@ export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0
|
|
|
38
126
|
},
|
|
39
127
|
top: [],
|
|
40
128
|
diagnosis: "No potential-hangs table found in the trace.",
|
|
129
|
+
status: "not_present",
|
|
41
130
|
};
|
|
42
131
|
}
|
|
43
132
|
const allEntries = [];
|
|
@@ -53,7 +142,17 @@ export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0
|
|
|
53
142
|
hangType: asFormatted(row["hang-type"]) ?? "",
|
|
54
143
|
});
|
|
55
144
|
}
|
|
56
|
-
const filtered = allEntries.filter((e) =>
|
|
145
|
+
const filtered = allEntries.filter((e) => {
|
|
146
|
+
if (e.durationMs < minDurationMs)
|
|
147
|
+
return false;
|
|
148
|
+
if (timeRangeMs) {
|
|
149
|
+
const startMs = e.startNs / 1_000_000;
|
|
150
|
+
if (startMs < timeRangeMs.startMs || startMs > timeRangeMs.endMs) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
});
|
|
57
156
|
const hangs = filtered.filter((e) => e.hangType === "Hang");
|
|
58
157
|
const microhangs = filtered.filter((e) => e.hangType === "Microhang");
|
|
59
158
|
const totalDurationMs = filtered.reduce((sum, e) => sum + e.durationMs, 0);
|
|
@@ -62,6 +161,19 @@ export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0
|
|
|
62
161
|
const top = [...filtered]
|
|
63
162
|
.sort((a, b) => b.durationMs - a.durationMs)
|
|
64
163
|
.slice(0, topN);
|
|
164
|
+
// Enrich each top hang with main-thread violation classifications when
|
|
165
|
+
// the supplemental top-frame map was supplied. We mutate the cloned
|
|
166
|
+
// entries in `top` because they are not aliased back into `filtered`
|
|
167
|
+
// after the spread above.
|
|
168
|
+
if (topFramesByHangStartNs) {
|
|
169
|
+
for (const entry of top) {
|
|
170
|
+
const frame = topFramesByHangStartNs[hangFrameMapKey(entry.startNs)];
|
|
171
|
+
if (frame == null)
|
|
172
|
+
continue;
|
|
173
|
+
const violation = classifyHangFrame(frame);
|
|
174
|
+
entry.mainThreadViolations = violation ? [violation] : [];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
65
177
|
const diagnosis = buildHangDiagnosis(filtered.length, hangs.length, microhangs.length, longestMs, averageMs);
|
|
66
178
|
return {
|
|
67
179
|
ok: true,
|
|
@@ -76,6 +188,7 @@ export function analyzeHangsFromXml(xml, tracePath, topN = 10, minDurationMs = 0
|
|
|
76
188
|
},
|
|
77
189
|
top,
|
|
78
190
|
diagnosis,
|
|
191
|
+
status: "available",
|
|
79
192
|
};
|
|
80
193
|
}
|
|
81
194
|
function buildHangDiagnosis(rows, hangs, microhangs, longestMs, averageMs) {
|
|
@@ -109,6 +222,6 @@ export async function analyzeHangs(input) {
|
|
|
109
222
|
if (result.code !== 0) {
|
|
110
223
|
throw new Error(`xctrace export failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
111
224
|
}
|
|
112
|
-
return analyzeHangsFromXml(result.stdout, tracePath, input.topN ?? 10, input.minDurationMs ?? 0);
|
|
225
|
+
return analyzeHangsFromXml(result.stdout, tracePath, input.topN ?? 10, input.minDurationMs ?? 0, input.timeRangeMs, input.topFramesByHangStartNs);
|
|
113
226
|
}
|
|
114
227
|
//# sourceMappingURL=analyzeHangs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeHangs.js","sourceRoot":"","sources":["../../src/tools/analyzeHangs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAElC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,2GAA2G,CAC5G;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,8DAA8D,CAAC;IAC3E,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,WAAW,EAAE;SACb,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CACP,
|
|
1
|
+
{"version":3,"file":"analyzeHangs.js","sourceRoot":"","sources":["../../src/tools/analyzeHangs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type ReferenceTreeEntry } from "../parsers/referenceTree.js";
|
|
2
3
|
import { type Verbosity } from "../parsers/shortenClassName.js";
|
|
3
4
|
import type { LeaksReport, NextCallSuggestion } from "../types.js";
|
|
4
5
|
export declare const analyzeMemgraphSchema: z.ZodObject<{
|
|
@@ -6,16 +7,22 @@ export declare const analyzeMemgraphSchema: z.ZodObject<{
|
|
|
6
7
|
fullChains: z.ZodDefault<z.ZodBoolean>;
|
|
7
8
|
verbosity: z.ZodDefault<z.ZodEnum<["compact", "normal", "full"]>>;
|
|
8
9
|
maxClassesInChain: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
referenceTreeTopN: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
9
12
|
}, "strip", z.ZodTypeAny, {
|
|
10
13
|
path: string;
|
|
11
14
|
fullChains: boolean;
|
|
12
15
|
verbosity: "compact" | "normal" | "full";
|
|
13
16
|
maxClassesInChain: number;
|
|
17
|
+
referenceTreeTopN: number;
|
|
18
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
14
19
|
}, {
|
|
15
20
|
path: string;
|
|
16
21
|
fullChains?: boolean | undefined;
|
|
17
22
|
verbosity?: "compact" | "normal" | "full" | undefined;
|
|
18
23
|
maxClassesInChain?: number | undefined;
|
|
24
|
+
referenceTreeTopN?: number | undefined;
|
|
25
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
19
26
|
}>;
|
|
20
27
|
export type AnalyzeMemgraphInput = z.infer<typeof analyzeMemgraphSchema>;
|
|
21
28
|
export interface CycleSummary {
|
|
@@ -57,8 +64,40 @@ export interface AnalyzeMemgraphResult {
|
|
|
57
64
|
fullReport?: LeaksReport;
|
|
58
65
|
/** Plain-English diagnosis (one liner). */
|
|
59
66
|
diagnosis: string;
|
|
60
|
-
/** Pipeline hints
|
|
67
|
+
/** Pipeline hints. Chain `classifyCycle` next, then `reachableFromCycle` to scope blame. */
|
|
61
68
|
suggestedNextCalls?: NextCallSuggestion[];
|
|
69
|
+
/**
|
|
70
|
+
* Top live classes by instance count from the heap reference tree.
|
|
71
|
+
* Populated when `leakCount` is 0 and `referenceTreeTopN > 0`. Surfaces
|
|
72
|
+
* the abandoned-memory shape that the standard `leaks` count misses:
|
|
73
|
+
* objects that are technically reachable (so not "leaked" in the strict
|
|
74
|
+
* sense) but whose growth across repeated workflows indicates a real
|
|
75
|
+
* accumulation bug (KVO observers not invalidated, NotificationCenter
|
|
76
|
+
* observers leaked, caches that never evict, etc.). These classes are
|
|
77
|
+
* not formal leaks; they are reachable-but-suspicious and worth diffing
|
|
78
|
+
* against a baseline via `analyzeAbandonedMemory(before, after)` for
|
|
79
|
+
* the verdict.
|
|
80
|
+
*
|
|
81
|
+
* **Raw view.** Includes framework noise (NSMutableDictionary, CFString,
|
|
82
|
+
* libMainThreadChecker bss, etc.) that grows with normal app activity
|
|
83
|
+
* and is rarely the actionable leak. Useful for cache-bloat or
|
|
84
|
+
* collection-explosion investigations.
|
|
85
|
+
*/
|
|
86
|
+
abandonedMemoryTop?: ReferenceTreeEntry[];
|
|
87
|
+
/**
|
|
88
|
+
* Same top-N reference-tree analysis as `abandonedMemoryTop` but filtered
|
|
89
|
+
* to actionable classes only. Foundation collection types (NSMutableDictionary,
|
|
90
|
+
* CFString, ...), ObjC runtime metadata (Class.data, OBJC_METACLASS_),
|
|
91
|
+
* Apple-framework static-data sections (__DATA __bss / __data / __common),
|
|
92
|
+
* Stack of thread / non-object / VM region rows, and the `<<TOTAL>>`
|
|
93
|
+
* summary are all excluded. The remaining list surfaces AV*, KVO*,
|
|
94
|
+
* SwiftUI app-level types, user-named classes, and other classes the
|
|
95
|
+
* fix would actually live in. New in v1.10.
|
|
96
|
+
*
|
|
97
|
+
* Use this list for the "what should I worry about?" question; use
|
|
98
|
+
* `abandonedMemoryTop` for "what's the full heap distribution?".
|
|
99
|
+
*/
|
|
100
|
+
abandonedMemorySuspects?: ReferenceTreeEntry[];
|
|
62
101
|
}
|
|
63
102
|
/**
|
|
64
103
|
* Pure function: take a `leaks` stdout string and a source path, produce a structured analysis.
|
|
@@ -3,6 +3,8 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { runCommand } from "../runtime/exec.js";
|
|
5
5
|
import { parseLeaksOutput, rootCyclesOnly } from "../parsers/leaksOutput.js";
|
|
6
|
+
import { parseReferenceTreeText, isFrameworkNoise, } from "../parsers/referenceTree.js";
|
|
7
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
6
8
|
import { shortenForVerbosity, } from "../parsers/shortenClassName.js";
|
|
7
9
|
import { suggestionClassifyCycle, suggestionReachableFromCycle, } from "../runtime/suggestions.js";
|
|
8
10
|
export const analyzeMemgraphSchema = z.object({
|
|
@@ -24,7 +26,15 @@ export const analyzeMemgraphSchema = z.object({
|
|
|
24
26
|
.positive()
|
|
25
27
|
.max(50)
|
|
26
28
|
.default(10)
|
|
27
|
-
.describe("Cap on how many unique class names to surface per cycle's `classesInChain` array. Default 10
|
|
29
|
+
.describe("Cap on how many unique class names to surface per cycle's `classesInChain` array. Default 10, enough to identify app-level types without flooding the response."),
|
|
30
|
+
referenceTreeTopN: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.nonnegative()
|
|
34
|
+
.max(200)
|
|
35
|
+
.default(20)
|
|
36
|
+
.describe("When `leakCount` is 0 (the typical abandoned-memory case), also run `leaks --referenceTree --groupByType --noContent` and surface the top N classes by live instance count in `abandonedMemoryTop[]`. Set to 0 to skip the second leaks invocation. Default 20."),
|
|
37
|
+
outputFormat: outputFormatField,
|
|
28
38
|
});
|
|
29
39
|
/**
|
|
30
40
|
* Pure function: take a `leaks` stdout string and a source path, produce a structured analysis.
|
|
@@ -130,6 +140,60 @@ export async function analyzeMemgraph(input) {
|
|
|
130
140
|
if (result.code !== 0 && result.code !== 1) {
|
|
131
141
|
throw new Error(`leaks failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
132
142
|
}
|
|
133
|
-
|
|
143
|
+
const summary = summarizeLeaks(result.stdout, path, input.fullChains ?? false, input.verbosity ?? "compact", input.maxClassesInChain ?? 10);
|
|
144
|
+
// Abandoned-memory surface: when `leaks` finds zero leaks, the reference
|
|
145
|
+
// tree still carries information about what the heap is holding alive.
|
|
146
|
+
// This is the only signal for the "orphaned KVO observer" / "abandoned
|
|
147
|
+
// cache" class of bugs that don't form a closed cycle. We invoke a second
|
|
148
|
+
// leaks pass with `--referenceTree --groupByType --noContent` and rank
|
|
149
|
+
// classes by live instance count. The caller can then chain into
|
|
150
|
+
// `analyzeAbandonedMemory(before, after)` to confirm growth shape across
|
|
151
|
+
// a workflow.
|
|
152
|
+
const topN = input.referenceTreeTopN ?? 20;
|
|
153
|
+
if (summary.totals.leakCount === 0 && topN > 0) {
|
|
154
|
+
// Capture a much larger pool than `topN` so that after filtering for
|
|
155
|
+
// the actionable view (`abandonedMemorySuspects`), we still have enough
|
|
156
|
+
// entries to surface the user-relevant classes that would otherwise
|
|
157
|
+
// be ranked below framework noise in the raw view. The notelet
|
|
158
|
+
// investigation showed AVPlayerItem at raw rank ~82 in a ~200-class
|
|
159
|
+
// heap; 10x of a 20-element default reliably surfaces classes ranked
|
|
160
|
+
// outside the raw top 20 after the noise filter trims the leaders.
|
|
161
|
+
const captureTopN = Math.max(topN, topN * 10, 200);
|
|
162
|
+
const rawTop = await captureReferenceTreeTop(path, captureTopN);
|
|
163
|
+
if (rawTop.length > 0) {
|
|
164
|
+
// Raw view: first `topN` entries unchanged from existing behavior.
|
|
165
|
+
summary.abandonedMemoryTop = rawTop.slice(0, topN);
|
|
166
|
+
// Filtered view: drop framework noise, keep ranking, then trim to topN.
|
|
167
|
+
const filtered = rawTop.filter((e) => !isFrameworkNoise(e.className));
|
|
168
|
+
if (filtered.length > 0) {
|
|
169
|
+
summary.abandonedMemorySuspects = filtered.slice(0, topN);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return summary;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Spawn a second `leaks` invocation with the reference-tree flags and parse
|
|
177
|
+
* the top N classes by instance count. Exposed as a helper so the
|
|
178
|
+
* integration is testable without spawning, by passing a precomputed
|
|
179
|
+
* stdout via the pure `parseReferenceTreeText` directly.
|
|
180
|
+
*
|
|
181
|
+
* Failure here is non-fatal: if leaks fails on the second invocation, we
|
|
182
|
+
* return an empty array rather than throwing, so the main analyze result
|
|
183
|
+
* is still returned to the caller. The most likely failure path is the
|
|
184
|
+
* same macOS 26.x kernel regression that hit the first invocation, which
|
|
185
|
+
* would already be surfaced via the earlier captureMemgraph step.
|
|
186
|
+
*/
|
|
187
|
+
async function captureReferenceTreeTop(path, topN) {
|
|
188
|
+
try {
|
|
189
|
+
const result = await runCommand("leaks", [path, "--referenceTree", "--groupByType", "--noContent"], { timeoutMs: 5 * 60_000 });
|
|
190
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
return parseReferenceTreeText(result.stdout, topN);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
134
198
|
}
|
|
135
199
|
//# sourceMappingURL=analyzeMemgraph.js.map
|