Metal GPU Trace

io.github.partprogramming09Published by GitHub account partprogramming09v0.22.0Updated Nov 7, 2026

Measure Wine and macOS programs' GPU use on Apple silicon with Metal System Trace

VerifiedSTDIODesktop onlyDeveloper ToolsData & Analytics

Overview

AI-generated overview

Records and analyzes GPU performance of Wine and macOS programs on Apple silicon with Metal System Trace, plus Windows frame data via PresentMon.

What you can do
  • Record a program's GPU use and get FPS, GPU ms per frame, frame pacing and memory
  • Build baselines, compare runs, judge regressions, check budgets and follow trends across runs
  • Diagnose translation layers for too many short encoders and sweep one variable across runs
  • Read kept .trace tables and Metal .gputrace captures without Xcode
  • Download and record bundled test workloads, and look up Apple GPU family facts
When to use it
Useful when profiling or regression-testing a game or app's GPU performance on an Apple silicon Mac, especially under Wine or a translation layer such as DXMT, Mullion or MoltenVK, or when comparing native Metal against a translated layer.
Requirements
Runs locally via uvx from PyPI (Python 3.9+), with zsh and Xcode installed for xctrace. Environment variables set the output directory (METAL_TRACE_OUT), Wine directory (METAL_TRACE_WINE), Wine prefix, extra readable directories and trace retention. Windows recording needs Intel's PresentMon, installed via a tool or placed on disk. No account or API key.
Before you install
Recording stops every program of the Wine prefix, or every process named like a native program, when it ends. Recordings need large free disk space (about 0.8 GB per recorded second) and long read-back times. Workload and PresentMon tools download binaries from GitHub, verified by pinned hashes. Optional HTTP mode requires a token and listens only on localhost.

Installation

In SourceWeft

  1. Open Metal GPU Trace in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

metal-gpu-trace

[test]

Measure how a Wine or macOS program uses the GPU on an Apple silicon Mac, from the command line or through an AI coding agent: GPU time per frame, frame pacing, FPS and memory, recorded with Instruments' Metal System Trace; whether a translation layer (DXMT, Mullion, MoltenVK) splits the work into too many short encoders; Metal .gputrace captures read without Xcode; and Apple's published facts on each GPU family to read the numbers against. Python 3.9+ standard library, zsh, and Xcode (for xctrace). MIT license. Español.

FileWhat it does
perf_record.shRecords a program for N seconds with Metal System Trace and writes <label>_<date>.json (GPU ms per frame, frame intervals, FPS, Metal and resident memory) plus per-frame and memory CSVs. Deletes the .trace afterwards unless KEEP_TRACE=1.
perf_report.pySummarizes an existing .trace the same way.
gputrace_read.pyReads a Metal .gputrace capture without Xcode: command buffers, encoders, draws, debug groups. Call names marked probable are not confirmed.
data/apple-gpu-families.jsonApple7 to Apple10: chips, what each family adds, limits that differ, with Apple's sources.
metal_trace_mcp.pyAn MCP server over all of the above.
test_metal_trace_mcp.pySelf-check without Instruments: python3 -I test_metal_trace_mcp.py prints ok; pytest runs the same tests.

Recording

sh
PERF_OUT=~/perf-runs zsh perf_record.sh game-d3d12 30 /path/to/wine ~/.wine /path/to/game game.exePERF_OUT=~/perf-runs zsh perf_record.sh app-metal 30 native - /path/to/app ./app

Arguments: label, seconds, a Wine directory holding bin/wine (or native), the Wine prefix, the working directory, the program and its arguments. Without PERF_OUT, runs go to ./perf-runs. Reading the trace afterwards takes about a minute on an M1 Pro (87 s for a 10 s recording); through the MCP server the whole call times out after 10 s per recorded second plus 120 s, and an overrun recording leaves no files behind. One recording runs at a time; keep other GPU work off the Mac while it records.

Under Wine, give it 30 s: Wine's start-up takes part of the recording. A recording with under 100 frames is too short to compare.

Disk use

