Skip to content

feat(openfeature): emit server-side EVP flagevaluation#8902

Draft
leoromanovsky wants to merge 29 commits into
masterfrom
leo.romanovsky/ffl-2446-evp-flagevaluation-nodejs
Draft

feat(openfeature): emit server-side EVP flagevaluation#8902
leoromanovsky wants to merge 29 commits into
masterfrom
leo.romanovsky/ffl-2446-evp-flagevaluation-nodejs

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Motivation

Customers and APM need the same server-side feature-flag evaluation signal across SDKs so rollout behavior can be correlated with application behavior without SDK-specific blind spots. This Node.js contribution adds bounded EVP flagevaluation delivery while preserving the existing OTel feature_flag.evaluations metric path, so adoption can be judged on smoothness and time-to-land with APM approval.

Changes

  • Adds the Node.js EVP flagevaluation writer path behind DD_FLAGGING_EVALUATION_COUNTS_ENABLED.
  • Keeps the existing OTel flag-eval-metrics-hook.js path unchanged.
  • Renames the EVP capture hook to FlagEvalEVPHook / flag_eval_evp_hook.js so it is distinct from the OTel metrics hook.
  • Uses the singular Agent proxy route /evp_proxy/v2/api/v2/flagevaluation.
  • Keeps OpenFeature reason out of the enqueue path, event model, aggregation keys, payload shape, tests, and TypeScript declarations.
  • Captures OpenFeature error details as schema-visible error.message.
  • Flattens and prunes evaluation context before buffering, with deterministic ordering and 256 field / 256 character bounds.
  • Aggregates by schema-visible dimensions only: flag key, variant key, allocation key, runtime-default state, error message, targeting key when available, and pruned context.
  • Uses bounded two-tier aggregation: full buckets first, degraded buckets without targeting key/context after cap pressure, then counted drops after degraded overflow.
  • Splits aggregate flushes by encoded uncompressed JSON bytes, including the {context, flagEvaluations} wrapper, before posting to EVP.
  • Degrades a single oversized full-fidelity row by omitting targeting_key and context; drops only if the degraded row still cannot fit the event or payload limit.
  • Wires the OpenFeature sirun benchmark into the canonical benchmark matrix without changing the existing OTel metric path.
  • Companion OpenFeature node-server metadata PR: Align Node.js flagevaluation EVP metadata openfeature-js-client#317

Decisions

  • OpenFeature reason is intentionally not part of EVP payloads or aggregate keys because the worker schema does not accept it.
  • Degraded buckets retain only schema-visible serialized dimensions; they do not claim OTel-cardinality parity when visible EVP fields differ from OTel metric attributes.
  • The logging hook performs bounded capture/enqueue only; aggregation and payload splitting run on the scheduled writer drain off the user evaluation call stack.
  • Queue overflow, degraded overflow, and oversized degraded rows are best-effort drops with counters/logging, not blocking behavior on the user evaluation path.
  • The split decision uses encoded uncompressed JSON bytes. Compression, if any, is a transport detail after the 5 MiB EVP request-size decision.
  • The dogfooding service identity must be ffe-dogfooding-nodejs; ffe-dogfooding-node did not receive the required RC configuration.
  • Hook names follow the cross-SDK architecture split: metrics for the existing OTel counter path, EVP for the flagevaluation path.
flowchart TD
  A[drained aggregated rows] --> B[serialize candidate batch as JSON]
  B --> C{batch <= 5 MiB?}
  C -- yes --> D[post asynchronously through EVP proxy]
  C -- no --> E{current row fits degraded?}
  E -- yes --> F[omit targeting_key and context]
  F --> B
  E -- no --> G[drop, log, count]
Loading

Validation Evidence

Dogfooding App

  • Dogfooding service-name fix: https://github.com/DataDog/ffe-dogfooding/pull/87
  • ffe-dogfooding app-nodejs was run with local dd-trace-js, @datadog/flagging-core, and @datadog/openfeature-node-server artifacts and reached PROVIDER_READY.
  • Evaluated ffe-dogfooding-string-flag five times for each public-safe targeting key, keeping the evaluation context stable per key so batching/aggregation is observable:
    • nodejs-batch-evp-agent-20260623T024835Z-alpha
    • nodejs-batch-evp-agent-20260623T024835Z-bravo
    • nodejs-batch-evp-agent-20260623T024835Z-charlie
  • App-side result: all 15 evaluations returned variant_2 with reason=TARGETING_MATCH.

