Skip to content

Conversation

@leoromanovsky
Copy link

@leoromanovsky leoromanovsky commented Feb 7, 2026

Motivation

Add FFE support to dd-trace-php. PHP applications can evaluate feature flags delivered via Remote Config using the same datadog-ffe Rust engine used by Ruby and Python.

Changes

  • Rust FFI layer (components-rs/ffe.rs): C-callable bridge to datadog-ffe::rules_based — config store, evaluate, result accessors
  • C extension (ext/ddtrace.c): ffe_evaluate, ffe_has_config, ffe_config_changed, ffe_load_config internal functions that marshal PHP arrays to FfeAttribute structs
  • Remote Config (components-rs/remote_config.rs): Register FfeFlags product + FfeFlagConfigurationRules capability; handle add/remove of FFE configs
  • PHP Provider (src/DDTrace/FeatureFlags/Provider.php): Singleton that checks RC config state, calls native evaluate, parses JSON results, reports exposures
  • Exposure pipeline: LRU dedup cache (65K entries) + batched writer to /evp_proxy/v2/api/v2/exposures (1000 event buffer cap)
  • OpenFeature adapter (src/DDTrace/OpenFeature/DataDogProvider.php): Implements AbstractProvider for the open-feature/sdk composer package
  • Build: Add datadog-ffe to RUST_FILES in Makefile for PECL packaging
  • Tests: LRU cache unit tests, exposure cache unit tests, 220 evaluation correctness tests from JSON fixtures
  • Config: DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED gating via X-macro in ext/configuration.h

Decisions

Evaluation in Rust, not PHP. All flag evaluation (UFC parsing, targeting rules, shard hashing, allocation resolution) happens in libdatadog's datadog-ffe crate via FFI. PHP only handles orchestration (config lifecycle, exposure dedup, HTTP transport). This matches Ruby and Python — no language re-implements evaluation logic.

Global config behind Mutex<FfeState>. The Rust FFE config is stored in a lazy_static global with a Mutex. PHP is single-threaded per process, so RwLock would be unnecessary complexity. The changed flag and config are bundled in one struct to avoid torn reads.

Reuses existing RC pipeline. FFE configs flow through the same sidecar → ddog_process_remote_configs() path as APM Tracing and Live Debugger. No new polling mechanism.

Structured attributes, not JSON blobs. The C extension converts PHP arrays into FfeAttribute structs (typed: string/number/bool) before calling Rust, avoiding JSON encode/decode overhead on the hot path.

Exposure dedup uses length-prefixed composite keys. Key = len(flag):flag:subject, value = len(variant):variant:allocation. Avoids collision from delimiters appearing in flag/subject strings.

ExposureWriter caps at 1000 events per request. Matches Ruby and Python. Flush via register_shutdown_function.

Cargo.lock: minimal additions only. Started from master's lockfile and ran cargo check — only 73 new crates added (datadog-ffe transitive deps), 2 unavoidable minor bumps (once_cell 1.20→1.21 required by pyo3, os_info 3.9→3.14 required by crashtracker). No edition2024 crates, no gratuitous upgrades.

Test Results

Unit tests (local, no extension): 243 tests, 75 assertions, 1 skipped
Unit tests (Docker with extension): 243 tests, 735 assertions — all pass

System tests (FEATURE_FLAGGING_AND_EXPERIMENTATION, apache-mod-8.0):

13 tests, 0 failures, 0 errors, 1 xfail
12 passed, 1 skipped (exposure cache cross-request dedup — PHP shared-nothing arch)

Cross-language dogfood parity (6 flags, 6 SDKs):

ALL MATCH: boolean, float, integer, JSON, string flags across Go, Java, Node, PHP, Python, Ruby

Companion PRs

LOC (excluding Cargo.lock + JSON fixtures)

Category Lines
Rust FFI 323
C extension 126
PHP source 829
PHP tests 559
Build/wiring 8
Total 1,845

@datadog-datadog-prod-us1
Copy link

datadog-datadog-prod-us1 bot commented Feb 7, 2026

⚠️ Tests

Fix all issues with Cursor

⚠️ Warnings

🧪 25 Tests failed

    testSearchPhpBinaries from integration.DDTrace\Tests\Integration\PHPInstallerTest (Fix with Cursor)

    testSimplePushAndProcess from laravel-58-test.DDTrace\Tests\Integrations\Laravel\V5_8\QueueTest (Fix with Cursor)

testSimplePushAndProcess from laravel-8x-test.DDTrace\Tests\Integrations\Laravel\V8_x\QueueTest (Datadog) (Fix with Cursor)
DDTrace\Tests\Integrations\Laravel\V8_x\QueueTest::testSimplePushAndProcess
Test code or tested code printed unexpected output: spanLinksTraceId: 698d2600000000009224db5bc9ac484d
tid: 698d260000000000
hexProcessTraceId: 9224db5bc9ac484d
hexProcessSpanId: e6ffdf430bf92560
processTraceId: 10530783015971080269
processSpanId: 16645268226841322848

