diff --git a/Cargo.lock b/Cargo.lock index 7a0fe64ab9..3e849caed9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1382,6 +1382,7 @@ dependencies = [ "libdd-trace-protobuf", "libdd-trace-stats", "libdd-trace-utils", + "lru", "manual_future", "memory-stats", "microseh", @@ -2082,6 +2083,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "hdrhistogram" @@ -2870,7 +2876,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" -version = "4.0.0" +version = "5.0.0" dependencies = [ "anyhow", "arc-swap", @@ -2934,7 +2940,7 @@ dependencies = [ [[package]] name = "libdd-library-config" -version = "1.1.0" +version = "2.0.0" dependencies = [ "anyhow", "libc 0.2.177", @@ -3127,7 +3133,7 @@ dependencies = [ [[package]] name = "libdd-trace-obfuscation" -version = "3.0.0" +version = "3.1.0" dependencies = [ "anyhow", "criterion", @@ -3158,7 +3164,7 @@ dependencies = [ [[package]] name = "libdd-trace-stats" -version = "3.0.0" +version = "4.0.0" dependencies = [ "anyhow", "arc-swap", @@ -3185,7 +3191,7 @@ dependencies = [ [[package]] name = "libdd-trace-utils" -version = "4.0.0" +version = "5.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3287,6 +3293,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "mach2" version = "0.5.0" diff --git a/components-rs/common.h b/components-rs/common.h index a39a3bd0fb..c5da88c97c 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -276,19 +276,16 @@ typedef struct _zend_string _zend_string; #define ddog_MultiTargetFetcher_DEFAULT_CLIENTS_LIMIT 100 -typedef enum ddog_Log { - DDOG_LOG_ERROR = 1, - DDOG_LOG_WARN = 2, - DDOG_LOG_INFO = 3, - DDOG_LOG_DEBUG = 4, - DDOG_LOG_TRACE = 5, - DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), - DDOG_LOG_STARTUP = (3 | (2 << 4)), - DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), - DDOG_LOG_SPAN = (4 | (3 << 4)), - DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), - DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), -} ddog_Log; +typedef enum ddog_ConfigurationOrigin { + DDOG_CONFIGURATION_ORIGIN_ENV_VAR, + DDOG_CONFIGURATION_ORIGIN_CODE, + DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, + DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_DEFAULT, + DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_CALCULATED, +} ddog_ConfigurationOrigin; typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_READ, @@ -297,37 +294,30 @@ typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_RESTORE, } ddog_DynamicConfigUpdateMode; -typedef enum ddog_InBodyLocation { - DDOG_IN_BODY_LOCATION_NONE, - DDOG_IN_BODY_LOCATION_START, - DDOG_IN_BODY_LOCATION_END, -} ddog_InBodyLocation; - typedef enum ddog_EvaluateAt { DDOG_EVALUATE_AT_ENTRY, DDOG_EVALUATE_AT_EXIT, } ddog_EvaluateAt; -typedef enum ddog_MetricKind { - DDOG_METRIC_KIND_COUNT, - DDOG_METRIC_KIND_GAUGE, - DDOG_METRIC_KIND_HISTOGRAM, - DDOG_METRIC_KIND_DISTRIBUTION, -} ddog_MetricKind; - -typedef enum ddog_SpanProbeTarget { - DDOG_SPAN_PROBE_TARGET_ACTIVE, - DDOG_SPAN_PROBE_TARGET_ROOT, -} ddog_SpanProbeTarget; +typedef enum ddog_InBodyLocation { + DDOG_IN_BODY_LOCATION_NONE, + DDOG_IN_BODY_LOCATION_START, + DDOG_IN_BODY_LOCATION_END, +} ddog_InBodyLocation; -typedef enum ddog_ProbeStatus { - DDOG_PROBE_STATUS_RECEIVED, - DDOG_PROBE_STATUS_INSTALLED, - DDOG_PROBE_STATUS_EMITTING, - DDOG_PROBE_STATUS_ERROR, - DDOG_PROBE_STATUS_BLOCKED, - DDOG_PROBE_STATUS_WARNING, -} ddog_ProbeStatus; +typedef enum ddog_Log { + DDOG_LOG_ERROR = 1, + DDOG_LOG_WARN = 2, + DDOG_LOG_INFO = 3, + DDOG_LOG_DEBUG = 4, + DDOG_LOG_TRACE = 5, + DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), + DDOG_LOG_STARTUP = (3 | (2 << 4)), + DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), + DDOG_LOG_SPAN = (4 | (3 << 4)), + DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), + DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), +} ddog_Log; typedef enum ddog_Method { DDOG_METHOD_GET = 0, @@ -342,22 +332,12 @@ typedef enum ddog_Method { DDOG_METHOD_OTHER = 9, } ddog_Method; -typedef enum ddog_ConfigurationOrigin { - DDOG_CONFIGURATION_ORIGIN_ENV_VAR, - DDOG_CONFIGURATION_ORIGIN_CODE, - DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, - DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_DEFAULT, - DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_CALCULATED, -} ddog_ConfigurationOrigin; - -typedef enum ddog_MetricType { - DDOG_METRIC_TYPE_GAUGE, - DDOG_METRIC_TYPE_COUNT, - DDOG_METRIC_TYPE_DISTRIBUTION, -} ddog_MetricType; +typedef enum ddog_MetricKind { + DDOG_METRIC_KIND_COUNT, + DDOG_METRIC_KIND_GAUGE, + DDOG_METRIC_KIND_HISTOGRAM, + DDOG_METRIC_KIND_DISTRIBUTION, +} ddog_MetricKind; typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_TRACERS, @@ -373,17 +353,20 @@ typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_SIDECAR, } ddog_MetricNamespace; -typedef enum ddog_RemoteConfigProduct { - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, - DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, - DDOG_REMOTE_CONFIG_PRODUCT_ASM, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, - DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS, - DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, -} ddog_RemoteConfigProduct; +typedef enum ddog_MetricType { + DDOG_METRIC_TYPE_GAUGE, + DDOG_METRIC_TYPE_COUNT, + DDOG_METRIC_TYPE_DISTRIBUTION, +} ddog_MetricType; + +typedef enum ddog_ProbeStatus { + DDOG_PROBE_STATUS_RECEIVED, + DDOG_PROBE_STATUS_INSTALLED, + DDOG_PROBE_STATUS_EMITTING, + DDOG_PROBE_STATUS_ERROR, + DDOG_PROBE_STATUS_BLOCKED, + DDOG_PROBE_STATUS_WARNING, +} ddog_ProbeStatus; typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_ACTIVATION = 1, @@ -433,14 +416,29 @@ typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_FFE_FLAG_CONFIGURATION_RULES = 46, } ddog_RemoteConfigCapabilities; +typedef enum ddog_RemoteConfigProduct { + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, + DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, + DDOG_REMOTE_CONFIG_PRODUCT_ASM, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, + DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS, + DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, +} ddog_RemoteConfigProduct; + +typedef enum ddog_SpanProbeTarget { + DDOG_SPAN_PROBE_TARGET_ACTIVE, + DDOG_SPAN_PROBE_TARGET_ROOT, +} ddog_SpanProbeTarget; + typedef struct ddog_AgentInfoReader ddog_AgentInfoReader; typedef struct ddog_DebuggerPayload ddog_DebuggerPayload; typedef struct ddog_DslString ddog_DslString; -typedef struct ddog_FfeResult ddog_FfeResult; - typedef struct ddog_HashMap_ShmCacheKey__ShmCache ddog_HashMap_ShmCacheKey__ShmCache; /** @@ -480,6 +478,24 @@ typedef struct ddog_SidecarTransport ddog_SidecarTransport; */ typedef struct ddog_SpanConcentrator ddog_SpanConcentrator; +typedef struct ddog_FfeResult { + struct _zend_string *value_json; + struct _zend_string *variant; + struct _zend_string *allocation_key; + int32_t reason; + int32_t error_code; + bool do_log; + bool valid; +} ddog_FfeResult; + +typedef struct ddog_FfeAttribute { + ddog_CharSlice key; + int32_t value_type; + ddog_CharSlice string_value; + double number_value; + bool bool_value; +} ddog_FfeAttribute; + /** * Flags selecting which Remote Config products/capabilities to subscribe to. * @@ -510,16 +526,6 @@ typedef struct ddog_Tag { typedef struct _zend_string *ddog_OwnedZendString; -struct ddog_FfeResult { - ddog_OwnedZendString value_json; - ddog_OwnedZendString variant; - ddog_OwnedZendString allocation_key; - int32_t reason; - int32_t error_code; - bool do_log; - bool valid; -}; - typedef struct _zend_string *(*ddog_DynamicConfigUpdate)(ddog_CharSlice config, ddog_OwnedZendString value, enum ddog_DynamicConfigUpdateMode mode); @@ -704,14 +710,6 @@ typedef struct ddog_Vec_DebuggerPayload { */ typedef uint64_t ddog_QueueId; -typedef struct ddog_FfeAttribute { - ddog_CharSlice key; - int32_t value_type; - ddog_CharSlice string_value; - double number_value; - bool bool_value; -} ddog_FfeAttribute; - /** * A (key, value) pair for peer-service tags, borrowed from PHP/concentrator memory. */ @@ -945,18 +943,18 @@ typedef struct ddog_DebuggerValue ddog_DebuggerValue; #define ddog_EVALUATOR_RESULT_REDACTED (const void*)-2 -typedef enum ddog_FieldType { - DDOG_FIELD_TYPE_STATIC, - DDOG_FIELD_TYPE_ARG, - DDOG_FIELD_TYPE_LOCAL, -} ddog_FieldType; - typedef enum ddog_DebuggerType { DDOG_DEBUGGER_TYPE_DIAGNOSTICS, DDOG_DEBUGGER_TYPE_SNAPSHOTS, DDOG_DEBUGGER_TYPE_LOGS, } ddog_DebuggerType; +typedef enum ddog_FieldType { + DDOG_FIELD_TYPE_STATIC, + DDOG_FIELD_TYPE_ARG, + DDOG_FIELD_TYPE_LOCAL, +} ddog_FieldType; + typedef struct ddog_Entry ddog_Entry; typedef struct ddog_HashMap_CowStr__Value ddog_HashMap_CowStr__Value; @@ -1085,6 +1083,16 @@ typedef struct ddog_OwnedCharSlice { void (*free)(ddog_CharSlice); } ddog_OwnedCharSlice; +typedef enum ddog_LogLevel { + DDOG_LOG_LEVEL_ERROR, + DDOG_LOG_LEVEL_WARN, + DDOG_LOG_LEVEL_DEBUG, +} ddog_LogLevel; + +typedef enum ddog_TelemetryWorkerBuilderBoolProperty { + DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, +} ddog_TelemetryWorkerBuilderBoolProperty; + typedef enum ddog_TelemetryWorkerBuilderEndpointProperty { DDOG_TELEMETRY_WORKER_BUILDER_ENDPOINT_PROPERTY_CONFIG_ENDPOINT, } ddog_TelemetryWorkerBuilderEndpointProperty; @@ -1106,16 +1114,6 @@ typedef enum ddog_TelemetryWorkerBuilderStrProperty { DDOG_TELEMETRY_WORKER_BUILDER_STR_PROPERTY_ROOT_SESSION_ID, } ddog_TelemetryWorkerBuilderStrProperty; -typedef enum ddog_TelemetryWorkerBuilderBoolProperty { - DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, -} ddog_TelemetryWorkerBuilderBoolProperty; - -typedef enum ddog_LogLevel { - DDOG_LOG_LEVEL_ERROR, - DDOG_LOG_LEVEL_WARN, - DDOG_LOG_LEVEL_DEBUG, -} ddog_LogLevel; - typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; /** @@ -1223,6 +1221,39 @@ 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; + /** + * UTF-8 JSON object. Empty, invalid, or non-object JSON is serialized as + * an empty subject attribute object. + */ + 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 but not recommended for the pointer to be null when the 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. @@ -1257,25 +1288,37 @@ typedef struct ddog_SenderParameters { ddog_CharSlice url; } ddog_SenderParameters; +typedef enum ddog_crasht_BuildIdType { + DDOG_CRASHT_BUILD_ID_TYPE_GNU, + DDOG_CRASHT_BUILD_ID_TYPE_GO, + DDOG_CRASHT_BUILD_ID_TYPE_PDB, + DDOG_CRASHT_BUILD_ID_TYPE_SHA1, +} ddog_crasht_BuildIdType; + /** - * Stacktrace collection occurs in the context of a crashing process. - * If the stack is sufficiently corruputed, it is possible (but unlikely), - * for stack trace collection itself to crash. - * We recommend fully enabling stacktrace collection, but having an environment - * variable to allow downgrading the collector. + * Result type for runtime callback registration */ -typedef enum ddog_crasht_StacktraceCollection { - DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, - DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, - /** - * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information - * and also unwind inlined functions. Enabling this feature will not only provide symbolic - * details, but may also yield additional or less stack frames compared to other - * configurations. - */ - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, -} ddog_crasht_StacktraceCollection; +typedef enum ddog_crasht_CallbackResult { + DDOG_CRASHT_CALLBACK_RESULT_OK, + DDOG_CRASHT_CALLBACK_RESULT_ERROR, +} ddog_crasht_CallbackResult; + +typedef enum ddog_crasht_DemangleOptions { + DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, + DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, +} ddog_crasht_DemangleOptions; + +typedef enum ddog_crasht_ErrorKind { + DDOG_CRASHT_ERROR_KIND_PANIC, + DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, + DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, +} ddog_crasht_ErrorKind; + +typedef enum ddog_crasht_FileType { + DDOG_CRASHT_FILE_TYPE_APK, + DDOG_CRASHT_FILE_TYPE_ELF, + DDOG_CRASHT_FILE_TYPE_PE, +} ddog_crasht_FileType; /** * This enum represents operations a the tracked library might be engaged in. @@ -1300,12 +1343,6 @@ typedef enum ddog_crasht_OpTypes { DDOG_CRASHT_OP_TYPES_SIZE, } ddog_crasht_OpTypes; -typedef enum ddog_crasht_ErrorKind { - DDOG_CRASHT_ERROR_KIND_PANIC, - DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, - DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, -} ddog_crasht_ErrorKind; - /** * See https://man7.org/linux/man-pages/man2/sigaction.2.html * MUST REMAIN IN SYNC WITH THE ENUM IN emit_sigcodes.c @@ -1378,31 +1415,25 @@ typedef enum ddog_crasht_SignalNames { DDOG_CRASHT_SIGNAL_NAMES_UNKNOWN, } ddog_crasht_SignalNames; -typedef enum ddog_crasht_BuildIdType { - DDOG_CRASHT_BUILD_ID_TYPE_GNU, - DDOG_CRASHT_BUILD_ID_TYPE_GO, - DDOG_CRASHT_BUILD_ID_TYPE_PDB, - DDOG_CRASHT_BUILD_ID_TYPE_SHA1, -} ddog_crasht_BuildIdType; - -typedef enum ddog_crasht_FileType { - DDOG_CRASHT_FILE_TYPE_APK, - DDOG_CRASHT_FILE_TYPE_ELF, - DDOG_CRASHT_FILE_TYPE_PE, -} ddog_crasht_FileType; - -typedef enum ddog_crasht_DemangleOptions { - DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, - DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, -} ddog_crasht_DemangleOptions; - /** - * Result type for runtime callback registration + * Stacktrace collection occurs in the context of a crashing process. + * If the stack is sufficiently corruputed, it is possible (but unlikely), + * for stack trace collection itself to crash. + * We recommend fully enabling stacktrace collection, but having an environment + * variable to allow downgrading the collector. */ -typedef enum ddog_crasht_CallbackResult { - DDOG_CRASHT_CALLBACK_RESULT_OK, - DDOG_CRASHT_CALLBACK_RESULT_ERROR, -} ddog_crasht_CallbackResult; +typedef enum ddog_crasht_StacktraceCollection { + DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, + DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, + /** + * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information + * and also unwind inlined functions. Enabling this feature will not only provide symbolic + * details, but may also yield additional or less stack frames compared to other + * configurations. + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, +} ddog_crasht_StacktraceCollection; typedef struct ddog_crasht_CrashInfo ddog_crasht_CrashInfo; diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index cbcd6a9d02..1d6d866a13 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -61,18 +61,6 @@ int posix_spawn_file_actions_addchdir_np(void *file_actions, const char *path); uint64_t dd_fnv1a_64(const uint8_t *data, uintptr_t len); -bool ddog_ffe_load_config(ddog_CharSlice json); - -bool ddog_ffe_has_config(void); - -uint64_t ddog_ffe_config_version(void); - -struct ddog_FfeResult ddog_ffe_evaluate(ddog_CharSlice flag_key, - int32_t expected_type, - ddog_CharSlice targeting_key, - const struct ddog_FfeAttribute *attributes, - uintptr_t attributes_count); - const char *ddog_normalize_process_tag_value(ddog_CharSlice tag_value); void ddog_free_normalized_tag_value(const char *ptr); @@ -118,6 +106,18 @@ void ddog_agent_info_json_free(char *ptr); */ void ddog_apply_agent_info_concentrator_config(struct ddog_AgentInfoReader *reader); +bool ddog_ffe_load_config(ddog_CharSlice json); + +bool ddog_ffe_has_config(void); + +uint64_t ddog_ffe_config_version(void); + +struct ddog_FfeResult ddog_ffe_evaluate(ddog_CharSlice flag_key, + int32_t expected_type, + ddog_CharSlice targeting_key, + const struct ddog_FfeAttribute *attributes, + uintptr_t attributes_count); + bool ddog_shall_log(enum ddog_Log category); void ddog_set_error_log_level(bool once); diff --git a/components-rs/ffe.rs b/components-rs/ffe.rs index 33b132af7a..83530ee00c 100644 --- a/components-rs/ffe.rs +++ b/components-rs/ffe.rs @@ -1,4 +1,4 @@ -use crate::bytes::OwnedZendString; +use crate::bytes::{OwnedZendString, ZendString}; use datadog_ffe::rules_based::{ self as ffe, AssignmentReason, AssignmentValue, Attribute, Configuration, EvaluationContext, EvaluationError, ExpectedFlagType, Str, UniversalFlagConfig, @@ -6,6 +6,7 @@ use datadog_ffe::rules_based::{ use libdd_common_ffi::slice::{AsBytes, CharSlice}; use std::cell::RefCell; use std::collections::HashMap; +use std::ptr::NonNull; use std::sync::Arc; struct FfeState { @@ -92,9 +93,9 @@ const TYPE_OBJECT: i32 = 4; #[repr(C)] pub struct FfeResult { - pub value_json: Option, - pub variant: Option, - pub allocation_key: Option, + pub value_json: Option>, + pub variant: Option>, + pub allocation_key: Option>, pub reason: i32, pub error_code: i32, pub do_log: bool, @@ -210,9 +211,9 @@ fn result_from_assignment(assignment: Result) Ok(assignment) => { let value_json = assignment_value_to_json(&assignment.value); FfeResult { - value_json: Some(value_json.as_str().into()), - variant: Some(assignment.variation_key.as_str().into()), - allocation_key: Some(assignment.allocation_key.as_str().into()), + value_json: result_string(value_json.as_str().into()), + variant: result_string(assignment.variation_key.as_str().into()), + allocation_key: result_string(assignment.allocation_key.as_str().into()), reason: match assignment.reason { AssignmentReason::Static => REASON_STATIC, AssignmentReason::TargetingMatch => REASON_TARGETING_MATCH, @@ -237,7 +238,7 @@ fn result_from_assignment(assignment: Result) }; FfeResult { - value_json: Some("null".into()), + value_json: result_string("null".into()), variant: None, allocation_key: None, reason, @@ -249,6 +250,12 @@ fn result_from_assignment(assignment: Result) } } +fn result_string(value: OwnedZendString) -> Option> { + let ptr = value.0; + std::mem::forget(value); + Some(ptr) +} + fn invalid_result() -> FfeResult { FfeResult { value_json: None, @@ -410,8 +417,9 @@ mod tests { assert_eq!(result.reason, REASON_SPLIT); assert_eq!(result.error_code, ERROR_NONE); assert_eq!(result.do_log, true); + let value_json = unsafe { result.value_json.unwrap().as_ref() }; assert_eq!( - std::str::from_utf8(result.value_json.as_ref().unwrap().as_ref()).unwrap(), + std::str::from_utf8(value_json.as_ref()).unwrap(), r#""empty-targeting-key""# ); clear_config(); diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 4a3aa61741..14887dfd3c 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -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, @@ -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); @@ -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); @@ -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); @@ -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 */ diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 08cabc95e2..9a5a93bd8f 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -525,7 +525,11 @@ static void ddtrace_activate(void) { pthread_once(&dd_activate_once_control, dd_activate_once); zai_config_rinit(); - if (!ddtrace_disable && (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER())) { + if (!ddtrace_disable && ( + get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || + get_global_DD_TRACE_SIDECAR_TRACE_SENDER() || + get_global_DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED() + )) { ddtrace_sidecar_ensure_active(); } @@ -1932,6 +1936,8 @@ static PHP_RSHUTDOWN_FUNCTION(ddtrace) { ddtrace_rshutdown_remote_config(); } + ddtrace_ffe_flush_exposures(); + if (!ddtrace_disable) { ddtrace_autoload_rshutdown(); @@ -2769,6 +2775,12 @@ PHP_FUNCTION(DDTrace_Internal_handle_fork) { dd_internal_handle_fork(); } +PHP_FUNCTION(DDTrace_Internal_flush_ffe_exposures) { + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_BOOL(ddtrace_ffe_flush_exposures()); +} + PHP_FUNCTION(DDTrace_dogstatsd_count) { zend_string *metric; zend_long value; @@ -2975,6 +2987,16 @@ PHP_FUNCTION(DDTrace_Testing_ffe_load_config) { RETURN_BOOL(ddog_ffe_load_config(dd_zend_string_to_CharSlice(json))); } +static zend_string *ddtrace_ffe_attributes_json(zval *attrs_zv) { + smart_str buf = {0}; + zai_json_encode(&buf, attrs_zv, 0); + if (!buf.s) { + return zend_string_init("{}", sizeof("{}") - 1, 0); + } + smart_str_0(&buf); + return smart_str_extract(&buf); +} + static void ddtrace_ffe_update_property(zval *object, const char *name, size_t name_len, zval *value) { zend_string *property_name = zend_string_init(name, name_len, 0); zend_update_property_ex(ddtrace_ce_ffe_result, Z_OBJ_P(object), property_name, value); @@ -3017,6 +3039,22 @@ static void ddtrace_ffe_update_empty_array_property(zval *object, const char *na zval_ptr_dtor(&property_value); } +static void ddtrace_ffe_refresh_remote_config(void) { + if (!DDTRACE_G(remote_config_state)) { + return; + } + + if (DDTRACE_G(reread_remote_configuration)) { + DDTRACE_G(reread_remote_configuration) = 0; + ddog_process_remote_configs(DDTRACE_G(remote_config_state)); + return; + } + + if (!ddog_ffe_has_config()) { + ddtrace_check_for_new_config_now(); + } +} + PHP_FUNCTION(DDTrace_ffe_evaluate) { zend_string *flag_key; zend_long type_id_zl; @@ -3032,6 +3070,9 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) { zend_string *key; zval *value; struct ddog_FfeResult result; + zend_string *value_json; + zend_string *variant; + zend_string *allocation_key; ZEND_PARSE_PARAMETERS_START(4, 4) Z_PARAM_STR(flag_key) @@ -3041,6 +3082,7 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) { ZEND_PARSE_PARAMETERS_END(); type_id = (int32_t) type_id_zl; + ddtrace_ffe_refresh_remote_config(); attributes = Z_ARRVAL_P(attrs_zv); attrs_count = zend_hash_num_elements(attributes); @@ -3117,10 +3159,28 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) { RETURN_NULL(); } + value_json = result.value_json; + variant = result.variant; + allocation_key = result.allocation_key; + + if (result.do_log && allocation_key && variant) { + zend_string *subject_attributes_json = ddtrace_ffe_attributes_json(attrs_zv); + ddtrace_ffe_record_exposure( + ZSTR_VAL(flag_key), + ZSTR_LEN(flag_key), + targeting_key ? ZSTR_VAL(targeting_key) : NULL, + targeting_key ? ZSTR_LEN(targeting_key) : 0, + subject_attributes_json, + ZSTR_VAL(allocation_key), + ZSTR_VAL(variant) + ); + zend_string_release(subject_attributes_json); + } + object_init_ex(return_value, ddtrace_ce_ffe_result); - ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("valueJson"), result.value_json); - ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("variant"), result.variant); - ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("allocationKey"), result.allocation_key); + ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("valueJson"), value_json); + ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("variant"), variant); + ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("allocationKey"), allocation_key); ddtrace_ffe_update_long_property(return_value, ZEND_STRL("reason"), result.reason); ddtrace_ffe_update_long_property(return_value, ZEND_STRL("errorCode"), result.error_code); ddtrace_ffe_update_bool_property(return_value, ZEND_STRL("doLog"), result.do_log); diff --git a/ext/ddtrace.h b/ext/ddtrace.h index 6f9e5cc08c..3ee560c8d9 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -161,6 +161,9 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace) ddog_SidecarTransport *sidecar; ddog_QueueId sidecar_queue_id; MUTEX_T sidecar_universal_service_tags_mutex; + void *ffe_exposure_buffer; + size_t ffe_exposure_buffer_len; + size_t ffe_exposure_buffer_cap; ddog_AgentRemoteConfigReader *agent_config_reader; ddog_RemoteConfigState *remote_config_state; bool remote_config_writing; // true while RC WRITE mode INI update is in progress diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index ee00a8ee3b..23e798f3c9 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -1061,6 +1061,13 @@ function add_span_flag(\DDTrace\SpanData $span, int $flag): void {} */ function handle_fork(): void {} + /** + * Flushes native FFE exposure batches for integration tests. + * + * @internal + */ + function flush_ffe_exposures(): bool {} + } namespace datadog\appsec\v2 { diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 9722e64a32..34c1e64b21 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -244,6 +244,8 @@ ZEND_END_ARG_INFO() #define arginfo_DDTrace_Internal_handle_fork arginfo_DDTrace_flush +#define arginfo_DDTrace_Internal_flush_ffe_exposures arginfo_DDTrace_are_endpoints_collected + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_v2_track_user_login_success, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, login, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, user, MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_NULL, "null") @@ -428,6 +430,7 @@ ZEND_FUNCTION(DDTrace_Testing_emit_asm_event); ZEND_FUNCTION(DDTrace_Testing_normalize_tag_value); ZEND_FUNCTION(DDTrace_Internal_add_span_flag); ZEND_FUNCTION(DDTrace_Internal_handle_fork); +ZEND_FUNCTION(DDTrace_Internal_flush_ffe_exposures); ZEND_FUNCTION(datadog_appsec_v2_track_user_login_success); ZEND_FUNCTION(datadog_appsec_v2_track_user_login_failure); ZEND_FUNCTION(dd_trace_env_config); @@ -527,6 +530,7 @@ static const zend_function_entry ext_functions[] = { 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) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "add_span_flag"), zif_DDTrace_Internal_add_span_flag, arginfo_DDTrace_Internal_add_span_flag, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "handle_fork"), zif_DDTrace_Internal_handle_fork, arginfo_DDTrace_Internal_handle_fork, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "flush_ffe_exposures"), zif_DDTrace_Internal_flush_ffe_exposures, arginfo_DDTrace_Internal_flush_ffe_exposures, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("datadog\\appsec\\v2", "track_user_login_success"), zif_datadog_appsec_v2_track_user_login_success, arginfo_datadog_appsec_v2_track_user_login_success, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("datadog\\appsec\\v2", "track_user_login_failure"), zif_datadog_appsec_v2_track_user_login_failure, arginfo_datadog_appsec_v2_track_user_login_failure, 0, NULL, NULL) ZEND_FE(dd_trace_env_config, arginfo_dd_trace_env_config) diff --git a/ext/sidecar.c b/ext/sidecar.c index 1713e86691..c72ebac4a4 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -18,6 +18,7 @@ #include "serializer.h" #include "remote_config.h" #include "process_tags.h" +#include "span.h" #ifndef _WIN32 #include "coms.h" #endif @@ -28,6 +29,51 @@ ddog_Endpoint *ddtrace_endpoint; ddog_Endpoint *dogstatsd_endpoint; // always set when ddtrace_endpoint is set struct ddog_InstanceId *ddtrace_sidecar_instance_id; +#define DDTRACE_FFE_EXPOSURE_BUFFER_LIMIT 1000 + +typedef struct { + uint64_t timestamp_ms; + zend_string *flag_key; + zend_string *subject_id; + zend_string *subject_attributes_json; + zend_string *allocation_key; + zend_string *variant; +} ddtrace_ffe_exposure; + +static void ddtrace_ffe_release_exposure(ddtrace_ffe_exposure *exposure) { + if (!exposure) { + return; + } + if (exposure->flag_key) { + zend_string_release(exposure->flag_key); + } + if (exposure->subject_id) { + zend_string_release(exposure->subject_id); + } + if (exposure->subject_attributes_json) { + zend_string_release(exposure->subject_attributes_json); + } + if (exposure->allocation_key) { + zend_string_release(exposure->allocation_key); + } + if (exposure->variant) { + zend_string_release(exposure->variant); + } +} + +void ddtrace_ffe_clear_exposures(void) { + ddtrace_ffe_exposure *buffer = (ddtrace_ffe_exposure *) DDTRACE_G(ffe_exposure_buffer); + for (size_t i = 0; i < DDTRACE_G(ffe_exposure_buffer_len); i++) { + ddtrace_ffe_release_exposure(&buffer[i]); + } + if (buffer) { + efree(buffer); + } + DDTRACE_G(ffe_exposure_buffer) = NULL; + DDTRACE_G(ffe_exposure_buffer_len) = 0; + DDTRACE_G(ffe_exposure_buffer_cap) = 0; +} + // Best-effort pointer for the signal handler (SIGTERM/SIGINT). Set to the first // per-thread connection; never cleared until MSHUTDOWN. Not atomic: concurrent // shutdown is already a best-effort race for signal handlers, so atomicity of @@ -675,6 +721,96 @@ void ddtrace_sidecar_dogstatsd_count(zend_string *metric, zend_long value, zval ddog_Vec_Tag_drop(vec); } +void ddtrace_ffe_record_exposure( + const char *flag_key, + size_t flag_key_len, + const char *targeting_key, + size_t targeting_key_len, + zend_string *subject_attributes_json, + const char *allocation_key, + const char *variant +) { + if (!flag_key || flag_key_len == 0 || !allocation_key || !variant || variant[0] == '\0') { + return; + } + + if (DDTRACE_G(ffe_exposure_buffer_len) >= DDTRACE_FFE_EXPOSURE_BUFFER_LIMIT) { + return; + } + + if (DDTRACE_G(ffe_exposure_buffer_len) == DDTRACE_G(ffe_exposure_buffer_cap)) { + size_t new_cap = DDTRACE_G(ffe_exposure_buffer_cap) == 0 ? 8 : DDTRACE_G(ffe_exposure_buffer_cap) * 2; + if (new_cap > DDTRACE_FFE_EXPOSURE_BUFFER_LIMIT) { + new_cap = DDTRACE_FFE_EXPOSURE_BUFFER_LIMIT; + } + DDTRACE_G(ffe_exposure_buffer) = safe_erealloc( + DDTRACE_G(ffe_exposure_buffer), + new_cap, + sizeof(ddtrace_ffe_exposure), + 0 + ); + DDTRACE_G(ffe_exposure_buffer_cap) = new_cap; + } + + ddtrace_ffe_exposure *buffer = (ddtrace_ffe_exposure *) DDTRACE_G(ffe_exposure_buffer); + ddtrace_ffe_exposure *exposure = &buffer[DDTRACE_G(ffe_exposure_buffer_len)++]; + exposure->timestamp_ms = ddtrace_nanoseconds_realtime() / 1000000; + exposure->flag_key = zend_string_init(flag_key, flag_key_len, 0); + exposure->subject_id = zend_string_init(targeting_key ? targeting_key : "", targeting_key ? targeting_key_len : 0, 0); + exposure->subject_attributes_json = subject_attributes_json ? zend_string_copy(subject_attributes_json) : zend_string_init("{}", sizeof("{}") - 1, 0); + exposure->allocation_key = zend_string_init(allocation_key, strlen(allocation_key), 0); + exposure->variant = zend_string_init(variant, strlen(variant), 0); +} + +bool ddtrace_ffe_flush_exposures(void) { + size_t exposure_count = DDTRACE_G(ffe_exposure_buffer_len); + ddtrace_ffe_exposure *buffer = (ddtrace_ffe_exposure *) DDTRACE_G(ffe_exposure_buffer); + + if (exposure_count == 0 || !buffer) { + return false; + } + + if (!DDTRACE_G(sidecar) || !ddtrace_sidecar_instance_id || !DDTRACE_G(sidecar_queue_id)) { + ddtrace_ffe_clear_exposures(); + return false; + } + + ddog_FfeExposure *ffi_exposures = safe_emalloc(exposure_count, sizeof(ddog_FfeExposure), 0); + for (size_t i = 0; i < exposure_count; i++) { + ffi_exposures[i] = (ddog_FfeExposure) { + .timestamp_ms = buffer[i].timestamp_ms, + .flag_key = dd_zend_string_to_CharSlice(buffer[i].flag_key), + .subject_id = dd_zend_string_to_CharSlice(buffer[i].subject_id), + .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), + }; + } + + ddog_FfeTelemetryContext context = { + .service = dd_zend_string_to_CharSlice(get_DD_SERVICE()), + .env = dd_zend_string_to_CharSlice(get_DD_ENV()), + .version = dd_zend_string_to_CharSlice(get_DD_VERSION()), + }; + ddog_Slice_FfeExposure exposure_slice = { + .ptr = ffi_exposures, + .len = exposure_count, + }; + + bool flushed = ddtrace_ffi_try( + "Failed sending FFE exposure batch to sidecar", + ddog_sidecar_send_ffe_exposure_batch( + &DDTRACE_G(sidecar), + ddtrace_sidecar_instance_id, + &DDTRACE_G(sidecar_queue_id), + &context, + exposure_slice)); + + efree(ffi_exposures); + ddtrace_ffe_clear_exposures(); + return flushed; +} + void ddtrace_sidecar_dogstatsd_distribution(zend_string *metric, double value, zval *tags) { if (!DDTRACE_G(sidecar) || !get_DD_INTEGRATION_METRICS_ENABLED()) { return; diff --git a/ext/sidecar.h b/ext/sidecar.h index 8db3a57db5..d9d5944b38 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -72,6 +72,10 @@ void ddtrace_sidecar_dogstatsd_gauge(zend_string *metric, double value, zval *ta void ddtrace_sidecar_dogstatsd_histogram(zend_string *metric, double value, zval *tags); void ddtrace_sidecar_dogstatsd_set(zend_string *metric, zend_long value, zval *tags); +void ddtrace_ffe_record_exposure(const char *flag_key, size_t flag_key_len, const char *targeting_key, size_t targeting_key_len, zend_string *subject_attributes_json, const char *allocation_key, const char *variant); +bool ddtrace_ffe_flush_exposures(void); +void ddtrace_ffe_clear_exposures(void); + bool ddtrace_alter_test_session_token(zval *old_value, zval *new_value, zend_string *new_str); static inline ddog_CharSlice dd_zend_string_to_CharSlice(zend_string *str) { diff --git a/libdatadog b/libdatadog index cea1e44edd..6d23848a80 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit cea1e44edddd9124f75d5095f31026904a1f58d8 +Subproject commit 6d23848a80b3aa5dab8de2323d434f7cc1eb2fe7 diff --git a/tests/ext/ffe/native_bridge_evaluate.phpt b/tests/ext/ffe/native_bridge_evaluate.phpt index a136623d27..c8a89c1c51 100644 --- a/tests/ext/ffe/native_bridge_evaluate.phpt +++ b/tests/ext/ffe/native_bridge_evaluate.phpt @@ -7,6 +7,8 @@ function show($label, $value) { } show('has_config_before', \DDTrace\ffe_has_config()); +show('native_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' @@ -126,6 +128,8 @@ show('parse_error', \DDTrace\ffe_evaluate('bad.flag', 0, 'user-1', array())); ?> --EXPECT-- has_config_before=false +native_exposure_flush_exists=true +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 diff --git a/tooling/bin/build-debug-artifact b/tooling/bin/build-debug-artifact index 135f5392aa..3836a58a94 100755 --- a/tooling/bin/build-debug-artifact +++ b/tooling/bin/build-debug-artifact @@ -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 @@ -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 ────────────────────────────────────────────────