-
Notifications
You must be signed in to change notification settings - Fork 167
port ffe feature flagging sdk to php #3630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
18f7f00 to
508a8c8
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ 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.
🚀 New features to boost your workflow:
|
ff84598 to
9f36eb7
Compare
7c43198 to
7bbed08
Compare
Benchmarks [ tracer ]Benchmark execution time: 2026-02-10 03:08:01 Comparing candidate commit a24b1ae in PR branch Found 3 performance improvements and 40 performance regressions! Performance is the same for 150 metrics, 1 unstable metrics. scenario:ComposerTelemetryBench/benchTelemetryParsing
scenario:ContextPropagationBench/benchExtractHeaders128Bit
scenario:ContextPropagationBench/benchExtractHeaders128Bit-opcache
scenario:ContextPropagationBench/benchExtractHeaders64Bit
scenario:ContextPropagationBench/benchExtractTraceContext128Bit
scenario:ContextPropagationBench/benchExtractTraceContext128Bit-opcache
scenario:ContextPropagationBench/benchExtractTraceContext64Bit
scenario:ContextPropagationBench/benchInject128Bit
scenario:ContextPropagationBench/benchInject64Bit
scenario:EmptyFileBench/benchEmptyFileBaseline
scenario:EmptyFileBench/benchEmptyFileDdprof
scenario:EmptyFileBench/benchEmptyFileOverhead
scenario:HookBench/benchHookOverheadInstallHookOnFunction
scenario:HookBench/benchHookOverheadInstallHookOnMethod
scenario:HookBench/benchHookOverheadTraceFunction
scenario:HookBench/benchHookOverheadTraceMethod
scenario:HookBench/benchWithoutHook
scenario:LaravelBench/benchLaravelBaseline
scenario:LaravelBench/benchLaravelDdprof
scenario:LaravelBench/benchLaravelOverhead
scenario:MessagePackSerializationBench/benchMessagePackSerialization
scenario:PDOBench/benchPDOBaseline
scenario:PHPRedisBench/benchRedisBaseline
scenario:SamplingRuleMatchingBench/benchGlobMatching1
scenario:SamplingRuleMatchingBench/benchGlobMatching2
scenario:SamplingRuleMatchingBench/benchGlobMatching3
scenario:SamplingRuleMatchingBench/benchGlobMatching4
scenario:SamplingRuleMatchingBench/benchRegexMatching1
scenario:SamplingRuleMatchingBench/benchRegexMatching2
scenario:SamplingRuleMatchingBench/benchRegexMatching3
scenario:SamplingRuleMatchingBench/benchRegexMatching4
scenario:SpanBench/benchDatadogAPI
scenario:SymfonyBench/benchSymfonyBaseline
scenario:SymfonyBench/benchSymfonyDdprof
scenario:SymfonyBench/benchSymfonyOverhead
scenario:TraceAnnotationsBench/benchTraceAnnotationOverhead
scenario:TraceFlushBench/benchFlushTrace
scenario:TraceSerializationBench/benchSerializeTrace
|
## 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>
1990188 to
867337e
Compare
Implement UFCv1 evaluation engine, exposure event reporting, and Remote Config integration for the FFE product. - Add DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED config - Add FFE RC product subscription and config delivery via sidecar - Add PHP evaluator with full condition/sharding support (217/217 tests pass) - Add exposure event writer with LRU deduplication cache - Add Provider API with singleton pattern - Add datadog-ffe native crate dependency and C FFI bindings - Wire get_ffe_config/ffe_config_changed internal functions
- Fix EvaluationContext construction (no Deserialize/Default) - Fix AssignmentValue::Json struct pattern - Add parse_evaluation_context helper - Resolve workspace inheritance in datadog-ffe Cargo.toml - Remove unused imports
Remove stale git references to libdatadog v25.0.0 and use local submodule path dependencies for datadog-ffe and datadog-ffe-ffi.
Add DDTrace\OpenFeature\DataDogProvider that implements the OpenFeature PHP SDK's AbstractProvider interface, wrapping the internal FFE evaluation engine.
Base FFE changes on the original pinned commit (534d009c) instead of latest main, to avoid pulling in unrelated debugger/telemetry changes that break existing tests.
Use master Cargo.lock as base and only add new workspace crates (datadog-ffe and dependencies) to avoid bumping existing crates to versions requiring edition 2024 (rmp, rmpv, time).
…DER_ENABLED The INI config defaults to false and shadows the env var in some SAPIs. Check getenv() first for reliability across all SAPIs.
Replace the pure PHP UFC evaluator with the native datadog-ffe engine from libdatadog. The evaluation path is now: RC config bytes → ddog_ffe_load_config() → native Configuration evaluate() → ddog_ffe_evaluate() → native get_assignment() - Remove Evaluator.php (482 lines of PHP reimplementation) - Remove EvaluatorTest.php - Add ffe_load_config, ffe_has_config, ffe_evaluate internal functions - Update Provider to call native engine via dd_trace_internal_fn - Add C header declarations for all ddog_ffe_* functions
Add ddog_ffe_load_config() FFI function to load UFC JSON config directly into the Rust FFE engine without Remote Config, enabling test-time config injection. Add 220 parametric evaluation tests driven by shared cross-tracer JSON fixtures (merged from dd-trace-py and dd-trace-java configs) and 8 LRU cache unit tests covering eviction, promotion, and edge cases.
- Fix exposure dedup cache to match Java canonical impl: create ExposureCache class with length-prefixed composite keys (no collision), add() returns bool like Java's LRUExposureCache.add(), always promotes LRU position even for duplicates - Add 12 ExposureCache tests matching Java's LRUExposureCacheTest - Add LRUCache.put() (returns old value) and size() methods with tests - Fix Provider to handle config removal via ffe_config_changed(), clear exposure cache when RC removes config - Auto-flush exposures on request shutdown via register_shutdown_function - Reduce ExposureWriter curl timeout to 500ms/100ms (was 5s/2s) - Combine FFE_CONFIG + FFE_CONFIG_CHANGED into single FfeState struct behind one Mutex for atomic updates, add RwLock justification comment - Remove unused datadog-ffe-ffi dependency from Cargo.toml - Change LRUCache eviction from while to if (only one entry added) - Clarify continue-in-switch comment in ddtrace.c ffe_evaluate handler
- Cap ExposureWriter buffer at 1000 events (matches Ruby/Python) - Return variant key and allocation_key from Provider::evaluate() so callers can distinguish the variant identifier from the value - Regenerate Cargo.lock after rebase on master
a24b1ae to
24e4304
Compare
The bootstrap stubs dd_trace_internal_fn(), so function_exists()
always returned true even without the extension. Use
extension_loaded('ddtrace') instead to correctly skip when the
real extension isn't available.
Motivation
Add Feature Flagging and Experimentation (FFE) support to dd-trace-php. PHP applications can now evaluate feature flags delivered via Remote Config using the same
datadog-ffeRust engine used by Ruby and Python.Changes
components-rs/ffe.rs): C-callable bridge todatadog-ffe::rules_based— config store, evaluate, result accessorsext/ddtrace.c):ffe_evaluate,ffe_has_config,ffe_config_changed,ffe_load_configinternal functions that marshal PHP arrays toFfeAttributestructscomponents-rs/remote_config.rs): RegisterFfeFlagsproduct +FfeFlagConfigurationRulescapability; handle add/remove of FFE configssrc/DDTrace/FeatureFlags/Provider.php): Singleton that checks RC config state, calls native evaluate, parses JSON results, reports exposures/evp_proxy/v2/api/v2/exposures(1000 event buffer cap)src/DDTrace/OpenFeature/DataDogProvider.php): ImplementsAbstractProviderfor theopen-feature/sdkcomposer packageDD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLEDgating via X-macro inext/configuration.hDecisions
Evaluation in Rust, not PHP. All flag evaluation (UFC parsing, targeting rules, shard hashing, allocation resolution) happens in
libdatadog'sdatadog-ffecrate 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 alazy_staticglobal with aMutex. PHP is single-threaded per process, soRwLockwould be unnecessary complexity. Thechangedflag andconfigare 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
FfeAttributestructs (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. This avoids collision from delimiters appearing in flag/subject strings — a subtle issue the simpler|separator has.ExposureWriter caps at 1000 events per request. Matches Ruby and Python. Events beyond the cap are silently dropped. Flush happens via
register_shutdown_functionat request end.Companion PRs