Each recording keeps a JSON summary and three CSVs, about 2 MB for 10 s (most of it the Metal memory samples). The .trace (tens to hundreds of MB) is deleted unless KEEP_TRACE=1. KEEP_RUNS=N keeps only the newest N recordings. PERF_MEMORY=0 skips the Metal memory table, the slowest to read: the summary then has no metal_memory_mib and there is no memory CSV (41 % faster to summarize a 626 MB trace). The MCP tools perf_record and perf_baseline_record offer the same as memory=false.

MCP server

Install

The server runs with uvx from PyPI, with nothing to clone (brew install uv installs it). Recording also needs Xcode installed (xcrun xctrace version prints a version).

sh
uvx metal-gpu-trace

This is the command each client below runs. Desktop apps do not see Homebrew's PATH: give them the full path that which uvx prints (/opt/homebrew/bin/uvx on Apple silicon). uvx keeps the version it first downloaded; run the command once with --refresh after uvx to get the latest. uvx [email protected] pins a version.

The server reads these environment variables:

VariableWhat it sets
METAL_TRACE_OUTWhere recordings go. Give an absolute path: the default, ./perf-runs, depends on the client's working directory.
METAL_TRACE_WINEThe Wine directory (holding bin/wine) that perf_record uses when the call names none.
WINEPREFIXThe Wine prefix used when the call names none; ~/.wine otherwise.
METAL_TRACE_READMore directories the tools may read, separated by :.
METAL_TRACE_LOGoff silences the one-line stderr log per call.
KEEP_TRACE1 keeps each recording's .trace, for trace_tables, trace_query or Instruments.
KEEP_RUNSKeeps only the newest N recordings.
PERF_ENVNAME=VALUE pairs, separated by spaces, set for the recorded program.
PERF_INSTRUMENTSInstruments added to the Metal System Trace template, separated by ; (xcrun xctrace list instruments names them); their tables need the .trace, so pair it with KEEP_TRACE=1.

Claude Code

sh
claude mcp add --scope user metal-trace -e METAL_TRACE_OUT=$HOME/perf-runs -e METAL_TRACE_WINE=/path/to/wine \  -- uvx metal-gpu-traceclaude mcp list

claude mcp list shows metal-trace as connected. --scope user makes it available in every project; leave it out to add it to the current project only.

Windows

On Windows the server records native programs (perf_record_windows.py); no Wine and no Metal. METAL_TRACE_WINE is not needed: perf_record and perf_attach use native. METAL_TRACE_READ is separated by ;.

Frames come from Intel's PresentMon console application (MIT): one row per Present() of the process, with MsBetweenPresents and the GPU time MsGPUTime. Download PresentMon-<version>-x64.exe from its releases, check its SHA-256 against the one GitHub shows, and call the presentmon_install tool, or put it in %LOCALAPPDATA%\metal-gpu-trace or on PATH, or point PRESENTMON_EXE at it. The user must be an administrator or in the "Performance Log Users" group. Without PresentMon the summary falls back to DWM composition intervals (gpu.source is dwm-composition): they cover the whole desktop, miss games in exclusive full screen, and carry no GPU time. With neither a frame log, PresentMon nor DWM samples, a recording fails; it never invents frames.

What cannot be measured is null, not zero: the 1% low needs 100 frames and the 0.1% low 1000; stutter rate and jitter need two frames; thermal_throttling_detected needs GPU times; a drawstress log holds one mean per line, so its summary (gpu.frame_times) has no frame-level percentiles, stutter or jitter. DWM keeps only intervals between consecutive composed frames and drops a jump of several frames. perf_diagnose calls a large working set with uneven frame times MEMORY_PRESSURE_SUSPECTED: a suspicion, since memory bandwidth is not measured.

powershell
winget install --id=astral-sh.uv -eclaude mcp add --scope user metal-trace -e METAL_TRACE_OUT=%USERPROFILE%\perf-runs -- uvx metal-gpu-traceclaude mcp list

To run from a clone instead of PyPI (needs Python 3.9 or newer):

powershell
claude mcp add --scope user metal-trace -e METAL_TRACE_OUT=%USERPROFILE%\perf-runs -- python C:\path\to\metal-gpu-trace\metal_trace_mcp.py

In JSON clients use "command": "uvx" (or the full path where uvx prints) and Windows paths with doubled backslashes. Claude Desktop reads %APPDATA%\Claude\claude_desktop_config.json. Run the Windows tests with python -m pytest test_perf_record_windows.py.