System Tests

Staging End-To-End

  • Dogfooding ran without the local mock-intake EVP tee/proxy, so the Agent sent EVP traffic through the normal backend path.
  • Retriever staging query used eventplatform.system.track(TRACK => 'flagevaluation') against us1.staging.dog for the exact targeting keys above.
  • Backend rows observed:
    • nodejs-batch-evp-agent-20260623T024835Z-alpha: evaluation_count=5, flag.key=ffe-dogfooding-string-flag, variant.key=variant_2, allocation.key=allocation-override-392dd7c149f8
    • nodejs-batch-evp-agent-20260623T024835Z-bravo: evaluation_count=5, flag.key=ffe-dogfooding-string-flag, variant.key=variant_2, allocation.key=allocation-override-392dd7c149f8
    • nodejs-batch-evp-agent-20260623T024835Z-charlie: evaluation_count=5, flag.key=ffe-dogfooding-string-flag, variant.key=variant_2, allocation.key=allocation-override-392dd7c149f8

…egation

- Add FLAGEVALUATIONS_ENDPOINT constant to constants/constants.js
- Implement FlagEvaluationsWriter extending BaseFFEWriter with:
  - Two-tier aggregation (full → degraded → drop-counted)
  - Comparable canonical-context key (sorted, type-tagged, length-delimited; no hash)
  - Caps: globalCap=131072 / perFlagCap=10000 / degradedCap=32768
  - Context pruning: 256 fields / 256 chars before keying
  - Flush interval 10000ms; endpoint /evp_proxy/v2/api/v2/flagevaluations
  - runtime_default_used from absent variant; omitempty optional fields per tier
- Add writer unit spec (21 tests, all passing)
… wiring

