User-Space Dynamic Tracing Playbook (USDT + Uprobes + eBPF)

2026-03-25 · systems

User-Space Dynamic Tracing Playbook (USDT + Uprobes + eBPF)

Date: 2026-03-25
Category: knowledge
Scope: Practical, production-safe tracing for user-space services without redeploying application code.


1) Why this matters

When incidents happen in production, teams often face a bad tradeoff:

USDT and uprobes provide a third path: attach observability at runtime, directly to user-space execution points, with bounded blast radius.

Core principle: treat dynamic tracing as an operational control surface, not an emergency-only hack.


2) Mental model: USDT vs uprobe

A) Uprobe / uretprobe

B) USDT (User Statically Defined Tracing)

Rule of thumb:


3) Tooling stack by lifecycle stage

  1. bpftrace (first response)
    • one-liners, rapid exploration, low setup friction.
  2. BCC (structured scripts)
    • reusable scripts and richer helper ecosystem.
  3. libbpf + CO-RE (productionized)
    • ship stable daemons/agents with explicit contracts and CI.

Brendan Gregg’s practical guidance is still useful: start with bpftrace for short scripts, move to heavier tooling only when complexity demands it.


4) Prerequisites and environment checks

Before attaching probes in production:

For modern eBPF auto-instrumentation stacks (for example OTel OBI), baseline requirements commonly include Linux 5.8+ (or distro backports), root/capability model, and architecture compatibility (x86_64/arm64).


5) Safe attach workflow (operator runbook)

Step 1 — Enumerate probes first

# list USDT probes in a binary
bpftrace -l "usdt:/path/to/bin:*"

# list probes for a running PID (includes shared-lib USDT)
bpftrace -lp <PID> "usdt:*"

For SDT-enabled binaries, perf list, perf probe, and BCC tplist are useful cross-checks.

Step 2 — Start with count-only probes

Avoid printf storms first. Validate event rates cheaply.

bpftrace -e 'usdt:/path/to/bin:main:run_start { @hits = count(); }'

Step 3 — Scope to one process

Use PID scoping aggressively during incident triage:

bpftrace -p <PID> -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { @m = count(); }'

-p semantics matter: for USDT/uprobes/uretprobes, attachment is process-specific; this is a major safety lever.

Step 4 — Handle short-lived process races

For child-process tracing, prefer -c path when possible:

bpftrace -c './target-binary --args' -e 'usdt:./target-binary:* { @x = count(); }'

bpftrace pauses child post-execve and resumes after attaching USDT probes, reducing startup race loss.

Step 5 — Promote only after canary validation

Promotion gates:


6) Overhead budgeting (the part people skip)

Disabled USDT probes are designed for minimal overhead (commonly close to zero in practice). But once enabled, cost scales with:

Practical order of operations:

  1. count,
  2. histogram/sampled aggregation,
  3. selective argument capture,
  4. stack traces only when required.

Avoid raw event streaming as default; aggregate in-kernel whenever possible.


7) Failure modes and mitigations

A) Symbol drift / stripped binaries

B) ABI drift in function arguments

C) Lost events under burst

D) Probe blast radius too broad

E) Permission surprises in hardened hosts


8) How this fits with OpenTelemetry eBPF auto-instrumentation

OTel eBPF instrumentation (OBI) gives broad zero-code visibility (HTTP/S, gRPC, RED metrics, multiple languages) and is excellent for fast baseline coverage.

But OBI has an intentional limit: it is generic by design. For domain-specific internals (matching engine state, custom queues, proprietary protocol phases), pair OBI with targeted USDT/uprobes.

Best pattern:


9) 30-day adoption plan

Week 1:

Week 2:

Week 3:

Week 4:


10) One-line takeaway

USDT and uprobes are most valuable when treated as a governed production interface: narrow scope, explicit overhead budgets, and repeatable attach/rollback discipline.


References