Claude Desktop, Cursor and other JSON clients

These apps do not expand ~ or $HOME: write absolute paths.

json
{  "mcpServers": {    "metal-trace": {      "command": "/opt/homebrew/bin/uvx",      "args": ["metal-gpu-trace"],      "env": {        "METAL_TRACE_OUT": "/absolute/path/to/perf-runs",        "METAL_TRACE_WINE": "/absolute/path/to/wine"      }    }  }}
ClientFile
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json (Settings, Developer, Edit Config); restart the app
Cursor~/.cursor/mcp.json
VS Code.vscode/mcp.json in the project; the top key is servers instead of mcpServers, and the server adds "type": "stdio"

Codex CLI

In ~/.codex/config.toml:

toml
[mcp_servers.metal-trace]command = "/opt/homebrew/bin/uvx"args = ["metal-gpu-trace"]env = { METAL_TRACE_OUT = "/absolute/path/to/perf-runs", METAL_TRACE_WINE = "/absolute/path/to/wine" }

Tools

ToolReads or records
perf_recordRecords a program and returns its summary; when it ends it stops every program of its Wine prefix, or every process named like a native program. counters=true also records Metal GPU Counters: gpu_counters (the limiters, utilization, occupancy and memory bandwidth of the GPU, with the main limiter) in the summary, at about 40 s of read-back per recorded second; not under Windows
perf_attachRecords a program that is already running, by pid or name, and leaves it running
perf_runsLists recordings, newest first, one page at a time, whether each kept its .trace, and the translation layer it ran on (a fingerprint of its Direct3D DLLs, and its Wine)
perf_compareCompares two recordings: headline numbers, encoders, GPU idle time and each encoder label's GPU ms per frame, largest change first; flags those under 100 frames. markdown=true adds the numbers as Markdown tables for a PR comment; skip_s leaves out the first seconds of each recording (start-up) and recomputes the GPU numbers, hitches and Metal memory without them. perf_gate and perf_panel take skip_s too
perf_baselineSaves several equal recordings as a named reference and returns the range each number spans across them and spread_pct, how much equal runs differ (the noise a change must beat); alone, lists the references. skip_s saves each run without its first seconds and perf_check applies the same cut to the run it judges
perf_baseline_recordRecords the same program count times (default 3) with perf_record's arguments and saves the runs as a reference in one call. skip_s as in perf_baseline
perf_cold_warmRecords the same program twice in a row and says how many more shader-compile hitches the first run has than the second (first_seen_hitches), with each run's hitches, hitches per minute and graphics compiler time; the Metal shader cache is not cleared between them
perf_abRecords the same program with two environments (env_a, env_b: NAME=VALUE pairs) in the order A B B A and says per metric (FPS, 1 % low, GPU ms, stutter) whether B is better, worse or the same: better or worse only when every B value is outside A's range
perf_sweepRecords the same program once per value of one variable (dim, size, example, api or env:NAME), 2 to 8 values, rounds times (every other round in reverse order), and returns per value the median FPS, 1 % low, GPU ms per frame and stutter; with 3 or more numeric values it fits GPU ms per frame on the value (fixed ms and ms per draw for dim) with its r2
perf_checkJudges a recording against a reference: regressed when FPS falls under the range by more than margin_pct (default 3), GPU ms per frame rises over it, or encoders or command buffers per frame exceed it; improved or same otherwise, with the reasons and the full comparison
perf_gateChecks a recording against budgets you set (FPS and 1% low floors; stutter, p95 GPU ms, hitch and Metal MiB ceilings) and returns pass or fail with each limit and value; a missing number fails
perf_historyFollows FPS, 1% low, GPU ms, stutter, hitches and Metal MiB across the runs whose name starts with a prefix, oldest first, with the change from first to last
perf_panelWrites <name>.panel.html: a page with GPU time per frame and its hitches, the numbers, Metal memory and, for two runs, A against B. It has no server and loads nothing from the network; the viewer hides or reorders widgets and exports the layout as JSON. With --http, /panel#token=...&runs=a,b shows it live, refreshed every 10 s. layout (tab ids in order) saves panel.json in the runs directory as the default tabs of every panel, the live one included; the GPU counters tab shows when a run has them
perf_export_traceWrites a recording's GPU frames as <run>.chrome-trace, one slice per frame, for ui.perfetto.dev or chrome://tracing
perf_diagnoseSays whether a recording splits its GPU work into too many short encoders, as translation layers do, with encoders and command buffers per frame, the labels with the most GPU time and the GPU's idle time
trace_tablesLists the tables of a kept .trace, named by its run or its path
trace_countCounts the rows of several tables of a kept .trace, in all and per process; by default the tables that matter for a translation layer (GPU intervals, encoders, compiler spills, compiled shaders, allocations, counters, Time Profiler), so an empty one shows before reading it
trace_queryOne page of rows of one table, optionally one process
capture_summarySummarizes a .gputrace; lists its 100 most frequent calls out of calls_total
capture_recordsOne page of a .gputrace's records
gpu_familyApple's facts for a family or chip (Apple7, M1 Pro, M3 Max)
presentmon_installWindows only: downloads PresentMon, checks its pinned SHA-256 and keeps it in %LOCALAPPDATA%\metal-gpu-trace, where perf_record and perf_attach find it; Claude Code asks before every call
workload_listLists the test programs workload_fetch can download; for bgfx-examples, example_traits gives each example's class and its encoders per frame in native Metal
workload_fetchDownloads a test program once, checks its SHA-256 and keeps it in ~/Library/Caches/metal-gpu-trace; Claude Code asks before every call

