Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
16d7817
Add FFE evaluation completion hook
leoromanovsky May 22, 2026
eba4c86
Add FFE exposure writer
leoromanovsky May 22, 2026
fc69e9f
Load evaluation-completed hook classes in canonical FFE PHPT
leoromanovsky May 23, 2026
92ef9a3
Load evaluation-completed hook classes in canonical FFE PHPT
leoromanovsky May 23, 2026
49d53ef
docs(ffe): add PR-stack and system diagrams for PR #3909
leoromanovsky May 23, 2026
81c01a7
Migrate FFE exposure transport to libdatadog sidecar
leoromanovsky May 23, 2026
0c4b4b3
Update libdatadog submodule to pick up FFE dispatch fix
leoromanovsky May 23, 2026
64c4267
tooling: place mktemp dirs under OUTPUT_DIR to avoid silent no-op on …
leoromanovsky May 23, 2026
ea04875
docs(ffe): quote diagram titles, switch system to TD, re-render at hi…
leoromanovsky May 23, 2026
f319d47
docs(ffe): quote diagram titles, switch system to TD, re-render at hi…
leoromanovsky May 23, 2026
13aca28
docs(ffe): drop 'Hook seam' wording, use 'Hook layer'
leoromanovsky May 23, 2026
28782fb
ExposureWriter: surface first drop with a one-time warning + TODO
leoromanovsky May 24, 2026
f775277
ExposureWriter: flush-on-full so long-running runtimes don't silently…
leoromanovsky May 24, 2026
8eedb0e
chore(ffe): remove generated stack diagrams
leoromanovsky May 24, 2026
04adf69
chore(ffe): remove generated stack diagrams
leoromanovsky May 24, 2026
e9b713c
Merge branch 'leo.romanovsky/milestone-1-runtime-evaluation' into leo…
leoromanovsky May 26, 2026
f4a1546
Merge branch 'leo.romanovsky/m2-m3-evaluation-completed-base' into le…
leoromanovsky May 26, 2026
ec49560
Merge branch 'leo.romanovsky/milestone-1-runtime-evaluation' into leo…
leoromanovsky May 27, 2026
61f6cc1
Merge branch 'leo.romanovsky/m2-m3-evaluation-completed-base' into le…
leoromanovsky May 27, 2026
76118cb
fix(ffe): tidy native exposure branch base
leoromanovsky May 27, 2026
b28e705
Merge milestone 1 runtime evaluation base
leoromanovsky May 27, 2026
c4bceb1
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 27, 2026
e82793e
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
5e38adc
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
c8cb8f5
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
a30c3ef
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
59d0ea1
Fix FFE exposure sidecar activation
leoromanovsky May 28, 2026
59be76d
Update FFE exposure dependency and result ABI
leoromanovsky May 28, 2026
416749d
Bump libdatadog exposure runtime
leoromanovsky May 28, 2026
94de19a
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
11cc8fa
Remove unreachable FFE exposure cleanup branch
leoromanovsky Jun 2, 2026
643272b
Drop redundant FFE exposure release guards
leoromanovsky Jun 2, 2026
e4d254e
Pass FFE exposures as zend strings
leoromanovsky Jun 2, 2026
31ccc9f
Document FFE exposure CLI flush timing
leoromanovsky Jun 2, 2026
436f80e
Move FFE exposure flush helper to testing namespace
leoromanovsky Jun 2, 2026
4557494
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
1cb2a0b
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
203281e
Move FFE exposure buffering into tracer
leoromanovsky Jun 2, 2026
bcf1342
Fully move FFE to tracer/
bwoebi Jun 2, 2026
bdaca4f
Fix compile error
bwoebi Jun 2, 2026
00564ae
Remove FFE evaluation RC polling
leoromanovsky Jun 2, 2026
a802e10
Proper empty string
bwoebi Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions components-rs/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,34 @@ typedef struct ddog_TracerHeaderTags {
bool client_computed_stats;
} ddog_TracerHeaderTags;

typedef struct ddog_FfeTelemetryContext {
ddog_CharSlice service;
ddog_CharSlice env;
ddog_CharSlice version;
} ddog_FfeTelemetryContext;