- Add FlagEvalEVPHook: cheap scalar extraction in finally() + non-blocking enqueue
  (reviewer concern #7 — Finally covers error/default; no inline aggregation)
- Wire FlagEvaluationsWriter + FlagEvalEVPHook in flagging_provider.js behind
  DD_FLAGGING_EVALUATION_COUNTS_ENABLED killswitch (default on)
- OTel EvalMetricsHook remains always registered (PRES-01 non-regression)
- Destroy FlagEvaluationsWriter in onClose() alongside SpanEnrichmentHook
- Update flagging_provider.spec.js to stub new writer/hook, cover killswitch,
  update hook count assertions (3 hooks when all enabled)
- flag_evaluations.js: remove extra spaces before inline comment (no-multi-spaces);
  change interval literal 10000 → 10_000 (numeric-separators-style)
- flagging_provider.js: route DD_FLAGGING_EVALUATION_COUNTS_ENABLED through the
  config system instead of reading process.env directly (eslint-process-env rule);
  flip negated condition to positive (no-negated-condition rule)
  — register env var in supported-configurations.json as
  experimental.flaggingProvider.evaluationCountsEnabled (default true)
  and update generated-config-types.d.ts accordingly
- Tests updated to set mockConfig.experimental.flaggingProvider.evaluationCountsEnabled
  instead of process.env for killswitch coverage; behaviour unchanged
@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Jun 12, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 3 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-js | benchmark: [24, 3]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-js | benchmark: [26, 3]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-js | benchmark: [20, 3]   View in Datadog   GitLab

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 93.51%
Overall Coverage: 88.02%

Useful? React with 👍 / 👎

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

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.52941% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.72%. Comparing base (3ca1a63) to head (b926681).
⚠️ Report is 54 commits behind head on master.

Files with missing lines Patch % Lines
...-trace/src/openfeature/writers/flag_evaluations.js 98.34% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8902      +/-   ##
==========================================
+ Coverage   92.49%   93.72%   +1.23%     
==========================================
  Files         879      892      +13     
  Lines       50491    51188     +697     
  Branches     9945    11911    +1966     
==========================================
+ Hits        46701    47977    +1276     
+ Misses       3790     3211     -579     
Flag Coverage Δ
aiguard 34.89% <100.00%> (?)
aiguard-integration 41.80% <100.00%> (?)
aiguard-integration-active ?
aiguard-integration-latest ?
aiguard-integration-maintenance ?
aiguard-macos ?
aiguard-ubuntu ?
aiguard-windows ?
apm-bucket-0 34.86% <100.00%> (?)
apm-bucket-1 40.35% <100.00%> (?)
apm-bucket-2 37.35% <100.00%> (?)
apm-capabilities-tracing 47.95% <11.79%> (?)
apm-capabilities-tracing-macos ?
apm-capabilities-tracing-ubuntu-active ?
apm-capabilities-tracing-ubuntu-latest ?
apm-capabilities-tracing-ubuntu-maintenance ?
apm-capabilities-tracing-windows ?
apm-integrations-aerospike 33.14% <100.00%> (?)
apm-integrations-aerospike-18-gte.5.2.0 ?
apm-integrations-aerospike-20-gte.5.5.0 ?
apm-integrations-aerospike-22-gte.5.12.1 ?
apm-integrations-aerospike-22-gte.6.0.0 ?
apm-integrations-aerospike-eol- ?
apm-integrations-confluentinc-kafka-javascript 40.04% <100.00%> (?)
apm-integrations-confluentinc-kafka-javascript-18 ?
apm-integrations-confluentinc-kafka-javascript-20 ?
apm-integrations-confluentinc-kafka-javascript-22 ?
apm-integrations-confluentinc-kafka-javascript-24 ?
apm-integrations-couchbase 33.26% <100.00%> (?)
apm-integrations-couchbase-18 ?
apm-integrations-couchbase-eol ?
apm-integrations-dns ?
apm-integrations-elasticsearch ?
apm-integrations-http 42.06% <100.00%> (?)
apm-integrations-http-maintenance ?
apm-integrations-http-oldest ?
apm-integrations-http2 ?
apm-integrations-kafkajs 40.28% <100.00%> (?)
apm-integrations-kafkajs-latest ?
apm-integrations-kafkajs-oldest ?
apm-integrations-net ?
apm-integrations-next 29.51% <100.00%> (?)
apm-integrations-next-11.1.4 ?
apm-integrations-next-12.3.7 ?
apm-integrations-next-13.0.0 ?
apm-integrations-next-13.2.0 ?
apm-integrations-next-13.5.11 ?
apm-integrations-next-14.0.0 ?
apm-integrations-next-14.2.35 ?
apm-integrations-next-14.2.6 ?
apm-integrations-next-14.2.7 ?
apm-integrations-next-15.0.0 ?
apm-integrations-next-15.4.0 ?
apm-integrations-next-latest ?
apm-integrations-oracledb ?
apm-integrations-prisma 35.05% <100.00%> (?)
apm-integrations-prisma-latest-all ?
apm-integrations-restify ?
apm-integrations-sharedb ?
apm-integrations-tedious 33.91% <100.00%> (+0.46%) ⬆️
appsec 57.40% <100.00%> (?)
appsec-express ?
appsec-express_fastify_graphql 53.75% <100.00%> (?)
appsec-fastify ?
appsec-graphql ?
appsec-integration 36.26% <100.00%> (?)
appsec-integration-active ?
appsec-integration-latest ?
appsec-integration-maintenance ?
appsec-integration-oldest ?
appsec-kafka ?
appsec-kafka_ldapjs_lodash 43.69% <100.00%> (?)
appsec-ldapjs ?
appsec-lodash ?
appsec-macos ?
appsec-mongodb-core ?
appsec-mongodb-core_mongoose_mysql 48.82% <100.00%> (?)
appsec-mongoose ?
appsec-mysql ?
appsec-next 28.02% <100.00%> (?)
appsec-next-latest-11.1.4 ?
appsec-next-latest-12.3.7 ?
appsec-next-latest-13.0.0 ?
appsec-next-latest-13.2.0 ?
appsec-next-latest-13.5.11 ?
appsec-next-latest-14.0.0 ?
appsec-next-latest-14.2.35 ?
appsec-next-latest-14.2.6 ?
appsec-next-latest-14.2.7 ?
appsec-next-latest-latest ?
appsec-next-oldest-11.1.4 ?
appsec-next-oldest-12.3.7 ?
appsec-next-oldest-13.0.0 ?
appsec-next-oldest-13.2.0 ?
appsec-next-oldest-13.5.11 ?
appsec-next-oldest-14.0.0 ?
appsec-next-oldest-14.2.6 ?
appsec-next-oldest-14.2.7 ?
appsec-next-oldest-latest ?
appsec-node-serialize ?
appsec-node-serialize_passport_postgres 47.89% <100.00%> (?)
appsec-passport ?
appsec-postgres ?
appsec-sourcing ?
appsec-sourcing_stripe_template 45.57% <100.00%> (?)
appsec-stripe ?
appsec-template ?
appsec-ubuntu ?
appsec-windows ?
debugger 44.42% <100.00%> (?)
debugger-ubuntu-active ?
debugger-ubuntu-latest ?
debugger-ubuntu-maintenance ?
debugger-ubuntu-oldest ?
instrumentations-bucket-0 28.17% <100.00%> (?)
instrumentations-bucket-1 37.42% <100.00%> (?)
instrumentations-bucket-10 40.44% <100.00%> (?)
instrumentations-bucket-11 27.97% <100.00%> (?)
instrumentations-bucket-12 28.68% <100.00%> (?)
instrumentations-bucket-13 27.80% <100.00%> (?)
instrumentations-bucket-2 30.25% <100.00%> (?)
instrumentations-bucket-3 35.92% <100.00%> (?)
instrumentations-bucket-4 28.08% <100.00%> (?)
instrumentations-bucket-5 36.29% <100.00%> (?)
instrumentations-bucket-6 38.27% <100.00%> (?)
instrumentations-bucket-7 36.01% <100.00%> (?)
instrumentations-bucket-8 36.97% <100.00%> (?)
instrumentations-bucket-9 39.54% <100.00%> (?)
instrumentations-instrumentation-ai ?
instrumentations-instrumentation-aws-sdk ?
instrumentations-instrumentation-bluebird ?
instrumentations-instrumentation-body-parser ?
instrumentations-instrumentation-child_process ?
instrumentations-instrumentation-connect ?
instrumentations-instrumentation-cookie-parser ?
instrumentations-instrumentation-couchbase 46.54% <ø> (?)
instrumentations-instrumentation-couchbase-18 ?
instrumentations-instrumentation-couchbase-eol ?
instrumentations-instrumentation-crypto ?
instrumentations-instrumentation-express ?
instrumentations-instrumentation-express-mongo-sanitize ?
instrumentations-instrumentation-express-multi-version ?
instrumentations-instrumentation-express-session ?
instrumentations-instrumentation-fastify ?
instrumentations-instrumentation-fetch ?
instrumentations-instrumentation-fs ?
instrumentations-instrumentation-generic-pool ?
instrumentations-instrumentation-hono ?
instrumentations-instrumentation-http ?
instrumentations-instrumentation-http-client-options ?
instrumentations-instrumentation-kafkajs ?
instrumentations-instrumentation-knex ?
instrumentations-instrumentation-koa ?
instrumentations-instrumentation-light-my-request ?
instrumentations-instrumentation-mongoose ?
instrumentations-instrumentation-multer ?
instrumentations-instrumentation-mysql2 ?
instrumentations-instrumentation-openai-lifecycle ?
instrumentations-instrumentation-otel-sdk-trace ?
instrumentations-instrumentation-passport ?
instrumentations-instrumentation-passport-http ?
instrumentations-instrumentation-passport-local ?
instrumentations-instrumentation-pg ?
instrumentations-instrumentation-promise ?
instrumentations-instrumentation-promise-js ?
instrumentations-instrumentation-q ?
instrumentations-instrumentation-restify ?
instrumentations-instrumentation-router ?
instrumentations-instrumentation-stripe ?
instrumentations-instrumentation-url ?
instrumentations-instrumentation-when ?
instrumentations-instrumentation-zlib ?
instrumentations-integration-esbuild 24.87% <100.00%> (?)
instrumentations-integration-esbuild-0.16.12-active ?
instrumentations-integration-esbuild-0.16.12-latest ?
instrumentations-integration-esbuild-0.16.12-maintenance ?
instrumentations-integration-esbuild-0.16.12-oldest ?
instrumentations-integration-esbuild-latest-latest ?
instrumentations-integration-esbuild-latest-maintenance ?
instrumentations-integration-esbuild-latest-oldest ?
llmobs-ai ?
llmobs-ai_anthropic_bedrock 39.55% <100.00%> (?)
llmobs-bedrock ?
llmobs-google-genai ?
llmobs-google-genai_langchain_vertex-ai 36.99% <100.00%> (?)
llmobs-langchain ?
llmobs-openai 39.60% <100.00%> (?)
llmobs-openai-latest ?
llmobs-openai-oldest ?
llmobs-sdk 43.62% <100.00%> (?)
llmobs-sdk-active ?
llmobs-sdk-latest ?
llmobs-sdk-maintenance ?
llmobs-sdk-oldest ?
llmobs-vertex-ai ?
master-coverage 93.72% <98.52%> (?)
openfeature 38.02% <50.73%> (?)
openfeature-macos ?
openfeature-ubuntu ?
openfeature-unit 56.00% <98.52%> (?)
openfeature-unit-active ?
openfeature-unit-latest ?
openfeature-unit-maintenance ?
openfeature-unit-oldest ?
openfeature-windows ?
platform-core ?
platform-core_esbuild_instrumentations-misc 23.37% <100.00%> (?)
platform-esbuild ?
platform-instrumentations-misc ?
platform-integration 47.43% <100.00%> (?)
platform-integration-maintenance ?
platform-shimmer ?
platform-shimmer_unit-guardrails_webpack 18.90% <100.00%> (?)
platform-unit-guardrails ?
platform-webpack ?
plugins-aws-durable-execution-sdk-js ?
plugins-azure-cosmos ?
plugins-azure-event-hubs ?
plugins-azure-service-bus ?
plugins-body-parser ?
plugins-bucket-0 36.34% <100.00%> (?)
plugins-bucket-1 39.61% <100.00%> (?)
plugins-bucket-11 38.48% <100.00%> (?)
plugins-bucket-17 39.17% <100.00%> (?)
plugins-bucket-18 42.02% <100.00%> (?)
plugins-bucket-19 39.58% <100.00%> (?)
plugins-bucket-20 43.27% <100.00%> (?)
plugins-bucket-4 37.73% <100.00%> (?)
plugins-bullmq ?
plugins-bullmq_cassandra_cookie 39.78% <100.00%> (?)
plugins-cassandra ?
plugins-cookie ?
plugins-cookie-parser ?
plugins-cookie-parser_crypto_dd-trace-api 33.23% <100.00%> (?)
plugins-crypto ?
plugins-dd-trace-api ?
plugins-express-mongo-sanitize ?
plugins-express-session ?
plugins-fastify ?
plugins-fetch ?
plugins-fetch_fs_generic-pool 36.07% <100.00%> (?)
plugins-fs ?
plugins-generic-pool ?
plugins-google-cloud-pubsub ?
plugins-google-cloud-pubsub_grpc_handlebars 43.12% <100.00%> (?)
plugins-grpc ?
plugins-handlebars ?
plugins-hapi ?
plugins-hapi_hono_ioredis 37.80% <100.00%> (?)
plugins-hono ?
plugins-ioredis ?
plugins-jest ?
plugins-jest_knex_langgraph 32.62% <100.00%> (?)
plugins-knex ?
plugins-langgraph ?
plugins-ldapjs ?
plugins-ldapjs_light-my-request_limitd-client 27.93% <100.00%> (?)
plugins-light-my-request ?
plugins-limitd-client ?
plugins-lodash ?
plugins-lodash_mariadb_memcached 35.19% <100.00%> (?)
plugins-mariadb ?
plugins-microgateway-core ?
plugins-modelcontextprotocol-sdk ?
plugins-moleculer ?
plugins-mongodb ?
plugins-mongodb-core ?
plugins-mongodb_mongodb-core_mongoose 36.32% <100.00%> (?)
plugins-mongoose ?
plugins-multer_mysql_mysql2 35.16% <100.00%> (?)
plugins-mysql ?
plugins-mysql2 ?
plugins-nats ?
plugins-nats_node-serialize_opensearch 37.16% <100.00%> (?)
plugins-node-serialize ?
plugins-opensearch ?
plugins-passport-http ?
plugins-passport-http_pino_postgres 35.54% <100.00%> (?)
plugins-pino ?
plugins-postgres ?
plugins-process ?
plugins-process_pug_redis 34.28% <100.00%> (?)
plugins-pug ?
plugins-redis ?
plugins-router ?
plugins-sequelize ?
plugins-test-and-upstream-amqp10 ?
plugins-test-and-upstream-amqplib ?
plugins-test-and-upstream-apollo ?
plugins-test-and-upstream-bunyan ?
plugins-test-and-upstream-connect ?
plugins-test-and-upstream-graphql ?
plugins-test-and-upstream-koa ?
plugins-test-and-upstream-protobufjs ?
plugins-test-and-upstream-rhea ?
plugins-undici ?
plugins-undici_url_valkey 35.90% <100.00%> (?)
plugins-url ?
plugins-valkey ?
plugins-vm ?
plugins-vm_winston_ws 37.57% <100.00%> (?)
plugins-winston ?
profiling 43.63% <100.00%> (?)
profiling-macos ?
profiling-ubuntu ?
profiling-windows ?
serverless-aws-sdk-aws-sdk 33.31% <100.00%> (?)
serverless-aws-sdk-bedrockruntime 32.17% <100.00%> (?)
serverless-aws-sdk-client 37.21% <ø> (?)
serverless-aws-sdk-dynamodb 34.11% <100.00%> (?)
serverless-aws-sdk-eventbridge 27.26% <100.00%> (?)
serverless-aws-sdk-kinesis 37.42% <100.00%> (?)
serverless-aws-sdk-lambda 34.61% <100.00%> (?)
serverless-aws-sdk-latest-aws-sdk ?
serverless-aws-sdk-latest-bedrockruntime ?
serverless-aws-sdk-latest-client ?
serverless-aws-sdk-latest-dynamodb ?
serverless-aws-sdk-latest-eventbridge ?
serverless-aws-sdk-latest-kinesis ?
serverless-aws-sdk-latest-lambda ?
serverless-aws-sdk-latest-s3 ?
serverless-aws-sdk-latest-serverless-peer-service ?
serverless-aws-sdk-latest-sns ?
serverless-aws-sdk-latest-sqs ?
serverless-aws-sdk-latest-util ?
serverless-aws-sdk-oldest-aws-sdk ?
serverless-aws-sdk-oldest-bedrockruntime ?
serverless-aws-sdk-oldest-client ?
serverless-aws-sdk-oldest-dynamodb ?
serverless-aws-sdk-oldest-eventbridge ?
serverless-aws-sdk-oldest-kinesis ?
serverless-aws-sdk-oldest-lambda ?
serverless-aws-sdk-oldest-s3 ?
serverless-aws-sdk-oldest-serverless-peer-service ?
serverless-aws-sdk-oldest-sns ?
serverless-aws-sdk-oldest-sqs ?
serverless-aws-sdk-oldest-stepfunctions ?
serverless-aws-sdk-oldest-util ?
serverless-aws-sdk-s3 32.61% <100.00%> (?)
serverless-aws-sdk-serverless-peer-service 39.48% <100.00%> (?)
serverless-aws-sdk-sns 38.27% <100.00%> (?)
serverless-aws-sdk-sqs 38.01% <100.00%> (?)
serverless-aws-sdk-stepfunctions 33.19% <100.00%> (?)
serverless-aws-sdk-util 47.95% <ø> (?)
serverless-azure-durable-functions ?
serverless-azure-functions-eventhubs ?
serverless-azure-functions-servicebus ?
serverless-bucket-0 39.47% <100.00%> (?)
serverless-lambda 34.34% <100.00%> (-0.04%) ⬇️
test-optimization-cucumber 52.58% <100.00%> (?)
test-optimization-cucumber-latest-7.0.0 ?
test-optimization-cucumber-latest-latest ?
test-optimization-cypress 49.74% <100.00%> (?)
test-optimization-cypress-latest-12.0.0-commonJS ?
test-optimization-cypress-latest-12.0.0-esm ?
test-optimization-cypress-latest-14.5.4-commonJS ?
test-optimization-cypress-latest-14.5.4-esm ?
test-optimization-cypress-latest-latest-commonJS ?
test-optimization-cypress-latest-latest-esm ?
test-optimization-cypress-oldest-12.0.0-commonJS ?
test-optimization-cypress-oldest-12.0.0-esm ?
test-optimization-cypress-oldest-14.5.4-commonJS ?
test-optimization-cypress-oldest-14.5.4-esm ?
test-optimization-jest 55.68% <100.00%> (?)
test-optimization-jest-latest-oldest ?
test-optimization-jest-oldest-latest ?
test-optimization-jest-oldest-oldest ?
test-optimization-mocha 53.66% <100.00%> (?)
test-optimization-mocha-latest-latest ?
test-optimization-mocha-latest-oldest ?
test-optimization-mocha-oldest-latest ?
test-optimization-mocha-oldest-oldest ?
test-optimization-playwright-latest-latest-playwright-active-test-span ?
test-optimization-playwright-latest-latest-playwright-atr ?
test-optimization-playwright-latest-latest-playwright-efd ?
test-optimization-playwright-latest-latest-playwright-final-status ?
test-optimization-playwright-latest-latest-playwright-impacted-tests ?
test-optimization-playwright-latest-latest-playwright-reporting ?
test-optimization-playwright-latest-latest-playwright-test-management ?
test-optimization-playwright-latest-oldest-playwright-active-test-span ?
test-optimization-playwright-latest-oldest-playwright-atr ?
test-optimization-playwright-latest-oldest-playwright-efd ?
test-optimization-playwright-latest-oldest-playwright-final-status ?
test-optimization-playwright-latest-oldest-playwright-impacted-tests ?
test-optimization-playwright-latest-oldest-playwright-reporting ?
test-optimization-playwright-latest-oldest-playwright-test-management ?
test-optimization-playwright-oldest-latest-playwright-active-test-span ?
test-optimization-playwright-oldest-latest-playwright-atr ?
test-optimization-playwright-oldest-latest-playwright-efd ?
test-optimization-playwright-oldest-latest-playwright-final-status ?
test-optimization-playwright-oldest-latest-playwright-impacted-tests ?
test-optimization-playwright-oldest-latest-playwright-reporting ?
test-optimization-playwright-oldest-latest-playwright-test-management ?
test-optimization-playwright-oldest-oldest-playwright-active-test-span ?
test-optimization-playwright-oldest-oldest-playwright-atr ?
test-optimization-playwright-oldest-oldest-playwright-efd ?
test-optimization-playwright-oldest-oldest-playwright-final-status ?
test-optimization-playwright-oldest-oldest-playwright-impacted-tests ?
test-optimization-playwright-oldest-oldest-playwright-reporting ?
test-optimization-playwright-oldest-oldest-playwright-test-management ?
test-optimization-playwright-playwright-atr 43.59% <100.00%> (?)
test-optimization-playwright-playwright-efd 43.88% <100.00%> (?)
test-optimization-playwright-playwright-final-status 43.93% <100.00%> (?)
test-optimization-playwright-playwright-impacted-tests 43.42% <100.00%> (?)
test-optimization-playwright-playwright-reporting 43.50% <100.00%> (?)
test-optimization-playwright-playwright-test-management 44.94% <100.00%> (?)
test-optimization-playwright-playwright-test-span 44.79% <100.00%> (?)
test-optimization-selenium 45.53% <100.00%> (?)
test-optimization-selenium-oldest ?
test-optimization-testopt 48.29% <100.00%> (?)
test-optimization-testopt-active ?
test-optimization-testopt-latest ?
test-optimization-testopt-maintenance ?
test-optimization-testopt-oldest ?
test-optimization-vitest 50.94% <100.00%> (?)
test-optimization-vitest-latest ?
test-optimization-vitest-oldest ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@dd-octo-sts

dd-octo-sts Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 6.43 MB
Deduped: 7.49 MB
No deduping: 7.49 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.2.0 | 104.26 kB | 843.44 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@pr-commenter

pr-commenter Bot commented Jun 12, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-24 14:52:38

Comparing candidate commit 8193ba8 in PR branch leo.romanovsky/ffl-2446-evp-flagevaluation-nodejs with baseline commit 3ca1a63 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1982 metrics, 13 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-152722.307µs; +152493.774µs] or [-5.822%; +5.813%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-175.207ms; +166.557ms] or [-7.088%; +6.738%]

