Share a py-spy profile
py-spy is the tool you reach for when a Python process is already
misbehaving and you cannot restart it to add instrumentation. It attaches from
the outside, samples the stacks, and writes a flame graph:
py-spy record -o profile.svg --pid 12345 --duration 30
The SVG it writes is interactive — click a frame to zoom, Ctrl-F to highlight every stack containing a symbol. That interactivity is where the diagnosis lives, and it does not survive the trip to anyone else:
- GitHub strips
<script>from SVG when it renders it. You get the picture. - Slack flattens it into an image preview.
- A Jira attachment downloads rather than opens.
So the profile ends up as a cropped screenshot with an arrow drawn on it, and whoever reads it cannot check the stack underneath your arrow.
Publish it
Drag profile.svg into the app, or POST it:
py-spy record -o profile.svg --pid "$(pgrep -f gunicorn | head -1)" --duration 30
curl -fsS -X POST "https://commareports.com/api/v1/reports" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile svg profile.svg \
--arg t "py-spy — web-3, 12:40 UTC" '{title: $t, html: $svg}')"
Comma renders report content with scripts enabled inside a sandboxed iframe
(allow-scripts, no allow-same-origin), which is precisely the capability
GitHub's sanitizer takes away. Zoom, reset and search all work at the URL.
Useful flags when the profile is for someone else to read:
--subprocesses— include forked workers, so a Gunicorn or Celery pool shows up as one graph instead of one arbitrary child.--idle— include idle threads. Usually noise, occasionally the whole answer when the problem is that everything is blocked on one lock.--native— include C extension frames, which is how you tell "slow in numpy" apart from "slow in our loop around numpy".
Profiling during an incident
Re-profiling is normal — you capture, someone changes a setting, you capture again. PATCH the same report id rather than creating a new one:
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile svg profile.svg \
--arg t "py-spy — web-3, after pool bump" '{title: $t, html: $svg}')"
One link in the incident channel, and it stays correct as the investigation moves. The revision list is the timeline.
Set the access deliberately
A production Python profile contains your module paths, your third-party dependency versions, and often enough function names to reconstruct the shape of a service. Reports are private by default; keep incident profiles on team or named-reviewer access rather than a public link. See the sharing model.
Limits
- Entry SVG: 5 MB. A 30-second capture is comfortably inside it. A
multi-minute
--subprocesses --nativecapture on a large pool can approach it — shorten the duration rather than dropping frames. - 60 requests/minute per token.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.