metal-gpu-trace 0.4.0__tar.gz
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.
- metal_gpu_trace-0.4.0/.github/workflows/publish.yml +56 -0
- metal_gpu_trace-0.4.0/.github/workflows/test.yml +16 -0
- metal_gpu_trace-0.4.0/.gitignore +5 -0
- metal_gpu_trace-0.4.0/CHANGELOG.md +30 -0
- metal_gpu_trace-0.4.0/LICENSE +21 -0
- metal_gpu_trace-0.4.0/PKG-INFO +174 -0
- metal_gpu_trace-0.4.0/README.md +153 -0
- metal_gpu_trace-0.4.0/data/apple-gpu-families.json +63 -0
- metal_gpu_trace-0.4.0/es/README.md +154 -0
- metal_gpu_trace-0.4.0/glama.json +4 -0
- metal_gpu_trace-0.4.0/gputrace_read.py +108 -0
- metal_gpu_trace-0.4.0/metal_trace_mcp.py +422 -0
- metal_gpu_trace-0.4.0/perf_record.sh +71 -0
- metal_gpu_trace-0.4.0/perf_report.py +293 -0
- metal_gpu_trace-0.4.0/pyproject.toml +43 -0
- metal_gpu_trace-0.4.0/server.json +36 -0
- metal_gpu_trace-0.4.0/test_metal_trace_mcp.py +231 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: macos-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.9"
|
|
15
|
+
- run: python -I test_metal_trace_mcp.py
|
|
16
|
+
# the tag, the code and server.json must name the same version
|
|
17
|
+
- run: |
|
|
18
|
+
tag=${GITHUB_REF_NAME#v}
|
|
19
|
+
code=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' metal_trace_mcp.py)
|
|
20
|
+
python -I -c "import json, sys; s = json.load(open('server.json')); sys.exit(0 if s['version'] == s['packages'][0]['version'] == '$tag' == '$code' else 'tag $tag, code $code and server.json differ')"
|
|
21
|
+
|
|
22
|
+
# the GitHub release goes out even when PyPI is not set up yet
|
|
23
|
+
release:
|
|
24
|
+
needs: test
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: astral-sh/setup-uv@v6
|
|
31
|
+
- run: uv build
|
|
32
|
+
- name: GitHub release with the changelog section
|
|
33
|
+
env:
|
|
34
|
+
GH_TOKEN: ${{ github.token }}
|
|
35
|
+
run: |
|
|
36
|
+
version=${GITHUB_REF_NAME#v}
|
|
37
|
+
awk -v v="## $version " 'index($0, v) == 1 {on = 1; next} /^## / {on = 0} on' CHANGELOG.md > notes.md
|
|
38
|
+
gh release create "$GITHUB_REF_NAME" dist/* --title "$GITHUB_REF_NAME" --notes-file notes.md
|
|
39
|
+
|
|
40
|
+
publish:
|
|
41
|
+
needs: test
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
environment: pypi
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write
|
|
46
|
+
contents: read
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
- uses: astral-sh/setup-uv@v6
|
|
50
|
+
- run: uv build
|
|
51
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
52
|
+
- name: Publish to the MCP Registry
|
|
53
|
+
run: |
|
|
54
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
|
|
55
|
+
./mcp-publisher login github-oidc
|
|
56
|
+
./mcp-publisher publish
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: macos-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python: ["3.9", "3.13"]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: ${{ matrix.python }}
|
|
16
|
+
- run: python -I test_metal_trace_mcp.py
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Versions follow [Semantic Versioning](https://semver.org); each one is a `vX.Y.Z` tag. `metal-gpu-trace --version`
|
|
4
|
+
and the MCP `initialize` answer (`serverInfo.version`) print the installed one.
|
|
5
|
+
|
|
6
|
+
## 0.4.0 — 2026-10-17
|
|
7
|
+
|
|
8
|
+
- `perf_diagnose`: says whether a recording splits its GPU work into too many short encoders, as translation layers
|
|
9
|
+
do, with encoders and command buffers per frame, the labels with the most GPU time and the GPU's idle time.
|
|
10
|
+
Defaults (4 encoders per frame, 25 µs, 25 % short) come from bgfx 17-drawstress on an M1 Pro: 7 encoders per frame
|
|
11
|
+
and 50 % under 25 µs under Mullion, 2 and 2 % in native Metal.
|
|
12
|
+
- Summaries gain `encoders` and `gpu_idle`, from `metal-gpu-intervals` and `metal-application-encoders-list`.
|
|
13
|
+
Summaries written by older versions have neither: record again to diagnose them.
|
|
14
|
+
- Every tool parameter has a description; `metal-gpu-trace --version`.
|
|
15
|
+
- Registry files: `server.json` (MCP Registry), `glama.json`; `CHANGELOG.md`.
|
|
16
|
+
- `trace_tables` and `trace_query` take a run name; `perf_runs` says whether each run kept its `.trace`
|
|
17
|
+
(`trace_kept`).
|
|
18
|
+
- A trace without `metal-gpu-intervals` fails with a hint; summaries list other missing tables in `missing_tables`.
|
|
19
|
+
- `capture_summary` returns `calls_total`.
|
|
20
|
+
- Tools carry `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`; `initialize`
|
|
21
|
+
returns usage `instructions`.
|
|
22
|
+
- A recording's deadline grows with its length (10 s per recorded second plus 120 s), and one that overruns leaves
|
|
23
|
+
no files behind.
|
|
24
|
+
- `perf-runs/` is created only when a recording needs it. Tests run under pytest; CI on macOS with Python 3.9 and
|
|
25
|
+
3.13.
|
|
26
|
+
|
|
27
|
+
## 0.2.0
|
|
28
|
+
|
|
29
|
+
- First release on GitHub: `perf_record`, `perf_attach`, `perf_runs`, `perf_compare`, `trace_tables`,
|
|
30
|
+
`trace_query`, `capture_summary`, `capture_records`, `gpu_family`, and the GPU family resources.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hernan Flórez Burgos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: metal-gpu-trace
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Measure Wine and macOS programs' GPU use on Apple silicon with Metal System Trace, from the shell or an MCP client
|
|
5
|
+
Project-URL: Homepage, https://github.com/partprogramming09/metal-gpu-trace
|
|
6
|
+
Project-URL: Changelog, https://github.com/partprogramming09/metal-gpu-trace/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/partprogramming09/metal-gpu-trace/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: apple-silicon,gpu,instruments,mcp,mcp-server,metal,profiling,wine,xctrace
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Classifier: Topic :: System :: Benchmark
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# metal-gpu-trace
|
|
23
|
+
|
|
24
|
+
[](https://github.com/partprogramming09/metal-gpu-trace/actions/workflows/test.yml)
|
|
25
|
+
|
|
26
|
+
<!-- mcp-name: io.github.partprogramming09/metal-gpu-trace -->
|
|
27
|
+
|
|
28
|
+
Measure how a Wine or macOS program uses the GPU on an Apple silicon Mac, from the command line or through an AI
|
|
29
|
+
coding agent: GPU time per frame, frame pacing, FPS and memory, recorded with Instruments' Metal System Trace; whether
|
|
30
|
+
a translation layer (DXMT, Mullion, MoltenVK) splits the work into too many short encoders; Metal
|
|
31
|
+
`.gputrace` captures read without Xcode; and Apple's published facts on each GPU family to read the numbers against.
|
|
32
|
+
Python 3.9+ standard library, `zsh`, and Xcode (for `xctrace`). MIT license. [Español](es/README.md).
|
|
33
|
+
|
|
34
|
+
| File | What it does |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `perf_record.sh` | Records 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`. |
|
|
37
|
+
| `perf_report.py` | Summarizes an existing `.trace` the same way. |
|
|
38
|
+
| `gputrace_read.py` | Reads a Metal `.gputrace` capture without Xcode: command buffers, encoders, draws, debug groups. Call names marked `probable` are not confirmed. |
|
|
39
|
+
| `data/apple-gpu-families.json` | Apple7 to Apple10: chips, what each family adds, limits that differ, with Apple's sources. |
|
|
40
|
+
| `metal_trace_mcp.py` | An MCP server over all of the above. |
|
|
41
|
+
| `test_metal_trace_mcp.py` | Self-check without Instruments: `python3 -I test_metal_trace_mcp.py` prints `ok`; `pytest` runs the same tests. |
|
|
42
|
+
|
|
43
|
+
## Recording
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
PERF_OUT=~/perf-runs zsh perf_record.sh game-d3d12 30 /path/to/wine ~/.wine /path/to/game game.exe
|
|
47
|
+
PERF_OUT=~/perf-runs zsh perf_record.sh app-metal 30 native - /path/to/app ./app
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Arguments: label, seconds, a Wine directory holding `bin/wine` (or `native`), the Wine prefix, the working directory,
|
|
51
|
+
the program and its arguments. Without `PERF_OUT`, runs go to `./perf-runs`. Reading the trace afterwards takes about
|
|
52
|
+
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
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
Under Wine, give it 30 s: Wine's start-up takes part of the recording. A recording with under 100 frames is too short
|
|
56
|
+
to compare.
|
|
57
|
+
|
|
58
|
+
## Disk use
|
|
59
|
+
|
|
60
|
+
Each recording keeps a JSON summary and three CSVs, about 2 MB for 10 s (most of it the Metal memory samples). The
|
|
61
|
+
`.trace` (tens to hundreds of MB) is deleted unless `KEEP_TRACE=1`. `KEEP_RUNS=N` keeps only the newest N recordings.
|
|
62
|
+
|
|
63
|
+
## MCP server
|
|
64
|
+
|
|
65
|
+
### Install
|
|
66
|
+
|
|
67
|
+
The server runs with [`uvx`](https://docs.astral.sh/uv/) straight from this repo, with nothing to clone
|
|
68
|
+
(`brew install uv` installs it). Recording also needs Xcode installed (`xcrun xctrace version` prints a version).
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
uvx --from git+https://github.com/partprogramming09/metal-gpu-trace metal-gpu-trace
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This is the command each client below runs. Desktop apps do not see Homebrew's `PATH`: give them the full path that
|
|
75
|
+
`which uvx` prints (`/opt/homebrew/bin/uvx` on Apple silicon). `uvx` keeps the version it first downloaded; run
|
|
76
|
+
the command once with `--refresh` after `uvx` to get the latest.
|
|
77
|
+
|
|
78
|
+
The server reads these environment variables:
|
|
79
|
+
|
|
80
|
+
| Variable | What it sets |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `METAL_TRACE_OUT` | Where recordings go. Give an absolute path: the default, `./perf-runs`, depends on the client's working directory. |
|
|
83
|
+
| `METAL_TRACE_WINE` | The Wine directory (holding `bin/wine`) that `perf_record` uses when the call names none. |
|
|
84
|
+
| `WINEPREFIX` | The Wine prefix used when the call names none; `~/.wine` otherwise. |
|
|
85
|
+
| `METAL_TRACE_READ` | More directories the tools may read, separated by `:`. |
|
|
86
|
+
| `METAL_TRACE_LOG` | `off` silences the one-line stderr log per call. |
|
|
87
|
+
| `KEEP_TRACE` | `1` keeps each recording's `.trace`, for `trace_tables`, `trace_query` or Instruments. |
|
|
88
|
+
| `KEEP_RUNS` | Keeps only the newest N recordings. |
|
|
89
|
+
| `PERF_ENV` | `NAME=VALUE` pairs, separated by spaces, set for the recorded program. |
|
|
90
|
+
|
|
91
|
+
### Claude Code
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
claude mcp add --scope user metal-trace -e METAL_TRACE_OUT=$HOME/perf-runs -e METAL_TRACE_WINE=/path/to/wine \
|
|
95
|
+
-- uvx --from git+https://github.com/partprogramming09/metal-gpu-trace metal-gpu-trace
|
|
96
|
+
claude mcp list
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`claude mcp list` shows `metal-trace` as connected. `--scope user` makes it available in every project; leave it out
|
|
100
|
+
to add it to the current project only.
|
|
101
|
+
|
|
102
|
+
### Claude Desktop, Cursor and other JSON clients
|
|
103
|
+
|
|
104
|
+
These apps do not expand `~` or `$HOME`: write absolute paths.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"metal-trace": {
|
|
110
|
+
"command": "/opt/homebrew/bin/uvx",
|
|
111
|
+
"args": ["--from", "git+https://github.com/partprogramming09/metal-gpu-trace", "metal-gpu-trace"],
|
|
112
|
+
"env": {
|
|
113
|
+
"METAL_TRACE_OUT": "/absolute/path/to/perf-runs",
|
|
114
|
+
"METAL_TRACE_WINE": "/absolute/path/to/wine"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Client | File |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (Settings, Developer, Edit Config); restart the app |
|
|
124
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
125
|
+
| VS Code | `.vscode/mcp.json` in the project; the top key is `servers` instead of `mcpServers`, and the server adds `"type": "stdio"` |
|
|
126
|
+
|
|
127
|
+
### Codex CLI
|
|
128
|
+
|
|
129
|
+
In `~/.codex/config.toml`:
|
|
130
|
+
|
|
131
|
+
```toml
|
|
132
|
+
[mcp_servers.metal-trace]
|
|
133
|
+
command = "/opt/homebrew/bin/uvx"
|
|
134
|
+
args = ["--from", "git+https://github.com/partprogramming09/metal-gpu-trace", "metal-gpu-trace"]
|
|
135
|
+
env = { METAL_TRACE_OUT = "/absolute/path/to/perf-runs", METAL_TRACE_WINE = "/absolute/path/to/wine" }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Tools
|
|
139
|
+
|
|
140
|
+
| Tool | Reads or records |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `perf_record` | Records 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 |
|
|
143
|
+
| `perf_attach` | Records a program that is already running, by pid or name, and leaves it running |
|
|
144
|
+
| `perf_runs` | Lists recordings, newest first, one page at a time, and whether each kept its `.trace` |
|
|
145
|
+
| `perf_compare` | Compares two recordings; flags those under 100 frames |
|
|
146
|
+
| `perf_diagnose` | Says 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 |
|
|
147
|
+
| `trace_tables` | Lists the tables of a kept `.trace`, named by its run or its path |
|
|
148
|
+
| `trace_query` | One page of rows of one table, optionally one process |
|
|
149
|
+
| `capture_summary` | Summarizes a `.gputrace`; lists its 100 most frequent calls out of `calls_total` |
|
|
150
|
+
| `capture_records` | One page of a `.gputrace`'s records |
|
|
151
|
+
| `gpu_family` | Apple's facts for a family or chip (`Apple7`, `M1 Pro`, `M3 Max`) |
|
|
152
|
+
|
|
153
|
+
The same facts are MCP resources: `metal-trace://gpu-families` (all) and `metal-trace://gpu-families/apple7` to
|
|
154
|
+
`apple10`. They restate Apple's [Metal Feature Set Tables](https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf)
|
|
155
|
+
of 21 May 2026, [`MTLGPUFamily`](https://developer.apple.com/documentation/metal/mtlgpufamily) and the
|
|
156
|
+
[M3 tech talk](https://developer.apple.com/videos/play/tech-talks/111375/); check a device with `supportsFamily`.
|
|
157
|
+
|
|
158
|
+
How it follows the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18) and
|
|
159
|
+
[Claude Code's limits](https://code.claude.com/docs/en/mcp):
|
|
160
|
+
|
|
161
|
+
- stdout carries only MCP messages; each tool call logs one line to stderr (`METAL_TRACE_LOG=off` silences it), with
|
|
162
|
+
no arguments or paths.
|
|
163
|
+
- Lists come in pages (`offset`, `limit`, default 50, at most 500) with `total` and `next_offset`; a result over
|
|
164
|
+
40 000 characters is refused with a hint to ask for a smaller page.
|
|
165
|
+
- Files are read only inside `METAL_TRACE_OUT` and the directories in `METAL_TRACE_READ` (separated by `:`).
|
|
166
|
+
- Failures come back as `isError` results that say what to do, without stack traces or local paths. Summaries cut
|
|
167
|
+
absolute paths to file names, so they can be shared.
|
|
168
|
+
- Every `xctrace` call has a timeout.
|
|
169
|
+
|
|
170
|
+
## Credits
|
|
171
|
+
|
|
172
|
+
- The record layout of a `.gputrace` command stream follows [tmc/gputrace](https://github.com/tmc/gputrace) (MIT); no
|
|
173
|
+
code is copied. The call ids were matched against Xcode's command list on macOS 27.0.1.
|
|
174
|
+
- GPU family facts: Apple's documents linked above, restated, not quoted.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# metal-gpu-trace
|
|
2
|
+
|
|
3
|
+
[](https://github.com/partprogramming09/metal-gpu-trace/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
<!-- mcp-name: io.github.partprogramming09/metal-gpu-trace -->
|
|
6
|
+
|
|
7
|
+
Measure how a Wine or macOS program uses the GPU on an Apple silicon Mac, from the command line or through an AI
|
|
8
|
+
coding agent: GPU time per frame, frame pacing, FPS and memory, recorded with Instruments' Metal System Trace; whether
|
|
9
|
+
a translation layer (DXMT, Mullion, MoltenVK) splits the work into too many short encoders; Metal
|
|
10
|
+
`.gputrace` captures read without Xcode; and Apple's published facts on each GPU family to read the numbers against.
|
|
11
|
+
Python 3.9+ standard library, `zsh`, and Xcode (for `xctrace`). MIT license. [Español](es/README.md).
|
|
12
|
+
|
|
13
|
+
| File | What it does |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `perf_record.sh` | Records 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`. |
|
|
16
|
+
| `perf_report.py` | Summarizes an existing `.trace` the same way. |
|
|
17
|
+
| `gputrace_read.py` | Reads a Metal `.gputrace` capture without Xcode: command buffers, encoders, draws, debug groups. Call names marked `probable` are not confirmed. |
|
|
18
|
+
| `data/apple-gpu-families.json` | Apple7 to Apple10: chips, what each family adds, limits that differ, with Apple's sources. |
|
|
19
|
+
| `metal_trace_mcp.py` | An MCP server over all of the above. |
|
|
20
|
+
| `test_metal_trace_mcp.py` | Self-check without Instruments: `python3 -I test_metal_trace_mcp.py` prints `ok`; `pytest` runs the same tests. |
|
|
21
|
+
|
|
22
|
+
## Recording
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
PERF_OUT=~/perf-runs zsh perf_record.sh game-d3d12 30 /path/to/wine ~/.wine /path/to/game game.exe
|
|
26
|
+
PERF_OUT=~/perf-runs zsh perf_record.sh app-metal 30 native - /path/to/app ./app
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Arguments: label, seconds, a Wine directory holding `bin/wine` (or `native`), the Wine prefix, the working directory,
|
|
30
|
+
the program and its arguments. Without `PERF_OUT`, runs go to `./perf-runs`. Reading the trace afterwards takes about
|
|
31
|
+
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
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
Under Wine, give it 30 s: Wine's start-up takes part of the recording. A recording with under 100 frames is too short
|
|
35
|
+
to compare.
|
|
36
|
+
|
|
37
|
+
## Disk use
|
|
38
|
+
|
|
39
|
+
Each recording keeps a JSON summary and three CSVs, about 2 MB for 10 s (most of it the Metal memory samples). The
|
|
40
|
+
`.trace` (tens to hundreds of MB) is deleted unless `KEEP_TRACE=1`. `KEEP_RUNS=N` keeps only the newest N recordings.
|
|
41
|
+
|
|
42
|
+
## MCP server
|
|
43
|
+
|
|
44
|
+
### Install
|
|
45
|
+
|
|
46
|
+
The server runs with [`uvx`](https://docs.astral.sh/uv/) straight from this repo, with nothing to clone
|
|
47
|
+
(`brew install uv` installs it). Recording also needs Xcode installed (`xcrun xctrace version` prints a version).
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
uvx --from git+https://github.com/partprogramming09/metal-gpu-trace metal-gpu-trace
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This is the command each client below runs. Desktop apps do not see Homebrew's `PATH`: give them the full path that
|
|
54
|
+
`which uvx` prints (`/opt/homebrew/bin/uvx` on Apple silicon). `uvx` keeps the version it first downloaded; run
|
|
55
|
+
the command once with `--refresh` after `uvx` to get the latest.
|
|
56
|
+
|
|
57
|
+
The server reads these environment variables:
|
|
58
|
+
|
|
59
|
+
| Variable | What it sets |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `METAL_TRACE_OUT` | Where recordings go. Give an absolute path: the default, `./perf-runs`, depends on the client's working directory. |
|
|
62
|
+
| `METAL_TRACE_WINE` | The Wine directory (holding `bin/wine`) that `perf_record` uses when the call names none. |
|
|
63
|
+
| `WINEPREFIX` | The Wine prefix used when the call names none; `~/.wine` otherwise. |
|
|
64
|
+
| `METAL_TRACE_READ` | More directories the tools may read, separated by `:`. |
|
|
65
|
+
| `METAL_TRACE_LOG` | `off` silences the one-line stderr log per call. |
|
|
66
|
+
| `KEEP_TRACE` | `1` keeps each recording's `.trace`, for `trace_tables`, `trace_query` or Instruments. |
|
|
67
|
+
| `KEEP_RUNS` | Keeps only the newest N recordings. |
|
|
68
|
+
| `PERF_ENV` | `NAME=VALUE` pairs, separated by spaces, set for the recorded program. |
|
|
69
|
+
|
|
70
|
+
### Claude Code
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
claude mcp add --scope user metal-trace -e METAL_TRACE_OUT=$HOME/perf-runs -e METAL_TRACE_WINE=/path/to/wine \
|
|
74
|
+
-- uvx --from git+https://github.com/partprogramming09/metal-gpu-trace metal-gpu-trace
|
|
75
|
+
claude mcp list
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`claude mcp list` shows `metal-trace` as connected. `--scope user` makes it available in every project; leave it out
|
|
79
|
+
to add it to the current project only.
|
|
80
|
+
|
|
81
|
+
### Claude Desktop, Cursor and other JSON clients
|
|
82
|
+
|
|
83
|
+
These apps do not expand `~` or `$HOME`: write absolute paths.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"metal-trace": {
|
|
89
|
+
"command": "/opt/homebrew/bin/uvx",
|
|
90
|
+
"args": ["--from", "git+https://github.com/partprogramming09/metal-gpu-trace", "metal-gpu-trace"],
|
|
91
|
+
"env": {
|
|
92
|
+
"METAL_TRACE_OUT": "/absolute/path/to/perf-runs",
|
|
93
|
+
"METAL_TRACE_WINE": "/absolute/path/to/wine"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Client | File |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (Settings, Developer, Edit Config); restart the app |
|
|
103
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
104
|
+
| VS Code | `.vscode/mcp.json` in the project; the top key is `servers` instead of `mcpServers`, and the server adds `"type": "stdio"` |
|
|
105
|
+
|
|
106
|
+
### Codex CLI
|
|
107
|
+
|
|
108
|
+
In `~/.codex/config.toml`:
|
|
109
|
+
|
|
110
|
+
```toml
|
|
111
|
+
[mcp_servers.metal-trace]
|
|
112
|
+
command = "/opt/homebrew/bin/uvx"
|
|
113
|
+
args = ["--from", "git+https://github.com/partprogramming09/metal-gpu-trace", "metal-gpu-trace"]
|
|
114
|
+
env = { METAL_TRACE_OUT = "/absolute/path/to/perf-runs", METAL_TRACE_WINE = "/absolute/path/to/wine" }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Tools
|
|
118
|
+
|
|
119
|
+
| Tool | Reads or records |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `perf_record` | Records 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 |
|
|
122
|
+
| `perf_attach` | Records a program that is already running, by pid or name, and leaves it running |
|
|
123
|
+
| `perf_runs` | Lists recordings, newest first, one page at a time, and whether each kept its `.trace` |
|
|
124
|
+
| `perf_compare` | Compares two recordings; flags those under 100 frames |
|
|
125
|
+
| `perf_diagnose` | Says 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 |
|
|
126
|
+
| `trace_tables` | Lists the tables of a kept `.trace`, named by its run or its path |
|
|
127
|
+
| `trace_query` | One page of rows of one table, optionally one process |
|
|
128
|
+
| `capture_summary` | Summarizes a `.gputrace`; lists its 100 most frequent calls out of `calls_total` |
|
|
129
|
+
| `capture_records` | One page of a `.gputrace`'s records |
|
|
130
|
+
| `gpu_family` | Apple's facts for a family or chip (`Apple7`, `M1 Pro`, `M3 Max`) |
|
|
131
|
+
|
|
132
|
+
The same facts are MCP resources: `metal-trace://gpu-families` (all) and `metal-trace://gpu-families/apple7` to
|
|
133
|
+
`apple10`. They restate Apple's [Metal Feature Set Tables](https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf)
|
|
134
|
+
of 21 May 2026, [`MTLGPUFamily`](https://developer.apple.com/documentation/metal/mtlgpufamily) and the
|
|
135
|
+
[M3 tech talk](https://developer.apple.com/videos/play/tech-talks/111375/); check a device with `supportsFamily`.
|
|
136
|
+
|
|
137
|
+
How it follows the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18) and
|
|
138
|
+
[Claude Code's limits](https://code.claude.com/docs/en/mcp):
|
|
139
|
+
|
|
140
|
+
- stdout carries only MCP messages; each tool call logs one line to stderr (`METAL_TRACE_LOG=off` silences it), with
|
|
141
|
+
no arguments or paths.
|
|
142
|
+
- Lists come in pages (`offset`, `limit`, default 50, at most 500) with `total` and `next_offset`; a result over
|
|
143
|
+
40 000 characters is refused with a hint to ask for a smaller page.
|
|
144
|
+
- Files are read only inside `METAL_TRACE_OUT` and the directories in `METAL_TRACE_READ` (separated by `:`).
|
|
145
|
+
- Failures come back as `isError` results that say what to do, without stack traces or local paths. Summaries cut
|
|
146
|
+
absolute paths to file names, so they can be shared.
|
|
147
|
+
- Every `xctrace` call has a timeout.
|
|
148
|
+
|
|
149
|
+
## Credits
|
|
150
|
+
|
|
151
|
+
- The record layout of a `.gputrace` command stream follows [tmc/gputrace](https://github.com/tmc/gputrace) (MIT); no
|
|
152
|
+
code is copied. The call ids were matched against Xcode's command list on macOS 27.0.1.
|
|
153
|
+
- GPU family facts: Apple's documents linked above, restated, not quoted.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"about": "Apple silicon GPU families Apple7 to Apple10: chips, first family with each feature, and limits that differ. Facts restated from Apple's public documents, not Apple's text. Check a device with MTLDevice.supportsFamily; families nest, so an Apple9 GPU also supports Apple7 and Apple8.",
|
|
3
|
+
"sources": {
|
|
4
|
+
"families": "https://developer.apple.com/documentation/metal/mtlgpufamily",
|
|
5
|
+
"feature_set_tables": "https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf",
|
|
6
|
+
"feature_set_tables_date": "2026-05-21",
|
|
7
|
+
"m3_tech_talk": "https://developer.apple.com/videos/play/tech-talks/111375/"
|
|
8
|
+
},
|
|
9
|
+
"guards": {
|
|
10
|
+
"M1 only": "Apple7 && !Apple8",
|
|
11
|
+
"before M3": "!Apple9"
|
|
12
|
+
},
|
|
13
|
+
"families": {
|
|
14
|
+
"Apple7": {
|
|
15
|
+
"mac_chips": ["M1", "M1 Pro", "M1 Max", "M1 Ultra"],
|
|
16
|
+
"other_chips": ["A14"],
|
|
17
|
+
"adds": ["mesh shading", "SIMD-scoped and quad-scoped reductions", "SIMD-scoped matrix multiply",
|
|
18
|
+
"floating-point atomics", "primitive ID", "barycentric coordinates", "border color",
|
|
19
|
+
"layered rendering to multisample textures", "MetalFX temporal upscaling",
|
|
20
|
+
"acquire/release memory ordering atomics", "Metal 4"],
|
|
21
|
+
"limits": {"mesh_grid_threadgroups": 1024, "argument_buffer_samplers_per_stage": 996,
|
|
22
|
+
"mesh_payload_and_geometry_per_draw": "up to 4 GB", "texture_2d_px": 16384}
|
|
23
|
+
},
|
|
24
|
+
"Apple8": {
|
|
25
|
+
"mac_chips": ["M2 series"],
|
|
26
|
+
"other_chips": ["A15", "A16"],
|
|
27
|
+
"adds": ["lossy texture compression", "SIMD shift and fill", "sparse depth/stencil on every device",
|
|
28
|
+
"placement sparse on every device", "query texture LOD on every device",
|
|
29
|
+
"64-bit atomic min/max on ulong in buffers and textures, macOS only"],
|
|
30
|
+
"limits": {"mesh_grid_threadgroups": 1024, "argument_buffer_samplers_per_stage": 996,
|
|
31
|
+
"mesh_payload_and_geometry_per_draw": "up to 4 GB", "texture_2d_px": 16384}
|
|
32
|
+
},
|
|
33
|
+
"Apple9": {
|
|
34
|
+
"mac_chips": ["M3 series", "M4 series"],
|
|
35
|
+
"other_chips": ["A17 Pro", "A18"],
|
|
36
|
+
"adds": ["full 64-bit atomics", "BC pixel formats on every device", "mesh draws in indirect command buffers",
|
|
37
|
+
"indirect mesh draw arguments", "ICB memory barriers for rendering",
|
|
38
|
+
"row-major matrices in acceleration structures", "per-component motion in ray tracing",
|
|
39
|
+
"MetalFX denoised upscaling", "Dynamic Caching (registers allocated as the program runs)"],
|
|
40
|
+
"limits": {"mesh_grid_threadgroups": 1048575, "argument_buffer_samplers_per_stage": 500000,
|
|
41
|
+
"mesh_payload_and_geometry_per_draw": "not stated", "texture_2d_px": 16384}
|
|
42
|
+
},
|
|
43
|
+
"Apple10": {
|
|
44
|
+
"mac_chips": ["M5 series"],
|
|
45
|
+
"other_chips": ["A19"],
|
|
46
|
+
"adds": ["depth bounds testing", "sampler min/max reduction", "sampler LOD bias",
|
|
47
|
+
"pre-raster per-vertex values", "ICB raster and depth/stencil state", "atomics on cube map textures",
|
|
48
|
+
"universal texture compression", "32 768 px textures", "16x16 tiles with 8x MSAA"],
|
|
49
|
+
"limits": {"mesh_grid_threadgroups": 4194303, "argument_buffer_samplers_per_stage": 500000,
|
|
50
|
+
"mesh_payload_and_geometry_per_draw": "not stated", "texture_2d_px": 32768}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"same_on_all": {"threads_per_threadgroup": 1024, "threadgroup_memory_bytes": 32768,
|
|
54
|
+
"mesh_pipeline_payload_bytes": 16384,
|
|
55
|
+
"argument_buffer_textures_or_buffers_per_stage": 1000000},
|
|
56
|
+
"footnotes": [
|
|
57
|
+
"64-bit atomics: none on Apple7; Apple8 has only ulong min/max, only on macOS; the full set starts with Apple9.",
|
|
58
|
+
"Mesh draws in indirect command buffers start with Apple9; earlier families refuse such mesh pipelines.",
|
|
59
|
+
"Inline constexpr samplers count toward the argument buffer sampler limit.",
|
|
60
|
+
"Mesh shaders on Apple7 and Apple8 can use up to 4 GB of payload and mesh geometry per draw.",
|
|
61
|
+
"Function pointers and ray tracing in render pipelines are not compatible with mesh shading."
|
|
62
|
+
]
|
|
63
|
+
}
|