perf_record takes workload, api (mtl, mtl-x64, vk, d3d11 or d3d12), dim, size and example instead of program, to record a fetched test program (workload_list says what each has). mtl-x64 is native Metal built for x86_64 and run under Rosetta, like Wine's programs; size ("1920x1080") sets drawstress's window. The first workload is bgfx's 17-drawstress (BSD-2, 9.3 MB), built from source by .github/workflows/workloads.yml: a grid of cubes, dim³ draws per frame, with its shaders inside the program. The compare-layer prompt (/metal-trace:compare-layer drawstress d3d12 in Claude Code) records it in native Metal and under the Wine in METAL_TRACE_WINE, then runs perf_diagnose and perf_compare.

The second workload, bgfx-examples (55 MiB), holds thirty bgfx examples, each a different load (many cubes, textures, render-target chains, a G-buffer, compute, reflective shadow maps, post-processing, draws the GPU decides, text and interface, shadows, transparency, GPU read-back, virtual textures, upscaling, texture formats, occlusion queries, tessellation, multiple windows, off-screen rendering; the last twenty-one measured in native Metal and Wine D3D12 only, example_traits in workload_list gives each one's class and encoders per frame in both, and 40-svt writes a 370 MB tile cache to temp/ in the workload's folder on its first run): perf_record(workload="bgfx-examples", api="d3d12", example="21-deferred"). Two macOS levers change the Mac under a recording: memory_free_pct (20 to 60) holds the whole Mac at that percentage of RAM free (memory_pressure), and background runs the program under taskpolicy -b, a hard throttle. A small GPU-bound program does not feel the first (drawstress stayed at 728 FPS and its resident memory fell); the second slows it several times over. Repeat a run that uses either.

The third workload, godot, is a lit 3D scene a real engine builds from code (Godot 4.7.2, Forward+): 400 meshes, 8 lights, a shadowed sun, api="mtl" for Godot's Metal driver and api="d3d12" under Wine for its Direct3D 12 driver, with args="-- meshes=800 lights=16" to change it. workload_fetch("godot") keeps the 10 KB project from this repository's release and downloads Godot's official macOS (171 MB) and Windows (86 MB) builds from godotengine/godot-builds, checking the SHA-512 this package pins; it needs about 257 MB and some seconds. The scene has no imported assets, so Godot does not import anything. Godot compiles its pipelines on every start, which makes it a case for perf_cold_warm: its shader-compile hitches came back unchanged in the second run.

