Record a Time Profiler trace
recordTimeProfileCapture a time profile trace from an iOS app on a device or simulator. Specify the target app, duration, and output path for performance analysis.
Instructions
[mg.trace] Wrapper around xcrun xctrace record. Capture a .trace bundle from a running app on a device or simulator. Required: exactly 1 of deviceId/simulatorId, exactly 1 of attachAppName/attachPid/launchBundleId, an output path ending in .trace. Defaults: template = "Time Profiler", durationSec = 90.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| template | No | xctrace template name (e.g. "Time Profiler", "Animation Hitches", "Allocations"). Default "Time Profiler". | Time Profiler |
| deviceId | No | UDID of a physical device. Mutually exclusive with `simulatorId`. | |
| simulatorId | No | UDID of a simulator. Mutually exclusive with `deviceId`. Use `listTraceDevices` to find UDIDs. | |
| attachAppName | No | Attach to a running app by name (e.g. "DemoApp"). Mutually exclusive with `attachPid` and `launchBundleId`. | |
| attachPid | No | Attach by PID. Mutually exclusive with `attachAppName` and `launchBundleId`. | |
| launchBundleId | No | Launch app by bundle id and start recording at launch. Mutually exclusive with `attachAppName` and `attachPid`. | |
| durationSec | No | Recording duration in seconds (default 90, max 600). | |
| output | Yes | Absolute path where the resulting `.trace` bundle should be written. Must end in `.trace`. |
Implementation Reference
- src/tools/recordTimeProfile.ts:115-141 (handler)Core handler function that executes `xcrun xctrace record` with the provided input parameters, validates the output directory exists, builds xctrace arguments via buildXctraceArgs, runs the command with a timeout, and returns the result.
export async function recordTimeProfile( input: RecordTimeProfileInput, ): Promise<RecordTimeProfileResult> { const output = resolvePath(input.output); const outDir = dirname(output); if (!existsSync(outDir)) { throw new Error(`Output directory does not exist: ${outDir}`); } const args = buildXctraceArgs({ ...input, output }); const result = await runCommand("xcrun", args, { // Allow 30s grace beyond the recording duration for export/finalization. timeoutMs: (input.durationSec + 60) * 1_000, }); if (result.code !== 0) { throw new Error( `xctrace record failed (code ${result.code}): ${result.stderr || result.stdout}`, ); } return { ok: true, command: `xcrun ${args.join(" ")}`, output, durationSec: input.durationSec, template: input.template, stderr: result.stderr || undefined, }; } - src/tools/recordTimeProfile.ts:10-58 (schema)Input schema shape (recordTimeProfileShape) defining all parameters: template, deviceId, simulatorId, attachAppName, attachPid, launchBundleId, durationSec, output. Used directly as inputSchema in registration.
export const recordTimeProfileShape = { template: z .string() .default("Time Profiler") .describe( "xctrace template name (e.g. \"Time Profiler\", \"Animation Hitches\", \"Allocations\"). Default \"Time Profiler\".", ), deviceId: z .string() .optional() .describe("UDID of a physical device. Mutually exclusive with `simulatorId`."), simulatorId: z .string() .optional() .describe( "UDID of a simulator. Mutually exclusive with `deviceId`. Use `listTraceDevices` to find UDIDs.", ), attachAppName: z .string() .optional() .describe( "Attach to a running app by name (e.g. \"DemoApp\"). Mutually exclusive with `attachPid` and `launchBundleId`.", ), attachPid: z .number() .int() .positive() .optional() .describe("Attach by PID. Mutually exclusive with `attachAppName` and `launchBundleId`."), launchBundleId: z .string() .optional() .describe( "Launch app by bundle id and start recording at launch. Mutually exclusive with `attachAppName` and `attachPid`.", ), durationSec: z .number() .int() .positive() .max(600) .default(90) .describe("Recording duration in seconds (default 90, max 600)."), output: z .string() .min(1) .describe( "Absolute path where the resulting `.trace` bundle should be written. Must end in `.trace`.", ), } as const; - src/tools/recordTimeProfile.ts:60-87 (schema)Full validation schema (recordTimeProfileSchema) with superRefine logic ensuring exactly one of deviceId/simulatorId, exactly one of attachAppName/attachPid/launchBundleId, and output ends with .trace.
export const recordTimeProfileSchema = z .object(recordTimeProfileShape) .superRefine((val, ctx) => { const targets = [val.deviceId, val.simulatorId].filter(Boolean).length; if (targets !== 1) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Provide exactly one of `deviceId` or `simulatorId`.", }); } const attaches = [val.attachAppName, val.attachPid, val.launchBundleId].filter( (v) => v !== undefined, ).length; if (attaches !== 1) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Provide exactly one of `attachAppName`, `attachPid`, or `launchBundleId`.", }); } if (!val.output.endsWith(".trace")) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["output"], message: "`output` must end in `.trace`.", }); } }); - src/index.ts:281-295 (registration)Tool registration via server.registerTool('recordTimeProfile', ...) with title, description, inputSchema (using recordTimeProfileShape), and async handler that calls recordTimeProfile(input).
server.registerTool( "recordTimeProfile", { title: "Record a Time Profiler trace", description: "[mg.trace] Wrapper around `xcrun xctrace record`. Capture a `.trace` bundle from a running app on a device or simulator. Required: exactly 1 of `deviceId`/`simulatorId`, exactly 1 of `attachAppName`/`attachPid`/`launchBundleId`, an `output` path ending in `.trace`. Defaults: template = \"Time Profiler\", durationSec = 90.", inputSchema: recordTimeProfileShape, }, async (input) => { const result = await recordTimeProfile(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); - Pure helper function (buildXctraceArgs) that builds the xctrace command-line arguments array from the input parameters.
export function buildXctraceArgs(input: RecordTimeProfileInput): string[] { const args = ["xctrace", "record", "--template", input.template]; if (input.deviceId) args.push("--device", input.deviceId); else if (input.simulatorId) args.push("--device", input.simulatorId); if (input.attachAppName) args.push("--attach", input.attachAppName); else if (input.attachPid) args.push("--attach", String(input.attachPid)); if (input.launchBundleId) { args.push("--launch", "--", input.launchBundleId); } args.push("--time-limit", `${input.durationSec}s`); args.push("--output", resolvePath(input.output)); return args; }