Documentation
¶
Overview ¶
Package python implements injection of CPython 3.12+'s perf trampoline (sys.activate_stack_trampoline) into running processes via ptrace, so that perf-agent can resolve Python JIT frames to qualnames without requiring the target to be launched with `python -X perf`.
Lifecycle: each profiling run activates the trampoline at start and deactivates at end. Activation is idempotent — re-running on the same process is safe and is the supported pattern for continuous profiling. If the target was launched with `python -X perf`, the deactivate-at-end will turn the trampoline off; users who want to keep `-X perf` always-on should not pass --inject-python.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotPython is returned when the target is not a CPython process. // Examples: a Go binary, a non-Python executable, or a process whose // libpython/exe lacks the interpreter symbols we need. ErrNotPython = errors.New("not a python process") // ErrPythonTooOld is returned when a Python process is detected but its // libpython SONAME indicates a version older than 3.12. The // sys.activate_stack_trampoline primitive does not exist on older versions. ErrPythonTooOld = errors.New("python version too old (need 3.12+)") // ErrNoPerfTrampoline is returned at activation time (not detection) when // PyRun_SimpleString returns non-zero — typically because the interpreter // was compiled without --enable-perf-trampoline. Distributors strip // internal symbols from production libpython, so we cannot detect this // from the symbol table; we rely on the runtime return value. ErrNoPerfTrampoline = errors.New("python activation refused (likely built without --enable-perf-trampoline)") // ErrStaticallyLinkedNoSymbols is returned when neither libpython nor // /proc/<pid>/exe exposes the libpython internal symbols needed for // remote calls (PyRun_SimpleString, PyGILState_Ensure, PyGILState_Release). ErrStaticallyLinkedNoSymbols = errors.New("python interpreter symbols not resolvable") )
Detection-result sentinels. All are non-fatal: detection returns one of these (wrapped) when a process should be skipped without aborting the run.
Functions ¶
func ActivatePayload ¶
func ActivatePayload() []byte
ActivatePayload returns the byte slice (NUL-terminated) to write into the target's address space before calling PyRun_SimpleString. Caller must NOT mutate the returned slice.
func DeactivatePayload ¶
func DeactivatePayload() []byte
DeactivatePayload returns the byte slice (NUL-terminated) for the shutdown deactivation call. Caller must NOT mutate the returned slice.
Types ¶
type Detector ¶
Detector inspects a process and reports whether it is a Python 3.12+ candidate suitable for trampoline injection.
type LowLevelInjector ¶
type LowLevelInjector interface {
RemoteActivate(pid uint32, addrs SymbolAddrsForTarget) error
RemoteDeactivate(pid uint32, addrs SymbolAddrsForTarget) error
}
LowLevelInjector is the contract Manager uses for the ptrace dance. The production implementation wraps inject/ptraceop.Injector; tests can stub it.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates Python perf-trampoline injection across a profile run: detection ladder, strict/lenient policy, in-memory tracked-PID set, bounded shutdown deactivation, and idempotent late-arrival activation.
func NewManager ¶
NewManager constructs a Manager.
Defaults:
- opts.Logger → slog.Default() if nil
- opts.DeactivateDeadline → 5 * time.Second if zero
- opts.Detector → NewDetector("/proc", logger) if nil
opts.Injector has no in-package default — inject/python deliberately does not import inject/ptraceop, so the low-level injector must be supplied by the caller. perfagent.Agent wires it via the ptraceopBridge; tests inject stubs. NewManager panics with a clear message if Injector is nil; surfacing that wiring mistake at construction is friendlier than the deep NPE the first ActivateAll call would otherwise raise.
func (*Manager) ActivateAll ¶
ActivateAll runs detection and activation for the given PIDs. Returns nil in lenient mode (errors are logged + counted); returns the first error in strict mode. Caller blocks on this; it is fine to call once at profile start before BPF attach.
func (*Manager) ActivateLate ¶
ActivateLate is the mmap-watcher hook for new exec events during -a mode. Always lenient (logs + counts on error). Idempotent: dedupes by tracked and pending sets.
func (*Manager) DeactivateAll ¶
DeactivateAll runs the bounded shutdown deactivation pass. Tolerates ESRCH (process gone). Honors ctx cancellation AND the configured deactivation deadline (5s default).
type Options ¶
type Options struct {
// StrictPerPID makes ActivateAll fail-fast on the first error. Used for
// --pid N --inject-python; lenient (false) for -a --inject-python.
StrictPerPID bool
// Logger receives structured log lines for every detect/activate/deactivate
// event. nil → slog.Default().
Logger *slog.Logger
// Injector overrides the default ptraceop-based injector. Tests inject
// stubs; production passes nil and gets the real ptraceop.Injector.
// In production this is wired by perfagent.Agent (Task 8) since this
// package does not import inject/ptraceop directly.
Injector LowLevelInjector
// Detector overrides the default /proc-based detector. Tests inject stubs;
// production passes nil.
Detector Detector
// DeactivateDeadline caps the total time spent in DeactivateAll. Default
// 5 seconds.
DeactivateDeadline time.Duration
}
Options configures a Manager.
type Stats ¶
type Stats struct {
Activated atomic.Uint64
Deactivated atomic.Uint64
SkippedNotPython atomic.Uint64
SkippedTooOld atomic.Uint64
SkippedNoTramp atomic.Uint64
SkippedNoSymbols atomic.Uint64
ActivateFailed atomic.Uint64
DeactivateFailed atomic.Uint64
}
Stats holds counters that operators inspect after a run. All counters are safe for concurrent use.
type SymbolAddrsForTarget ¶
SymbolAddrsForTarget is the data the LowLevelInjector needs to perform one remote-call sequence — independent of inject/ptraceop's exact struct layout to keep the test boundary clean.
type Target ¶
type Target struct {
PID uint32
LibPythonPath string // on-disk path used for ELF parsing
LoadBase uint64 // address from /proc/<pid>/maps for libpython
PyGILEnsureAddr uint64
PyGILReleaseAddr uint64
PyRunStringAddr uint64
Major, Minor int // detected libpython version
}
Target describes a Python 3.12+ process that detection has confirmed is suitable for trampoline injection. All address fields are absolute remote addresses (load_base + symbol_offset) ready to be passed to ptraceop.