Reading the numbers across runs

  • A summary says where it ran and what the GPU did: machine (chip, cores, RAM, macOS, screen_locked), gpu_activity (utilization of the device, renderer and tiler, split scenes, GPU recoveries: it covers the whole GPU, not only the program), resource_allocations (allocation events per frame, by type; a running total, not memory in use) and command_buffer_errors.
  • A locked Mac session leaves native Metal windows unpaced: 3 000 to 4 000 FPS against 120 with vsync, on the same examples. Record with the Mac unlocked; perf_compare returns screen when one run was recorded locked and the other not.
  • GPU ms per frame follow the GPU's performance state: a run paced at 120 FPS sits in Minimum and takes more ms for the same work (21-deferred: 1.75 ms paced, 0.27 ms unpaced). perf_compare returns gpu_state when two runs sat in different states, and perf_diagnose returns display_synced_hz when the FPS sits on a display rate. Compare runs paced alike: native Metal and D3D12 under Wine with Mullion, both at 120 FPS, do compare.
  • With counters=true, each listed hitch also has top_limiter: the Limiter counter with the highest mean over the interval before it (window_ms, read in whole 10 ms bins; null when no sample falls in it), which can differ from the whole run's main limiter.

api=vk runs the same program through Vulkan on MoltenVK. MoltenVK is not shipped: set METAL_TRACE_MOLTENVK to a directory (no spaces) holding an arm64 libMoltenVK.dylib, such as the one in a Vulkan SDK or brew install molten-vk. The run's layer names MoltenVK with the library's SHA-256, so perf_compare against a native Metal run says it compares two layers. In one measurement at dim=20, 10 s each: native Metal 2 encoders per frame; MoltenVK 13, 91 % of them short, and the GPU idle 89 % of the time (perf_diagnose: too_many_short_encoders true). One pair of runs, not a benchmark.

api=vk is native only. drawstress.exe --vk under Wine needs a Wine built with Vulkan: the Wine of DXMT and Apple's Game Porting Toolkit were tried and both log Wine was built without Vulkan support, so the recording holds no GPU frames. CrossOver's Wine has Vulkan but runs only inside one of its bottles, which was not tried.

The same facts are MCP resources: metal-trace://gpu-families (all) and metal-trace://gpu-families/apple7 to Each saved run is a resource too: metal-trace://runs/<run> reads its summary. apple10. They restate Apple's Metal Feature Set Tables of 21 May 2026, MTLGPUFamily and the M3 tech talk; check a device with supportsFamily.

How it follows the MCP specification and Claude Code's limits:

  • stdout carries only MCP messages; each tool call logs one line to stderr (METAL_TRACE_LOG=off silences it), with no arguments or paths.
  • Lists come in pages (offset, limit, default 50, at most 500) with total and next_offset; a result over 40 000 characters is refused with a hint to ask for a smaller page.
  • Files are read only inside METAL_TRACE_OUT and the directories in METAL_TRACE_READ (separated by :).
  • Failures come back as isError results that say what to do, without stack traces or local paths. Summaries cut absolute paths to file names, so they can be shared.
  • Every xctrace call has a timeout.

HTTP

METAL_TRACE_TOKEN=<secret of 16+ characters> metal-gpu-trace --http [--port N] serves the same tools at POST http://127.0.0.1:8765/mcp (Streamable HTTP, one JSON reply per request), for a client on the same Mac that cannot start a stdio server. Every request needs Authorization: Bearer <token>. It listens on 127.0.0.1 only, refuses a request whose Host or Origin is not localhost or 127.0.0.1 (DNS rebinding), and does not start without the token. Cancel a running call with notifications/cancelled from another connection. There is no progress stream over HTTP, and no remote access: reach another Mac through an SSH tunnel (ssh -L 8765:127.0.0.1:8765 mac), not by opening the port.

Windows tests on a real machine

GitHub's windows-latest has no GPU and no desktop session, so PresentMon and DWM recordings are only tested on a real Windows machine. windows/winbridge-run-tests.ps1 updates a checkout there and runs test_perf_record_windows.py; it is written as a WinBridge task (fixed name, script pinned by SHA-256, no arguments). To register it, copy it to the laptop, set $repo to the checkout's path, take its hash there with Get-FileHash <file> -Algorithm SHA256 (Git may have changed the line endings, so the hash of the copy in this repo is not the one to use), and add a [tasks.<name>] table with script and sha256 to the agent's TOML.

CI gate