scenario:appsec-control-20

  • unstable execution_time [-139.927ms; +116.221ms] or [-8.231%; +6.837%]

scenario:appsec-control-24

  • unstable execution_time [-91763.723µs; +93612.356µs] or [-7.659%; +7.814%]

scenario:appsec-control-26

  • unstable execution_time [-98578.143µs; +98110.710µs] or [-8.328%; +8.289%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-227.608ms; +480.299ms] or [-4.779%; +10.085%]
  • unstable execution_time [-229.313ms; +476.299ms] or [-4.741%; +9.847%]

scenario:plugin-graphql-long-with-depth-and-collapse-off-24

  • unstable cpu_user_time [-313.235ms; +285.929ms] or [-6.730%; +6.143%]
  • unstable execution_time [-320.475ms; +303.679ms] or [-6.471%; +6.132%]
  • unstable max_rss_usage [-31.396MB; +37.361MB] or [-5.563%; +6.620%]

scenario:plugin-graphql-long-with-depth-off-20

  • unstable max_rss_usage [-9971.765KB; +11700.908KB] or [-6.799%; +7.977%]

scenario:plugin-graphql-long-with-depth-off-26

  • unstable max_rss_usage [-48.943MB; -1.538MB] or [-25.365%; -0.797%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-6352.803KB; +4602.470KB] or [-7.739%; +5.607%]

…val hot path

Split the Finally hook into a cheap synchronous capture and a deferred
aggregator: the hook enqueues a raw event onto a bounded hand-off queue
(drop-and-count on overflow) and a setImmediate-scheduled drain runs the
prune + canonical-key + two-tier aggregation off the caller's evaluation
call stack. flush()/destroy() drain pending events first so no queued
evaluation is lost on flush or shutdown.

Source variant from evaluationDetails.variant (not the evaluated value)
and allocationKey/eval-time from evaluationDetails.flagMetadata, matching
the OTel eval-metrics hook. Eval-time falls back to hook-fire time since
the Datadog Node evaluator does not stamp dd.eval.timestamp_ms.

Add evaluationCountsEnabled to the public type declarations, wire the
hook and aggregator into the openfeature benchmark, and add lifecycle,
JSON-schema, async-boundary, and backpressure tests.
The EVP flagevaluation hot-path benchmark lived only in benchmark/openfeature.js,
which is reached via benchmark/index.js (npm run bench) — a path CI does not run.
The canonical CI benchmark suite is sirun (benchmark/sirun/), executed via
.gitlab/benchmarks/bp-runner.yml (BENCHMARKS_PATH: benchmark/sirun -> runall.sh),
which discovers benchmarks by iterating subdirectories with a meta.json.

