From 13f46a74e3ad3073128419813ad19e9a6bfff61d Mon Sep 17 00:00:00 2001 From: Danyal Khan Date: Tue, 25 Aug 2026 14:10:18 -0400 Subject: [PATCH] feat(ffe): send the split serial id on exposure events [EX-3425] The exposures intake uses the serial id to find the holdout an allocation comes from. The compiler rewrites a holdout into an ordinary allocation before an SDK receives it, so the serial id is the only link back to it. Carry the serial id from the evaluation result through the exposure buffer into the sidecar FFI struct. Serial ids are zero-based per organization, so 0 is a real value and cannot signal absence; the value travels with a separate presence flag, matching the existing FfeResult convention. Advance libdatadog to 0c0c60b96, which adds the two fields to ddog_FfeExposure and owns serialization, the wire key, and exposure deduplication. Regenerate the affected headers with make cbindgen. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 40 ++++++++++++++++++++++++++++++++++++++++ components-rs/common.h | 3 +++ components-rs/sidecar.h | 21 +++++++++++++++++++++ libdatadog | 2 +- tracer/ffe.c | 10 +++++++++- tracer/ffe.h | 3 ++- tracer/functions.c | 4 +++- 7 files changed, 79 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 057c1e06aa4..6d512f8c6cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2922,6 +2922,7 @@ dependencies = [ "tracing", "uuid", "web-time", + "zrip", "zstd", ] @@ -3129,6 +3130,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-util", + "zrip", "zstd", ] @@ -3151,6 +3153,7 @@ dependencies = [ "chrono", "futures", "futures-util", + "getrandom 0.2.15", "hashbrown 0.15.2", "http 1.4.2", "http-body-util", @@ -3165,6 +3168,7 @@ dependencies = [ "manual_future", "prost", "rand 0.8.5", + "ring", "serde", "serde_json", "serde_with", @@ -3375,6 +3379,7 @@ dependencies = [ "tokio", "tracing", "urlencoding", + "zrip", "zstd", ] @@ -7203,6 +7208,41 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zrip" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964fe9f1ea10a0d183fc143a7525266cbe16978883dbe304725da34b314c04cf" +dependencies = [ + "zrip-core", + "zrip-decode", + "zrip-encode", +] + +[[package]] +name = "zrip-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc201ba56175f86e67cc88bdaf1a7af9c061815c7dde719c7a6a1ed5aaa186b" + +[[package]] +name = "zrip-decode" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038f795e49887bdeaab197fff84c8165644484a98dc3f2354e5df3d2e5f4f978" +dependencies = [ + "zrip-core", +] + +[[package]] +name = "zrip-encode" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3dfdcbb503db2045716492d5ab31cd82f2852d0d93d8edf6a9235653d9da685" +dependencies = [ + "zrip-core", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/components-rs/common.h b/components-rs/common.h index 516014cb992..51e2d711091 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -437,6 +437,7 @@ typedef enum ddog_RemoteConfigProduct { DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS, DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGING, DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGING_SYMBOL_DB, + DDOG_REMOTE_CONFIG_PRODUCT_DEBUG, } ddog_RemoteConfigProduct; typedef enum ddog_SpanProbeTarget { @@ -1246,6 +1247,8 @@ typedef struct ddog_FfeExposure { ddog_CharSlice subject_attributes_json; ddog_CharSlice allocation_key; ddog_CharSlice variant; + int32_t serial_id; + bool has_serial_id; } ddog_FfeExposure; typedef struct ddog_Slice_FfeExposure { diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 72ea5e9e020..f0145771642 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -307,6 +307,27 @@ ddog_MaybeError ddog_sidecar_send_trace_v04_bytes(struct ddog_SidecarTransport * ddog_CharSlice data, const struct ddog_TracerHeaderTags *tracer_header_tags); +/** + * Sends a V1-encoded trace to the sidecar via shared memory. The sidecar decodes the V1 + * `TracerPayload`, can inspect it, and re-encodes it as V1 msgpack on the way to the agent's + * `/v1.0/traces` endpoint. + */ +ddog_MaybeError ddog_sidecar_send_trace_v1_shm(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + struct ddog_ShmHandle *shm_handle, + uintptr_t len, + const struct ddog_TracerHeaderTags *tracer_header_tags); + +/** + * Sends a V1-encoded trace as bytes to the sidecar. The sidecar decodes the V1 `TracerPayload`, + * can inspect it, and re-encodes it as V1 msgpack on the way to the agent's `/v1.0/traces` + * endpoint. + */ +ddog_MaybeError ddog_sidecar_send_trace_v1_bytes(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice data, + const struct ddog_TracerHeaderTags *tracer_header_tags); + ddog_MaybeError ddog_sidecar_send_debugger_data(struct ddog_SidecarTransport **transport, const struct ddog_InstanceId *instance_id, ddog_QueueId queue_id, diff --git a/libdatadog b/libdatadog index 378be45c30e..0c0c60b968e 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 378be45c30e9c62a1203c4cc2069aaaf8d1f4673 +Subproject commit 0c0c60b968e7f4f2f6e43fffc3f7a1710c1eade5 diff --git a/tracer/ffe.c b/tracer/ffe.c index 13653a2fab0..bf0a1cce629 100644 --- a/tracer/ffe.c +++ b/tracer/ffe.c @@ -31,6 +31,8 @@ typedef struct { zend_string *subject_attributes_json; zend_string *allocation_key; zend_string *variant; + int32_t serial_id; + bool has_serial_id; } dd_ffe_exposure; static void dd_ffe_release_metric(dd_ffe_metric *metric) { @@ -168,7 +170,9 @@ void ddtrace_ffe_record_exposure( zend_string *targeting_key, zend_string *subject_attributes_json, zend_string *allocation_key, - zend_string *variant + zend_string *variant, + int32_t serial_id, + bool has_serial_id ) { if (ZSTR_LEN(flag_key) == 0 || ZSTR_LEN(variant) == 0) { return; @@ -200,6 +204,8 @@ void ddtrace_ffe_record_exposure( exposure->subject_attributes_json = zend_string_copy(subject_attributes_json); exposure->allocation_key = zend_string_copy(allocation_key); exposure->variant = zend_string_copy(variant); + exposure->serial_id = serial_id; + exposure->has_serial_id = has_serial_id; } bool ddtrace_ffe_flush_exposures(void) { @@ -224,6 +230,8 @@ bool ddtrace_ffe_flush_exposures(void) { .subject_attributes_json = dd_zend_string_to_CharSlice(buffer[i].subject_attributes_json), .allocation_key = dd_zend_string_to_CharSlice(buffer[i].allocation_key), .variant = dd_zend_string_to_CharSlice(buffer[i].variant), + .serial_id = buffer[i].serial_id, + .has_serial_id = buffer[i].has_serial_id, }; } diff --git a/tracer/ffe.h b/tracer/ffe.h index 0b61ab9a210..4da7f951f28 100644 --- a/tracer/ffe.h +++ b/tracer/ffe.h @@ -3,12 +3,13 @@ #include #include +#include #include bool ddtrace_ffe_record_evaluation_metric(zend_string *flag_key, zend_string *variant, const char *reason, const char *error_type, zend_string *allocation_key); bool ddtrace_ffe_flush_evaluation_metrics(void); -void ddtrace_ffe_record_exposure(zend_string *flag_key, zend_string *targeting_key, zend_string *subject_attributes_json, zend_string *allocation_key, zend_string *variant); +void ddtrace_ffe_record_exposure(zend_string *flag_key, zend_string *targeting_key, zend_string *subject_attributes_json, zend_string *allocation_key, zend_string *variant, int32_t serial_id, bool has_serial_id); bool ddtrace_ffe_flush_exposures(void); #endif // DDTRACE_FFE_H diff --git a/tracer/functions.c b/tracer/functions.c index 4fa26d489ca..9b1b3bd46e7 100644 --- a/tracer/functions.c +++ b/tracer/functions.c @@ -1900,7 +1900,9 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) { targeting_key, subject_attributes_json, allocation_key, - variant + variant, + result.serial_id, + result.has_serial_id ); zend_string_release(subject_attributes_json); }