memorydetective 1.13.0 → 1.15.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 +46 -0
- package/README.md +8 -2
- package/USAGE.md +29 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/schemaDiscovery.d.ts +88 -0
- package/dist/parsers/schemaDiscovery.js +144 -0
- package/dist/parsers/schemaDiscovery.js.map +1 -0
- package/dist/parsers/xctraceXml.d.ts +5 -0
- package/dist/parsers/xctraceXml.js +3 -0
- package/dist/parsers/xctraceXml.js.map +1 -1
- package/dist/tools/analyzeAllocations.d.ts +5 -1
- package/dist/tools/analyzeAllocations.js +17 -1
- package/dist/tools/analyzeAllocations.js.map +1 -1
- package/dist/tools/analyzeAnimationHitches.d.ts +5 -1
- package/dist/tools/analyzeAnimationHitches.js +17 -1
- package/dist/tools/analyzeAnimationHitches.js.map +1 -1
- package/dist/tools/analyzeAppLaunch.d.ts +3 -0
- package/dist/tools/analyzeAppLaunch.js +17 -1
- package/dist/tools/analyzeAppLaunch.js.map +1 -1
- package/dist/tools/analyzeEnergyImpact.d.ts +69 -0
- package/dist/tools/analyzeEnergyImpact.js +239 -0
- package/dist/tools/analyzeEnergyImpact.js.map +1 -0
- package/dist/tools/analyzeHangs.d.ts +63 -3
- package/dist/tools/analyzeHangs.js +143 -19
- package/dist/tools/analyzeHangs.js.map +1 -1
- package/dist/tools/analyzeLeakTimeline.d.ts +75 -0
- package/dist/tools/analyzeLeakTimeline.js +213 -0
- package/dist/tools/analyzeLeakTimeline.js.map +1 -0
- package/dist/tools/analyzeMemoryFootprint.d.ts +72 -0
- package/dist/tools/analyzeMemoryFootprint.js +234 -0
- package/dist/tools/analyzeMemoryFootprint.js.map +1 -0
- package/dist/tools/analyzeNetworkActivity.d.ts +99 -0
- package/dist/tools/analyzeNetworkActivity.js +312 -0
- package/dist/tools/analyzeNetworkActivity.js.map +1 -0
- package/dist/tools/analyzeTimeProfile.d.ts +10 -1
- package/dist/tools/analyzeTimeProfile.js +63 -8
- package/dist/tools/analyzeTimeProfile.js.map +1 -1
- package/dist/tools/countAlive.d.ts +35 -1
- package/dist/tools/countAlive.js +124 -29
- package/dist/tools/countAlive.js.map +1 -1
- package/dist/tools/inspectTrace.js +124 -18
- package/dist/tools/inspectTrace.js.map +1 -1
- package/dist/tools/recordTimeProfile.d.ts +83 -0
- package/dist/tools/recordTimeProfile.js +135 -0
- package/dist/tools/recordTimeProfile.js.map +1 -1
- package/dist/tools/replayScenario.d.ts +20 -4
- package/dist/tools/replayScenario.js +66 -0
- package/dist/tools/replayScenario.js.map +1 -1
- package/dist/tools/summarizeTrace.d.ts +6 -3
- package/dist/tools/summarizeTrace.js +49 -2
- package/dist/tools/summarizeTrace.js.map +1 -1
- package/dist/tools/verifyFix.d.ts +27 -0
- package/dist/tools/verifyFix.js +78 -4
- package/dist/tools/verifyFix.js.map +1 -1
- package/dist/types.d.ts +28 -0
- package/package.json +2 -2
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `analyzeNetworkActivity`: parses xctrace's network-connections schema
|
|
3
|
+
* from a `.trace` recorded with the Network Profile template. v1.14 item A.
|
|
4
|
+
*
|
|
5
|
+
* "The network is slow" / "my SDK is chatty" / "slow launch because of
|
|
6
|
+
* one API call" are top-3 iOS perf complaints. Pre-v1.14 we had zero
|
|
7
|
+
* coverage of the network family. XcodeTraceMCP's regex map listed it
|
|
8
|
+
* as one of their five instrument families. This analyzer closes the
|
|
9
|
+
* gap with the same shape as analyzeHangs / analyzeAnimationHitches:
|
|
10
|
+
*
|
|
11
|
+
* - Bytes-in/out, duration, status-code, and URL/host extracted per
|
|
12
|
+
* connection.
|
|
13
|
+
* - Aggregates: total bytes, slowest response, average response, count
|
|
14
|
+
* per HTTP status bucket.
|
|
15
|
+
* - Top-N by duration (the "which calls blocked the user?" view) and
|
|
16
|
+
* top-N by bytes (the "which calls are bloating my budget?" view).
|
|
17
|
+
* - Per-host aggregates surfacing chatty SDKs without manually grouping.
|
|
18
|
+
*
|
|
19
|
+
* Resilient to column-name drift across xctrace versions: each field
|
|
20
|
+
* is looked up under multiple plausible mnemonics, falling back to the
|
|
21
|
+
* one that yields data.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { resolve as resolvePath } from "node:path";
|
|
26
|
+
import { runCommand } from "../runtime/exec.js";
|
|
27
|
+
import { fetchDiscoveredSchemas } from "../parsers/schemaDiscovery.js";
|
|
28
|
+
import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
|
|
29
|
+
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
30
|
+
export const analyzeNetworkActivitySchema = z.object({
|
|
31
|
+
tracePath: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe("Absolute path to a `.trace` bundle recorded with a Network template (`xcrun xctrace record --template 'Network Profile' --attach <app|pid>`)."),
|
|
35
|
+
topN: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.positive()
|
|
39
|
+
.default(10)
|
|
40
|
+
.describe("Return the top N rows for each ranking dimension (by-duration + by-bytes). Default 10."),
|
|
41
|
+
minBytes: z
|
|
42
|
+
.number()
|
|
43
|
+
.nonnegative()
|
|
44
|
+
.default(0)
|
|
45
|
+
.describe("Filter out connections that transferred fewer than this many bytes (in + out combined). Useful for cutting tiny pings out of the by-bytes view."),
|
|
46
|
+
outputFormat: outputFormatField,
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* Helper: pull a string field from a row trying multiple plausible
|
|
50
|
+
* column names. xctrace varies network column mnemonics across iOS
|
|
51
|
+
* versions and templates; we try them all rather than failing on a
|
|
52
|
+
* single canonical name.
|
|
53
|
+
*/
|
|
54
|
+
function pickString(row, keys) {
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = asFormatted(row[k]);
|
|
57
|
+
if (v && v.trim().length > 0)
|
|
58
|
+
return v.trim();
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Same idea as pickString but for numeric fields. */
|
|
63
|
+
function pickNumber(row, keys) {
|
|
64
|
+
for (const k of keys) {
|
|
65
|
+
const v = asNumber(row[k]);
|
|
66
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
67
|
+
return v;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
/** Extract a host string from a URL-or-host value. Falls back to the
|
|
72
|
+
* raw input when it does not look like a URL (already a host). */
|
|
73
|
+
export function extractHost(urlOrHost) {
|
|
74
|
+
if (!urlOrHost)
|
|
75
|
+
return undefined;
|
|
76
|
+
// Strip scheme + path. Accepts http://host:port/path, host:port/path, bare host.
|
|
77
|
+
const stripped = urlOrHost
|
|
78
|
+
.replace(/^https?:\/\//i, "")
|
|
79
|
+
.replace(/^.*?:\/\//, "");
|
|
80
|
+
const slash = stripped.indexOf("/");
|
|
81
|
+
const hostPort = slash >= 0 ? stripped.slice(0, slash) : stripped;
|
|
82
|
+
// Drop the trailing :port for cleaner aggregation.
|
|
83
|
+
const colon = hostPort.indexOf(":");
|
|
84
|
+
return colon >= 0 ? hostPort.slice(0, colon) : hostPort;
|
|
85
|
+
}
|
|
86
|
+
function statusBucket(code) {
|
|
87
|
+
if (code == null)
|
|
88
|
+
return "n/a";
|
|
89
|
+
if (code >= 200 && code < 300)
|
|
90
|
+
return "2xx";
|
|
91
|
+
if (code >= 300 && code < 400)
|
|
92
|
+
return "3xx";
|
|
93
|
+
if (code >= 400 && code < 500)
|
|
94
|
+
return "4xx";
|
|
95
|
+
if (code >= 500 && code < 600)
|
|
96
|
+
return "5xx";
|
|
97
|
+
return "other";
|
|
98
|
+
}
|
|
99
|
+
/** Pure: turn the network-connections XML into the analyzed result. */
|
|
100
|
+
export function analyzeNetworkActivityFromXml(xml, tracePath, topN = 10, minBytes = 0) {
|
|
101
|
+
const tables = parseXctraceXml(xml);
|
|
102
|
+
// The Network template historically uses "network-connections"; some
|
|
103
|
+
// older builds use plain "network". Both are covered by SCHEMA_FAMILIES
|
|
104
|
+
// network -> the discovered schema name was passed by the caller, but
|
|
105
|
+
// the table here may carry either canonical name when synthetic
|
|
106
|
+
// fixtures are in play. Accept whichever matches.
|
|
107
|
+
const table = tables.find((t) => /network/i.test(t.schema) || /connection/i.test(t.schema) || /^http/i.test(t.schema));
|
|
108
|
+
if (!table) {
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
tracePath,
|
|
112
|
+
totals: {
|
|
113
|
+
rows: 0,
|
|
114
|
+
totalBytesIn: 0,
|
|
115
|
+
totalBytesOut: 0,
|
|
116
|
+
longestMs: 0,
|
|
117
|
+
averageMs: 0,
|
|
118
|
+
statusBuckets: {},
|
|
119
|
+
},
|
|
120
|
+
topByDuration: [],
|
|
121
|
+
topByBytes: [],
|
|
122
|
+
byHost: [],
|
|
123
|
+
diagnosis: "No network-connections table found in the trace.",
|
|
124
|
+
status: "not_present",
|
|
125
|
+
supportStatus: [
|
|
126
|
+
{
|
|
127
|
+
kind: "network-connections",
|
|
128
|
+
status: "not_present",
|
|
129
|
+
reason: "Schema absent from the trace TOC.",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const entries = [];
|
|
135
|
+
for (const row of table.rows) {
|
|
136
|
+
const startNs = pickNumber(row, ["start", "time", "connect-time", "event-time"]) ?? 0;
|
|
137
|
+
const durationNs = pickNumber(row, [
|
|
138
|
+
"duration",
|
|
139
|
+
"response-time",
|
|
140
|
+
"transaction-duration",
|
|
141
|
+
"elapsed",
|
|
142
|
+
]);
|
|
143
|
+
const url = pickString(row, ["url", "host", "endpoint", "connect-event-host", "request-url"]);
|
|
144
|
+
const method = pickString(row, ["method", "http-method", "request-method"]);
|
|
145
|
+
const statusCode = pickNumber(row, [
|
|
146
|
+
"status-code",
|
|
147
|
+
"http-status",
|
|
148
|
+
"response-code",
|
|
149
|
+
"status",
|
|
150
|
+
]);
|
|
151
|
+
const bytesIn = pickNumber(row, [
|
|
152
|
+
"bytes-in",
|
|
153
|
+
"response-bytes",
|
|
154
|
+
"received-bytes",
|
|
155
|
+
"bytes-received",
|
|
156
|
+
"rx-bytes",
|
|
157
|
+
]);
|
|
158
|
+
const bytesOut = pickNumber(row, [
|
|
159
|
+
"bytes-out",
|
|
160
|
+
"request-bytes",
|
|
161
|
+
"sent-bytes",
|
|
162
|
+
"bytes-sent",
|
|
163
|
+
"tx-bytes",
|
|
164
|
+
]);
|
|
165
|
+
const totalBytes = (bytesIn ?? 0) + (bytesOut ?? 0);
|
|
166
|
+
if (totalBytes < minBytes)
|
|
167
|
+
continue;
|
|
168
|
+
entries.push({
|
|
169
|
+
startNs,
|
|
170
|
+
...(asFormatted(row.start) ? { startFmt: asFormatted(row.start) } : {}),
|
|
171
|
+
...(durationNs != null ? { durationNs, durationMs: durationNs / 1_000_000 } : {}),
|
|
172
|
+
...(asFormatted(row.duration) ? { durationFmt: asFormatted(row.duration) } : {}),
|
|
173
|
+
...(url ? { url, host: extractHost(url) } : {}),
|
|
174
|
+
...(method ? { method } : {}),
|
|
175
|
+
...(statusCode != null ? { statusCode } : {}),
|
|
176
|
+
...(bytesIn != null ? { bytesIn } : {}),
|
|
177
|
+
...(bytesOut != null ? { bytesOut } : {}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const totalBytesIn = entries.reduce((s, e) => s + (e.bytesIn ?? 0), 0);
|
|
181
|
+
const totalBytesOut = entries.reduce((s, e) => s + (e.bytesOut ?? 0), 0);
|
|
182
|
+
const durations = entries
|
|
183
|
+
.map((e) => e.durationMs ?? 0)
|
|
184
|
+
.filter((d) => d > 0);
|
|
185
|
+
const longestMs = durations.length > 0 ? Math.max(...durations) : 0;
|
|
186
|
+
const averageMs = durations.length > 0
|
|
187
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
188
|
+
: 0;
|
|
189
|
+
const statusBuckets = {};
|
|
190
|
+
for (const e of entries) {
|
|
191
|
+
const bucket = statusBucket(e.statusCode);
|
|
192
|
+
statusBuckets[bucket] = (statusBuckets[bucket] ?? 0) + 1;
|
|
193
|
+
}
|
|
194
|
+
const topByDuration = [...entries]
|
|
195
|
+
.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0))
|
|
196
|
+
.slice(0, topN);
|
|
197
|
+
const topByBytes = [...entries]
|
|
198
|
+
.sort((a, b) => (b.bytesIn ?? 0) + (b.bytesOut ?? 0) - ((a.bytesIn ?? 0) + (a.bytesOut ?? 0)))
|
|
199
|
+
.slice(0, topN);
|
|
200
|
+
const hostMap = new Map();
|
|
201
|
+
for (const e of entries) {
|
|
202
|
+
if (!e.host)
|
|
203
|
+
continue;
|
|
204
|
+
const cur = hostMap.get(e.host) ?? {
|
|
205
|
+
host: e.host,
|
|
206
|
+
count: 0,
|
|
207
|
+
bytesIn: 0,
|
|
208
|
+
bytesOut: 0,
|
|
209
|
+
longestMs: 0,
|
|
210
|
+
};
|
|
211
|
+
cur.count += 1;
|
|
212
|
+
cur.bytesIn += e.bytesIn ?? 0;
|
|
213
|
+
cur.bytesOut += e.bytesOut ?? 0;
|
|
214
|
+
if (e.durationMs != null && e.durationMs > cur.longestMs) {
|
|
215
|
+
cur.longestMs = e.durationMs;
|
|
216
|
+
}
|
|
217
|
+
hostMap.set(e.host, cur);
|
|
218
|
+
}
|
|
219
|
+
const byHost = Array.from(hostMap.values()).sort((a, b) => b.count - a.count);
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
tracePath,
|
|
223
|
+
totals: {
|
|
224
|
+
rows: entries.length,
|
|
225
|
+
totalBytesIn,
|
|
226
|
+
totalBytesOut,
|
|
227
|
+
longestMs,
|
|
228
|
+
averageMs,
|
|
229
|
+
statusBuckets,
|
|
230
|
+
},
|
|
231
|
+
topByDuration,
|
|
232
|
+
topByBytes,
|
|
233
|
+
byHost,
|
|
234
|
+
diagnosis: buildDiagnosis(entries.length, longestMs, totalBytesIn, totalBytesOut, byHost),
|
|
235
|
+
status: "available",
|
|
236
|
+
supportStatus: [
|
|
237
|
+
{
|
|
238
|
+
kind: "network-connections",
|
|
239
|
+
status: "available",
|
|
240
|
+
sourceSchemas: ["network-connections"],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function buildDiagnosis(rows, longestMs, totalBytesIn, totalBytesOut, byHost) {
|
|
246
|
+
if (rows === 0) {
|
|
247
|
+
return "No network activity in the recording window (or all rows were filtered out by minBytes).";
|
|
248
|
+
}
|
|
249
|
+
const parts = [];
|
|
250
|
+
parts.push(`${rows} network requests captured.`);
|
|
251
|
+
const totalKB = (totalBytesIn + totalBytesOut) / 1024;
|
|
252
|
+
if (totalKB >= 1) {
|
|
253
|
+
parts.push(`${totalKB.toFixed(1)} KB total (${(totalBytesIn / 1024).toFixed(1)} KB in, ${(totalBytesOut / 1024).toFixed(1)} KB out).`);
|
|
254
|
+
}
|
|
255
|
+
if (longestMs > 0) {
|
|
256
|
+
parts.push(`Slowest: ${longestMs.toFixed(0)}ms.`);
|
|
257
|
+
}
|
|
258
|
+
if (byHost.length > 0) {
|
|
259
|
+
const top = byHost[0];
|
|
260
|
+
parts.push(`Chattiest host: \`${top.host}\` (${top.count} requests).`);
|
|
261
|
+
}
|
|
262
|
+
if (longestMs > 3000) {
|
|
263
|
+
parts.push("At least one request over 3s. Likely the user-visible perf gap.");
|
|
264
|
+
}
|
|
265
|
+
return parts.join(" ");
|
|
266
|
+
}
|
|
267
|
+
export async function analyzeNetworkActivity(input) {
|
|
268
|
+
const tracePath = resolvePath(input.tracePath);
|
|
269
|
+
if (!existsSync(tracePath)) {
|
|
270
|
+
throw new Error(`Trace bundle not found: ${tracePath}`);
|
|
271
|
+
}
|
|
272
|
+
const { network: schemaName } = await fetchDiscoveredSchemas(runCommand, tracePath, ["network"]);
|
|
273
|
+
const result = await runCommand("xcrun", [
|
|
274
|
+
"xctrace",
|
|
275
|
+
"export",
|
|
276
|
+
"--input",
|
|
277
|
+
tracePath,
|
|
278
|
+
"--xpath",
|
|
279
|
+
`/trace-toc/run/data/table[@schema="${schemaName}"]`,
|
|
280
|
+
], { timeoutMs: 5 * 60_000 });
|
|
281
|
+
if (result.code !== 0) {
|
|
282
|
+
// Schema may simply not be present in this trace (e.g. Time Profiler
|
|
283
|
+
// template without Network instrument). Surface as "not_present"
|
|
284
|
+
// rather than throwing so summarizeTrace can branch on status.
|
|
285
|
+
return {
|
|
286
|
+
ok: true,
|
|
287
|
+
tracePath,
|
|
288
|
+
totals: {
|
|
289
|
+
rows: 0,
|
|
290
|
+
totalBytesIn: 0,
|
|
291
|
+
totalBytesOut: 0,
|
|
292
|
+
longestMs: 0,
|
|
293
|
+
averageMs: 0,
|
|
294
|
+
statusBuckets: {},
|
|
295
|
+
},
|
|
296
|
+
topByDuration: [],
|
|
297
|
+
topByBytes: [],
|
|
298
|
+
byHost: [],
|
|
299
|
+
diagnosis: "Network schema not exportable from this trace (likely recorded with a non-Network template).",
|
|
300
|
+
status: "not_present",
|
|
301
|
+
supportStatus: [
|
|
302
|
+
{
|
|
303
|
+
kind: "network-connections",
|
|
304
|
+
status: "not_exportable",
|
|
305
|
+
reason: "xctrace export failed; likely recorded with a non-Network template.",
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return analyzeNetworkActivityFromXml(result.stdout, tracePath, input.topN ?? 10, input.minBytes ?? 0);
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=analyzeNetworkActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeNetworkActivity.js","sourceRoot":"","sources":["../../src/tools/analyzeNetworkActivity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { DataStatus } from "../types.js";
|
|
2
|
+
import type { DataStatus, SupportStatus } from "../types.js";
|
|
3
3
|
export declare const analyzeTimeProfileSchema: z.ZodObject<{
|
|
4
4
|
tracePath: z.ZodString;
|
|
5
5
|
topN: z.ZodDefault<z.ZodNumber>;
|
|
@@ -19,6 +19,8 @@ export interface SampleEntry {
|
|
|
19
19
|
weightFmt?: string;
|
|
20
20
|
threadName?: string;
|
|
21
21
|
symbol?: string;
|
|
22
|
+
/** Binary name for the leaf frame (e.g. "CoreFoundation", "libsystem_kernel.dylib"). Useful when the symbol is an unsymbolicated hex address. */
|
|
23
|
+
binary?: string;
|
|
22
24
|
}
|
|
23
25
|
export interface AnalyzeTimeProfileResult {
|
|
24
26
|
ok: boolean;
|
|
@@ -34,6 +36,9 @@ export interface AnalyzeTimeProfileResult {
|
|
|
34
36
|
/**
|
|
35
37
|
* Optional notice explaining a known limitation
|
|
36
38
|
* (e.g. xctrace crashed exporting the time-profile schema).
|
|
39
|
+
*
|
|
40
|
+
* @deprecated v1.14 item I. The same information now flows through
|
|
41
|
+
* `supportStatus[0].reason`. Kept for backwards compat.
|
|
37
42
|
*/
|
|
38
43
|
notice?: string;
|
|
39
44
|
diagnosis: string;
|
|
@@ -41,8 +46,12 @@ export interface AnalyzeTimeProfileResult {
|
|
|
41
46
|
* Disambiguates empty arrays into "no data in the trace" vs "trace
|
|
42
47
|
* could not be exported" vs "data was exported partially". Agents
|
|
43
48
|
* should branch on this rather than `totalSamples === 0`.
|
|
49
|
+
*
|
|
50
|
+
* @deprecated v1.14 item I. Use `supportStatus[]` instead.
|
|
44
51
|
*/
|
|
45
52
|
status: DataStatus;
|
|
53
|
+
/** v1.14+. Unified per-area status. See {@link SupportStatus}. */
|
|
54
|
+
supportStatus: SupportStatus[];
|
|
46
55
|
}
|
|
47
56
|
/**
|
|
48
57
|
* Pure analysis from a chunk of xctrace XML. Aggregates sample counts per
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { runCommand } from "../runtime/exec.js";
|
|
5
|
+
import { fetchDiscoveredSchemas } from "../parsers/schemaDiscovery.js";
|
|
5
6
|
import { parseXctraceXml, asNumber, asFormatted, } from "../parsers/xctraceXml.js";
|
|
6
7
|
import { outputFormatField } from "../runtime/responseFormatter.js";
|
|
7
8
|
export const analyzeTimeProfileSchema = z.object({
|
|
@@ -33,6 +34,13 @@ export function analyzeTimeProfileFromXml(xml, tracePath, topN = 20) {
|
|
|
33
34
|
topRows: [],
|
|
34
35
|
diagnosis: "No time-profile table found in the export.",
|
|
35
36
|
status: "not_present",
|
|
37
|
+
supportStatus: [
|
|
38
|
+
{
|
|
39
|
+
kind: "time-profile",
|
|
40
|
+
status: "not_present",
|
|
41
|
+
reason: "Schema absent from the trace TOC.",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
46
|
const rows = [];
|
|
@@ -40,14 +48,33 @@ export function analyzeTimeProfileFromXml(xml, tracePath, topN = 20) {
|
|
|
40
48
|
for (const row of tp.rows) {
|
|
41
49
|
const weight = asNumber(row.weight);
|
|
42
50
|
const weightFmt = asFormatted(row.weight);
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// xctrace's time-profile schema names the backtrace column `stack`
|
|
52
|
+
// (mnemonic), with the underlying engineering-type `backtrace`. The
|
|
53
|
+
// parser keys cells by mnemonic, so the cell is at `row.stack`.
|
|
54
|
+
//
|
|
55
|
+
// The leaf frame's @_name attribute is the symbol when it could be
|
|
56
|
+
// resolved (e.g. `_CFRunLoopRunSpecificWithOptions`) or a raw hex
|
|
57
|
+
// address when unsymbolicated. In the unsymbolicated case we keep
|
|
58
|
+
// the binary name (e.g. `libsystem_kernel.dylib`) so aggregations
|
|
59
|
+
// still cluster by library instead of every sample being a unique
|
|
60
|
+
// address. Pre-2026-05-15 the parser only read `@_fmt` so this whole
|
|
61
|
+
// metadata was invisible and `topSymbols` was just the weight column
|
|
62
|
+
// text repeating "1.00 ms" for every sample.
|
|
63
|
+
const leafFrame = row.stack?.nested?.frame;
|
|
64
|
+
const binary = leafFrame?.nested?.binary?.name;
|
|
65
|
+
const frameName = leafFrame?.name;
|
|
66
|
+
// Real Apple traces expose the symbol on the leaf frame's @_name; some
|
|
67
|
+
// synthetic test fixtures use a dedicated <symbol> column instead. Try
|
|
68
|
+
// the stack first, fall back to the dedicated column.
|
|
69
|
+
const symbol = pickSymbol(frameName, binary) ?? asFormatted(row.symbol) ?? undefined;
|
|
49
70
|
const threadName = row.thread?.fmt ?? undefined;
|
|
50
|
-
rows.push({
|
|
71
|
+
rows.push({
|
|
72
|
+
weight,
|
|
73
|
+
weightFmt,
|
|
74
|
+
...(symbol ? { symbol } : {}),
|
|
75
|
+
...(binary ? { binary } : {}),
|
|
76
|
+
...(threadName ? { threadName } : {}),
|
|
77
|
+
});
|
|
51
78
|
if (symbol) {
|
|
52
79
|
symbolCounts.set(symbol, (symbolCounts.get(symbol) ?? 0) + 1);
|
|
53
80
|
}
|
|
@@ -69,8 +96,27 @@ export function analyzeTimeProfileFromXml(xml, tracePath, topN = 20) {
|
|
|
69
96
|
? "No samples found in the time-profile table."
|
|
70
97
|
: `${rows.length} samples; top symbol: ${topSymbols[0]?.symbol ?? "unknown"} (${topSymbols[0]?.samples ?? 0} samples).`,
|
|
71
98
|
status: "available",
|
|
99
|
+
supportStatus: [
|
|
100
|
+
{
|
|
101
|
+
kind: "time-profile",
|
|
102
|
+
status: "available",
|
|
103
|
+
sourceSchemas: ["time-profile"],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
72
106
|
};
|
|
73
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Pick the most useful identifier for a sample given the leaf frame name +
|
|
110
|
+
* binary. Resolved symbol wins; otherwise we cluster by binary so the
|
|
111
|
+
* aggregation is still meaningful for unsymbolicated traces.
|
|
112
|
+
*/
|
|
113
|
+
function pickSymbol(frameName, binary) {
|
|
114
|
+
if (frameName && !/^0x[0-9a-f]+$/i.test(frameName))
|
|
115
|
+
return frameName;
|
|
116
|
+
if (binary)
|
|
117
|
+
return frameName ? `${binary} (${frameName})` : binary;
|
|
118
|
+
return frameName;
|
|
119
|
+
}
|
|
74
120
|
const SIGSEGV_NOTICE = `xctrace crashed exporting the time-profile schema (SIGSEGV). This is a known issue with heavy time-profile data and unsymbolicated traces.
|
|
75
121
|
|
|
76
122
|
Workarounds:
|
|
@@ -82,13 +128,14 @@ export async function analyzeTimeProfile(input) {
|
|
|
82
128
|
if (!existsSync(tracePath)) {
|
|
83
129
|
throw new Error(`Trace bundle not found: ${tracePath}`);
|
|
84
130
|
}
|
|
131
|
+
const { "time-profile": schemaName } = await fetchDiscoveredSchemas(runCommand, tracePath, ["time-profile"]);
|
|
85
132
|
const result = await runCommand("xcrun", [
|
|
86
133
|
"xctrace",
|
|
87
134
|
"export",
|
|
88
135
|
"--input",
|
|
89
136
|
tracePath,
|
|
90
137
|
"--xpath",
|
|
91
|
-
|
|
138
|
+
`/trace-toc/run/data/table[@schema="${schemaName}"]`,
|
|
92
139
|
], { timeoutMs: 5 * 60_000 });
|
|
93
140
|
if (result.code !== 0) {
|
|
94
141
|
// SIGSEGV typically reports as 139 (128 + 11). Surface a useful message.
|
|
@@ -102,6 +149,14 @@ export async function analyzeTimeProfile(input) {
|
|
|
102
149
|
notice: SIGSEGV_NOTICE,
|
|
103
150
|
diagnosis: "Could not export time-profile schema (xctrace crashed). See `notice` for workarounds.",
|
|
104
151
|
status: "not_exportable",
|
|
152
|
+
supportStatus: [
|
|
153
|
+
{
|
|
154
|
+
kind: "time-profile",
|
|
155
|
+
status: "not_exportable",
|
|
156
|
+
reason: SIGSEGV_NOTICE,
|
|
157
|
+
sourceSchemas: ["time-profile"],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
105
160
|
};
|
|
106
161
|
}
|
|
107
162
|
throw new Error(`xctrace export failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeTimeProfile.js","sourceRoot":"","sources":["../../src/tools/analyzeTimeProfile.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,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,qCAAqC,CAAC;IAClD,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,+DAA+D,CAAC;IAC5E,YAAY,EAAE,iBAAiB;CAChC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzeTimeProfile.js","sourceRoot":"","sources":["../../src/tools/analyzeTimeProfile.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,16 +5,19 @@ export declare const countAliveSchema: z.ZodObject<{
|
|
|
5
5
|
className: z.ZodOptional<z.ZodString>;
|
|
6
6
|
topN: z.ZodDefault<z.ZodNumber>;
|
|
7
7
|
includeReferenceTree: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
sortBy: z.ZodDefault<z.ZodEnum<["count", "totalBytes"]>>;
|
|
8
9
|
}, "strip", z.ZodTypeAny, {
|
|
9
10
|
path: string;
|
|
10
11
|
topN: number;
|
|
11
12
|
includeReferenceTree: boolean;
|
|
13
|
+
sortBy: "count" | "totalBytes";
|
|
12
14
|
className?: string | undefined;
|
|
13
15
|
}, {
|
|
14
16
|
path: string;
|
|
15
17
|
className?: string | undefined;
|
|
16
18
|
topN?: number | undefined;
|
|
17
19
|
includeReferenceTree?: boolean | undefined;
|
|
20
|
+
sortBy?: "count" | "totalBytes" | undefined;
|
|
18
21
|
}>;
|
|
19
22
|
export type CountAliveInput = z.infer<typeof countAliveSchema>;
|
|
20
23
|
export interface CountAliveEntry {
|
|
@@ -24,6 +27,22 @@ export interface CountAliveEntry {
|
|
|
24
27
|
byCycle?: number;
|
|
25
28
|
/** When `includeReferenceTree: true`, the reference-tree contribution. Often the only non-zero side on `leakCount: 0` memgraphs. */
|
|
26
29
|
byReferenceTree?: number;
|
|
30
|
+
/**
|
|
31
|
+
* v1.14+. Per-instance size in bytes derived from the memgraph. For
|
|
32
|
+
* fixed-size ObjC classes every instance has the same size; for
|
|
33
|
+
* variable-size classes (NSData with payload, etc.) this is an average
|
|
34
|
+
* (totalBytes / instanceCount, rounded). Absent when neither the
|
|
35
|
+
* cycle-side `[N]` annotation nor the reference-tree parens-size
|
|
36
|
+
* carried a number (rare).
|
|
37
|
+
*/
|
|
38
|
+
instanceSizeBytes?: number;
|
|
39
|
+
/**
|
|
40
|
+
* v1.14+. Total bytes attributed to this class: sum of per-instance
|
|
41
|
+
* sizes from the cycle forest plus the reference-tree totals. FLEX
|
|
42
|
+
* surfaces this as the "Size" sort column in its Live Objects view.
|
|
43
|
+
* Useful for "where is my memory going?" investigations.
|
|
44
|
+
*/
|
|
45
|
+
totalBytes?: number;
|
|
27
46
|
}
|
|
28
47
|
export interface CountAliveResult {
|
|
29
48
|
ok: boolean;
|
|
@@ -41,6 +60,21 @@ export interface CountAliveResult {
|
|
|
41
60
|
*/
|
|
42
61
|
actionableCounts?: CountAliveEntry[];
|
|
43
62
|
}
|
|
44
|
-
/** Pure: count node occurrences by exact className across the cycle forest.
|
|
63
|
+
/** Pure: count node occurrences by exact className across the cycle forest.
|
|
64
|
+
* Backwards-compatible API; for v1.14 byte aggregation use
|
|
65
|
+
* {@link countByClassWithBytes}. */
|
|
45
66
|
export declare function countByClass(report: LeaksReport): Map<string, number>;
|
|
67
|
+
/**
|
|
68
|
+
* Pure: aggregate occurrences AND bytes by exact className across the
|
|
69
|
+
* cycle forest. Each node's `instanceSize` is summed into `totalBytes`;
|
|
70
|
+
* the first non-zero `instanceSize` seen is recorded as the canonical
|
|
71
|
+
* `instanceSizeBytes` (ObjC classes are typically fixed-size, so the
|
|
72
|
+
* first value is representative). Nodes without a size annotation
|
|
73
|
+
* contribute to count but not bytes. v1.14.
|
|
74
|
+
*/
|
|
75
|
+
export declare function countByClassWithBytes(report: LeaksReport): Map<string, {
|
|
76
|
+
count: number;
|
|
77
|
+
totalBytes: number;
|
|
78
|
+
instanceSizeBytes?: number;
|
|
79
|
+
}>;
|
|
46
80
|
export declare function countAlive(input: CountAliveInput): Promise<CountAliveResult>;
|