Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/ci/appsec-gradle-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ If you need to inspect sidecar/helper or PHP issues:
- The `test` task itself is disabled (`tasks['test'].enabled = false`). Use versioned tasks like `test8.3-debug`.
- Docker images are pulled from `docker.io/datadog/dd-appsec-php-ci`. Without `-PfloatingImageTags`, images are resolved by SHA256 digest from `gradle/tag_mappings.gradle`. If a digest is not locally available, Docker will pull it.
- `buildPortableLibdatadogPhp` uses the
`nginx-fpm-php-8.5-release-musl` image with nightly Rust. The image must
`nginx-fpm-php-8.5-release-musl` image with stable Rust (`compile_rust.sh`
sets `RUSTC_BOOTSTRAP=1` for `-Zbuild-std`). The image must
be available locally or pullable.
- On first run, Gradle downloads its wrapper, dependencies, and Docker images. Expect 5-10 minutes. Subsequent runs with warm caches take ~20-50 seconds for a single test.
- **c-ares DNS failure in Alpine containers.** Alpine's `curl` and `git` use
Expand Down
19 changes: 9 additions & 10 deletions .claude/ci/building-locally.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ cargo +nightly fmt --all --quiet && \
cargo clippy --workspace --all-targets --all-features -- -D warnings
```

A rustc ≥1.87 toolchain override is active in the repo, so plain `cargo clippy`
picks up the correct toolchain — do NOT force `+stable` (the default stable is
older) or a pinned `+1.87.0`, and do not lint per-crate with `-p`. Only `fmt`
needs `+nightly`.
A rustc ≥1.91 toolchain override is active in the repo, so plain `cargo clippy`
picks up the correct toolchain. The `+nightly` toolchain is likely not in the
development images. You can install it temporarily in a running image, or run
it on the host.
Comment thread
realFlowControl marked this conversation as resolved.

## Tracer Extension (ddtrace.so)

Expand Down Expand Up @@ -212,18 +212,17 @@ helper artifact.

### For correctness tests (bookworm)

`CARGO_TARGET_DIR` **must** be set explicitly (see
[github-actions-profiler.md](github-actions-profiler.md) for why):

```bash
dockerh --cache profiler-8.3-nts --php nts \
datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
cd profiling && cargo rustc --features=trigger_time_sample \
--profile profiler-release --crate-type=cdylib
cd /project/dd-trace-php && make compile_profiler PROFILER_FEATURES=trigger_time_sample
'
```

Output: `tmp/build_profiler/modules/datadog-profiling.so`. See
[github-actions-profiler.md](github-actions-profiler.md) for
`PROFILER_FEATURES` and the ASAN variant (`make compile_profiler_asan`).

### For release / packaging / system tests (centos-7)

Bookworm is too recent for binary compatibility purposes.
Expand Down
130 changes: 74 additions & 56 deletions .claude/ci/github-actions-profiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
|--------|--------|-------------|
| `Profiling correctness / prof-correctness ({ver}, nts)` | `ubuntu-24.04` | Builds profiler + runs NTS correctness test cases |
| `Profiling correctness / prof-correctness ({ver}, zts)` | `ubuntu-24.04` | Same + `exceptions_zts` (requires `parallel` PECL extension) |
| `Profiling ASAN Tests / prof-asan ({ver}, {arch})` | `arm-8core-linux` / `ubuntu-8-core-latest` | Builds profiler with ASAN + runs `.phpt` profiling tests |
| `Profiling ASAN/UBSAN Tests / PHP 8.5 {nts,zts} UBSAN ({arch})` | `arm-8core-linux` / `ubuntu-8-core-latest` | Builds profiler with UBSAN + runs `.phpt` profiling tests |

Correctness matrix: PHP 8.0+ × {nts, zts}.
ASAN matrix: PHP 8.3+ × {arm64, amd64}.
ASAN matrix: PHP 8.3+ × {nts-asan, debug-zts-asan} × {arm64, amd64}.
UBSAN matrix: PHP 8.5 × {nts, zts} × {arm64, amd64}.

## What It Tests

Each job builds the profiler extension with `--features=trigger_time_sample`, then runs
PHP scripts that exercise profiling (allocations, wall/cpu time, exceptions, IO, timeline,
Each job builds the profiler with
`make compile_profiler PROFILER_FEATURES=trigger_time_sample`, then runs PHP
scripts that exercise profiling (allocations, wall/cpu time, exceptions, IO, timeline,
strange frames). The scripts output pprof files (zstd-compressed protobuf). The
`Datadog/prof-correctness/analyze` GitHub Action then checks each pprof against a JSON
expectations file.
Expand All @@ -36,7 +38,8 @@ ZTS adds: `exceptions_zts`.

Use `.claude/ci/dockerh` with the `datadog/dd-trace-ci:php-<VERSION>_bookworm-{N}` image
matching the PHP version under test (see `index.md` for image version and contents). The CI
uses clang-19 on ubuntu-24.04; clang-17 in the image works fine.
uses clang-20 (`LLVM_VERSION` in `prof_correctness.yml`) on ubuntu-24.04; clang-21 in the
image works fine.

Actions jobs use `shivammathur/setup-php` instead, but the same `dd-trace-ci`
image is a suitable local substitute.
Expand All @@ -50,33 +53,31 @@ PHP version being tested.

### Build the profiler extension

`cargo rustc` must be run from the `profiling/` subdirectory (the workspace `profiler-release`
profile is defined in the repo root `Cargo.toml`, but the crate itself lives in `profiling/`).

**`CARGO_TARGET_DIR` must be set explicitly** to `/project/dd-trace-php/target`. Without
it the `cbindgen` build script inside `libdatadog` calls `cargo locate-project --workspace`,
which resolves to the `libdatadog/` submodule's own workspace (not the repo root), and
tries to create `libdatadog/target/include/datadog/library-config.h`. That path is inside
the read-only source mount with no writable overlay, causing a
`ReadOnlyFilesystem (os error 30)` panic. Pointing `CARGO_TARGET_DIR` at the already-
overlaid `/project/dd-trace-php/target` fixes it.
Build through the top-level `Makefile`, the same way CI does. The build runs
out-of-tree in `tmp/build_profiler/` (writable under `dockerh`), and cargo's
target dir defaults to `tmp/build_profiler/target-profiling/`.

```bash
# NTS example (PHP 8.3)
dockerh --cache profiler-8.3-nts --php nts datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
cd profiling && cargo rustc --features=trigger_time_sample --profile profiler-release --crate-type=cdylib
dockerh --cache profiler-8.3-nts --php nts datadog/dd-trace-ci:php-8.3_bookworm-11 -- bash -c '
cd /project/dd-trace-php && make compile_profiler PROFILER_FEATURES=trigger_time_sample
'

