Skip to content

Enable PHP FFE exposure system tests#7031

Open
leoromanovsky wants to merge 1 commit into
mainfrom
leo.romanovsky/pr-h-php-ffe-exposures
Open

Enable PHP FFE exposure system tests#7031
leoromanovsky wants to merge 1 commit into
mainfrom
leo.romanovsky/pr-h-php-ffe-exposures

Conversation

@leoromanovsky
Copy link
Copy Markdown
Contributor

@leoromanovsky leoromanovsky commented May 28, 2026

Motivation

PHP FFE exposure logging now lives in the native dd-trace-php/runtime path and needs the shared FFE exposure system tests enabled so regressions are caught outside local dogfooding.

Planning/reference doc: https://docs.google.com/document/d/1NvMfTpZWLBlFmEFNjdnlMyeVpy5l7KD8qujGFco6w2w/edit?tab=t.0

Changes

This PR enables tests/ffe/test_exposures.py for PHP at v1.21.0-dev.

#7003 has already merged to main, so this PR is now intentionally just one manifest activation on top of main. It does not enable FFE evaluation-metric tests; those belong to the separate metrics validation stack for DataDog/dd-trace-php#3911 and #7033.

Decisions

The scope is deliberately one manifest activation. The shared exposure tests already cover the required product behavior: event generation, multiple RC files, malformed/empty RC handling, same-subject deduplication, different-subject emission, allocation/variant changes, doLog=false, missing flags, and empty targeting keys.

The PHP behavior being validated here is implemented in DataDog/dd-trace-php#3910 and the sidecar delivery/cache support is implemented in DataDog/libdatadog#2026.

Related PRs

Validation

Current branch state after rebase onto main:

$ PATH=/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH ./format.sh
All good, the system-tests CI will be happy!

Current PR diff is one file, enabling only tests/ffe/test_exposures.py for PHP at v1.21.0-dev.

Earlier local behavior validation for this activation used a matched PHP 8.2 NTS artifact and the php-fpm-8.2 weblog:

TEST_LIBRARY=php \
WEBLOG_VARIANT=php-fpm-8.2 \
./run.sh +l php FEATURE_FLAGGING_AND_EXPERIMENTATION tests/ffe/test_exposures.py

Result: 11 passed in 78.22s.

@datadog-prod-us1-4
Copy link
Copy Markdown

datadog-prod-us1-4 Bot commented May 28, 2026

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: c9fe071 | Docs | Datadog PR Page | Give us feedback!

@github-actions
Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

manifests/php.yml                                                       @DataDog/apm-php @DataDog/asm-php

Base automatically changed from leo.romanovsky/pr-g-php-ffe-scaffold to main May 29, 2026 15:48
gh-worker-dd-mergequeue-cf854d Bot pushed a commit to DataDog/libdatadog that referenced this pull request Jun 1, 2026
## Motivation

PHP FFE exposure delivery needs a native path with a cache that persists beyond a single PHP request/thread. The shared design doc is the cross-PR reference: https://docs.google.com/document/d/1NvMfTpZWLBlFmEFNjdnlMyeVpy5l7KD8qujGFco6w2w/edit?tab=t.0

This PR is exposure-only. Metrics were split into #2052 so reviewers can evaluate exposure cache and delivery separately from OTLP evaluation metrics.

## Changes

This adds caller-driven FFE exposure sidecar actions, exposure payload forwarding through the Agent EVP proxy, and a shared exposure cache that deduplicates repeated `(service, env, version, flag, subject)` assignments across PHP requests and sidecar connections.

The reusable FFE-domain pieces now live in `datadog-ffe` behind the `exposure-events` feature: exposure input types, the LRU deduplication cache, and JSON payload encoding. `datadog-sidecar` keeps only sidecar-specific work: deriving the agent EVP endpoint, building the HTTP request, applying the timeout, logging delivery failures, and integrating with sidecar lifecycle/actions.

Current PHP MVP path:

```mermaid
flowchart LR
    Eval["PHP native evaluation<br/>ddog_ffe_evaluate"]
    Batch["PHP tracer native memory<br/>request/thread-local exposure batch"]
    Shutdown["PHP RSHUTDOWN<br/>flush exposure batch"]
    Action["sidecar action<br/>record FFE exposures"]
    Domain["datadog-ffe<br/>feature: exposure-events<br/>types + cache + JSON encoder"]
    Sidecar["shared sidecar<br/>cross-request and cross-thread exposure cache"]
    Agent["Datadog Agent<br/>EVP proxy"]
    Intake["FFE exposure intake"]

    Eval -->|"doLog=true assignment"| Batch
    Batch --> Shutdown
    Shutdown --> Action
    Action --> Domain
    Domain --> Sidecar
    Sidecar --> Agent
    Agent --> Intake
```

Future Python/Ruby connection:

```mermaid
flowchart LR
    PyToday["dd-trace-py today<br/>host-language exposure writer"]
    RbToday["dd-trace-rb today<br/>host-language exposure writer"]
    PyFuture["dd-trace-py future<br/>explicit native opt-in"]
    RbFuture["dd-trace-rb future<br/>explicit native opt-in"]
    Native["libdatadog caller-driven<br/>FFE exposure action"]
    Shared["shared sidecar<br/>dedupe + EVP delivery"]
    Agent["Datadog Agent<br/>EVP proxy"]

    PyToday -. "current direct EVP path" .-> Agent
    RbToday -. "current direct EVP path" .-> Agent
    PyFuture -. "after ownership switch" .-> Native
    RbFuture -. "after ownership switch" .-> Native
    Native --> Shared
    Shared --> Agent
```

The future Python/Ruby arrows are intentionally not active behavior in this PR. They show why the reusable code lives in `datadog-ffe` rather than directly in sidecar internals, while preserving today's host-language ownership.

Why Python/Ruby do not double count today:

- Python and Ruby use libdatadog for evaluation only; the evaluator returns assignment metadata and does not enqueue exposure telemetry as a side effect.
- This PR adds a separate caller-driven sidecar action. Exposure emission happens only when an SDK explicitly records exposure candidates into that action. PHP wires this in its companion PR; Python and Ruby do not.
- Python and Ruby therefore keep exactly their current host-language EVP exposure writers. They are not also sending exposure candidates through this native sidecar path.
- The sidecar cache only deduplicates exposure candidates that enter the native sidecar path. It cannot protect direct host-language EVP writers, so future Python/Ruby migration must switch ownership to native logging and disable/bypass the host exposure writer for the same evaluations.

Reference implementation check: dd-trace-java follows the same exposure semantics and user ergonomics. Java's `DDEvaluator` is SDK-owned evaluation code; after resolving an assignment, it checks allocation `doLog`, builds an exposure event with flag, variant, allocation, targeting key, and context, and dispatches it through `FeatureFlaggingGateway`. `ExposureWriterImpl` subscribes to those exposure events, queues them, deduplicates with an LRU exposure cache, serializes service/env/version context, and posts to the Agent EVP proxy. Application code only calls the OpenFeature provider; it does not call an exposure API.

PHP mirrors that canonical shape, with PHP-specific lifecycle mechanics: the dd-trace-php evaluation bridge records `doLog=true` exposure candidates internally, request shutdown flushes the batch, and this PR's sidecar path owns cross-request deduplication and EVP delivery. For future Python/Ruby migration, the same rule applies: wire native exposure recording inside the SDK-owned evaluation path, and turn off the existing host-language exposure writer for those evaluations.


## Decisions

No telemetry is emitted automatically from shared libdatadog evaluator calls. SDKs must explicitly enqueue FFE telemetry actions. This remains required for Python/Ruby coexistence because those SDKs currently log exposures and metrics in host-language code.

The sidecar cache deduplicates only exposure candidates sent through this native sidecar path; it cannot deduplicate direct host-language EVP writers.

Future Python/Ruby migration must be an ownership switch, not an additional writer. When those SDKs opt into this native exposure path, their host-language exposure writers must be disabled or bypassed for the same evaluations to avoid double counting.

## Validation

Current head (`8be471fbc`) local validation:

```sh
cd /Users/leo.romanovsky/go/src/github.com/DataDog/libdatadog-ffe-sidecar-exposures
cargo fmt --check
cargo test -p datadog-ffe --features exposure-events telemetry::exposures
cargo test -p datadog-sidecar ffe_exposure
cargo check -p datadog-ffe
cargo check -p datadog-sidecar-ffi
```

Results: datadog-ffe exposure tests passed (4 passed), sidecar exposure tests passed (6 passed), default datadog-ffe check passed, sidecar FFI check passed, fmt check passed with only the repo stable-rustfmt warnings.

Prior downstream PHP behavior validation before the reusable-crate refactor, from DataDog/dd-trace-php#3910 using this PR at `6d23848a`:

```text
ffe-dogfooding subject=php-3910-split-1779981442
php7_exposures=1 php8_exposures=1
php7_metrics=0 php8_metrics=0
```

System-tests downstream validation:

```sh
TEST_LIBRARY=php ./run.sh FEATURE_FLAGGING_AND_EXPERIMENTATION tests/ffe/test_exposures.py -vv
```

Result: 11 passed in 77.53 seconds.

Related PRs: DataDog/dd-trace-php#3906, DataDog/dd-trace-php#3910, #2052, DataDog/system-tests#7031.



Co-authored-by: leo.romanovsky <leo.romanovsky@datadoghq.com>
@leoromanovsky leoromanovsky force-pushed the leo.romanovsky/pr-h-php-ffe-exposures branch from f54c036 to c9fe071 Compare June 3, 2026 18:53
@leoromanovsky leoromanovsky marked this pull request as ready for review June 3, 2026 19:12
@leoromanovsky leoromanovsky requested review from a team as code owners June 3, 2026 19:12
@leoromanovsky leoromanovsky enabled auto-merge (squash) June 3, 2026 19:15
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