Skip to content

OTLP profiles export foundation (experimental)#1934

Draft
adriangb wants to merge 3 commits into
mainfrom
profiling-otlp-spike
Draft

OTLP profiles export foundation (experimental)#1934
adriangb wants to merge 3 commits into
mainfrom
profiling-otlp-spike

Conversation

@adriangb
Copy link
Copy Markdown
Member

@adriangb adriangb commented May 18, 2026

What

Experimental support for shipping Python 3.15 profiling.sampling (Tachyon)
CPU profiles to the OpenTelemetry profiles signal.

Opening as a draft — it builds on two alpha foundations (the Python 3.15
profiler and the OTel profiles signal) and is gated to Python 3.15+, so it
ships experimental, not stable.

Usage

logfire.configure(profiling=True)

Off by default. On Python < 3.15, or where the platform / permissions don't
let the profiler attach, it's a no-op with a warning — the rest of Logfire is
unaffected. Nothing in the profiling path raises.

What's here

  • profiling/collapsed.py — parse the profiler's --collapsed output
  • profiling/otlp.py — build an OTLP ExportProfilesServiceRequest
  • profiling/exporter.py — gzip + POST, fails soft
  • profiling/supervisor.py — runs the profiler as a child process (chunked
    attach), converts + exports each chunk; prctl(PR_SET_PTRACER) lets it
    attach without root on Linux
  • configure(profiling=True) wiring — builds the exporter from the same
    region URL / token / session as the other signals (/v1development/profiles),
    and starts / stops the supervisor with Logfire's lifecycle
  • tests against real profiler output

Validated end-to-end against a local Grafana Pyroscope: real Tachyon output →
OTLP profiles → flamegraph.

Follow-ups (not in this PR)

  • Per-span correlation (Sample.link_indexLink(trace_id, span_id)) — v2
  • A non-3.15 / memory-profile path (e.g. Memray)

⚠️ The vendored proto is a temporary hack

logfire/_internal/profiling/_proto/ contains vendored, generated OTLP
profiles protobuf bindings instead of importing from opentelemetry-proto.

Why: the profiles signal is alpha (v1development) and the opentelemetry-proto
PyPI package ships a stale snapshot — its Sample field numbers predate a
renumbering in proto v1.10.0 and are wire-incompatible with current consumers
(Pyroscope silently drops the profile). These bindings are generated from
canonical proto v1.10.0, re-namespaced to lf_otlp.* so they don't collide
with the stale copy in protobuf's descriptor pool.

This directory should be deleted once opentelemetry-proto ships bindings
from proto ≥ v1.10.0. The upstream fix is open at
open-telemetry/opentelemetry-python#5223. See _proto/README.md.

🤖 Generated with Claude Code

Convert Python 3.15 `profiling.sampling` (Tachyon) collapsed-stack output
into the OpenTelemetry profiles signal and export it over HTTP.

- collapsed.py: parse Tachyon `--collapsed` output into folded stacks
- otlp.py: build an OTLP `ExportProfilesServiceRequest`
- exporter.py: gzip + POST the request, failing soft
- _proto/: vendored OTLP profiles protobuf bindings (see _proto/README.md)

Validated end-to-end against Grafana Pyroscope. Still to come: the
subprocess supervisor that runs the profiler, wiring into
`logfire.configure()`, and per-span correlation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 18, 2026

Deploying logfire-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 612383d
Status: ✅  Deploy successful!
Preview URL: https://bd9ed2cf.logfire-docs.pages.dev
Branch Preview URL: https://profiling-otlp-spike.logfire-docs.pages.dev

View logs

adriangb and others added 2 commits May 18, 2026 14:25
`ProfilingSupervisor` runs the Python 3.15 `profiling.sampling` profiler
as a child process that attaches back to this process, in repeated
fixed-duration chunks, converting each chunk to OTLP profiles and
exporting it.

- `prctl(PR_SET_PTRACER)` lets the child ptrace us without root on Linux
- degrades gracefully: disabled with a warning on Python < 3.15 or when
  the platform / permissions don't allow attaching; never raises
- judges chunk success by output presence, since the profiler exits 0
  even when it fails to read the target

Still to come: wiring into `logfire.configure()`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an experimental `profiling: bool` option to `logfire.configure()`.
When enabled (and sending to Logfire) it builds a `ProfilesExporter` from
the same region URL / token / session as the other signals, posting to
`/v1development/profiles`, and runs a `ProfilingSupervisor` for the
process lifetime. The supervisor is stopped on `logfire.shutdown()` and
on reconfiguration.

Off by default; a no-op with a warning on Python < 3.15.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant