Skip to content

Add FFE exposure emission#3910

Draft
leoromanovsky wants to merge 3 commits into
leo.romanovsky/milestone-1-runtime-evaluationfrom
leo.romanovsky/m2-ffe-exposures
Draft

Add FFE exposure emission#3910
leoromanovsky wants to merge 3 commits into
leo.romanovsky/milestone-1-runtime-evaluationfrom
leo.romanovsky/m2-ffe-exposures

Conversation

@leoromanovsky
Copy link
Copy Markdown

@leoromanovsky leoromanovsky commented May 22, 2026

Motivation

Milestone 2 needs PHP feature flag evaluations to emit server-side EVP exposures when the real evaluator marks an evaluation with doLog=true. This PR also lands the M2/M3 shared evaluation-completed seam (internal envelope and hook interface invoked by Client::evaluate()) so PHP 7 Datadog API and PHP 8 OpenFeature bridge share one path.

(Note: the shared seam was previously split out as a separate base PR #3909, which has been folded into this PR for review.)

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

Changes

Shared base (formerly #3909):

  • Add an internal EvaluationCompleted envelope and EvaluationCompletedHook interface invoked by Client::evaluate(). Hook failures never alter returned values.
  • Add NoopEvaluationCompletedHook, CompositeEvaluationCompletedHook, and DefaultEvaluationCompletedHook::create() factory.
  • Wire Client::createWithDependencies() to accept an optional hook (defaults to noop). Production Client::create() uses the default composite (exposure here, metric added in PR Add FFE evaluation metrics #3911).

M2 exposure emission:

  • Add an internal exposure writer with bounded batching, LRU deduplication, drop accounting, and shutdown flushing.
  • Emit server-side exposure payloads with service/env/version context, allocation key, flag key, variant key, subject id, attributes, and timestamp.
  • Add an Agent EVP transport for /evp_proxy/v2/api/v2/exposures using the event-platform-intake EVP subdomain.
  • Wire production Client::create() and the default OpenFeature provider to use the exposure hook through DefaultEvaluationCompletedHook.
  • Add unit coverage for doLog, payload shape, empty targeting keys, dedup/cache behavior, buffer overflow/drop behavior, transport failures, and Agent EVP request shape.

Decisions

  • Keep evaluator/exposure/envelope internals private; no public PHP APIs exposed.
  • Missing or empty targeting keys emit subject.id="", matching the M1 empty targeting key handling.
  • Deduplicate by (flag key, subject id) and cache (allocation key, variant) with default capacity 65536.
  • Exposure transport is best-effort. It does not alter returned feature flag values or details. Failed flushes are counted as dropped rather than retried synchronously.
  • The HTTP Agent send uses a raw socket POST instead of PHP HTTP stream wrappers so internal exposure delivery does not go through the traced HTTP client path.

Validation:

  • php -l src/api/FeatureFlags/Internal/EvaluationCompleted.php && php -l src/api/FeatureFlags/Internal/EvaluationCompletedHook.php && php -l src/api/FeatureFlags/Internal/NoopEvaluationCompletedHook.php
  • php -l src/api/FeatureFlags/Internal/Exposure/ExposureTransport.php && php -l src/api/FeatureFlags/Internal/Exposure/AgentExposureTransport.php && php -l src/api/FeatureFlags/Internal/Exposure/ExposureWriter.php && php -l src/api/FeatureFlags/Internal/Exposure/ExposureHook.php
  • php -l src/api/FeatureFlags/Client.php && php -l src/DDTrace/OpenFeature/DataDogProvider.php && php -l tests/api/Unit/FeatureFlags/ExposureWriterTest.php
  • php vendor/bin/phpunit --config phpunit.xml tests/api/Unit/FeatureFlags
  • php vendor/bin/phpunit --config phpunit.xml tests/OpenFeature/DataDogProviderTest.php
  • make test_featureflags
  • git diff --check
  • Live dogfooding validation against ffe-dogfooding-string-flag (experiment allocation, doLog=true): EVP exposures OK: php7=4 php8-openfeature=4 and OTLP metrics OK: php7=4 php8-openfeature=4 via FLAG_KEY=ffe-dogfooding-string-flag EXPECT_EVP=1 scripts/validate-local-php-telemetry.sh.

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

datadog-datadog-prod-us1 Bot commented May 22, 2026

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 60.71% (+0.01%)

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

@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented May 22, 2026

Benchmarks [ tracer ]

Benchmark execution time: 2026-05-23 03:40:57

Comparing candidate commit 92ef9a3 in PR branch leo.romanovsky/m2-ffe-exposures with baseline commit 16d7817 in branch leo.romanovsky/milestone-1-runtime-evaluation.

Found 0 performance improvements and 5 performance regressions! Performance is the same for 188 metrics, 1 unstable metrics.

scenario:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟥 execution_time [+2.898µs; +4.682µs] or [+2.896%; +4.679%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization-opcache

  • 🟥 execution_time [+2.301µs; +3.219µs] or [+2.319%; +3.244%]

scenario:SamplingRuleMatchingBench/benchRegexMatching2

  • 🟥 execution_time [+51.147ns; +157.453ns] or [+3.503%; +10.784%]

scenario:SamplingRuleMatchingBench/benchRegexMatching4

  • 🟥 execution_time [+36.071ns; +108.929ns] or [+2.460%; +7.429%]

scenario:TraceSerializationBench/benchSerializeTrace

  • 🟥 execution_time [+11.105µs; +30.095µs] or [+2.679%; +7.260%]

The canonical fixture PHPT explicitly enumerates the FFE classes it
requires before instantiating the Datadog FeatureFlags client. The
shared evaluation-completed envelope/hook added on this branch made
Client::createWithDependencies() reference NoopEvaluationCompletedHook,
EvaluationCompletedHook, and EvaluationCompleted, but the test helper
had not been updated, so the packaged/extension PHPT failed with
"Class DDTrace\FeatureFlags\Internal\NoopEvaluationCompletedHook not
found" before any fixture case ran.

Add the three new files to require_feature_flag_api so the PHPT
matches the runtime class graph used by Client::evaluate().
@leoromanovsky leoromanovsky changed the base branch from leo.romanovsky/m2-m3-evaluation-completed-base to leo.romanovsky/milestone-1-runtime-evaluation May 23, 2026 03:14
@leoromanovsky leoromanovsky marked this pull request as ready for review May 23, 2026 16:01
@leoromanovsky leoromanovsky requested a review from a team as a code owner May 23, 2026 16:01
@leoromanovsky leoromanovsky requested review from greghuels and sameerank and removed request for a team May 23, 2026 16:01
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92ef9a34dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +59 to +61
if (count($this->buffer) >= $this->bufferLimit) {
$this->dropped++;
return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Flush full exposure buffers instead of dropping

In long-running PHP runtimes such as Swoole/RoadRunner/CLI workers, the default hook only registers a shutdown flush, so this buffer can stay resident across many requests. Once 1,000 unique exposures accumulate, every later unique exposure is dropped here until the process exits, and the buffered events may not reach the Agent for hours or days; flushing when the buffer reaches the limit or from a request/periodic lifecycle hook would preserve exposure delivery for these runtimes.

Useful? React with 👍 / 👎.

@leoromanovsky leoromanovsky marked this pull request as draft May 23, 2026 16:10
leoromanovsky added a commit that referenced this pull request May 23, 2026
Adds Mermaid sources and rendered PNGs for the hook (this) PR plus a
README documenting the regeneration workflow.

- `docs/php-ffe-stack/stack-pr3909.mmd` + `.png` — 4-PR stack with this
  PR highlighted (M1 done; EVP and metrics as siblings to come).
- `docs/php-ffe-stack/system-pr3909.mmd` + `.png` — target system
  architecture; this PR contributes the EvaluationCompletedHook +
  OpenFeature provider hook surface. All downstream nodes (writers,
  sidecar FFI, sidecar process, backends) marked future.
- `docs/php-ffe-stack/README.md` — npx invocation for regenerating
  PNGs locally; PR-by-PR diagram table; architectural rule note.

The architectural rule encoded in the system diagram (all I/O via the
libdatadog sidecar) is the same rule Bob applied to PR #3910. See
DataDog/libdatadog#2026 for the sidecar-side support.
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