metal-gpu-trace check RUN BASELINE [--margin-pct N] runs perf_check without a client and prints one JSON line (verdict, reasons, hints). Exit code 0: same or improved; 1: regressed; 2: could not be judged (no such run or reference, or under 100 frames). Runs are read from METAL_TRACE_OUT. Recording needs Instruments, so the job runs on a self-hosted macOS runner:

yaml
jobs:  perf:    runs-on: [self-hosted, macOS, ARM64]    steps:      - run: zsh perf_record.sh pr native - . ./app      - run: metal-gpu-trace check "$(ls -t ~/perf-runs/pr_*.json | head -1 | xargs basename -s .json)" main-ref

Credits

  • The record layout of a .gputrace command stream follows tmc/gputrace (MIT); no code is copied. The call ids were matched against Xcode's command list on macOS 27.0.1.
  • GPU family facts: Apple's documents linked above, restated, not quoted.

Source: README.md at commit aed7acf

Tools

24

perf_record

Record a program

Records `program` (an .exe under Wine, or a macOS binary with wine="native"), or a test program fetched with workload_fetch (workload, api, dim), for `seconds` with Metal System Trace and returns its summary: GPU ms per frame, frame intervals, FPS, Metal and resident memory (memory=false skips the Metal memory table, the slowest to read). When it ends it stops every program of that Wine prefix (wineserver -k), or every process named like the program with native. counters=true also records Metal GPU Counters (`gpu_counters` in the summary: the units the GPU waited on, its utilization, occupancy and memory bandwidth); it adds about 40 s of read-back per recorded second.

Parametersapidimenvargssizewinelabelrequiredmemoryprefixexampleprogramseconds+5 more

perf_baseline_record

Record a reference

Records the same program `count` times in a row with perf_record's arguments and saves the runs as the reference `name` for perf_check, returning the range each number spans across them. Runs are named `name`-1, `name`-2, ... Each recording takes its seconds plus about a minute to read back. skip_s as in perf_baseline: the reference leaves out each run's start-up.

Parametersapidimenvargsnamerequiredsizewinecountmemoryprefixskip_sexample+7 more

perf_sweep

Record one run per value of a variable

Records the same program once per value of one variable (dim, example, api, size, or env:NAME for an environment variable) and returns, per value, the median of FPS, 1 % low, GPU ms per frame and stutter, and with 3 or more numeric values the straight line GPU ms per frame follows (fixed ms plus ms per draw for dim, per unit otherwise) with its r2. With rounds above 1 the values run in opposite order every other round, so drift hits all alike. Runs are named `name`-<value number>-r<round>; each takes its seconds plus about a minute to read back.

Parametersapidimenvargsnamerequiredsizewinememoryprefixroundsskip_svaluesrequired+9 more

perf_ab

Record two settings in A B B A order

Records the same program with two environments in the order A B B A (repeated `rounds` times, so drift hits both sides equally) and says per metric whether B is better, worse or the same as A, with every value behind it. env_a and env_b are NAME=VALUE pairs separated by spaces; empty means none. Runs are named `name`-a1, `name`-b1, ... Each recording takes its seconds plus about a minute to read back. skip_s leaves out the first seconds of every recording (the start-up) when the verdicts are drawn, as perf_compare does; the recordings are whole.

Parametersapidimargsnamerequiredsizewineenv_arequiredenv_brequiredmemoryprefixroundsskip_s+8 more

perf_cold_warm

Record a run twice to find first-seen shader stutter

Records the same program twice in a row and says what the first run suffers that the second does not: hitches whose cause is a shader compile (first-seen pipelines: the first run's count minus the second's, never below 0), with each run's hitch count and graphics compiler time. macOS only. Runs are named `name`-first and `name`-second; each recording takes its seconds plus about a minute to read back.

Parametersapidimenvargsnamerequiredsizewinememoryprefixexampleprogramseconds+5 more

perf_attach

Record a running program

Records a program that is already running, by pid or by a name in its command line (Game.exe under Wine), for `seconds` and returns the same summary as perf_record; the program keeps running. For games started by Steam, CrossOver, NotProton or any launcher: start the game or its benchmark there first.

Parameterslabelrequiredprocessrequiredseconds

perf_runs

List recordings

Lists the saved recordings, newest first, with frames, FPS, GPU ms per frame, memory peaks and the translation layer each ran on (fingerprint and Wine, or native).

Parameterslimitoffset

perf_compare

Compare two recordings

Compares two saved recordings (names from perf_runs), a the reference and b the run after a change: the headline numbers (FPS, GPU ms, frame pacing, memory), then what changed in the GPU work and where: encoders and command buffers per frame, the share of encoders under short_us, the GPU's idle time, and each encoder label's count and GPU ms per frame, largest change first. layer says whether both ran on the same translation layer: with different layers it compares layers, not one change. A Windows run against a Metal run has `measure`: the GPU ms and Metal memory differ in kind and the encoders exist only in Metal, so use FPS, its lows, stutter and jitter. Recordings with under 100 frames are flagged: too short to compare. With markdown true the result also has `markdown`: the same numbers as tables for a PR comment. skip_s leaves out the first seconds of each recording, where shaders compile and resources load: the GPU numbers, hitches and Metal memory are recomputed from the per-frame files without them, and `skipped_s` says it; thermal state, process memory and encoders stay whole-run. `screen` appears when one run was recorded with the screen locked and the other not: FPS is then not comparable.

Parametersarequiredbrequiredskip_smarkdownshort_us

perf_baseline

Name a reference

Names a reference for perf_check, or lists them. With name and runs (names from perf_runs, several runs of the same program recorded with no change between them), it saves copies of their summaries as the reference `name`, replacing a previous one, and returns the range each number spans across them: that range, not one run, is what perf_check judges against, since repeated runs differ by a few percent, and `spread_pct`, each range's width in % of its midpoint: the noise of this program on this machine, which a change must exceed. With name alone it shows that reference; with nothing it lists them. With skip_s the reference keeps each run without its first skip_s seconds (the start-up, as perf_compare's skip_s) and perf_check applies the same cut to the run it judges, so both are steady-state; the runs need their per-frame files for that.

Parametersnamerunsskip_s

perf_check

Check a run against a reference

Judges a recording against a reference saved with perf_baseline: regressed when FPS falls under the reference's lowest FPS by more than margin_pct, GPU ms per frame rises over its highest by more than margin_pct, or encoders or command buffers per frame exceed its highest; improved when a number leaves the range the other way and none regressed; same otherwise. Frame pacing, short encoders and GPU idle time are reported by the comparison but do not decide: they vary too much between equal runs. A run from Windows against a Metal reference (or the reverse) is judged on FPS alone. A reference saved with skip_s judges the run without its first skip_s seconds too (`skipped_s` says so). Returns the reasons and perf_compare's full comparison against the reference's first run.

Parametersrunrequiredbaselinerequiredmargin_pct

perf_export_trace

Export frames to Perfetto

Writes a recording's GPU frames as a Chrome trace file, `<run>.chrome-trace` in the runs directory, that ui.perfetto.dev and chrome://tracing open: one slice per GPU frame, from its start for its GPU time (the sum of its encoders' time, so a frame with parallel encoders can show shorter than it took), with the compute, fragment and vertex split as arguments. Returns the file name and the number of frames.

Parametersrunrequired

perf_gate

Check a run against budgets

Checks a recording against budgets and says pass or fail: a floor for FPS and the 1% low, a ceiling for the stutter rate, the p95 GPU ms per frame, the hitch count and the peak Metal MiB. Give at least one. A number the recording lacks fails its budget (value null): a gate must not pass on missing data. Returns each budget with its limit, the value and whether it held. Use it as a bar a change must clear, where perf_check judges against equal runs. skip_s leaves out the first seconds of the recording (the start-up), as perf_compare does.

Parametersrunrequiredskip_sfps_minhitches_maxmetal_mib_maxgpu_ms_p95_maxfps_1pct_low_minstutter_rate_pct_max

perf_history

Follow a number across runs

Lists the recordings whose name starts with `prefix`, oldest first, with their FPS, 1% low, GPU ms, stutter, hitches and peak Metal MiB, and the change from the first to the last: how a number moved over a series of runs, such as one recording per version of a translation layer. Read a trend as real only when it exceeds the spread_pct that perf_baseline reports for equal runs. Runs from Windows and Metal in one series are flagged: their GPU ms and memory do not compare. skip_s leaves out the first seconds of each recording (the start-up), as perf_compare does.

Parameterslimitprefixrequiredskip_s

