Skip to content

tmcgilchrist/ocaml_usdt

Repository files navigation

ocaml_usdt - USDT Probes for OCaml

Userland Statically Defined Tracing (USDT) probes for OCaml applications. Compatible with DTrace, SystemTap, bpftrace, and other standard Unix tracing tools.

Features

  • FFI-based approach - Works with standard OCaml (no compiler modifications)
  • Zero overhead when disabled - Probes compile to NOP instructions (~1-2ns)
  • Two-tier system:
    • Ocaml_usdt: Simple types (int, int64, string) for hot paths
    • Usdt_json: Complex OCaml types via JSON serialization
  • Type-safe - Compile-time checks with explicit serializers
  • Universal compatibility - Works with DTrace, SystemTap, bpftrace, perf
  • Platform support - macOS, Linux, *BSD, illumos

Inspired by Rust usdt crate.

Quick Start (5 minutes)

Installation

opam install ocaml_usdt

Or build from source:

cd ocaml_usdt
dune build
dune install

System Dependencies

Linux:

sudo apt-get install systemtap-sdt-dev bpftrace

macOS and FreeBSD:

# DTrace is built-in, no installation needed

Basic Usage

(* Simple probes *)
Ocaml_usdt.probe1 "request_count" 42;
Ocaml_usdt.probe2 "request_time" req_id duration_ns;
Ocaml_usdt.probe_string "error" "connection failed";

(* JSON probes for complex types *)
type request = {
  id: int;
  path: string;
  user: string;
} [@@deriving yojson]

Usdt_json.probe "http_request" request_to_yojson my_request

Tracing

List probes:

sudo bpftrace -l 'usdt:./my_app.exe:*'

Trace requests:

sudo bpftrace -e '
usdt:./my_app.exe:ocaml_app:generic__probe2 /str(arg0) == "request_time"/ {
    printf("Request %d took %ld ns\n", arg1, arg2);
}' -c ./my_app.exe

Trace JSON probes:

sudo bpftrace -e '
usdt:./my_app.exe:ocaml_app:probe__json {
    printf("%s: %s\n", str(arg0), str(arg1));
}' -c ./my_app.exe | grep "http_request" | jq .

Core API (Ocaml_usdt)

For hot paths with simple types:

(* Basic probes with 0-6 arguments *)
Ocaml_usdt.probe0 "app_start"
Ocaml_usdt.probe1 "counter" 42
Ocaml_usdt.probe2 "timing" req_id duration_ns
Ocaml_usdt.probe3 "stats" count min max

(* String probes *)
Ocaml_usdt.probe_string "status" "running"
Ocaml_usdt.probe_int_string "error" 404 "not found"

(* Conditional firing *)
if Ocaml_usdt.is_enabled "expensive_probe" then
  let data = expensive_computation () in
  Ocaml_usdt.probe1 "expensive_probe" data

(* Lazy evaluation *)
Ocaml_usdt.Lazy.probe2 "lazy_timing"
  (fun () -> compute_req_id ())
  (fun () -> compute_duration ())

Performance:

  • Disabled: ~1-2ns (NOP instruction)
  • Enabled: ~100-500ns (depends on tracer)

JSON API (Usdt_json)

For complex OCaml types:

type user = {
  id: int;
  name: string;
  email: string;
} [@@deriving yojson]

type request = {
  user: user;
  path: string;
  headers: (string * string) list;
} [@@deriving yojson]

(* Direct probe with explicit serializer *)
Usdt_json.probe "user_login" user_to_yojson my_user

(* Reusable probe function *)
let probe_request = Usdt_json.make_probe request_to_yojson "request" in
probe_request req1;
probe_request req2;

(* Lazy evaluation (only serializes if enabled) *)
Usdt_json.Lazy.probe "expensive_event"
  (fun () -> compute_expensive_data ())
  data_to_yojson

Performance:

  • Serialization: ~100-5000ns (depends on complexity)
  • Only happens when probe is enabled
  • Use Lazy.probe to defer computation

Examples

Simple Example

let () =
  Ocaml_usdt.probe0 "app_start";

  for i = 0 to 9 do
    let start = get_time_ns () in
    do_work ();
    let duration = Int64.sub (get_time_ns ()) start in
    Ocaml_usdt.probe2 "iteration" i duration
  done;

  Ocaml_usdt.probe0 "app_end"

Run: dune exec examples/simple/example.exe

HTTP Server Example

let handle_request req_id path =
  let start = get_time_ns () in
  Ocaml_usdt.probe_int_string "request_start" req_id path;

  let response = process_request () in

  let duration = Int64.sub (get_time_ns ()) start in
  Ocaml_usdt.probe3 "request_end" req_id duration response.status;

  response

Run: dune exec examples/http_server/server.exe

JSON Complex Types

type http_request = {
  request_id: int;
  user: user;
  path: string;
  headers: (string * string) list;
} [@@deriving yojson]

let probe_request = Usdt_json.make_probe http_request_to_yojson "http_request"

let handle req =
  probe_request req;
  process_request req

Run: dune exec examples/json_types/complex.exe

Tracing Scripts

Helper scripts in scripts/:

  • trace_simple.bt - Trace all simple probes
  • trace_json.bt - Trace JSON probes
  • check_probes.sh - Verify probes in binary
# Check if probes exist
./scripts/check_probes.sh ./my_app.exe

# Trace simple probes
sudo ./scripts/trace_simple.bt -c ./my_app.exe

# Trace JSON probes
sudo ./scripts/trace_json.bt -p $(pgrep my_app)

Provider Configuration

Default provider is ocaml_app. To customize:

Ocaml_usdt.set_provider "myapp"

Probes will appear as myapp:probe_name instead of ocaml_app:probe_name.

Troubleshooting

No probes found

Check build:

./scripts/check_probes.sh ./my_app.exe

Linux: Ensure systemtap-sdt-dev is installed macOS: DTrace is built-in

Probes not firing

  1. Ensure binary is running
  2. Verify probe names match exactly (case-sensitive)
  3. Check tracer is attached to correct PID
  4. Use sudo for tracing commands

Permission denied

Linux:

# Option 1: Use sudo
sudo bpftrace ...

# Option 2: Configure capabilities
sudo setcap cap_sys_admin,cap_sys_resource=eip $(which bpftrace)

Design Philosophy

See DESIGN.md for detailed architecture and rationale.

Key decisions:

  • FFI over compiler modifications (maintainability)
  • JSON for complex types (human-readable, flexible)
  • Explicit serializers (type safety, simplicity)
  • Zero overhead when disabled (production-safe)

Performance Characteristics

Operation Overhead (disabled) Overhead (enabled)
Simple probe (probe1-6) ~1-2ns ~100-500ns
String probe ~1-2ns ~100-500ns
JSON probe (small) ~1-2ns ~200-1000ns
JSON probe (large) ~1-2ns ~1000-5000ns

Platform Support

Platform DTrace SystemTap bpftrace perf
Linux
macOS
FreeBSD
illumos

Benchmarking

Comprehensive benchmarks are available in benchmark/ directory to measure probe overhead.

Requirements

opam install core core_bench ppx_deriving_yojson

Running Benchmarks

# Quick run (2 seconds)
make bench

# Save results for your platform
make bench-save

# Detailed run
dune exec --release benchmark/bench_core.exe -- -ascii -quota 10
dune exec --release benchmark/bench_json.exe -- -ascii -quota 10

See benchmark/README.md for details.

License

MIT

Contributing

Issues and PRs welcome!

See Also

About

Userland Statically Defined Tracing (USDT) probes for OCaml applications.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published