Add a benchmark/sirun/openfeature/ directory (meta.json + index.js + README)
mirroring the llmobs sirun bench: require.cache-stub the egress request module,
require the real FlagEvaluationsWriter + FlagEvalEVPHook from packages/dd-trace/src,
a pre-flight sanity assertion, then a startup-guard-fenced timed loop. Two variants:
flag-eval-hook (the synchronous Finally-hook cost charged to the caller's evaluation)
and aggregate (the deferred off-hot-path aggregator). Register the dir in CODEOWNERS
under the feature-flagging team, matching how llmobs owns its bench dir.
@leoromanovsky leoromanovsky changed the title [FFL-2446] dd-trace-js: emit EVP flagevaluation (Phase 2 fan-out) feat(openfeature): emit server-side EVP flagevaluation Jun 14, 2026
…-guard ceiling

The flag-eval-hook variant's COUNT=12000000 left the timed loop too short
relative to module load (the FlagEvaluationsWriter require chain). On a fast
runner the startup share crosses the startup-guard 10% ceiling, so done()
asserts and the variant exits non-zero — failing the GROUP 3 shard (the bucket
both openfeature variants land in) on every Node major in CI.

Raise COUNT to 40000000 so the loop dominates: measured startup share drops to
~2% with comfortable headroom across repeated runs. The aggregate variant
(per-iteration aggregator work, share ~0.15%) already had ample margin and is
unchanged.
The previous batching path rebuilt and re-encoded the whole candidate payload for every aggregate event. On a 10,050-event degradation batch, a throwaway benchmark measured old batching at 13.7-14.8s per flush versus 3.7-4.3ms with incremental byte accounting, with identical output length.

Validation: npm run test:openfeature
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant