Some checks failed
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
Everything this gateway does was **private convention**. It gates, meters, debits and seals every `tools/call`, `resources/read` and `prompts/get` — and a client had no way to discover that and no way to insist on it. A client that dropped the `_meta` block got served anyway, and a client talking to a gateway that enforced *nothing* could not tell the difference. Enforcement nobody can require is a courtesy, not a control. `initialize` is now the one discovery method not relayed verbatim: the gateway adds its own entry to `result.capabilities.extensions`, under the same reverse-domain key it already uses for per-call side data — one name for one thing. MCP's 2026-07-28 revision formalised `capabilities.extensions` for exactly this, with reverse-domain third-party keys and a 12-month deprecation policy behind it, so the advertisement has a defined place to live rather than an invented one. It names the profile it seals with and the methods it enforces, so a client can require the parts it needs instead of the bundle. `require_jcp(initialize_response, required)` is the half that makes the advertisement worth making. A client calls it before sending anything and **fails closed**: an endpoint advertising nothing is refused, and so is one that enforces `tools/call` but not `resources/read` — which is the case that matters, since the resource read is the egress door that was open until 971ef435. Garbage is refused rather than interpreted. Three tests. The advertisement round-trips through `require_jcp` with the right profile and method list; a client refuses a bare endpoint, refuses a partially enforcing one for the method it does not enforce, and refuses non-JSON; and advertising preserves everything else — an upstream's `protocolVersion`, `serverInfo`, its own `capabilities.tools`, and a third party's existing `extensions` entry are all untouched, while a response that is not a JSON-RPC result comes back byte-identical. That last property is the same one `attach_receipt` has to have: never mangle a payload you do not understand. `RequireError`'s `Display` is written out rather than derived, because this crate carries no `thiserror` dependency and three variants are not worth adding one for. 396 tests pass across the jcp workspace, 0 clippy findings in the crate. Also records a measurement in `jcp/tools/counter-resolution/`: the Apple `GPU Energy` rail's republish gaps, now over 90 s rather than 6. The short run gave a max of 992.2 ms, which sits just inside a one-second timeout and invites the conclusion that one second is enough. Over 90 s the same host produced **three gaps past 1000 ms — 1446, 1919 and 2033 ms** (n=2936) — so about 1 idle gap in 1000 exceeds a one-second wait and the worst was 2× it. It also pins the condition the right way round: under sustained load the rail is tight (max 42 ms, n=435); the long tail belongs to **idle**. Anything waiting a bounded time for this rail to republish should bound above ~2.5 s, or count the timeout rather than be surprised by it. Co-Authored-By: Claude Opus 5 <[email protected]>
88 lines
4.3 KiB
Markdown
88 lines
4.3 KiB
Markdown
# What a counter can actually resolve
|
||
|
||
Two harnesses behind the numbers `jcp-energy` declares. Both are the evidence
|
||
for a constant in the crate; neither is a test of the crate.
|
||
|
||
## `counter_resolution.py` — the per-process CPU counter's update period
|
||
|
||
`ri_energy_nj` is the only source in the survey that attributes energy to a
|
||
*principal* rather than to silicon, so whether it may grade a span decides
|
||
whether Apple silicon has any process-attributed reading at all.
|
||
|
||
Burns this thread's CPU for a fixed interval, samples the counter 60 times per
|
||
interval, and reports the fraction of intervals in which it advanced — the
|
||
crate's own liveness criterion is 80%.
|
||
|
||
Measured on an M5 Max, macOS 27:
|
||
|
||
| interval | advanced | median step | clears 80% |
|
||
|---:|---:|---:|---|
|
||
| 10 µs | 0% | — | no |
|
||
| 50 µs | 0% | — | no |
|
||
| 100 µs | 2% | 20.4 mJ | no |
|
||
| 500 µs | 5% | 21.1 mJ | no |
|
||
| 1 ms | 35% | 2.8 mJ | no |
|
||
| 2 ms | 63% | 4.9 mJ | no |
|
||
| **5 ms** | **87%** | 9.6 mJ | **yes** |
|
||
| 10 ms | 100% | 19.0 mJ | yes |
|
||
| 50 ms | 100% | 92.9 mJ | yes |
|
||
|
||
The crate declares **10 ms**, not the 5 ms where it first clears the criterion:
|
||
over-declaring a period refuses spans that would have been fine, while
|
||
under-declaring grades spans that were not, and at a ceiling only the second
|
||
error is dangerous.
|
||
|
||
Note the shape below 1 ms. The counter does not lag smoothly — on the few
|
||
intervals where it advanced at all, the median step was ~20 mJ, two orders
|
||
above the 1 ms step. It accrues in lumps, which is the same
|
||
batched-refresh pathology the crate already refuses on IOReport's CPU rails.
|
||
A sampler that read it every 500 µs would see nothing most of the time and a
|
||
20 mJ jump occasionally, and would report both as measurements.
|
||
|
||
This is also why a per-operation figure for an 11.85 ms decision is taken as
|
||
the mean of 200 decisions over a 2.6 s span rather than as 200 counter reads.
|
||
|
||
## `ioreport_rails.py` — which Apple rails exist and advance
|
||
|
||
Enumerates the IOReport `Energy Model` group unprivileged (364 rails on an
|
||
M5 Max) and prints each channel's name, unit and delta. What it establishes:
|
||
`GPU Energy` is live in **nJ**, every `CPU Energy` / `MCPU*` / `PACC*` /
|
||
`*_SRAM` rail is frozen for an unprivileged reader, and `ANE0` is the only ANE
|
||
channel — no `ANE Energy` aggregate, no sub-rails. Units differ *within one
|
||
sample*, so a summer that assumes one unit is wrong.
|
||
|
||
## `gpu_rail_period.py` / `gpu_rail_idle_tail.py` — when the GPU rail republishes
|
||
|
||
The `GPU Energy` rail has **no fixed period**. It republishes when there is
|
||
activity to report and holds its value when there is not, so the number you
|
||
measure depends entirely on which condition you measure in.
|
||
|
||
Sampling every 2 ms on an M5 Max, macOS 27, the gap between actual changes:
|
||
|
||
| condition | p50 | p90 | p95 | p99 | max | n |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| sustained MLX matmul | 13.1 ms | 15.0 ms | — | — | **42.0 ms** | 435 |
|
||
| idle, 6 s | 12.7 ms | 91.0 ms | — | — | 992.2 ms | 112 |
|
||
| **idle, 90 s** | 13.1 ms | 30.6 ms | 86.2 ms | 436.2 ms | **2032.8 ms** | 2936 |
|
||
|
||
Three things follow, and the third is why the long run exists.
|
||
|
||
**Under sustained load the rail is tight; the long tail is an *idle*
|
||
property.** That is the opposite of the intuitive reading, and it reconciles an
|
||
earlier fleet observation that put the hold at "300–600 ms under load": the
|
||
GGUF decode it came from is dispatch-latency-bound with idle gaps between
|
||
tokens, so it was sampling idle behaviour. 300–600 ms is about the idle
|
||
p98–p99 here, consistent with having seen that region and not past it.
|
||
|
||
**A declared period has to be the idle worst case.** A measurer cannot
|
||
guarantee the fabric stays busy, and a span landing entirely inside one hold
|
||
reads **zero** — which a naive grader would report as a measurement of zero.
|
||
`jcp-energy` declares 1 s for this rail on that basis.
|
||
|
||
**The sample max is not a bound.** The 6 s run gave a max of 992.2 ms, which
|
||
sits just inside a 1 s timeout and invites the conclusion that 1 s is enough.
|
||
Over 90 s the same host produced **three gaps past 1000 ms — 1446, 1919 and
|
||
2033 ms** — so roughly 1 idle gap in 1000 exceeds a one-second wait, and the
|
||
worst was 2× it. Any code that waits a bounded time for this rail to
|
||
republish should bound above ~2.5 s, or treat the timeout as a counted
|
||
outcome rather than a rare surprise.
|