phpvfscomposer://tests/vendor/phpunit/phpunit/phpunit:106
View all

ℹ️ Info

❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1ec323b | Docs | Datadog PR Page | Was this helpful? Give us feedback!

@leoromanovsky leoromanovsky force-pushed the feature/ffe-feature-flagging branch from 18f7f00 to 508a8c8 Compare February 7, 2026 03:32
@codecov-commenter
Copy link

codecov-commenter commented Feb 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.11%. Comparing base (b9986f9) to head (1ec323b).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3630      +/-   ##
==========================================
- Coverage   62.21%   62.11%   -0.11%     
==========================================
  Files         141      141              
  Lines       13387    13387              
  Branches     1753     1753              
==========================================
- Hits         8329     8315      -14     
- Misses       4260     4273      +13     
- Partials      798      799       +1     

see 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b9986f9...1ec323b. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pr-commenter
Copy link

pr-commenter bot commented Feb 7, 2026

Benchmarks [ tracer ]

Benchmark execution time: 2026-02-10 03:08:01

Comparing candidate commit a24b1ae in PR branch feature/ffe-feature-flagging with baseline commit e88099a in branch master.

Found 3 performance improvements and 40 performance regressions! Performance is the same for 150 metrics, 1 unstable metrics.

scenario:ComposerTelemetryBench/benchTelemetryParsing

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]
  • 🟩 execution_time [-2.106µs; -1.094µs] or [-17.263%; -8.967%]

scenario:ContextPropagationBench/benchExtractHeaders128Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:ContextPropagationBench/benchExtractHeaders128Bit-opcache

  • 🟩 execution_time [-887.327ns; -840.673ns] or [-51.330%; -48.631%]

scenario:ContextPropagationBench/benchExtractHeaders64Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:ContextPropagationBench/benchExtractTraceContext128Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:ContextPropagationBench/benchExtractTraceContext128Bit-opcache

  • 🟩 execution_time [-963.450ns; -830.550ns] or [-35.047%; -30.213%]

scenario:ContextPropagationBench/benchExtractTraceContext64Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:ContextPropagationBench/benchInject128Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:ContextPropagationBench/benchInject64Bit

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:EmptyFileBench/benchEmptyFileBaseline

  • 🟥 mem_peak [+146.912KB; +146.912KB] or [+2.838%; +2.838%]

scenario:EmptyFileBench/benchEmptyFileDdprof

  • 🟥 mem_peak [+147.683KB; +149.745KB] or [+2.849%; +2.889%]

scenario:EmptyFileBench/benchEmptyFileOverhead

  • 🟥 mem_peak [+146.912KB; +146.912KB] or [+2.838%; +2.838%]

scenario:HookBench/benchHookOverheadInstallHookOnFunction

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:HookBench/benchHookOverheadInstallHookOnMethod

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:HookBench/benchHookOverheadTraceFunction

  • 🟥 mem_peak [+146.992KB; +146.992KB] or [+3.308%; +3.308%]

scenario:HookBench/benchHookOverheadTraceMethod

  • 🟥 mem_peak [+146.990KB; +146.993KB] or [+3.261%; +3.261%]

scenario:HookBench/benchWithoutHook

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:LaravelBench/benchLaravelBaseline

  • 🟥 mem_peak [+146.944KB; +146.944KB] or [+2.838%; +2.838%]

scenario:LaravelBench/benchLaravelDdprof

  • 🟥 mem_peak [+146.220KB; +148.324KB] or [+2.820%; +2.861%]

scenario:LaravelBench/benchLaravelOverhead

  • 🟥 mem_peak [+146.944KB; +146.944KB] or [+2.838%; +2.838%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟥 mem_peak [+146.992KB; +146.992KB] or [+3.455%; +3.455%]

scenario:PDOBench/benchPDOBaseline

  • 🟥 mem_peak [+146.992KB; +146.992KB] or [+3.643%; +3.643%]

scenario:PHPRedisBench/benchRedisBaseline

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:SamplingRuleMatchingBench/benchGlobMatching1

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:SamplingRuleMatchingBench/benchGlobMatching2

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:SamplingRuleMatchingBench/benchGlobMatching3

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:SamplingRuleMatchingBench/benchGlobMatching4

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:SamplingRuleMatchingBench/benchRegexMatching1

  • 🟥 execution_time [+78.275ns; +135.525ns] or [+6.750%; +11.687%]
  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:SamplingRuleMatchingBench/benchRegexMatching2

  • 🟥 execution_time [+119.760ns; +178.040ns] or [+10.483%; +15.585%]
  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:SamplingRuleMatchingBench/benchRegexMatching3

  • 🟥 execution_time [+47.380ns; +121.420ns] or [+3.974%; +10.184%]
  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:SamplingRuleMatchingBench/benchRegexMatching4

  • 🟥 execution_time [+100.317ns; +145.283ns] or [+8.673%; +12.560%]
  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.675%; +3.675%]

scenario:SpanBench/benchDatadogAPI

  • 🟥 mem_peak [+146.904KB; +146.904KB] or [+3.676%; +3.676%]

scenario:SymfonyBench/benchSymfonyBaseline

  • 🟥 mem_peak [+146.944KB; +146.944KB] or [+2.838%; +2.838%]

scenario:SymfonyBench/benchSymfonyDdprof

  • 🟥 mem_peak [+144.917KB; +147.332KB] or [+2.795%; +2.841%]

scenario:SymfonyBench/benchSymfonyOverhead

  • 🟥 mem_peak [+146.944KB; +146.944KB] or [+2.838%; +2.838%]

scenario:TraceAnnotationsBench/benchTraceAnnotationOverhead

  • 🟥 mem_peak [+146.990KB; +146.995KB] or [+3.255%; +3.256%]

scenario:TraceFlushBench/benchFlushTrace

  • 🟥 mem_peak [+146.992KB; +146.992KB] or [+3.604%; +3.604%]

scenario:TraceSerializationBench/benchSerializeTrace

  • 🟥 mem_peak [+146.992KB; +146.992KB] or [+3.522%; +3.522%]

gh-worker-dd-mergequeue-cf854d bot pushed a commit to DataDog/libdatadog that referenced this pull request Feb 9, 2026
## Motivation

Add Feature Flagging and Experimentation (FFE) support to the remote config infrastructure, enabling tracers to subscribe to FFE_FLAGS configurations via the sidecar.

WIP: php tracer changes (DataDog/dd-trace-php#3630)

## Changes

- Add `FfeFlags` variant to `RemoteConfigProduct` enum
- Add `"FFE_FLAGS"` string mapping in Display and FromStr
- Add `FfeFlagConfigurationRules = 46` to `RemoteConfigCapabilities`
- Add `FfeFlags(Vec<u8>)` variant to `RemoteConfigData` to preserve raw config bytes

## Decisions

- Raw bytes are preserved (not parsed) in `FfeFlags(Vec<u8>)` since each tracer handles evaluation with the `datadog-ffe` crate directly
- Capability bit 46 matches the server-side FFE capability definition

Co-authored-by: leo.romanovsky <leo.romanovsky@datadoghq.com>
@leoromanovsky leoromanovsky force-pushed the feature/ffe-feature-flagging branch from 1990188 to 867337e Compare February 9, 2026 19:24
@leoromanovsky leoromanovsky changed the title Feature/ffe feature flagging port ffe feature flagging sdk to php Feb 9, 2026
@leoromanovsky leoromanovsky force-pushed the feature/ffe-feature-flagging branch from a24b1ae to 24e4304 Compare February 11, 2026 13:41
Add FFE support to dd-trace-php. Flag evaluation is delegated to
libdatadog's datadog-ffe Rust crate via FFI. PHP handles orchestration:
config lifecycle, exposure dedup, and HTTP transport.

Rust FFI layer (components-rs/ffe.rs):
- C-callable bridge to datadog-ffe::rules_based
- Global config store behind Mutex<FfeState>
- Structured attribute passing (no JSON on hot path)

C extension (ext/ddtrace.c):
- ffe_evaluate, ffe_has_config, ffe_config_changed, ffe_load_config
- Marshals PHP arrays to FfeAttribute structs

Remote Config (components-rs/remote_config.rs):
- Register FfeFlags product + FfeFlagConfigurationRules capability
- Handle add/remove of FFE configs via sidecar

PHP Provider (src/DDTrace/FeatureFlags/Provider.php):
- Singleton checking RC config state
- Calls native evaluate, parses JSON results
- Reports exposures via LRU-deduplicated writer

Exposure pipeline:
- LRU cache (65K entries) with length-prefixed composite keys
- Batched writer to /evp_proxy/v2/api/v2/exposures (1000 cap)
- Auto-flush via register_shutdown_function

OpenFeature adapter (src/DDTrace/OpenFeature/DataDogProvider.php):
- Implements AbstractProvider for open-feature/sdk

Build:
- Add datadog-ffe to RUST_FILES in Makefile for PECL packaging
- Cargo.lock: minimal additions only (73 new crates, no gratuitous bumps)
- Bump libdatadog to ed316b638 (FFE RC support, libdatadog#1532)

Tests:
- LRU cache unit tests (11 tests)
- Exposure cache unit tests (12 tests)
- 220 evaluation correctness tests from JSON fixtures

Config: DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED (default: false)
@leoromanovsky leoromanovsky force-pushed the feature/ffe-feature-flagging branch from 2a5d688 to 1ec323b Compare February 12, 2026 00:52
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.

2 participants