typedef struct ddog_FfeExposure {
uint64_t timestamp_ms;
ddog_CharSlice flag_key;
ddog_CharSlice subject_id;
ddog_CharSlice subject_attributes_json;
ddog_CharSlice allocation_key;
ddog_CharSlice variant;
} ddog_FfeExposure;

typedef struct ddog_Slice_FfeExposure {
/**
* Should be non-null and suitably aligned for the underlying type. It is
* allowed to point to read-only memory if `len` is zero.
*/
const struct ddog_FfeExposure *ptr;
/**
* The number of elements (not bytes) that `.ptr` points to. Must be less
* than or equal to `isize::MAX`.
*/
uintptr_t len;
} ddog_Slice_FfeExposure;

/**
* Holds the raw parts of a Rust Vec; it should only be created from Rust,
* never from C.
Expand Down
47 changes: 47 additions & 0 deletions components-rs/sidecar.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,22 @@ ddog_MaybeError ddog_sidecar_send_debugger_datum(struct ddog_SidecarTransport **
ddog_QueueId queue_id,
struct ddog_DebuggerPayload *payload);

/**
* Send structured FFE exposure events to the sidecar. The sidecar owns
* deduplication, JSON serialization, and Agent EVP delivery. This function is
* caller-driven; shared libdatadog evaluator calls do not log unless an SDK
* explicitly sends this action.
*
* # Safety
* `context` and every element in `exposures` must contain valid UTF-8
* `CharSlice` values. Empty `exposures` is a no-op.
*/
ddog_MaybeError ddog_sidecar_send_ffe_exposure_batch(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
const ddog_QueueId *queue_id,
const struct ddog_FfeTelemetryContext *context,
struct ddog_Slice_FfeExposure exposures);

ddog_MaybeError ddog_sidecar_send_debugger_diagnostics(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
ddog_QueueId queue_id,
Expand Down Expand Up @@ -441,8 +457,21 @@ ddog_SpanBytes *ddog_trace_new_span_with_capacities(ddog_TraceBytes *trace,
uintptr_t meta_size,
uintptr_t metrics_size);

/**
* The returned slice is an owned allocation that must be properly freed using
* [`ddog_free_charslice`].
*/
ddog_CharSlice ddog_span_debug_log(const ddog_SpanBytes *span);

/**
* Frees an owned [`CharSlice`]. Note that some functions of this API return borrowed slices that
* must NOT be freed. Only a few selected functions return slices that must be freed, and this is
* mentioned explicitly in their documentation.
*
* # Safety
*
* `slice` must be an owned char slice that has been returned by one of the functions of this API.
*/
void ddog_free_charslice(ddog_CharSlice slice);

void ddog_set_span_service(ddog_SpanBytes *span, ddog_CharSlice slice);
Expand Down Expand Up @@ -493,6 +522,10 @@ ddog_CharSlice ddog_get_span_meta(ddog_SpanBytes *span, ddog_CharSlice key);

bool ddog_has_span_meta(ddog_SpanBytes *span, ddog_CharSlice key);

/**
* The return value is an owned array of slices (`Box<[CharSlice]>`) that must be freed explicitly
* through [`ddog_span_free_keys_ptr`].
*/
ddog_CharSlice *ddog_span_meta_get_keys(ddog_SpanBytes *span, uintptr_t *out_count);

void ddog_add_span_metrics(ddog_SpanBytes *span, ddog_CharSlice key, double val);
Expand All @@ -513,8 +546,18 @@ ddog_CharSlice ddog_get_span_meta_struct(ddog_SpanBytes *span, ddog_CharSlice ke

bool ddog_has_span_meta_struct(ddog_SpanBytes *span, ddog_CharSlice key);

/**
* The return value is an array of slices (`Box<[CharSlice]>`) that must be freed explicitly
* through [`ddog_span_free_keys_ptr`].
*/
ddog_CharSlice *ddog_span_meta_struct_get_keys(ddog_SpanBytes *span, uintptr_t *out_count);

/**
* # Safety
*
* `keys_ptr` must have been returned by one of the `ddog_xxx_get_keys()` functions, and must not
* have been already freed.
*/
void ddog_span_free_keys_ptr(ddog_CharSlice *keys_ptr, uintptr_t count);

ddog_SpanLinkBytes *ddog_span_new_link(ddog_SpanBytes *span);
Expand Down Expand Up @@ -547,6 +590,10 @@ void ddog_add_event_attributes_int(ddog_SpanEventBytes *event, ddog_CharSlice ke

void ddog_add_event_attributes_float(ddog_SpanEventBytes *event, ddog_CharSlice key, double val);

/**
* The returned slice is an owned allocation that must be properly freed using
* [`ddog_free_charslice`].
*/
ddog_CharSlice ddog_serialize_trace_into_charslice(ddog_TraceBytes *trace);

#endif /* DDOG_SIDECAR_H */
1 change: 1 addition & 0 deletions config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ if test "$PHP_DDTRACE" != "no"; then
tracer/engine_api.c \
tracer/engine_hooks.c \
tracer/exception_serialize.c \
tracer/ffe.c \
tracer/functions.c \
tracer/git_metadata.c \
tracer/handlers_exception.c \
Expand Down
1 change: 1 addition & 0 deletions config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ if (PHP_DDTRACE != 'no') {
DDTRACE_TRACER_SOURCES += " engine_api.c";
DDTRACE_TRACER_SOURCES += " engine_hooks.c";
DDTRACE_TRACER_SOURCES += " exception_serialize.c";
DDTRACE_TRACER_SOURCES += " ffe.c";
DDTRACE_TRACER_SOURCES += " functions.c";
DDTRACE_TRACER_SOURCES += " git_metadata.c";
DDTRACE_TRACER_SOURCES += " handlers_curl" + (version < 800 ? "_php7" : "") + ".c";
Expand Down
2 changes: 1 addition & 1 deletion ext/datadog.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ ZEND_END_MODULE_GLOBALS(datadog)
#define PHP_DDTRACE_VERSION "0.0.0-unknown"
#endif

#endif // DATADOG_H
#endif // DATADOG_H
6 changes: 6 additions & 0 deletions tests/ext/ffe/native_bridge_evaluate.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ function show($label, $value) {
}

show('has_config_before', \DDTrace\ffe_has_config());
show('native_exposure_flush_exists', function_exists('DDTrace\\Testing\\flush_ffe_exposures'));
show('internal_exposure_flush_exists', function_exists('DDTrace\\Internal\\flush_ffe_exposures'));
show('old_exposure_forwarder_exists', function_exists('DDTrace\\send_ffe_exposures'));
show('provider_not_ready', \DDTrace\ffe_evaluate('string.flag', 0, 'user-1', array()));

$config = <<<'JSON'
Expand Down Expand Up @@ -126,6 +129,9 @@ show('parse_error', \DDTrace\ffe_evaluate('bad.flag', 0, 'user-1', array()));
?>
--EXPECT--
has_config_before=false
native_exposure_flush_exists=true
internal_exposure_flush_exists=false
old_exposure_forwarder_exists=false
provider_not_ready={"valueJson":"null","variant":null,"allocationKey":null,"reason":5,"errorCode":6,"doLog":false,"providerState":[],"errorMessage":null,"hasConfig":null,"configVersion":null}
load=true
has_config_after=true
Expand Down
9 changes: 6 additions & 3 deletions tooling/bin/build-debug-artifact
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ fi

# ─── Build cache management ───────────────────────────────────────────────────
CACHE_VOLUME="ddtrace-build-cache"
CACHE_TAG="${libc}-${arch}-${php_version}-${thread_safety}"
# The persistent build cache contains copied source files under tmp/build_extension.
# Keep sibling worktrees from reusing stale copied headers for the same PHP target.
CACHE_SOURCE_KEY="$(printf '%s' "$REPO_ROOT" | cksum | awk '{print $1}')"
CACHE_TAG="${libc}-${arch}-${php_version}-${thread_safety}-${CACHE_SOURCE_KEY}"
CACHE_TAG_LABEL="ddtrace.build.target"

if docker volume inspect "$CACHE_VOLUME" &>/dev/null; then
Expand Down Expand Up @@ -200,8 +203,8 @@ fi
echo "Building ddtrace ${php_version} ${thread_safety} [${libc}/${arch}]..."
echo "Image: ${DOCKER_IMAGE}"

TMP_OUT=$(mktemp -d)
TMP_PKG=$(mktemp -d)
TMP_OUT=$(mktemp -d "${OUTPUT_DIR%/}/.build-debug-artifact-out.XXXXXX")
TMP_PKG=$(mktemp -d "${OUTPUT_DIR%/}/.build-debug-artifact-pkg.XXXXXX")
trap 'rm -rf "$TMP_OUT" "$TMP_PKG"' EXIT

# ─── Build script construction ────────────────────────────────────────────────
Expand Down
4 changes: 3 additions & 1 deletion tracer/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#endif
#include "distributed_tracing_headers.h"
#include "engine_hooks.h"
#include "ffe.h"
#include "handlers_internal.h"
#include "inferred_proxy_headers.h"
#include "integrations/exec_integration.h"
Expand Down Expand Up @@ -605,6 +606,8 @@ void ddtrace_rshutdown(bool fast_shutdown) {
DDTRACE_G(active_stack) = NULL;
}

ddtrace_ffe_flush_exposures();

ddtrace_clean_git_object();
ddtrace_weak_resources_rshutdown();
}
Expand Down Expand Up @@ -727,4 +730,3 @@ void dd_prepare_for_new_trace(void) {
DDTRACE_G(traces_group_id) = ddtrace_coms_next_group_id();
#endif
}

7 changes: 7 additions & 0 deletions tracer/ddtrace.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,13 @@ function set_blocking_function(\DDTrace\RootSpanData $span, callable $blockingFu
*/
function ffe_load_config(string $json): bool {}

/**
* Flushes native FFE exposure batches for integration tests.
*
* @internal Used by extension tests only.
*/
function flush_ffe_exposures(): bool {}

/**
* Overrides PHP's default error handling.
*
Expand Down
4 changes: 4 additions & 0 deletions tracer/ddtrace_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_Testing_ffe_load_config,
ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0)
ZEND_END_ARG_INFO()

#define arginfo_DDTrace_Testing_flush_ffe_exposures arginfo_DDTrace_are_endpoints_collected

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_System_container_id, 0, 0, IS_STRING, 1)
ZEND_END_ARG_INFO()

Expand Down Expand Up @@ -415,6 +417,7 @@ ZEND_FUNCTION(DDTrace_ffe_evaluate);
ZEND_FUNCTION(DDTrace_ffe_has_config);
ZEND_FUNCTION(DDTrace_ffe_config_version);
ZEND_FUNCTION(DDTrace_Testing_ffe_load_config);
ZEND_FUNCTION(DDTrace_Testing_flush_ffe_exposures);
ZEND_FUNCTION(DDTrace_System_container_id);
ZEND_FUNCTION(DDTrace_System_process_tags_base_hash);
ZEND_FUNCTION(DDTrace_Config_integration_analytics_enabled);
Expand Down Expand Up @@ -522,6 +525,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\UserRequest", "notify_commit"), zif_DDTrace_UserRequest_notify_commit, arginfo_DDTrace_UserRequest_notify_commit, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\UserRequest", "set_blocking_function"), zif_DDTrace_UserRequest_set_blocking_function, arginfo_DDTrace_UserRequest_set_blocking_function, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "ffe_load_config"), zif_DDTrace_Testing_ffe_load_config, arginfo_DDTrace_Testing_ffe_load_config, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "flush_ffe_exposures"), zif_DDTrace_Testing_flush_ffe_exposures, arginfo_DDTrace_Testing_flush_ffe_exposures, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "trigger_error"), zif_DDTrace_Testing_trigger_error, arginfo_DDTrace_Testing_trigger_error, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "emit_asm_event"), zif_DDTrace_Testing_emit_asm_event, arginfo_DDTrace_Testing_emit_asm_event, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "normalize_tag_value"), zif_DDTrace_Testing_normalize_tag_value, arginfo_DDTrace_Testing_normalize_tag_value, 0, NULL, NULL)
Expand Down
4 changes: 4 additions & 0 deletions tracer/ddtrace_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ typedef struct {

HashTable resource_weak_storage;
dtor_func_t resource_dtor_func;

void *ffe_exposure_buffer;
size_t ffe_exposure_buffer_len;
size_t ffe_exposure_buffer_cap;
} ddtrace_globals;

#define DDTRACE_G(v) (DATADOG_G(ddtrace).v)
Expand Down
Loading
Loading