# ZTS example (PHP 8.1) — note --php zts, matching image version, and separate cache name
dockerh --cache profiler-8.1-zts --php zts datadog/dd-trace-ci:php-8.1_bookworm-6 -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
cd profiling && cargo rustc --features=trigger_time_sample --profile profiler-release --crate-type=cdylib
dockerh --cache profiler-8.1-zts --php zts datadog/dd-trace-ci:php-8.1_bookworm-11 -- bash -c '
cd /project/dd-trace-php && make compile_profiler PROFILER_FEATURES=trigger_time_sample
'
```

Output: `/project/dd-trace-php/target/profiler-release/libdatadog_php_profiling.so`
(persisted in the host cache at `~/.cache/dd-ci/<CACHE-NAME>/target/`).
Output: `/project/dd-trace-php/tmp/build_profiler/modules/datadog-profiling.so`.

`PROFILER_FEATURES` adds Cargo features on top of `profiling` (default: none).
The correctness tests need `trigger_time_sample` (`strange_frames.php`); add
more comma-separated, e.g. `PROFILER_FEATURES=trigger_time_sample,debug_stats`.
Features are fixed at configure time, so remove `tmp/build_profiler/` after
changing them.
Release packages don't use these targets (`.gitlab/build-profiler.sh` runs
`phpize`/`configure` with no extra features).

The second run reuses the build cache and completes in seconds. Never run `--clean-cache`
between iterations — the Rust build takes 5–15 minutes from scratch.
Expand All @@ -88,8 +89,7 @@ write pprof output there — no extra mounts needed:

```bash
dockerh --cache profiler-8.3-nts --php nts \
datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
datadog/dd-trace-ci:php-8.3_bookworm-11 -- bash -c '
export DD_PROFILING_LOG_LEVEL=warn # use "trace" only when debugging — trace is verbose and slows execution
export DD_PROFILING_EXPERIMENTAL_FEATURES_ENABLED=1
export DD_PROFILING_EXPERIMENTAL_EXCEPTION_SAMPLING_DISTANCE=1
Expand All @@ -100,7 +100,7 @@ TEST_CASE=allocations
OUT=/project/dd-trace-php/tmp/correctness/$TEST_CASE
mkdir -p $OUT
DD_PROFILING_OUTPUT_PPROF=$OUT/test.pprof \
php -d extension=/project/dd-trace-php/target/profiler-release/libdatadog_php_profiling.so \
php -d extension=/project/dd-trace-php/tmp/build_profiler/modules/datadog-profiling.so \
/project/dd-trace-php/profiling/tests/correctness/$TEST_CASE.php
ls -la $OUT/
'
Expand Down Expand Up @@ -134,7 +134,7 @@ The pprof files are zstd-compressed protobuf. Use `go tool pprof` (available in
dd-trace-ci image) to inspect them. Pass `--user root` so `apt-get install` works:

```bash
dockerh --cache profiler-8.3-nts --php nts datadog/dd-trace-ci:php-7.3_bookworm-6 --user root -- bash -c '
dockerh --cache profiler-8.3-nts --php nts datadog/dd-trace-ci:php-7.3_bookworm-11 --user root -- bash -c '
apt-get update -qq > /dev/null 2>&1 && apt-get install -y -qq zstd > /dev/null 2>&1

PPROF_DIR=/project/dd-trace-php/tmp/correctness/allocations
Expand Down Expand Up @@ -202,14 +202,15 @@ frame name formatting. The implementation is in `profiling/src/capi.rs` and

## Debug Build

For a debug (unoptimized) build:
For a debug (unoptimized) Rust build, add `RUST_DEBUG_BUILD=1` (use a separate
build dir, or remove `tmp/build_profiler/` first):

```bash
cargo rustc --features=trigger_time_sample --profile dev --crate-type=cdylib
make compile_profiler RUST_DEBUG_BUILD=1 PROFILER_BUILD_SUFFIX=profiler_debug \
PROFILER_FEATURES=trigger_time_sample
```

Output: `target/debug/libdatadog_php_profiling.so` (~144 MB vs ~20 MB for profiler-release).
Use the same `php -d extension=...` command, just point to the debug path.
Output: `tmp/build_profiler_debug/modules/datadog-profiling.so`.

## ZTS tests -- parallel PECL extension

Expand All @@ -222,28 +223,44 @@ CI, on the other hand, runs on a bare `ubuntu-24.04` runner and installs PHP via
installs version `v1.2.7` from GitHub via the `extensions` matrix parameter
(`parallel-krakjoe/parallel@v1.2.7`).

## ASAN Build
## ASAN / UBSAN Builds

Both jobs live in `.github/workflows/prof_asan.yml` and build through the
top-level `Makefile` (out-of-tree, in `tmp/build_profiler*/`), not by running
`phpize`/`./configure` in the source root.

- **ASAN** (`prof-asan`): `make compile_profiler_asan`. Uses the pinned
**stable** toolchain from `rust-toolchain.toml`; the target sets
`RUSTC_BOOTSTRAP=1` so stable accepts `-Zsanitizer=address` and
`-Zbuild-std=std,panic_abort` (std is rebuilt instrumented, which needs the
`rust-src` component and an explicit `--target`). Output:
`tmp/build_profiler_asan/modules/datadog-profiling.so`.
- **UBSAN** (`prof-ubsan`): `make compile_profiler` with UBSAN `CFLAGS`/`LDFLAGS`
and `-C link-arg=-fsanitize=...` in `RUSTFLAGS`. Output:
`tmp/build_profiler/modules/datadog-profiling.so`.

Builds the profiler with AddressSanitizer using a pinned nightly Rust toolchain
and clang-17, then runs the `.phpt` test suite with `--asan`.
`CC`/`CFLAGS`/`LDFLAGS` from the environment still apply to C code built by
cargo build scripts. The standalone profiler has no C of its own; the `.so` is
the Rust cdylib.

### Local reproduction
The workflow copies the `.so` into the extension dir rather than using
`make install_profiler`, because the install target also writes a
`datadog-profiling.ini` and the test step loads the extension with `-d
extension=...` (it would be loaded twice).

### Local reproduction (ASAN)

```bash
dockerh --cache profiler-asan-8.3-nts --php nts-asan \
datadog/dd-trace-ci:php-8.3_bookworm-6 --user root --privileged -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
export CC=clang-17
export CFLAGS="-fsanitize=address -fno-omit-frame-pointer"
dockerh --cache profiler-asan-8.5-nts --php nts-asan \
datadog/dd-trace-ci:php-8.5_bookworm-11 --user root --privileged -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/tmp/build-cargo
export CC=clang-21
export CFLAGS="-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer"
export LDFLAGS="-fsanitize=address -shared-libasan"
export RUSTC_LINKER=lld-17
RUST_TOOLCHAIN=nightly-2025-10-31

cd profiling
triplet=$(uname -m)-unknown-linux-gnu
RUSTFLAGS="-Zsanitizer=address" cargo +${RUST_TOOLCHAIN} build -Zbuild-std=std,panic_abort \
--target $triplet --profile profiler-release
cp -v "$CARGO_TARGET_DIR/$triplet/profiler-release/libdatadog_php_profiling.so" \

cd /project/dd-trace-php
make compile_profiler_asan
cp -v tmp/build_profiler_asan/modules/datadog-profiling.so \
"$(php-config --extension-dir)/datadog-profiling.so"

# run-tests.php writes temp files next to .phpt files, so both must be in a writable dir.
Expand All @@ -258,18 +275,19 @@ DD_PROFILING_OUTPUT_PPROF=/tmp/pprof \
'
```

Requires `--user root --privileged` — ASAN needs both.

The nightly toolchain version (`nightly-2025-10-31`) is pinned in
`.github/workflows/prof_asan.yml`, not in `profiling/rust-toolchain.toml`. Check
the workflow file for the current pinned version.
Requires `--user root --privileged` — ASAN needs both. For UBSAN, use
`--php nts` (or `zts`), the UBSAN flags from the workflow, `make
compile_profiler`, and `LD_PRELOAD` the clang UBSAN runtime when running tests
(see the workflow).

## Gotchas

- **Expected ASAN test counts:** 39 total, ~27 pass, ~12 skip (30%), 0 fail. The skips are normal
- **Expected test counts (PHP 8.5):** ASAN 47 total, 32 pass, 15 skip, 0 fail; UBSAN nts
47 total, 35 pass, 12 skip, 0 fail. The skips are normal
(platform/env conditions). A non-zero fail count indicates a real problem.
- The `profiler-release` profile is defined in the workspace root `Cargo.toml`, not in
`profiling/Cargo.toml`. It inherits from `release` with `panic = "abort"`.
- The profiler is built from the root `datadog-php` crate (`Cargo.toml`, `--features profiling`);
there is no `profiling/Cargo.toml`. The `profiler-release` profile is defined there too and
inherits from `release` with `panic = "abort"`.
- `dockerh` runs the container as your host UID so cache dirs are writable without any
permission tricks. Pass `--user root` after the image name if you need to install
packages with `apt-get`.
Expand Down
14 changes: 7 additions & 7 deletions .claude/ci/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ CI images are tagged `datadog/dd-trace-ci:php-{version}_bookworm-{N}` where `N`
is an iteration number shared across all GitLab appsec jobs. Find the current
value by searching for `bookworm-` in `.gitlab/generate-appsec.php`
.
The `php-8.3_bookworm-{N}` image contains: Rust (see
`profiling/rust-toolchain.toml` for the pinned version), clang-17, Go, and
The `php-8.3_bookworm-{N}` image contains: Rust (see `rust-toolchain.toml`
for the pinned version), clang-21, Go, and
multiple PHP builds under `/opt/php/` (nts, zts, debug, etc.). Use `--php nts`
(or another variant) with `dockerh` to select the right build — see the `--php`
section above.
Expand All @@ -122,7 +122,7 @@ in CI scripts are mirrors of `datadog/dd-trace-ci:TAG` on Docker Hub. Pull them
directly without authentication — no registry login or image export/import needed:

```bash
docker pull datadog/dd-trace-ci:php-8.3_bookworm-6
docker pull datadog/dd-trace-ci:php-8.3_bookworm-11
```

(The exception is registry.ddbuild.io/images/mirror/b1o7r7e0/nginx_musl_toolchain,
Expand Down Expand Up @@ -255,7 +255,7 @@ this file instead of duplicating build commands.
### Group A — Native Linux unit and extension tests

Runner: `arch:amd64` + `arch:arm64`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-6`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-11`
No Docker daemon — tests run directly in the container.

→ **[appsec-native-tests.md](appsec-native-tests.md)**
Expand All @@ -276,7 +276,7 @@ Covers: `Unit tests`, `PHP Language Tests`, `test_c`, `ASAN test_c`, `Opcache te
### Group B — Native Linux web framework tests

Runner: `arch:amd64`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-6`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-11`
GitLab service containers: test-agent, httpbin, request-replayer

→ **[tracer-web-tests.md](tracer-web-tests.md)**
Expand All @@ -291,7 +291,7 @@ Covers: `test_web_laravel_*`, `test_web_symfony_*`, `test_web_wordpress_*`,
### Group C — Native Linux service integration tests

Runner: `arch:amd64`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-6`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-11`
GitLab service containers: MySQL, Redis, Kafka, Elasticsearch, MongoDB, etc.

→ **[tracer-integration-tests.md](tracer-integration-tests.md)**
Expand All @@ -310,7 +310,7 @@ Covers: `test_integrations_amqp*`, `test_integrations_curl`, `test_integrations_
### Group D — Native Linux compile / artifact build

Runner: `arch:amd64` + `arch:arm64`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-6`
Image: `datadog/dd-trace-ci:php-{version}_bookworm-11`
Produces `.so` artifacts consumed by Groups B, C, H.

→ **[compile-artifacts.md](compile-artifacts.md)**
Expand Down
2 changes: 1 addition & 1 deletion .claude/project/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ request crashes and is shared across language tracers. See
`compile_rust.sh` (invoked from the Makefile) drives it. Minimize FFI surface;
headers are generated with cbindgen (see [components.md](components.md)).
Toolchain is pinned — see `Cargo.toml` (`rust-version`) and
`profiling/rust-toolchain.toml`, not a hardcoded version.
`rust-toolchain.toml`, not a hardcoded version.

## Configuration & INI

Expand Down
14 changes: 7 additions & 7 deletions .claude/project/profiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ agent.

## Key files & dirs

- `profiling/Cargo.toml` — `cdylib`; depends on `libdd-profiling`/`alloc`/
`common` from libdatadog.
- `profiling/rust-toolchain.toml` — pins Rust (distinct from the workspace
toolchain).
- There is no separate profiler crate: it is built from the root
`datadog-php` crate (`Cargo.toml`) with `--no-default-features --features
profiling`, which pulls in `libdd-profiling`/`libdd-alloc` from libdatadog.
`components-rs/lib.rs` includes `profiling/src/lib.rs` as the `profiling`
module, and `components-rs/build.rs` includes `profiling/build.rs`.
- `profiling/src/lib.rs` — module entry: `minit`/`rinit`/`prshutdown`, Zend
interrupt registration.
- `profiling/src/profiling/` — `mod.rs` (`Profiler` + `SampleValues`),
Expand All @@ -33,12 +34,11 @@ pprof via a background thread; `prshutdown` flushes.
Builds independently from the tracer; depends on libdatadog for pprof. The
main tracer looks up `ddog_php_prof_interrupt_function` by symbol to call on
interrupt (see [tracer.md](tracer.md)). Not sidecar-dependent — it uploads
directly (contrast with [sidecar.md](sidecar.md)). `build.rs` reads
`../VERSION`.
directly (contrast with [sidecar.md](sidecar.md)). `profiling/build.rs` reads
the repo-root `VERSION`.

## Gotchas

- Pinned `rust-toolchain.toml`, not the workspace default.
- `CARGO_TARGET_DIR` must be set (the Makefile uses `tmp/build_profiler`).
- `cdylib` is release-only — phpt tests fail on debug builds.
- Allocation hook differs: PHP 8.4+ vs ≤8.3 use different modules.
Expand Down
Loading
Loading