Cloudflare can sample a deployed Worker or Durable Object while it serves traffic. The result is a pprof file. Each frame is a function. Wider frames cost more CPU time or allocate more memory.
Source: Profiling in production.
Terms used here
- Isolate: one running copy of a Worker. A profile samples one isolate that is already loaded.
- CPU profile: where CPU time goes. It samples about every millisecond.
- Heap profile: where code allocates memory. It records a stack every 512 kB allocated. It does not show retained memory, so it cannot prove a leak by itself.
- Flat: cost in the function itself. Cum: cost in the function and everything it calls.
1. Pick the target
- Find the deployed script name. Alchemy names it from the app, resource, stage, and a suffix. Read it from the Alchemy state or the dashboard; do not guess it.
- Pick the version.
latestis the newest uploaded version, which may not be deployed. After an upload without a deploy, use the deployed version's id. - For a Durable Object, use the Worker that owns the namespace, the namespace id, and the 64-character instance id.
2. Get read access
Use one of these:
- The
cfCLI with its own login. - An API token with Workers Scripts Read, taken from your secret store for this one run.
Profiling is read-only. Do not add the token to the repo, .env.schema, or CI. Deploy credentials stay in Alchemy profiles.
3. Send traffic during the capture
A capture does not invoke your code. It records only what runs during the window.
- Start a load loop against the routes you suspect, then start the capture.
- Keep the loop running for the whole duration.
No recent executionsorno loaded isolatemeans the version had no traffic. Send traffic, then retry.
4. Capture
API, 10 seconds of CPU:
curl --fail --silent --show-error --request POST \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$SCRIPT/versions/latest/profile" \
--header "Authorization: Bearer $TOKEN" \
--header "Content-Type: application/json" \
--data '{"duration_ms": 10000, "profile_type": "cpu"}' \
--output worker-cpu.pprof
duration_msis 1000 to 50000.profile_typeiscpu(default) orheap.- For a Durable Object, add
namespace_idandactor_idto the body. - CLI:
cf workers versions profile latest --worker-id "$SCRIPT" --duration-ms 10000 --profile-type cpu > worker-cpu.pprof. - The dashboard has the same capture under the Worker's Observability tab, view Flamegraph.
- Captures are rate limited. On HTTP 429, wait for
Retry-After.
Check the response before reading it. A failure returns JSON. A success returns protobuf.
file worker-cpu.pprof
The docs say the API returns gzip. In a capture taken on 2026-10-09, it returned raw protobuf (application/vnd.google.protobuf). pprof reads both.
5. Read it
go tool pprof -top -nodecount=25 worker-cpu.pprof
go tool pprof -top -lines -nodecount=15 worker-cpu.pprof
go tool pprof -peek 'functionName' worker-cpu.pprof
go tool pprof -http=:0 worker-cpu.pprof
- Start with
-top. Ignore(idle)and(program). Note(garbage collector): a high share points to allocation, so take a heap profile next. - Use
-peekon a wide frame to see its callers and callees. - Function names come from the bundle. If the bundler keeps names, they are readable. Locations point at bundle files and lines, such as
file:/bundle/worker.js:3348, even when source maps upload. Search the bundle or the source for the function name. - In an Effect program, the fiber runtime (
runLoop,makePrimitiveProto,succeedWith) is the floor every request pays. Look below it for your own functions.
6. Act on one finding
- Name the widest function you own and the route that reaches it.
- Make one change. Common wins: compute a value once instead of per call, cache a completed value, stop building objects you throw away, remove code that a flag only half disables.
- Deploy, send the same traffic, capture again for the same duration, and compare the same frame.
- Record the before and after numbers in the commit message.
One capture samples one isolate for seconds. Take two or three before calling a pattern. Say "this capture shows", not "the Worker always".
Finish
- The profile file and the command that took it.
- The top frames you own, with flat and cum.
- One change and its measured effect, or the reason no change is worth it.