perf_panel

Write an HTML panel

Writes one HTML file, `<name>.panel.html` in the runs directory, that shows one or two recordings in a browser without a server: GPU time per frame with the hitches marked, the numbers (FPS and its lows, frame time, jitter, stutter, memory, thermal state), Metal memory over time and, for two runs, A against B. The file holds the data itself, no path, and loads nothing from the network; the viewer can hide and reorder the widgets and export the layout as JSON. preset `fps` is the timeline, numbers and memory; `compare` (two runs) puts A against B first. skip_s leaves out the first seconds of each recording (the start-up), as perf_compare does. layout (tab ids in the order to show) is saved as panel.json in the runs directory and becomes the default tab order of every panel, the live one too, until the viewer changes it in the browser; ids left out are hidden but can be switched back on; tabs are summary, ab, frames, encoders, counters, memory and data, and a panel.json you wrote yourself, or copied from the panel's Layout JSON, works the same. Returns the file name, the runs, the frames shown and the file's bytes.

Parametersnamerequiredrunsrequiredlayoutpresetskip_s

perf_diagnose

Diagnose encoder use

Says whether a saved recording splits its GPU work into too many short encoders, the pattern of translation layers such as DXMT, Mullion or MoltenVK, with the numbers behind it: encoders and command buffers per frame, the share of short encoders, the labels with the most GPU time and the GPU's idle time. Read the idle time together with the FPS: a program waiting for vsync idles too. Defaults come from bgfx 17-drawstress on an M1 Pro: 7 encoders per frame and 50% under 25 us under Mullion, 2 and 2% in native Metal.

Parametersrunrequiredshort_usshort_sharemin_encoders

trace_tables

List a trace's tables

Lists the data tables (schemas) a .trace recording holds; recordings keep their .trace only with KEEP_TRACE=1.

Parameterstracerequired

trace_count

Count a trace's rows

Counts the rows of several tables of a kept .trace, in all and per process, so a recording's useful tables show before reading them: a table with 0 rows needs another instrument or Xcode, not a bigger page.

Parameterstracerequiredtables

trace_query

Read a trace table

Returns one page of rows of one table of a .trace as {column: value}, optionally only one process's rows.

Parameterspidlimittracerequiredoffsetschemarequired

capture_summary

Summarize a capture

Reads a Metal .gputrace capture without Xcode: command buffers, encoders, draws, debug groups, and the 100 most frequent call ids out of calls_total.

Parameterscapturerequired

capture_records

Read a capture's records

Returns one page of the records of a .gputrace command stream, with their Metal call names when known.

Parameterslimitoffsetcapturerequired

gpu_family

Apple GPU family facts

Apple's published facts for one GPU family, by family ("Apple7") or chip ("M1 Pro"): its chips, what it adds, its limits, plus the limits shared by all families and the footnotes. Source URLs are in the result.

Parametersnamerequired

workload_list

List test programs

Lists the test programs workload_fetch can download, such as bgfx's drawstress: what each measures, its size, license and APIs, and whether it is already downloaded.

Takes no parameters.

workload_fetch

Download a test program

Downloads a test program from this project's GitHub releases once, checks its SHA-256 against the one this package pins, and keeps it in ~/Library/Caches/metal-gpu-trace; later calls reuse it. The programs are built from source by the project's workloads workflow. A workload with `downloads` (Godot) also fetches each of those zip files from its own publisher, checks its SHA-512 against the one data/workloads.json pins and unzips it into the workload's folder.

Parametersnamerequired

presentmon_install

Install PresentMon (Windows)

Windows only: downloads Intel's PresentMon console application (MIT) from its GitHub release, checks its SHA-256 against the one this package pins, and keeps it in %LOCALAPPDATA%\metal-gpu-trace, where perf_record and perf_attach find it; later calls reuse it. It needs administrator rights or the Performance Log Users group to run.

Takes no parameters.

Version history

7
  1. v0.22.0LatestNov 6, 2026
  2. v0.21.0Nov 6, 2026
  3. v0.19.0Nov 5, 2026
  4. v0.16.0Nov 3, 2026
  5. v0.8.3Nov 1, 2026
  6. v0.7.1Oct 18, 2026
  7. v0.5.0Oct 18, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts