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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `analyzeAbandonedMemory(beforePath, afterPath)`
|
|
3
|
+
*
|
|
4
|
+
* Diff two `.memgraph` snapshots on the reference-tree class counts (not the
|
|
5
|
+
* cycle list) and classify the GROWTH shape per class. Surfaces the family
|
|
6
|
+
* of bugs that the standard `diffMemgraphs` (cycle-focused) misses:
|
|
7
|
+
* orphaned KVO observers, never-removed NotificationCenter handlers, caches
|
|
8
|
+
* that never evict, singletons that retain payloads, and the long tail of
|
|
9
|
+
* "unknown growth" that warrants further inspection.
|
|
10
|
+
*
|
|
11
|
+
* The tool is the natural pair for the v1.8 verify-fix loop: capture a
|
|
12
|
+
* `before.memgraph`, ship the fix, capture an `after.memgraph`, then run
|
|
13
|
+
* this to confirm the suspect class went from N to <= 1. Validated end
|
|
14
|
+
* to end on the notelet investigation 2026-05-12 where AVPlayerItem went
|
|
15
|
+
* 342 to 0 across a fix that was invisible in `leaks` output but obvious
|
|
16
|
+
* in the reference tree.
|
|
17
|
+
*
|
|
18
|
+
* The classifier is pattern-catalog driven, same shape as `classifyCycle`:
|
|
19
|
+
* each grown class is matched against a small set of heuristics and tagged
|
|
20
|
+
* with a stable `classification` id + confidence tier. The agent can chain
|
|
21
|
+
* the result into `swiftSearchPattern` with the class name to locate the
|
|
22
|
+
* source.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { type ReferenceTreeEntry } from "../parsers/referenceTree.js";
|
|
26
|
+
import type { NextCallSuggestion } from "../types.js";
|
|
27
|
+
export declare const analyzeAbandonedMemoryShape: {
|
|
28
|
+
readonly beforePath: z.ZodString;
|
|
29
|
+
readonly afterPath: z.ZodString;
|
|
30
|
+
readonly topN: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
readonly classFilter: z.ZodOptional<z.ZodString>;
|
|
32
|
+
readonly outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
33
|
+
};
|
|
34
|
+
export declare const analyzeAbandonedMemorySchema: z.ZodObject<{
|
|
35
|
+
readonly beforePath: z.ZodString;
|
|
36
|
+
readonly afterPath: z.ZodString;
|
|
37
|
+
readonly topN: z.ZodDefault<z.ZodNumber>;
|
|
38
|
+
readonly classFilter: z.ZodOptional<z.ZodString>;
|
|
39
|
+
readonly outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
topN: number;
|
|
42
|
+
beforePath: string;
|
|
43
|
+
afterPath: string;
|
|
44
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
45
|
+
classFilter?: string | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
beforePath: string;
|
|
48
|
+
afterPath: string;
|
|
49
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
50
|
+
topN?: number | undefined;
|
|
51
|
+
classFilter?: string | undefined;
|
|
52
|
+
}>;
|
|
53
|
+
export type AnalyzeAbandonedMemoryInput = z.infer<typeof analyzeAbandonedMemorySchema>;
|
|
54
|
+
export type AbandonedMemoryClassification = "kvo-observer-orphaned" | "notificationcenter-observer-leaked" | "cache-too-aggressive" | "singleton-retains-payload" | "unknown-growth";
|
|
55
|
+
export interface AbandonedMemoryEntry {
|
|
56
|
+
className: string;
|
|
57
|
+
beforeCount: number;
|
|
58
|
+
afterCount: number;
|
|
59
|
+
delta: number;
|
|
60
|
+
beforeBytes: number;
|
|
61
|
+
afterBytes: number;
|
|
62
|
+
bytesDelta: number;
|
|
63
|
+
classification: AbandonedMemoryClassification;
|
|
64
|
+
confidence: "high" | "medium" | "low";
|
|
65
|
+
hint?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface AnalyzeAbandonedMemoryResult {
|
|
68
|
+
ok: boolean;
|
|
69
|
+
beforePath: string;
|
|
70
|
+
afterPath: string;
|
|
71
|
+
totals: {
|
|
72
|
+
classesGrown: number;
|
|
73
|
+
classesShrunk: number;
|
|
74
|
+
classesUnchanged: number;
|
|
75
|
+
netInstancesDelta: number;
|
|
76
|
+
netBytesDelta: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Classes that grew between before and after, ranked by absolute delta
|
|
80
|
+
* descending. Each entry carries a `classification` from the catalog plus
|
|
81
|
+
* a `confidence` tier. The agent can branch on `classification` to choose
|
|
82
|
+
* the right `swiftSearchPattern` / fix template.
|
|
83
|
+
*
|
|
84
|
+
* **Raw view.** Includes framework noise (NSMutableDictionary, CFString,
|
|
85
|
+
* libMainThreadChecker bss, etc.). Useful for cache-bloat investigations.
|
|
86
|
+
*/
|
|
87
|
+
growthByClass: AbandonedMemoryEntry[];
|
|
88
|
+
/**
|
|
89
|
+
* Classes that shrunk between before and after. Surfaced so the caller
|
|
90
|
+
* can confirm the fix freed the suspect class (e.g. AVPlayerItem in the
|
|
91
|
+
* notelet case went from 342 to 0). Sorted by absolute delta desc.
|
|
92
|
+
*
|
|
93
|
+
* **Raw view.** See `actionableShrinkage` for the filtered "what fix
|
|
94
|
+
* verifiably freed" view.
|
|
95
|
+
*/
|
|
96
|
+
shrinkageByClass: AbandonedMemoryEntry[];
|
|
97
|
+
/**
|
|
98
|
+
* `growthByClass` with framework noise filtered out (Foundation collection
|
|
99
|
+
* types, ObjC metadata, __DATA sections, allocator stacks, etc.). The
|
|
100
|
+
* remaining entries are user-actionable classes. New in v1.10.
|
|
101
|
+
*
|
|
102
|
+
* Use this when answering "what new bug just appeared?". Use the raw
|
|
103
|
+
* `growthByClass` when answering "what does the heap look like now?".
|
|
104
|
+
*/
|
|
105
|
+
actionableGrowth: AbandonedMemoryEntry[];
|
|
106
|
+
/**
|
|
107
|
+
* `shrinkageByClass` with framework noise filtered out. Use this in the
|
|
108
|
+
* verify-fix loop to confirm which app-level classes the fix actually
|
|
109
|
+
* freed. AVPlayerItem dropping from 342 to 0 shows up here at the top.
|
|
110
|
+
* New in v1.10.
|
|
111
|
+
*/
|
|
112
|
+
actionableShrinkage: AbandonedMemoryEntry[];
|
|
113
|
+
/** Plain-English diagnosis tying the highest-confidence growth to a fix hint. */
|
|
114
|
+
diagnosis: string;
|
|
115
|
+
/** Pipeline hints: chain into `swiftSearchPattern` against the top growth class. */
|
|
116
|
+
suggestedNextCalls?: NextCallSuggestion[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Pure: diff two reference-tree entry lists by class name, classify each
|
|
120
|
+
* class with a delta != 0, and return the structured result minus the
|
|
121
|
+
* filesystem header fields.
|
|
122
|
+
*
|
|
123
|
+
* Exposed so tests can drive it without subprocess spawning. The async
|
|
124
|
+
* wrapper around it handles the leaks invocations.
|
|
125
|
+
*/
|
|
126
|
+
export declare function buildAbandonedMemoryDiff(before: ReferenceTreeEntry[], after: ReferenceTreeEntry[], options: {
|
|
127
|
+
topN: number;
|
|
128
|
+
classFilter?: string;
|
|
129
|
+
}): Omit<AnalyzeAbandonedMemoryResult, "ok" | "beforePath" | "afterPath">;
|
|
130
|
+
/**
|
|
131
|
+
* Pure: classify a single class's growth shape based on its name + the
|
|
132
|
+
* presence of co-occurring NSKeyValueObservance growth.
|
|
133
|
+
*
|
|
134
|
+
* Heuristics (highest specificity first):
|
|
135
|
+
*
|
|
136
|
+
* - NSKeyValueObservance / NSKeyValueObservationInfo growth: high-confidence
|
|
137
|
+
* `kvo-observer-orphaned`. The KVO subsystem only allocates these tokens
|
|
138
|
+
* when `obj.observe(\.x) { ... }` is called; growth here means tokens
|
|
139
|
+
* never invalidated.
|
|
140
|
+
*
|
|
141
|
+
* - When KVO observation infrastructure grew, escalate any other class
|
|
142
|
+
* with delta >= 5 to `kvo-observer-orphaned` (medium confidence). These
|
|
143
|
+
* are typically the observed types being retained by orphaned observers
|
|
144
|
+
* (AVPlayerItem in the notelet case).
|
|
145
|
+
*
|
|
146
|
+
* - NSCache / NSCountedSet / NSMapTable / NSMutable{Array,Dictionary,Set}
|
|
147
|
+
* growth: medium-confidence `cache-too-aggressive`. Collection classes
|
|
148
|
+
* that grow across a workflow typically indicate missing eviction.
|
|
149
|
+
*
|
|
150
|
+
* - NotificationCenter observer block growth (NSConcreteNotification,
|
|
151
|
+
* __NSObserver, and similar): medium-confidence
|
|
152
|
+
* `notificationcenter-observer-leaked`.
|
|
153
|
+
*
|
|
154
|
+
* - Everything else: low-confidence `unknown-growth`. The agent should
|
|
155
|
+
* chain into `swiftSearchPattern` with the class name to confirm.
|
|
156
|
+
*/
|
|
157
|
+
export declare function classifyGrowth(className: string, delta: number, hasKvoCoOccurrence: boolean, kvoObservanceDelta: number): {
|
|
158
|
+
classification: AbandonedMemoryClassification;
|
|
159
|
+
confidence: "high" | "medium" | "low";
|
|
160
|
+
hint?: string;
|
|
161
|
+
};
|
|
162
|
+
export declare function analyzeAbandonedMemory(input: AnalyzeAbandonedMemoryInput): Promise<AnalyzeAbandonedMemoryResult>;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `analyzeAbandonedMemory(beforePath, afterPath)`
|
|
3
|
+
*
|
|
4
|
+
* Diff two `.memgraph` snapshots on the reference-tree class counts (not the
|
|
5
|
+
* cycle list) and classify the GROWTH shape per class. Surfaces the family
|
|
6
|
+
* of bugs that the standard `diffMemgraphs` (cycle-focused) misses:
|
|
7
|
+
* orphaned KVO observers, never-removed NotificationCenter handlers, caches
|
|
8
|
+
* that never evict, singletons that retain payloads, and the long tail of
|
|
9
|
+
* "unknown growth" that warrants further inspection.
|
|
10
|
+
*
|
|
11
|
+
* The tool is the natural pair for the v1.8 verify-fix loop: capture a
|
|
12
|
+
* `before.memgraph`, ship the fix, capture an `after.memgraph`, then run
|
|
13
|
+
* this to confirm the suspect class went from N to <= 1. Validated end
|
|
14
|
+
* to end on the notelet investigation 2026-05-12 where AVPlayerItem went
|
|
15
|
+
* 342 to 0 across a fix that was invisible in `leaks` output but obvious
|
|
16
|
+
* in the reference tree.
|
|
17
|
+
*
|
|
18
|
+
* The classifier is pattern-catalog driven, same shape as `classifyCycle`:
|
|
19
|
+
* each grown class is matched against a small set of heuristics and tagged
|
|
20
|
+
* with a stable `classification` id + confidence tier. The agent can chain
|
|
21
|
+
* the result into `swiftSearchPattern` with the class name to locate the
|
|
22
|
+
* source.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { resolve as resolvePath } from "node:path";
|
|
27
|
+
import { runCommand } from "../runtime/exec.js";
|
|
28
|
+
import { parseReferenceTreeText, isFrameworkNoise, } from "../parsers/referenceTree.js";
|
|
29
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
30
|
+
export const analyzeAbandonedMemoryShape = {
|
|
31
|
+
beforePath: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe("Absolute path to the baseline `.memgraph` (the BEFORE snapshot). Use `captureScenarioState({ label: 'before' })` to produce one in the standard verify-fix flow."),
|
|
35
|
+
afterPath: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.describe("Absolute path to the post-fix `.memgraph` (the AFTER snapshot). Same workflow as `beforePath`, after applying the candidate fix."),
|
|
39
|
+
topN: z
|
|
40
|
+
.number()
|
|
41
|
+
.int()
|
|
42
|
+
.positive()
|
|
43
|
+
.max(200)
|
|
44
|
+
.default(25)
|
|
45
|
+
.describe("Cap on `growthByClass[]` length. Default 25, max 200. Classes are ranked by absolute instance-count delta descending."),
|
|
46
|
+
classFilter: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Optional substring filter. When set, only classes whose name contains this substring are included in the response. Useful for verifying a specific class went to baseline without seeing the surrounding noise."),
|
|
50
|
+
outputFormat: outputFormatField,
|
|
51
|
+
};
|
|
52
|
+
export const analyzeAbandonedMemorySchema = z.object(analyzeAbandonedMemoryShape);
|
|
53
|
+
/**
|
|
54
|
+
* Pure: diff two reference-tree entry lists by class name, classify each
|
|
55
|
+
* class with a delta != 0, and return the structured result minus the
|
|
56
|
+
* filesystem header fields.
|
|
57
|
+
*
|
|
58
|
+
* Exposed so tests can drive it without subprocess spawning. The async
|
|
59
|
+
* wrapper around it handles the leaks invocations.
|
|
60
|
+
*/
|
|
61
|
+
export function buildAbandonedMemoryDiff(before, after, options) {
|
|
62
|
+
const beforeByName = new Map(before.map((e) => [e.className, e]));
|
|
63
|
+
const afterByName = new Map(after.map((e) => [e.className, e]));
|
|
64
|
+
const allNames = new Set([...beforeByName.keys(), ...afterByName.keys()]);
|
|
65
|
+
const raw = [];
|
|
66
|
+
for (const name of allNames) {
|
|
67
|
+
if (options.classFilter && !name.includes(options.classFilter))
|
|
68
|
+
continue;
|
|
69
|
+
const b = beforeByName.get(name);
|
|
70
|
+
const a = afterByName.get(name);
|
|
71
|
+
const beforeCount = b?.instanceCount ?? 0;
|
|
72
|
+
const afterCount = a?.instanceCount ?? 0;
|
|
73
|
+
const beforeBytes = b?.totalBytes ?? 0;
|
|
74
|
+
const afterBytes = a?.totalBytes ?? 0;
|
|
75
|
+
raw.push({
|
|
76
|
+
className: name,
|
|
77
|
+
beforeCount,
|
|
78
|
+
afterCount,
|
|
79
|
+
delta: afterCount - beforeCount,
|
|
80
|
+
beforeBytes,
|
|
81
|
+
afterBytes,
|
|
82
|
+
bytesDelta: afterBytes - beforeBytes,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Co-occurrence signal: did the KVO observation infrastructure grow?
|
|
86
|
+
// If yes, the same-direction growth of other large classes is best
|
|
87
|
+
// explained as "those classes are the observed types being retained
|
|
88
|
+
// by orphaned observers", and the classifier should escalate them to
|
|
89
|
+
// `kvo-observer-orphaned` instead of leaving them as `unknown-growth`.
|
|
90
|
+
const kvoObservanceGrowth = raw.find((r) => r.className === "NSKeyValueObservance")?.delta ?? 0;
|
|
91
|
+
const kvoObservationInfoGrowth = raw.find((r) => r.className === "NSKeyValueObservationInfo")?.delta ?? 0;
|
|
92
|
+
const hasKvoCoOccurrence = kvoObservanceGrowth >= 3 || kvoObservationInfoGrowth >= 3;
|
|
93
|
+
const grown = raw.filter((r) => r.delta > 0);
|
|
94
|
+
const shrunk = raw.filter((r) => r.delta < 0);
|
|
95
|
+
const unchanged = raw.filter((r) => r.delta === 0).length;
|
|
96
|
+
const growthByClass = grown
|
|
97
|
+
.map((r) => {
|
|
98
|
+
const { classification, confidence, hint } = classifyGrowth(r.className, r.delta, hasKvoCoOccurrence, kvoObservanceGrowth);
|
|
99
|
+
return {
|
|
100
|
+
className: r.className,
|
|
101
|
+
beforeCount: r.beforeCount,
|
|
102
|
+
afterCount: r.afterCount,
|
|
103
|
+
delta: r.delta,
|
|
104
|
+
beforeBytes: r.beforeBytes,
|
|
105
|
+
afterBytes: r.afterBytes,
|
|
106
|
+
bytesDelta: r.bytesDelta,
|
|
107
|
+
classification,
|
|
108
|
+
confidence,
|
|
109
|
+
...(hint ? { hint } : {}),
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.sort((a, b) => b.delta - a.delta || b.bytesDelta - a.bytesDelta);
|
|
113
|
+
const shrinkageByClass = shrunk
|
|
114
|
+
.map((r) => ({
|
|
115
|
+
className: r.className,
|
|
116
|
+
beforeCount: r.beforeCount,
|
|
117
|
+
afterCount: r.afterCount,
|
|
118
|
+
delta: r.delta,
|
|
119
|
+
beforeBytes: r.beforeBytes,
|
|
120
|
+
afterBytes: r.afterBytes,
|
|
121
|
+
bytesDelta: r.bytesDelta,
|
|
122
|
+
// Shrinkage entries are still classified for symmetry, but the
|
|
123
|
+
// classification reflects what the suspect-shaped class WAS doing
|
|
124
|
+
// before; the fix freed it, which is what we want to confirm.
|
|
125
|
+
classification: classifyGrowth(r.className, Math.abs(r.delta), false, 0).classification,
|
|
126
|
+
confidence: "high",
|
|
127
|
+
}))
|
|
128
|
+
.sort((a, b) => a.delta - b.delta || a.bytesDelta - b.bytesDelta);
|
|
129
|
+
const netInstancesDelta = raw.reduce((acc, r) => acc + r.delta, 0);
|
|
130
|
+
const netBytesDelta = raw.reduce((acc, r) => acc + r.bytesDelta, 0);
|
|
131
|
+
const totals = {
|
|
132
|
+
classesGrown: grown.length,
|
|
133
|
+
classesShrunk: shrunk.length,
|
|
134
|
+
classesUnchanged: unchanged,
|
|
135
|
+
netInstancesDelta,
|
|
136
|
+
netBytesDelta,
|
|
137
|
+
};
|
|
138
|
+
const diagnosis = buildDiagnosis(growthByClass, shrinkageByClass);
|
|
139
|
+
const suggestedNextCalls = buildSuggestedNextCalls(growthByClass);
|
|
140
|
+
// Actionable views: drop framework noise so the caller's first-look list
|
|
141
|
+
// surfaces app-level + AV + KVO classes instead of NSMutableDictionary +
|
|
142
|
+
// CFString + ObjC runtime data. Same ranking, just filtered. Falls back
|
|
143
|
+
// to topN, so the actionable list can be SHORTER than the raw view when
|
|
144
|
+
// most of the top entries are noise.
|
|
145
|
+
const actionableGrowth = growthByClass
|
|
146
|
+
.filter((e) => !isFrameworkNoise(e.className))
|
|
147
|
+
.slice(0, options.topN);
|
|
148
|
+
const actionableShrinkage = shrinkageByClass
|
|
149
|
+
.filter((e) => !isFrameworkNoise(e.className))
|
|
150
|
+
.slice(0, options.topN);
|
|
151
|
+
return {
|
|
152
|
+
totals,
|
|
153
|
+
growthByClass: growthByClass.slice(0, options.topN),
|
|
154
|
+
shrinkageByClass: shrinkageByClass.slice(0, options.topN),
|
|
155
|
+
actionableGrowth,
|
|
156
|
+
actionableShrinkage,
|
|
157
|
+
diagnosis,
|
|
158
|
+
...(suggestedNextCalls.length > 0 ? { suggestedNextCalls } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Pure: classify a single class's growth shape based on its name + the
|
|
163
|
+
* presence of co-occurring NSKeyValueObservance growth.
|
|
164
|
+
*
|
|
165
|
+
* Heuristics (highest specificity first):
|
|
166
|
+
*
|
|
167
|
+
* - NSKeyValueObservance / NSKeyValueObservationInfo growth: high-confidence
|
|
168
|
+
* `kvo-observer-orphaned`. The KVO subsystem only allocates these tokens
|
|
169
|
+
* when `obj.observe(\.x) { ... }` is called; growth here means tokens
|
|
170
|
+
* never invalidated.
|
|
171
|
+
*
|
|
172
|
+
* - When KVO observation infrastructure grew, escalate any other class
|
|
173
|
+
* with delta >= 5 to `kvo-observer-orphaned` (medium confidence). These
|
|
174
|
+
* are typically the observed types being retained by orphaned observers
|
|
175
|
+
* (AVPlayerItem in the notelet case).
|
|
176
|
+
*
|
|
177
|
+
* - NSCache / NSCountedSet / NSMapTable / NSMutable{Array,Dictionary,Set}
|
|
178
|
+
* growth: medium-confidence `cache-too-aggressive`. Collection classes
|
|
179
|
+
* that grow across a workflow typically indicate missing eviction.
|
|
180
|
+
*
|
|
181
|
+
* - NotificationCenter observer block growth (NSConcreteNotification,
|
|
182
|
+
* __NSObserver, and similar): medium-confidence
|
|
183
|
+
* `notificationcenter-observer-leaked`.
|
|
184
|
+
*
|
|
185
|
+
* - Everything else: low-confidence `unknown-growth`. The agent should
|
|
186
|
+
* chain into `swiftSearchPattern` with the class name to confirm.
|
|
187
|
+
*/
|
|
188
|
+
export function classifyGrowth(className, delta, hasKvoCoOccurrence, kvoObservanceDelta) {
|
|
189
|
+
if (className.includes("NSKeyValueObservance") ||
|
|
190
|
+
className.includes("NSKeyValueObservationInfo")) {
|
|
191
|
+
return {
|
|
192
|
+
classification: "kvo-observer-orphaned",
|
|
193
|
+
confidence: "high",
|
|
194
|
+
hint: "NSKeyValueObservance growth indicates `observe(\\.x) { ... }` tokens that were never invalidated. The token strongly retains the change closure (which usually captures self), and the closure is anchored in the KVO global observer registry. Use `[weak self]` inside the observe closure and call `token?.invalidate()` in `deinit`, or invalidate-then-nil before reassigning the token. See `classifyCycle` pattern `kvo.observation-not-invalidated`.",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// KVO co-occurrence escalation. Tightened in v1.10 after the v1.9 retro
|
|
198
|
+
// showed the broad `delta >= 5` rule was painting 25 unrelated classes
|
|
199
|
+
// as `kvo-observer-orphaned high` whenever NSKeyValueObservance grew at
|
|
200
|
+
// all. Three guards now:
|
|
201
|
+
// 1. Skip classes that look like framework noise (allocator stacks,
|
|
202
|
+
// memory zones, __DATA sections, summary rows, etc.)
|
|
203
|
+
// 2. Skip classes that are not "object-shaped" (anonymous bracket
|
|
204
|
+
// forms `<... 0xADDR> [size]`, byte-offset prefixes, etc.). These
|
|
205
|
+
// slip past Phase A's extractClassName when the leaks output uses
|
|
206
|
+
// a non-canonical form for them.
|
|
207
|
+
// 3. Require the candidate's delta to be a meaningful FRACTION of
|
|
208
|
+
// the KVO growth, not just any positive delta. A class with
|
|
209
|
+
// delta=5 when NSKeyValueObservance grew by +200 is statistical
|
|
210
|
+
// noise; the proportion thresholds below capture this.
|
|
211
|
+
if (hasKvoCoOccurrence && delta >= 5) {
|
|
212
|
+
const isObjectShaped = !isFrameworkNoise(className) &&
|
|
213
|
+
!/^<.*0x[0-9a-fA-F]+.*>/.test(className) &&
|
|
214
|
+
!/^\d+ bytes into\b/.test(className);
|
|
215
|
+
// Proportional thresholds. A class whose delta is below half of the
|
|
216
|
+
// NSKeyValueObservance delta is rejected as too small to be the
|
|
217
|
+
// observed type. Above 5x the KVO delta the confidence escalates
|
|
218
|
+
// to high (the class is the dominant observed type by a wide margin).
|
|
219
|
+
const mediumThreshold = Math.max(5, Math.floor(kvoObservanceDelta * 0.5));
|
|
220
|
+
const highThreshold = Math.max(50, Math.floor(kvoObservanceDelta * 5));
|
|
221
|
+
if (isObjectShaped && delta >= mediumThreshold) {
|
|
222
|
+
const confidence = delta >= highThreshold ? "high" : "medium";
|
|
223
|
+
return {
|
|
224
|
+
classification: "kvo-observer-orphaned",
|
|
225
|
+
confidence,
|
|
226
|
+
hint: `Co-occurring NSKeyValueObservance growth (+${kvoObservanceDelta}) suggests this type is the value being observed via \`observe(\\.x) { ... }\`. The orphaned observer holds the value alive. Fixing the observer (\`token.invalidate()\` on teardown) will free this class too. See \`classifyCycle\` pattern \`kvo.observation-not-invalidated\`.`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (/^NS(Cache|CountedSet|MapTable|MutableArray|MutableDictionary|MutableSet|HashTable)/.test(className)) {
|
|
231
|
+
return {
|
|
232
|
+
classification: "cache-too-aggressive",
|
|
233
|
+
confidence: "medium",
|
|
234
|
+
hint: "Bulk-storage class is growing across the workflow. Likely a cache or collection without eviction. For `NSCache`, set `countLimit` or `totalCostLimit`. For `NSMutable*`, audit the producer to confirm it is not appending without bounds. For domain caches, prefer `NSCache` over `NSMutableDictionary` when you want OS-driven eviction under memory pressure.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (/NotificationToken|NotificationObserver|__NSObserver|NSConcreteNotification|NSNotificationCenterObserver/.test(className)) {
|
|
238
|
+
return {
|
|
239
|
+
classification: "notificationcenter-observer-leaked",
|
|
240
|
+
confidence: "medium",
|
|
241
|
+
hint: "NotificationCenter observer block (added via `addObserver(forName:object:queue:using:)`) growth indicates the returned token was never passed to `removeObserver(_:)`. Store the token on `self` and remove it in `deinit`, or use the selector-based variant which auto-deregisters on dealloc.",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
classification: "unknown-growth",
|
|
246
|
+
confidence: "low",
|
|
247
|
+
hint: "Class grew between before and after but the catalog did not recognize a known abandoned-memory shape. Chain into `swiftSearchPattern` with this class name to locate the allocation sites, then inspect for missing teardown (observers, timers, dispatch sources, weak ownership invariants).",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function buildDiagnosis(grown, shrunk) {
|
|
251
|
+
if (grown.length === 0 && shrunk.length === 0) {
|
|
252
|
+
return "No class-count changes between before and after. Either the fix had no effect on the heap composition, or the workflow did not exercise the code path.";
|
|
253
|
+
}
|
|
254
|
+
if (grown.length === 0) {
|
|
255
|
+
const top = shrunk[0];
|
|
256
|
+
return `No growth detected. ${shrunk.length} class${shrunk.length === 1 ? "" : "es"} shrunk. Largest: ${top.className} (${top.beforeCount} to ${top.afterCount}, delta ${top.delta}). The fix appears to have closed an abandoned-memory chain.`;
|
|
257
|
+
}
|
|
258
|
+
const highConfidence = grown.filter((e) => e.confidence === "high");
|
|
259
|
+
const top = grown[0];
|
|
260
|
+
if (highConfidence.length > 0) {
|
|
261
|
+
const hc = highConfidence[0];
|
|
262
|
+
return `${grown.length} class${grown.length === 1 ? "" : "es"} grew. Top suspect: ${hc.className} (${hc.beforeCount} to ${hc.afterCount}, delta +${hc.delta}). Classification: ${hc.classification} (high confidence). ${hc.hint ?? ""}`;
|
|
263
|
+
}
|
|
264
|
+
return `${grown.length} class${grown.length === 1 ? "" : "es"} grew. Largest: ${top.className} (${top.beforeCount} to ${top.afterCount}, delta +${top.delta}). Classification: ${top.classification} (${top.confidence} confidence). Chain into swiftSearchPattern with the class name to locate the allocation site.`;
|
|
265
|
+
}
|
|
266
|
+
function buildSuggestedNextCalls(grown) {
|
|
267
|
+
if (grown.length === 0)
|
|
268
|
+
return [];
|
|
269
|
+
// Prefer the highest-confidence + largest-delta entry for the suggestion.
|
|
270
|
+
const ranked = [...grown].sort((a, b) => {
|
|
271
|
+
const confRank = (c) => c === "high" ? 2 : c === "medium" ? 1 : 0;
|
|
272
|
+
const diff = confRank(b.confidence) - confRank(a.confidence);
|
|
273
|
+
if (diff !== 0)
|
|
274
|
+
return diff;
|
|
275
|
+
return b.delta - a.delta;
|
|
276
|
+
});
|
|
277
|
+
const target = ranked[0];
|
|
278
|
+
return [
|
|
279
|
+
{
|
|
280
|
+
tool: "swiftSearchPattern",
|
|
281
|
+
args: {
|
|
282
|
+
pattern: target.className,
|
|
283
|
+
scope: "<your project root>",
|
|
284
|
+
},
|
|
285
|
+
why: `Locate the allocation site for ${target.className} (grew by +${target.delta} between snapshots; classified as ${target.classification}, ${target.confidence} confidence). The class name + a project-wide pattern search usually narrows to one or two files.`,
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Spawn `leaks --referenceTree --groupByType --noContent` against a .memgraph
|
|
291
|
+
* and return parsed entries. Wide topN here (1000) so the diff has the full
|
|
292
|
+
* picture; the user-facing `topN` only applies to the FINAL response slice.
|
|
293
|
+
*/
|
|
294
|
+
async function loadReferenceTree(path) {
|
|
295
|
+
const result = await runCommand("leaks", [path, "--referenceTree", "--groupByType", "--noContent"], { timeoutMs: 5 * 60_000 });
|
|
296
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
297
|
+
throw new Error(`leaks --referenceTree failed (code ${result.code}) on ${path}: ${result.stderr || result.stdout}`);
|
|
298
|
+
}
|
|
299
|
+
return parseReferenceTreeText(result.stdout, 1000);
|
|
300
|
+
}
|
|
301
|
+
export async function analyzeAbandonedMemory(input) {
|
|
302
|
+
const beforePath = resolvePath(input.beforePath);
|
|
303
|
+
const afterPath = resolvePath(input.afterPath);
|
|
304
|
+
if (!existsSync(beforePath)) {
|
|
305
|
+
throw new Error(`Before memgraph not found: ${beforePath}`);
|
|
306
|
+
}
|
|
307
|
+
if (!existsSync(afterPath)) {
|
|
308
|
+
throw new Error(`After memgraph not found: ${afterPath}`);
|
|
309
|
+
}
|
|
310
|
+
const [before, after] = await Promise.all([
|
|
311
|
+
loadReferenceTree(beforePath),
|
|
312
|
+
loadReferenceTree(afterPath),
|
|
313
|
+
]);
|
|
314
|
+
const diff = buildAbandonedMemoryDiff(before, after, {
|
|
315
|
+
topN: input.topN ?? 25,
|
|
316
|
+
...(input.classFilter ? { classFilter: input.classFilter } : {}),
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
beforePath,
|
|
321
|
+
afterPath,
|
|
322
|
+
...diff,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=analyzeAbandonedMemory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeAbandonedMemory.js","sourceRoot":"","sources":["../../src/tools/analyzeAbandonedMemory.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { DataStatus } from "../types.js";
|
|
2
3
|
export declare const analyzeAllocationsSchema: z.ZodObject<{
|
|
3
4
|
tracePath: z.ZodString;
|
|
4
5
|
topN: z.ZodDefault<z.ZodNumber>;
|
|
5
6
|
minBytes: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
6
8
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
topN: number;
|
|
8
9
|
tracePath: string;
|
|
10
|
+
topN: number;
|
|
9
11
|
minBytes: number;
|
|
12
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
10
13
|
}, {
|
|
11
14
|
tracePath: string;
|
|
15
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
12
16
|
topN?: number | undefined;
|
|
13
17
|
minBytes?: number | undefined;
|
|
14
18
|
}>;
|
|
@@ -38,9 +42,14 @@ export interface AnalyzeAllocationsResult {
|
|
|
38
42
|
};
|
|
39
43
|
/** Top categories by cumulative bytes. */
|
|
40
44
|
topByBytes: AllocationEntry[];
|
|
41
|
-
/** Top categories by allocation count (different signal
|
|
45
|
+
/** Top categories by allocation count (different signal, small frequent allocations). */
|
|
42
46
|
topByCount: AllocationEntry[];
|
|
43
47
|
diagnosis: string;
|
|
48
|
+
/**
|
|
49
|
+
* Disambiguates empty arrays into "no data in the trace" vs "trace could
|
|
50
|
+
* not be exported" vs "data was exported partially". See {@link DataStatus}.
|
|
51
|
+
*/
|
|
52
|
+
status: DataStatus;
|
|
44
53
|
}
|
|
45
54
|
/** Pure: turn parsed XML into the analyzed result. */
|
|
46
55
|
export declare function analyzeAllocationsFromXml(xml: string, tracePath: string, topN?: number, minBytes?: number): AnalyzeAllocationsResult;
|
|
@@ -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 analyzeAllocationsSchema = z.object({
|
|
7
8
|
tracePath: z
|
|
8
9
|
.string()
|
|
@@ -19,6 +20,7 @@ export const analyzeAllocationsSchema = z.object({
|
|
|
19
20
|
.nonnegative()
|
|
20
21
|
.default(0)
|
|
21
22
|
.describe("Filter out individual allocations smaller than this size in bytes (default 0). Use 1024 to focus on >1KB allocations."),
|
|
23
|
+
outputFormat: outputFormatField,
|
|
22
24
|
});
|
|
23
25
|
/** Pure: turn parsed XML into the analyzed result. */
|
|
24
26
|
export function analyzeAllocationsFromXml(xml, tracePath, topN = 15, minBytes = 0) {
|
|
@@ -38,6 +40,7 @@ export function analyzeAllocationsFromXml(xml, tracePath, topN = 15, minBytes =
|
|
|
38
40
|
topByBytes: [],
|
|
39
41
|
topByCount: [],
|
|
40
42
|
diagnosis: "No allocations table found in the trace.",
|
|
43
|
+
status: "not_present",
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
// The xctrace `allocations` schema has columns roughly like:
|
|
@@ -126,6 +129,7 @@ export function analyzeAllocationsFromXml(xml, tracePath, topN = 15, minBytes =
|
|
|
126
129
|
topByBytes,
|
|
127
130
|
topByCount,
|
|
128
131
|
diagnosis,
|
|
132
|
+
status: "available",
|
|
129
133
|
};
|
|
130
134
|
}
|
|
131
135
|
function buildDiagnosis(rows, cumulativeBytes, topByBytes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeAllocations.js","sourceRoot":"","sources":["../../src/tools/analyzeAllocations.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,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,+IAA+I,CAChJ;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,8DAA8D,CAAC;IAC3E,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,WAAW,EAAE;SACb,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CACP,uHAAuH,CACxH;
|
|
1
|
+
{"version":3,"file":"analyzeAllocations.js","sourceRoot":"","sources":["../../src/tools/analyzeAllocations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { DataStatus } from "../types.js";
|
|
2
3
|
export declare const analyzeAnimationHitchesSchema: 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
|
+
outputFormat: z.ZodOptional<z.ZodEnum<["markdown", "json", "both", "verify-fix-table"]>>;
|
|
6
18
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
topN: number;
|
|
8
19
|
tracePath: string;
|
|
20
|
+
topN: number;
|
|
9
21
|
minDurationMs: number;
|
|
22
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
23
|
+
timeRangeMs?: {
|
|
24
|
+
startMs: number;
|
|
25
|
+
endMs: number;
|
|
26
|
+
} | undefined;
|
|
10
27
|
}, {
|
|
11
28
|
tracePath: string;
|
|
29
|
+
outputFormat?: "markdown" | "json" | "both" | "verify-fix-table" | undefined;
|
|
12
30
|
topN?: number | undefined;
|
|
13
31
|
minDurationMs?: number | undefined;
|
|
32
|
+
timeRangeMs?: {
|
|
33
|
+
startMs: number;
|
|
34
|
+
endMs: number;
|
|
35
|
+
} | undefined;
|
|
14
36
|
}>;
|
|
15
37
|
export type AnalyzeAnimationHitchesInput = z.infer<typeof analyzeAnimationHitchesSchema>;
|
|
16
38
|
export interface HitchEntry {
|
|
@@ -37,7 +59,15 @@ export interface AnalyzeAnimationHitchesResult {
|
|
|
37
59
|
byType: Record<string, number>;
|
|
38
60
|
top: HitchEntry[];
|
|
39
61
|
diagnosis: string;
|
|
62
|
+
/**
|
|
63
|
+
* Disambiguates empty arrays into "no data in the trace" vs "trace could
|
|
64
|
+
* not be exported" vs "data was exported partially". See {@link DataStatus}.
|
|
65
|
+
*/
|
|
66
|
+
status: DataStatus;
|
|
40
67
|
}
|
|
41
68
|
/** Pure: turn parsed XML into the analyzed result. */
|
|
42
|
-
export declare function analyzeAnimationHitchesFromXml(xml: string, tracePath: string, topN?: number, minDurationMs?: number
|
|
69
|
+
export declare function analyzeAnimationHitchesFromXml(xml: string, tracePath: string, topN?: number, minDurationMs?: number, timeRangeMs?: {
|
|
70
|
+
startMs: number;
|
|
71
|
+
endMs: number;
|
|
72
|
+
}): AnalyzeAnimationHitchesResult;
|
|
43
73
|
export declare function analyzeAnimationHitches(input: AnalyzeAnimationHitchesInput): Promise<AnalyzeAnimationHitchesResult>;
|