From ff4923f7ad50652a99d1a7e762b0b509fe669318 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Wed, 22 Jul 2026 09:29:29 -0600 Subject: [PATCH 1/7] Publish OTel process and thread context from PHP --- Cargo.lock | 150 ++++-- appsec/cmake/extension.cmake | 1 + cbindgen.toml | 6 + components-rs/Cargo.toml | 5 + components-rs/common.h | 63 ++- components-rs/datadog.h | 145 +++--- components-rs/lib.rs | 366 +++++++++++++- components-rs/remote_config.rs | 189 +++++--- components-rs/sidecar.h | 5 - components-rs/sidecar.rs | 168 +++++-- components-rs/stats.rs | 53 +- components-rs/telemetry.h | 56 ++- .../context-discovery/context_discovery.c | 67 +++ .../context-discovery/context_discovery.h | 25 + config.m4 | 1 + config.w32 | 1 + datadog-windows.sym | 2 + datadog.sym | 4 +- ext/datadog.c | 37 ++ ext/datadog.h | 1 + ext/signals.c | 20 +- libdatadog | 2 +- profiling/Cargo.toml | 1 + profiling/build.rs | 11 +- profiling/src/bindings/mod.rs | 15 + profiling/src/lib.rs | 28 ++ profiling/src/module_globals.rs | 16 +- profiling/src/php_ffi.c | 241 ++++++++-- profiling/src/php_ffi.h | 50 +- profiling/src/profiling/mod.rs | 453 +++++++++++++++--- tracer/ddtrace.c | 16 +- tracer/distributed_tracing_headers.c | 2 + tracer/functions.c | 14 + tracer/handlers_fiber.c | 2 + tracer/otel_context.c | 299 ++++++++++++ tracer/otel_context.h | 93 ++++ tracer/profiling.c | 17 +- tracer/profiling.h | 8 - tracer/serializer.c | 2 +- tracer/span.c | 21 + tracer/span.h | 2 + 41 files changed, 2213 insertions(+), 445 deletions(-) create mode 100644 components/context-discovery/context_discovery.c create mode 100644 components/context-discovery/context_discovery.h create mode 100644 tracer/otel_context.c create mode 100644 tracer/otel_context.h diff --git a/Cargo.lock b/Cargo.lock index 55e8f08e197..1a41f590a73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.2", "once_cell", "version_check 0.9.5", "zerocopy 0.8.24", @@ -1334,15 +1333,20 @@ dependencies = [ "itertools 0.11.0", "lazy_static", "libc 0.2.186", + "libdd-alloc", "libdd-common", "libdd-common-ffi", + "libdd-crashtracker", "libdd-crashtracker-ffi", "libdd-data-pipeline", + "libdd-library-config", "libdd-library-config-ffi", + "libdd-otel-thread-ctx-ffi", "libdd-remote-config", "libdd-telemetry", "libdd-telemetry-ffi", "libdd-tinybytes", + "libdd-trace-protobuf", "libdd-trace-stats", "libdd-trace-utils", "log", @@ -1382,6 +1386,7 @@ dependencies = [ "libc 0.2.186", "libdd-alloc", "libdd-common", + "libdd-library-config", "libdd-library-config-ffi", "libdd-profiling", "log", @@ -1648,6 +1653,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1871,9 +1882,9 @@ checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1886,9 +1897,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1896,15 +1907,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1913,15 +1924,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", @@ -1930,15 +1941,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-timer" @@ -1948,9 +1959,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -1960,7 +1971,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2768,7 +2778,7 @@ dependencies = [ [[package]] name = "libdd-capabilities" -version = "2.0.0" +version = "2.1.0" dependencies = [ "anyhow", "bytes", @@ -2778,8 +2788,9 @@ dependencies = [ [[package]] name = "libdd-capabilities-impl" -version = "2.0.0" +version = "3.0.0" dependencies = [ + "anyhow", "bytes", "http", "http-body-util", @@ -2790,7 +2801,7 @@ dependencies = [ [[package]] name = "libdd-common" -version = "4.2.0" +version = "5.1.0" dependencies = [ "anyhow", "bytes", @@ -2822,6 +2833,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-platform-verifier", + "rustls-webpki", "serde", "static_assertions", "tempfile", @@ -2863,6 +2875,8 @@ dependencies = [ "goblin", "http", "libc 0.2.186", + "libdd-capabilities", + "libdd-capabilities-impl", "libdd-common", "libdd-libunwind-sys", "libdd-telemetry", @@ -2906,7 +2920,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" -version = "6.0.0" +version = "7.0.0" dependencies = [ "anyhow", "arc-swap", @@ -2916,6 +2930,7 @@ dependencies = [ "criterion", "duplicate 2.0.1", "either", + "futures", "getrandom 0.2.15", "http", "http-body-util", @@ -2934,6 +2949,7 @@ dependencies = [ "libdd-trace-protobuf", "libdd-trace-stats", "libdd-trace-utils", + "prost", "rand 0.8.5", "regex", "rmp-serde", @@ -2945,11 +2961,12 @@ dependencies = [ "tokio-util", "tracing", "uuid", + "web-time", ] [[package]] name = "libdd-ddsketch" -version = "1.0.1" +version = "1.1.0" dependencies = [ "criterion", "prost", @@ -2961,7 +2978,7 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" -version = "3.0.0" +version = "4.0.0" dependencies = [ "anyhow", "cadence", @@ -2974,7 +2991,7 @@ dependencies = [ [[package]] name = "libdd-library-config" -version = "2.0.0" +version = "3.0.0" dependencies = [ "anyhow", "libc 0.2.186", @@ -3025,6 +3042,25 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "libdd-otel-thread-ctx" +version = "1.0.0" +dependencies = [ + "anyhow", + "elf", + "libdd-otel-thread-ctx", + "object 0.36.7", +] + +[[package]] +name = "libdd-otel-thread-ctx-ffi" +version = "1.0.0" +dependencies = [ + "build_common", + "libdd-common-ffi", + "libdd-otel-thread-ctx", +] + [[package]] name = "libdd-profiling" version = "1.0.0" @@ -3082,7 +3118,7 @@ dependencies = [ [[package]] name = "libdd-remote-config" -version = "0.1.0" +version = "2.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3113,7 +3149,7 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" -version = "1.0.0" +version = "2.0.0" dependencies = [ "async-trait", "futures", @@ -3129,18 +3165,20 @@ dependencies = [ [[package]] name = "libdd-telemetry" -version = "5.0.1" +version = "6.0.0" dependencies = [ "anyhow", "async-trait", "base64 0.22.1", "bytes", "futures", + "getrandom 0.2.15", "hashbrown 0.15.2", "http", - "http-body-util", "httpmock", "libc 0.2.186", + "libdd-capabilities", + "libdd-capabilities-impl", "libdd-common", "libdd-ddsketch", "libdd-shared-runtime", @@ -3152,6 +3190,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "web-time", "winver", ] @@ -3162,6 +3201,7 @@ dependencies = [ "build_common", "function_name", "libc 0.2.186", + "libdd-capabilities-impl", "libdd-common", "libdd-common-ffi", "libdd-telemetry", @@ -3186,7 +3226,7 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" -version = "2.0.0" +version = "3.0.0" dependencies = [ "anyhow", "arbitrary", @@ -3198,7 +3238,7 @@ dependencies = [ [[package]] name = "libdd-trace-obfuscation" -version = "4.0.0" +version = "5.0.0" dependencies = [ "anyhow", "criterion", @@ -3215,7 +3255,7 @@ dependencies = [ [[package]] name = "libdd-trace-protobuf" -version = "3.0.2" +version = "4.0.0" dependencies = [ "bolero", "prost", @@ -3229,12 +3269,13 @@ dependencies = [ [[package]] name = "libdd-trace-stats" -version = "5.0.0" +version = "6.0.0" dependencies = [ "anyhow", "arc-swap", "async-trait", "criterion", + "futures", "hashbrown 0.15.2", "http", "httpmock", @@ -3242,7 +3283,9 @@ dependencies = [ "libdd-capabilities-impl", "libdd-common", "libdd-ddsketch", + "libdd-dogstatsd-client", "libdd-shared-runtime", + "libdd-telemetry", "libdd-trace-obfuscation", "libdd-trace-protobuf", "libdd-trace-utils", @@ -3252,11 +3295,12 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "web-time", ] [[package]] name = "libdd-trace-utils" -version = "8.0.0" +version = "9.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3268,12 +3312,14 @@ dependencies = [ "flate2", "futures", "getrandom 0.2.15", + "hex", "http", "http-body", "http-body-util", "httpmock", "hyper", "indexmap 2.12.1", + "itoa", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", @@ -3288,6 +3334,7 @@ dependencies = [ "rmpv", "rustc-hash 2.1.3", "serde", + "serde-transcode", "serde_json", "tempfile", "thin-vec", @@ -4028,12 +4075,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.32" @@ -4104,7 +4145,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" dependencies = [ - "libc 0.1.12", + "libc 0.2.186", "nix 0.30.1", ] @@ -4871,9 +4912,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -5066,6 +5107,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-transcode" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" +dependencies = [ + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.15" @@ -6376,6 +6426,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-root-certs" version = "1.0.6" diff --git a/appsec/cmake/extension.cmake b/appsec/cmake/extension.cmake index a3bbdf4fb97..18244ee3ac7 100644 --- a/appsec/cmake/extension.cmake +++ b/appsec/cmake/extension.cmake @@ -51,6 +51,7 @@ set_target_properties(zai PROPERTIES POSITION_INDEPENDENT_CODE 1) include(cmake/pcre2.cmake) file(GLOB_RECURSE EXT_SOURCE ${EXT_SOURCE_DIR}/*.c ${EXT_SOURCE_DIR}/*.cpp) +list(APPEND EXT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../components/context-discovery/context_discovery.c) add_library(extension SHARED ${EXT_SOURCE}) set_target_properties(extension PROPERTIES C_VISIBILITY_PRESET hidden diff --git a/cbindgen.toml b/cbindgen.toml index 2d4ec6b3588..9533815d510 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -24,6 +24,9 @@ rename_types = "PascalCase" "ZendString" = "_zend_string" "FILE" = "FILE" "EndpointConfig" = "ddog_crasht_EndpointConfig" +"Config" = "ddog_crasht_Config" +"Metadata" = "ddog_crasht_Metadata" +"AtomicU8" = "uint8_t" "MaybeOwnedZendString" = "_zend_string *" [enum] @@ -43,5 +46,8 @@ include = [ "datadog-live-debugger", "datadog-live-debugger-ffi", "libdd-remote-config", + "libdd-library-config", + "libdd-otel-thread-ctx", + "libdd-otel-thread-ctx-ffi", "uuid" ] diff --git a/components-rs/Cargo.toml b/components-rs/Cargo.toml index c4b8de1c341..c8e177eec9b 100644 --- a/components-rs/Cargo.toml +++ b/components-rs/Cargo.toml @@ -23,8 +23,13 @@ libdd-data-pipeline = { path = "../libdatadog/libdd-data-pipeline" } libdd-tinybytes = { path = "../libdatadog/libdd-tinybytes" } libdd-trace-utils = { path = "../libdatadog/libdd-trace-utils" } libdd-trace-stats = { path = "../libdatadog/libdd-trace-stats" } +libdd-crashtracker = { path = "../libdatadog/libdd-crashtracker" } libdd-crashtracker-ffi = { path = "../libdatadog/libdd-crashtracker-ffi", default-features = false, features = ["collector"] } libdd-library-config-ffi = { path = "../libdatadog/libdd-library-config-ffi", default-features = false } +libdd-library-config = { path = "../libdatadog/libdd-library-config", default-features = false, features = ["otel-thread-ctx"] } +libdd-trace-protobuf = { path = "../libdatadog/libdd-trace-protobuf" } +libdd-otel-thread-ctx-ffi = { path = "../libdatadog/libdd-otel-thread-ctx-ffi", default-features = false, features = ["tls-storage"] } +libdd-alloc = { path = "../libdatadog/libdd-alloc" } spawn_worker = { path = "../libdatadog/spawn_worker" } anyhow = { version = "1.0" } const-str = "0.5.6" diff --git a/components-rs/common.h b/components-rs/common.h index 9a148cb5d8b..e578f3f4c9b 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -412,8 +412,11 @@ typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_ENABLE_LIVE_DEBUGGING = 41, DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_DD_MULTICONFIG = 42, DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_TRACE_TAGGING_RULES = 43, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_EXTENDED_DATA_COLLECTION = 44, DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_MULTICONFIG = 45, DDOG_REMOTE_CONFIG_CAPABILITIES_FFE_FLAG_CONFIGURATION_RULES = 46, + DDOG_REMOTE_CONFIG_CAPABILITIES_DD_DATA_STREAMS_TRANSACTION_EXTRACTORS = 47, + DDOG_REMOTE_CONFIG_CAPABILITIES_LLM_OBS_ACTIVATION = 48, } ddog_RemoteConfigCapabilities; typedef enum ddog_RemoteConfigProduct { @@ -426,6 +429,7 @@ typedef enum ddog_RemoteConfigProduct { DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS, DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, + DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER_SYMBOL_DB, } ddog_RemoteConfigProduct; typedef enum ddog_SpanProbeTarget { @@ -435,6 +439,10 @@ typedef enum ddog_SpanProbeTarget { typedef struct ddog_AgentInfoReader ddog_AgentInfoReader; +typedef struct ddog_crasht_Config ddog_crasht_Config; + +typedef struct ddog_Configurator ddog_Configurator; + typedef struct ddog_DebuggerPayload ddog_DebuggerPayload; typedef struct ddog_DslString ddog_DslString; @@ -448,6 +456,8 @@ typedef struct ddog_InstanceId ddog_InstanceId; typedef struct ddog_MaybeShmLimiter ddog_MaybeShmLimiter; +typedef struct ddog_PhpOtelProcessContext ddog_PhpOtelProcessContext; + typedef struct ddog_ProbeCondition ddog_ProbeCondition; typedef struct ddog_ProbeValue ddog_ProbeValue; @@ -478,6 +488,8 @@ typedef struct ddog_SidecarTransport ddog_SidecarTransport; */ typedef struct ddog_SpanConcentrator ddog_SpanConcentrator; +typedef struct _zend_string *ddog_OwnedZendString; + typedef struct ddog_FfeResult { _zend_string * value_json; _zend_string * variant; @@ -524,8 +536,6 @@ typedef struct ddog_Tag { const struct ddog_DslString *value; } ddog_Tag; -typedef struct _zend_string *ddog_OwnedZendString; - typedef struct _zend_string *(*ddog_DynamicConfigUpdate)(ddog_CharSlice config, ddog_OwnedZendString value, enum ddog_DynamicConfigUpdateMode mode); @@ -710,6 +720,24 @@ typedef struct ddog_Vec_DebuggerPayload { */ typedef uint64_t ddog_QueueId; +/** + * A generic result type for when an operation may fail, + * but there's nothing to return in the case of success. + */ +typedef enum ddog_VoidResult_Tag { + DDOG_VOID_RESULT_OK, + DDOG_VOID_RESULT_ERR, +} ddog_VoidResult_Tag; + +typedef struct ddog_VoidResult { + ddog_VoidResult_Tag tag; + union { + struct { + struct ddog_Error err; + }; + }; +} ddog_VoidResult; + /** * A (key, value) pair for peer-service tags, borrowed from PHP/concentrator memory. */ @@ -1110,9 +1138,10 @@ typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; * The worker won't send data to the agent until you call `TelemetryWorkerHandle::send_start` * * To stop the worker, call `TelemetryWorkerHandle::send_stop` which trigger flush asynchronously - * then `TelemetryWorkerHandle::wait_for_shutdown` + * then `TelemetryWorkerHandle::wait_for_shutdown` (native only — wasm callers rely on the + * SharedRuntime worker JoinHandle instead). */ -typedef struct ddog_TelemetryWorkerHandle ddog_TelemetryWorkerHandle; +typedef struct ddog_TelemetryWorkerHandle_NativeCapabilities ddog_TelemetryWorkerHandle_NativeCapabilities; typedef enum ddog_Option_U64_Tag { DDOG_OPTION_U64_SOME_U64, @@ -1128,6 +1157,12 @@ typedef struct ddog_Option_U64 { }; } ddog_Option_U64; +/** + * FFI-facing alias: the C ABI surface is native-only, so the worker handle is + * always pinned to [`NativeCapabilities`]. + */ +typedef struct ddog_TelemetryWorkerHandle_NativeCapabilities ddog_TelemetryWorkerHandle; + typedef enum ddog_Option_Bool_Tag { DDOG_OPTION_BOOL_SOME_BOOL, DDOG_OPTION_BOOL_NONE_BOOL, @@ -1452,24 +1487,6 @@ typedef struct ddog_crasht_StackFrame ddog_crasht_StackFrame; typedef struct ddog_crasht_StackTrace ddog_crasht_StackTrace; -/** - * A generic result type for when an operation may fail, - * but there's nothing to return in the case of success. - */ -typedef enum ddog_VoidResult_Tag { - DDOG_VOID_RESULT_OK, - DDOG_VOID_RESULT_ERR, -} ddog_VoidResult_Tag; - -typedef struct ddog_VoidResult { - ddog_VoidResult_Tag tag; - union { - struct { - struct ddog_Error err; - }; - }; -} ddog_VoidResult; - typedef struct ddog_crasht_Slice_CharSlice { /** * Should be non-null and suitably aligned for the underlying type. It is @@ -1851,8 +1868,6 @@ typedef enum ddog_MetadataKind { DDOG_METADATA_KIND_CONTAINER_ID = 8, } ddog_MetadataKind; -typedef struct ddog_Configurator ddog_Configurator; - /** * This struct MUST be backward compatible. */ diff --git a/components-rs/datadog.h b/components-rs/datadog.h index a1ed98a082b..fdfad912b8c 100644 --- a/components-rs/datadog.h +++ b/components-rs/datadog.h @@ -27,6 +27,21 @@ extern ddog_VecRemoteConfigCapabilities DATADOG_REMOTE_CONFIG_CAPABILITIES; extern const uint8_t *DDOG_PHP_FUNCTION; +struct ddog_PhpOtelProcessContext *datadog_otel_process_context_new(void); + +bool datadog_otel_process_context_publish(struct ddog_PhpOtelProcessContext *storage, + ddog_CharSlice hostname, + ddog_CharSlice service, + ddog_CharSlice env, + ddog_CharSlice version, + ddog_CharSlice process_tags); + +bool datadog_otel_process_context_mapping(const struct ddog_PhpOtelProcessContext *storage, + const uint8_t **base, + uintptr_t *len); + +void datadog_otel_process_context_drop(struct ddog_PhpOtelProcessContext *storage); + /** * # Safety * Must be called from a single-threaded context, such as MINIT or first rinit. @@ -59,7 +74,8 @@ void datadog_endpoint_as_crashtracker_config(const struct ddog_Endpoint *endpoin void (*callback)(ddog_crasht_EndpointConfig, void*), void *userdata); -ddog_Configurator *ddog_library_configurator_new_dummy(bool debug_logs, ddog_CharSlice language); +struct ddog_Configurator *ddog_library_configurator_new_dummy(bool debug_logs, + ddog_CharSlice language); int posix_spawn_file_actions_addchdir_np(void *file_actions, const char *path); @@ -110,6 +126,64 @@ void ddog_agent_info_json_free(char *ptr); */ void ddog_apply_agent_info_concentrator_config(struct ddog_AgentInfoReader *reader); +void ddog_init_span_func(void (*free_func)(ddog_OwnedZendString), + void (*addref_func)(struct _zend_string*), + ddog_OwnedZendString (*init_func)(ddog_CharSlice)); + +void ddog_set_span_service_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); + +void ddog_set_span_name_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); + +void ddog_set_span_resource_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); + +void ddog_set_span_type_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); + +void ddog_add_span_meta_zstr(ddog_SpanBytes *ptr, + struct _zend_string *key, + struct _zend_string *val); + +void ddog_add_CharSlice_span_meta_zstr(ddog_SpanBytes *ptr, + ddog_CharSlice key, + struct _zend_string *val); + +void ddog_add_zstr_span_meta_str(ddog_SpanBytes *ptr, struct _zend_string *key, const char *val); + +void ddog_add_str_span_meta_str(ddog_SpanBytes *ptr, const char *key, const char *val); + +void ddog_add_str_span_meta_zstr(ddog_SpanBytes *ptr, const char *key, struct _zend_string *val); + +void ddog_add_str_span_meta_CharSlice(ddog_SpanBytes *ptr, const char *key, ddog_CharSlice val); + +void ddog_del_span_meta_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); + +void ddog_del_span_meta_str(ddog_SpanBytes *ptr, const char *key); + +bool ddog_has_span_meta_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); + +bool ddog_has_span_meta_str(ddog_SpanBytes *ptr, const char *key); + +ddog_CharSlice ddog_get_span_meta_str(ddog_SpanBytes *span, const char *key); + +void ddog_add_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key, double val); + +bool ddog_has_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); + +void ddog_del_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); + +void ddog_add_span_metrics_str(ddog_SpanBytes *ptr, const char *key, double val); + +bool ddog_get_span_metrics_str(ddog_SpanBytes *ptr, const char *key, double *result); + +void ddog_del_span_metrics_str(ddog_SpanBytes *ptr, const char *key); + +void ddog_add_span_meta_struct_zstr(ddog_SpanBytes *ptr, + struct _zend_string *key, + struct _zend_string *val); + +void ddog_add_zstr_span_meta_struct_CharSlice(ddog_SpanBytes *ptr, + struct _zend_string *key, + ddog_CharSlice val); + bool ddog_ffe_load_config(ddog_CharSlice json); bool ddog_ffe_has_config(void); @@ -189,7 +263,12 @@ void ddog_rshutdown_remote_config(struct ddog_RemoteConfigState *remote_config); void ddog_shutdown_remote_config(struct ddog_RemoteConfigState*); -void ddog_drop_probe(struct ddog_Probe probe); +/** + * Free the FFI-owned allocations in a `Probe` (the `tags` vec and the nested + * span-decoration / log allocations) by consuming it; borrowed `CharSlice`s are + * left untouched. Called from `dd_probe_dtor` when a probe is uninstalled. + */ +void ddog_drop_probe(struct ddog_Probe); void ddog_log_debugger_data(const struct ddog_Vec_DebuggerPayload *payloads); @@ -202,6 +281,10 @@ ddog_MaybeError ddog_send_debugger_diagnostics(const struct ddog_RemoteConfigSta const struct ddog_Probe *probe, uint64_t timestamp); +struct ddog_VoidResult datadog_crasht_init_without_receiver(struct ddog_crasht_Config config, + ddog_crasht_Metadata metadata, + uint32_t sidecar_master_pid); + void ddog_sidecar_enable_appsec(ddog_CharSlice shared_lib_path, ddog_CharSlice socket_file_path, ddog_CharSlice lock_file_path, @@ -417,62 +500,4 @@ bool ddog_check_stats_trace_filter(ddog_CharSlice resource, const void *root_span, ddog_RootTagLookupFn lookup_fn); -void ddog_init_span_func(void (*free_func)(ddog_OwnedZendString), - void (*addref_func)(struct _zend_string*), - ddog_OwnedZendString (*init_func)(ddog_CharSlice)); - -void ddog_set_span_service_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); - -void ddog_set_span_name_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); - -void ddog_set_span_resource_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); - -void ddog_set_span_type_zstr(ddog_SpanBytes *ptr, struct _zend_string *str); - -void ddog_add_span_meta_zstr(ddog_SpanBytes *ptr, - struct _zend_string *key, - struct _zend_string *val); - -void ddog_add_CharSlice_span_meta_zstr(ddog_SpanBytes *ptr, - ddog_CharSlice key, - struct _zend_string *val); - -void ddog_add_zstr_span_meta_str(ddog_SpanBytes *ptr, struct _zend_string *key, const char *val); - -void ddog_add_str_span_meta_str(ddog_SpanBytes *ptr, const char *key, const char *val); - -void ddog_add_str_span_meta_zstr(ddog_SpanBytes *ptr, const char *key, struct _zend_string *val); - -void ddog_add_str_span_meta_CharSlice(ddog_SpanBytes *ptr, const char *key, ddog_CharSlice val); - -void ddog_del_span_meta_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); - -void ddog_del_span_meta_str(ddog_SpanBytes *ptr, const char *key); - -bool ddog_has_span_meta_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); - -bool ddog_has_span_meta_str(ddog_SpanBytes *ptr, const char *key); - -ddog_CharSlice ddog_get_span_meta_str(ddog_SpanBytes *span, const char *key); - -void ddog_add_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key, double val); - -bool ddog_has_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); - -void ddog_del_span_metrics_zstr(ddog_SpanBytes *ptr, struct _zend_string *key); - -void ddog_add_span_metrics_str(ddog_SpanBytes *ptr, const char *key, double val); - -bool ddog_get_span_metrics_str(ddog_SpanBytes *ptr, const char *key, double *result); - -void ddog_del_span_metrics_str(ddog_SpanBytes *ptr, const char *key); - -void ddog_add_span_meta_struct_zstr(ddog_SpanBytes *ptr, - struct _zend_string *key, - struct _zend_string *val); - -void ddog_add_zstr_span_meta_struct_CharSlice(ddog_SpanBytes *ptr, - struct _zend_string *key, - ddog_CharSlice val); - #endif /* DDTRACE_PHP_H */ diff --git a/components-rs/lib.rs b/components-rs/lib.rs index f73beec72d2..61fb19ddc82 100644 --- a/components-rs/lib.rs +++ b/components-rs/lib.rs @@ -4,6 +4,7 @@ #![allow(static_mut_refs)] // remove with move to Rust 2024 edition pub mod agent_info; +pub mod bytes; pub mod ffe; pub mod log; pub mod remote_config; @@ -11,11 +12,13 @@ pub mod sidecar; pub mod stats; pub mod telemetry; pub mod trace_filter; -pub mod bytes; -use libdd_common::entity_id::{get_container_id, set_cgroup_file}; use http::uri::{PathAndQuery, Scheme}; use http::Uri; +use libdd_alloc::{Allocator, VirtualAllocator}; +use libdd_common::entity_id::{get_container_id, set_cgroup_file}; +use libdd_library_config::otel_process_ctx::{self, ProcessContextMapping}; +use libdd_library_config::tracer_metadata::{ThreadLocalMetadata, TracerMetadata}; use std::borrow::Cow; use std::ffi::{c_char, OsStr}; #[cfg(unix)] @@ -23,14 +26,15 @@ use std::path::Path; use std::ptr::null_mut; use uuid::Uuid; -pub use libdd_crashtracker_ffi::*; -pub use libdd_library_config_ffi::*; pub use datadog_sidecar_ffi::*; -use libdd_common::{parse_uri, Endpoint}; #[cfg(unix)] use libdd_common::connector::uds::socket_path_to_uri; +use libdd_common::{parse_uri, Endpoint}; use libdd_common_ffi::slice::AsBytes; pub use libdd_common_ffi::*; +pub use libdd_crashtracker_ffi::*; +pub use libdd_library_config_ffi::*; +pub use libdd_otel_thread_ctx_ffi::*; pub use libdd_telemetry_ffi::*; #[no_mangle] @@ -53,6 +57,296 @@ pub static mut datadog_formatted_root_session_id: [u8; 36] = [0u8; 36]; #[allow(non_upper_case_globals)] pub static mut datadog_formatted_parent_session_id: [u8; 36] = [0u8; 36]; +const PHP_OTEL_PROCESS_CONTEXT_SIZE: usize = 16 * 1024; + +pub struct PhpOtelProcessContext { + allocation: std::option::Option>, + initialized: bool, +} + +#[cfg(not(target_os = "linux"))] +fn allocate_otel_process_context( + allocator: &impl Allocator, +) -> std::result::Result, libdd_alloc::AllocError> { + let layout = std::alloc::Layout::from_size_align(PHP_OTEL_PROCESS_CONTEXT_SIZE, 8) + .expect("the fixed OTel process-context layout is valid"); + allocator.allocate_zeroed(layout) +} + +impl PhpOtelProcessContext { + fn mapping(&self) -> std::io::Result { + let allocation = self.allocation.ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "no caller-owned mapping") + })?; + unsafe { + ProcessContextMapping::from_raw_parts( + allocation.as_ptr().cast::(), + allocation.len(), + ) + } + } +} + +impl Drop for PhpOtelProcessContext { + fn drop(&mut self) { + if self.initialized { + if let Ok(mapping) = self.mapping() { + otel_process_ctx::invalidate(mapping); + } + } + if let (Some(allocation), Ok(layout)) = ( + self.allocation, + std::alloc::Layout::from_size_align(PHP_OTEL_PROCESS_CONTEXT_SIZE, 8), + ) { + unsafe { (VirtualAllocator {}).deallocate(allocation.cast(), layout) }; + } + } +} + +#[no_mangle] +pub extern "C" fn datadog_otel_process_context_new() -> *mut PhpOtelProcessContext { + #[cfg(target_os = "linux")] + { + return Box::into_raw(Box::new(PhpOtelProcessContext { + allocation: None, + initialized: false, + })); + } + #[cfg(not(target_os = "linux"))] + match allocate_otel_process_context(&VirtualAllocator {}) { + Ok(allocation) => Box::into_raw(Box::new(PhpOtelProcessContext { + allocation: Some(allocation), + initialized: false, + })), + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn datadog_otel_process_context_publish( + storage: *mut PhpOtelProcessContext, + hostname: CharSlice<'_>, + service: CharSlice<'_>, + env: CharSlice<'_>, + version: CharSlice<'_>, + process_tags: CharSlice<'_>, +) -> bool { + let Some(storage) = (unsafe { storage.as_mut() }) else { + return false; + }; + let value = |slice: CharSlice<'_>| { + let value = slice.to_utf8_lossy(); + (!value.is_empty()).then(|| value.into_owned()) + }; + let runtime_id = unsafe { + (!datadog_runtime_id.is_nil()).then(|| datadog_runtime_id.as_hyphenated().to_string()) + }; + let metadata = TracerMetadata { + runtime_id, + tracer_language: "php".to_owned(), + tracer_version: include_str!("../VERSION").trim().to_owned(), + hostname: hostname.to_utf8_lossy().into_owned(), + service_name: value(service), + service_env: value(env), + service_version: value(version), + process_tags: value(process_tags), + container_id: get_container_id().map(str::to_owned), + threadlocal_metadata: Some(ThreadLocalMetadata { + attribute_keys: vec![ + "service.name".to_owned(), + "service.version".to_owned(), + "deployment.environment.name".to_owned(), + "datadog.thread_id".to_owned(), + ], + ..Default::default() + }), + ..Default::default() + }; + let context = metadata.to_otel_process_ctx(); + #[cfg(target_os = "linux")] + { + return match otel_process_ctx::publish(&context) { + Ok(()) => { + storage.initialized = true; + true + } + Err(error) => { + storage.initialized = false; + tracing::error!("failed to publish PHP OTel process context: {error}"); + false + } + }; + } + #[cfg(not(target_os = "linux"))] + let mapping = match storage.mapping() { + Ok(mapping) => mapping, + Err(error) => { + tracing::error!("invalid PHP OTel process-context allocation: {error}"); + storage.initialized = false; + return false; + } + }; + #[cfg(not(target_os = "linux"))] + let result = if storage.initialized { + otel_process_ctx::update(mapping, &context) + } else { + otel_process_ctx::initialize(mapping, &context) + }; + #[cfg(not(target_os = "linux"))] + match result { + Ok(()) => { + storage.initialized = true; + true + } + Err(error) => { + otel_process_ctx::invalidate(mapping); + storage.initialized = false; + tracing::error!("failed to publish PHP OTel process context: {error}"); + false + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn datadog_otel_process_context_mapping( + storage: *const PhpOtelProcessContext, + base: *mut *const u8, + len: *mut usize, +) -> bool { + let (Some(storage), Some(base), Some(len)) = ( + unsafe { storage.as_ref() }, + unsafe { base.as_mut() }, + unsafe { len.as_mut() }, + ) else { + return false; + }; + let Some(allocation) = storage.allocation else { + return false; + }; + *base = allocation.as_ptr().cast::(); + *len = allocation.len(); + true +} + +#[no_mangle] +pub unsafe extern "C" fn datadog_otel_process_context_drop(storage: *mut PhpOtelProcessContext) { + if !storage.is_null() { + drop(unsafe { Box::from_raw(storage) }); + } +} + +#[cfg(all(test, not(target_os = "linux")))] +mod otel_process_context_tests { + use super::*; + + struct FailingAllocator; + + unsafe impl Allocator for FailingAllocator { + fn allocate( + &self, + _layout: std::alloc::Layout, + ) -> std::result::Result, libdd_alloc::AllocError> { + Err(libdd_alloc::AllocError) + } + + unsafe fn deallocate(&self, _ptr: std::ptr::NonNull, _layout: std::alloc::Layout) { + unreachable!("a failing allocator cannot produce an allocation") + } + } + + #[test] + fn allocation_failure_is_non_fatal() { + assert!(allocate_otel_process_context(&FailingAllocator).is_err()); + } + + #[test] + fn unavailable_storage_fails_closed() { + assert!(!unsafe { + datadog_otel_process_context_publish( + std::ptr::null_mut(), + CharSlice::from("host"), + CharSlice::from("service"), + CharSlice::from("env"), + CharSlice::from("version"), + CharSlice::from("process-tag:value"), + ) + }); + } + + #[test] + fn normal_context_is_inline_and_oversized_context_invalidates_publication() { + let storage = datadog_otel_process_context_new(); + assert!(!storage.is_null()); + let mut base = std::ptr::null(); + let mut len = 0; + assert!(unsafe { datadog_otel_process_context_mapping(storage, &mut base, &mut len) }); + assert_eq!(len, PHP_OTEL_PROCESS_CONTEXT_SIZE); + assert!(unsafe { + datadog_otel_process_context_publish( + storage, + CharSlice::from("host"), + CharSlice::from("service"), + CharSlice::from("env"), + CharSlice::from("version"), + CharSlice::from("process-tag:value"), + ) + }); + + assert!(unsafe { datadog_otel_process_context_mapping(storage, &mut base, &mut len) }); + assert!(!base.is_null()); + assert_eq!(len, PHP_OTEL_PROCESS_CONTEXT_SIZE); + let payload_size = unsafe { + std::sync::atomic::AtomicU32::from_ptr(base.add(12).cast_mut().cast()) + .load(std::sync::atomic::Ordering::Relaxed) + }; + assert!( + payload_size < 1024, + "normal process context is unexpectedly large" + ); + let mapping = unsafe { + ProcessContextMapping::from_raw_parts(base.cast_mut(), len).expect("valid mapping") + }; + let context = otel_process_ctx::decode(mapping).expect("decodable process context"); + assert_eq!( + otel_process_ctx::threadlocal_attribute_key_map(&context), + Some(vec![ + "datadog.local_root_span_id".to_owned(), + "service.name".to_owned(), + "service.version".to_owned(), + "deployment.environment.name".to_owned(), + "datadog.thread_id".to_owned(), + ]) + ); + let process_tags = context + .extra_attributes + .iter() + .find(|attribute| attribute.key == "datadog.process_tags") + .and_then(|attribute| attribute.value.as_ref()) + .and_then(|value| value.value.as_ref()); + assert!(matches!( + process_tags, + Some(libdd_trace_protobuf::opentelemetry::proto::common::v1::any_value::Value::StringValue(value)) + if value == "process-tag:value" + )); + + let oversized = "x".repeat(PHP_OTEL_PROCESS_CONTEXT_SIZE * 2); + assert!(!unsafe { + datadog_otel_process_context_publish( + storage, + CharSlice::from("host"), + CharSlice::from("service"), + CharSlice::from("env"), + CharSlice::from("version"), + CharSlice::from(oversized.as_str()), + ) + }); + assert!(unsafe { datadog_otel_process_context_mapping(storage, &mut base, &mut len) }); + assert!(otel_process_ctx::decode(mapping).is_err()); + + unsafe { datadog_otel_process_context_drop(storage) }; + } +} + /// # Safety /// Must be called from a single-threaded context, such as MINIT or first rinit. #[no_mangle] @@ -66,7 +360,9 @@ pub unsafe extern "C" fn datadog_generate_runtime_id() { pub unsafe extern "C" fn datadog_generate_session_id() { datadog_session_id = Uuid::new_v4(); datadog_runtime_id = datadog_session_id; - datadog_session_id.as_hyphenated().encode_lower(&mut datadog_formatted_session_id); + datadog_session_id + .as_hyphenated() + .encode_lower(&mut datadog_formatted_session_id); unsafe fn set(name: &str, value: &mut [u8; 36], force: bool) { if let Ok(str) = std::env::var(name) { @@ -78,11 +374,22 @@ pub unsafe extern "C" fn datadog_generate_session_id() { } } } - std::env::set_var(name, OsStr::from_encoded_bytes_unchecked(&datadog_formatted_session_id)); + std::env::set_var( + name, + OsStr::from_encoded_bytes_unchecked(&datadog_formatted_session_id), + ); } - set("_DD_PARENT_PHP_SESSION_ID", &mut datadog_formatted_parent_session_id, true); - set("_DD_ROOT_PHP_SESSION_ID", &mut datadog_formatted_root_session_id, false); + set( + "_DD_PARENT_PHP_SESSION_ID", + &mut datadog_formatted_parent_session_id, + true, + ); + set( + "_DD_ROOT_PHP_SESSION_ID", + &mut datadog_formatted_root_session_id, + false, + ); } #[no_mangle] @@ -151,22 +458,30 @@ pub unsafe extern "C" fn datadog_parse_agent_url( } #[cfg(unix)] -fn otel_metrics_endpoint_from_unix_socket(_socket_path: &str) -> std::option::Option> { - socket_path_to_uri(Path::new(_socket_path)).ok().and_then(|uri| { - let mut parts = uri.into_parts(); - parts.path_and_query = Some(PathAndQuery::from_static("/v1/metrics")); - Uri::from_parts(parts) - .ok() - .map(|url| Box::new(Endpoint::from_url(url))) - }) +fn otel_metrics_endpoint_from_unix_socket( + _socket_path: &str, +) -> std::option::Option> { + socket_path_to_uri(Path::new(_socket_path)) + .ok() + .and_then(|uri| { + let mut parts = uri.into_parts(); + parts.path_and_query = Some(PathAndQuery::from_static("/v1/metrics")); + Uri::from_parts(parts) + .ok() + .map(|url| Box::new(Endpoint::from_url(url))) + }) } #[no_mangle] -pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_url(url: CharSlice) -> std::option::Option> { +pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_url( + url: CharSlice, +) -> std::option::Option> { let url_str = url.to_utf8_lossy(); #[cfg(unix)] if let Some(socket_path) = url_str.strip_prefix("unix://") { - let socket_path = socket_path.strip_suffix("/v1/metrics").unwrap_or(socket_path); + let socket_path = socket_path + .strip_suffix("/v1/metrics") + .unwrap_or(socket_path); return otel_metrics_endpoint_from_unix_socket(socket_path); } parse_uri(url_str.as_ref()) @@ -175,7 +490,9 @@ pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_url(url: CharSlice) } #[no_mangle] -pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url(url: CharSlice) -> std::option::Option> { +pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url( + url: CharSlice, +) -> std::option::Option> { let url_str = url.to_utf8_lossy(); #[cfg(unix)] if let Some(socket_path) = url_str.strip_prefix("unix://") { @@ -183,7 +500,10 @@ pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url(url: CharS } if url_str.starts_with("http") { let parsed = parse_uri(url_str.as_ref()).ok(); - let scheme = parsed.as_ref().and_then(|u| u.scheme_str()).unwrap_or("http"); + let scheme = parsed + .as_ref() + .and_then(|u| u.scheme_str()) + .unwrap_or("http"); let host = parsed .as_ref() .and_then(|u| u.host()) @@ -271,9 +591,7 @@ pub unsafe extern "C" fn dd_fnv1a_64(data: *const u8, len: usize) -> u64 { } #[no_mangle] -pub extern "C" fn ddog_normalize_process_tag_value( - tag_value: CharSlice, -) -> *const c_char { +pub extern "C" fn ddog_normalize_process_tag_value(tag_value: CharSlice) -> *const c_char { let value = tag_value.to_utf8_lossy(); let mut out = String::new(); diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs index 51e2c575d73..26e6b62d67c 100644 --- a/components-rs/remote_config.rs +++ b/components-rs/remote_config.rs @@ -1,3 +1,4 @@ +use crate::bytes::{dangling_zend_string, OwnedZendString, ZendString}; use crate::sidecar::MaybeShmLimiter; use datadog_ffe::rules_based::{Configuration, UniversalFlagConfig}; use datadog_live_debugger::debugger_defs::{DebuggerData, DebuggerPayload}; @@ -7,20 +8,22 @@ use datadog_live_debugger_ffi::evaluator::{ddog_register_expr_evaluator, Evaluat use datadog_live_debugger_ffi::send_data::{ ddog_debugger_diagnostics_create_unboxed, ddog_snapshot_redacted_type, }; -use libdd_remote_config::config::dynamic::{Configs, DynamicConfigFile, TracingSamplingRuleProvenance}; -use libdd_remote_config::fetch::ConfigInvariants; -use libdd_remote_config::{ - default_registry, RemoteConfigCapabilities, RemoteConfigParsed, RemoteConfigProduct, Target, -}; use datadog_sidecar::service::blocking::SidecarTransport; use datadog_sidecar::service::{InstanceId, QueueId}; use datadog_sidecar::shm_remote_config::{RemoteConfigManager, RemoteConfigUpdate}; use datadog_sidecar_ffi::ddog_sidecar_send_debugger_diagnostics; +use itertools::Itertools; use libdd_common::tag::Tag; use libdd_common::Endpoint; use libdd_common_ffi::slice::AsBytes; use libdd_common_ffi::{CharSlice, MaybeError}; -use itertools::Itertools; +use libdd_remote_config::config::dynamic::{ + Configs, DynamicConfigFile, TracingSamplingRuleProvenance, +}; +use libdd_remote_config::fetch::ConfigInvariants; +use libdd_remote_config::{ + default_registry, RemoteConfigCapabilities, RemoteConfigParsed, RemoteConfigProduct, Target, +}; use regex_automata::dfa::regex::Regex; use serde::Serialize; use std::borrow::Cow; @@ -31,7 +34,6 @@ use std::mem; use std::ptr::NonNull; use std::sync::Arc; use tracing::debug; -use crate::bytes::{ZendString, OwnedZendString, dangling_zend_string}; pub const DYANMIC_CONFIG_UPDATE_UNMODIFIED: *mut ZendString = 1isize as *mut ZendString; @@ -54,7 +56,8 @@ static mut DYNAMIC_CONFIG_UPDATE: Option = None; type VecRemoteConfigProduct = libdd_common_ffi::Vec; #[no_mangle] -pub static mut DATADOG_REMOTE_CONFIG_PRODUCTS: VecRemoteConfigProduct = libdd_common_ffi::Vec::new(); +pub static mut DATADOG_REMOTE_CONFIG_PRODUCTS: VecRemoteConfigProduct = + libdd_common_ffi::Vec::new(); type VecRemoteConfigCapabilities = libdd_common_ffi::Vec; #[no_mangle] @@ -90,6 +93,7 @@ fn compute_merged_configs(active_configs: &HashMap) pub struct RemoteConfigState { manager: RemoteConfigManager, + service: String, live_debugger: LiveDebuggerState, dynamic_config: DynamicConfig, } @@ -160,7 +164,8 @@ pub unsafe extern "C" fn ddog_init_remote_config(flags: RemoteConfigFlags) { if ffe_enabled { DATADOG_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::FfeFlags); - DATADOG_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::FfeFlagConfigurationRules); + DATADOG_REMOTE_CONFIG_CAPABILITIES + .push(RemoteConfigCapabilities::FfeFlagConfigurationRules); } if live_debugging_enabled { @@ -213,11 +218,15 @@ pub unsafe extern "C" fn ddog_init_remote_config_state( .expect("FFE is distinct from default products"), ); Box::new(RemoteConfigState { - manager: RemoteConfigManager::new_with_registry(ConfigInvariants { - language: "php".to_string(), - tracer_version: include_str!("../VERSION").trim().into(), - endpoint: endpoint.clone(), - }, registry), + manager: RemoteConfigManager::new_with_registry( + ConfigInvariants { + language: "php".to_string(), + tracer_version: include_str!("../VERSION").trim().into(), + endpoint: endpoint.clone(), + }, + registry, + ), + service: String::new(), live_debugger: LiveDebuggerState { di_enabled, ..Default::default() @@ -290,10 +299,15 @@ fn map_config_value(config: &Configs) -> Cow<'_, str> { fn use_rc_config<'a>(config: &Configs, user_value: &'a [u8], _rc_value: &'a str) -> bool { match config { - Configs::DynamicInstrumentationEnabled(_) | Configs::ExceptionReplayEnabled(_) | Configs::CodeOriginEnabled(_) => { + Configs::DynamicInstrumentationEnabled(_) + | Configs::ExceptionReplayEnabled(_) + | Configs::CodeOriginEnabled(_) => { let user_str = String::from_utf8_lossy(user_value); - user_str.parse::().unwrap_or(0) != 0 || user_str.eq_ignore_ascii_case("true") || user_str.eq_ignore_ascii_case("yes") || user_str.eq_ignore_ascii_case("on") - }, + user_str.parse::().unwrap_or(0) != 0 + || user_str.eq_ignore_ascii_case("true") + || user_str.eq_ignore_ascii_case("yes") + || user_str.eq_ignore_ascii_case("on") + } _ => true, } } @@ -303,7 +317,11 @@ fn reset_old_config(name: &str, val: Option) { if let Some(val) = val { DYNAMIC_CONFIG_UPDATE.unwrap()(name.into(), val, DynamicConfigUpdateMode::Write); } else { - DYNAMIC_CONFIG_UPDATE.unwrap()(name.into(), dangling_zend_string(), DynamicConfigUpdateMode::Restore); + DYNAMIC_CONFIG_UPDATE.unwrap()( + name.into(), + dangling_zend_string(), + DynamicConfigUpdateMode::Restore, + ); } } } @@ -329,25 +347,51 @@ fn insert_new_configs( let user_value = if let Some(old_zstr) = old_value { old_zstr.as_ref().map(|v| v.0) } else { - let val = unsafe { DYNAMIC_CONFIG_UPDATE.unwrap()(name.into(), dangling_zend_string(), DynamicConfigUpdateMode::Read) }; + let val = unsafe { + DYNAMIC_CONFIG_UPDATE.unwrap()( + name.into(), + dangling_zend_string(), + DynamicConfigUpdateMode::Read, + ) + }; if val == DYANMIC_CONFIG_UPDATE_UNMODIFIED { None } else { Some(NonNull::new(val).unwrap()) } }; - (old_value.is_some(), user_value.map(|v| { - if use_rc_config(config, unsafe { v.as_ref() }.as_ref(), val.as_ref()) { - val.as_ref().into() - } else { - OwnedZendString::from_copy(v) - } - }).unwrap_or_else(|| val.as_ref().into())) + ( + old_value.is_some(), + user_value + .map(|v| { + if use_rc_config(config, unsafe { v.as_ref() }.as_ref(), val.as_ref()) { + val.as_ref().into() + } else { + OwnedZendString::from_copy(v) + } + }) + .unwrap_or_else(|| val.as_ref().into()), + ) }; - let original = unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.into(), merged, if is_update { DynamicConfigUpdateMode::Write } else { DynamicConfigUpdateMode::ReadWrite }); + let original = unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()( + name.into(), + merged, + if is_update { + DynamicConfigUpdateMode::Write + } else { + DynamicConfigUpdateMode::ReadWrite + }, + ); if let Some(original) = NonNull::new(original) { - old_config_values.insert(name.into(), if original.as_ptr() == DYANMIC_CONFIG_UPDATE_UNMODIFIED { None } else { Some(OwnedZendString(original)) }); + old_config_values.insert( + name.into(), + if original.as_ptr() == DYANMIC_CONFIG_UPDATE_UNMODIFIED { + None + } else { + Some(OwnedZendString(original)) + }, + ); } found_configs.insert(mem::discriminant(config)); } @@ -391,7 +435,8 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt match value.product { RemoteConfigProduct::LiveDebugger => { let val = Box::new((data, MaybeShmLimiter::open(limiter_index))); - let rc_ref: &mut RemoteConfigState = unsafe { mem::transmute(remote_config as *mut _) }; // sigh, borrow checker + let rc_ref: &mut RemoteConfigState = + unsafe { mem::transmute(remote_config as *mut _) }; // sigh, borrow checker let config_id = value.config_id.clone(); let entry = remote_config.live_debugger.active.entry(value.config_id); let (parsed, limiter) = match entry { @@ -414,9 +459,13 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt let priority = config_data.priority(); let configs: Vec = config_data.lib_config.clone().into(); if !configs.is_empty() { - remote_config.dynamic_config.active_configs - .insert(value.config_id, ActiveDynamicConfig { priority, configs }); - let merged = compute_merged_configs(&remote_config.dynamic_config.active_configs); + remote_config.dynamic_config.active_configs.insert( + value.config_id, + ActiveDynamicConfig { priority, configs }, + ); + let merged = compute_merged_configs( + &remote_config.dynamic_config.active_configs, + ); insert_new_configs( &mut remote_config.dynamic_config.old_config_values, &mut remote_config.dynamic_config.merged_configs, @@ -428,8 +477,12 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt RemoteConfigProduct::FfeFlags => { debug!("Received FFE flags configuration"); if let Some(ufc) = data.downcast::() { - if let Ok(ufc_owned) = UniversalFlagConfig::from_json(ufc.to_json().to_vec()) { - crate::ffe::store_config(Configuration::from_server_response(ufc_owned)); + if let Ok(ufc_owned) = + UniversalFlagConfig::from_json(ufc.to_json().to_vec()) + { + crate::ffe::store_config(Configuration::from_server_response( + ufc_owned, + )); } } } @@ -439,18 +492,26 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt } RemoteConfigUpdate::Remove(path) => match path.product { RemoteConfigProduct::LiveDebugger => { - if let Some(boxed) = remote_config.live_debugger.active.remove(&path.config_id) { + if let Some(boxed) = remote_config.live_debugger.active.remove(&path.config_id) + { if let Some(debugger) = boxed.0.downcast::() { remove_config(remote_config, &path.config_id, debugger); } } } RemoteConfigProduct::ApmTracing => { - if remote_config.dynamic_config.active_configs.remove(&path.config_id).is_some() { + if remote_config + .dynamic_config + .active_configs + .remove(&path.config_id) + .is_some() + { if remote_config.dynamic_config.active_configs.is_empty() { remove_old_configs(remote_config); } else { - let merged = compute_merged_configs(&remote_config.dynamic_config.active_configs); + let merged = compute_merged_configs( + &remote_config.dynamic_config.active_configs, + ); insert_new_configs( &mut remote_config.dynamic_config.old_config_values, &mut remote_config.dynamic_config.merged_configs, @@ -533,12 +594,19 @@ fn apply_config( } } -fn remove_config(remote_config: &mut RemoteConfigState, config_id: &str, debugger: &LiveDebuggingData) { +fn remove_config( + remote_config: &mut RemoteConfigState, + config_id: &str, + debugger: &LiveDebuggingData, +) { if let Some(callbacks) = unsafe { &LIVE_DEBUGGER_CALLBACKS } { match debugger { LiveDebuggingData::Probe(probe) => { if let Some(id) = remote_config.live_debugger.spans_map.remove(config_id) { - debug!("Removing live debugger probe {} (config {})", probe.id, config_id); + debug!( + "Removing live debugger probe {} (config {})", + probe.id, config_id + ); (callbacks.remove_probe)(id); } } @@ -560,14 +628,18 @@ fn remove_config(remote_config: &mut RemoteConfigState, config_id: &str, debugge /// For dynamic config entries the value is "apm_tracing". /// The returned pointer must be freed with `ddog_remote_config_loaded_configs_free`. #[no_mangle] -pub extern "C" fn ddog_remote_config_get_loaded_configs(remote_config: &RemoteConfigState) -> *mut c_char { +pub extern "C" fn ddog_remote_config_get_loaded_configs( + remote_config: &RemoteConfigState, +) -> *mut c_char { let mut entries: Vec<(String, String)> = Vec::new(); for (config_id, boxed) in &remote_config.live_debugger.active { if let Some(debugger) = boxed.0.downcast::() { let value = match debugger { LiveDebuggingData::Probe(p) => format!(r#"{{"type":"probe","id":"{}"}}"#, p.id), - LiveDebuggingData::ServiceConfiguration(sc) => format!(r#"{{"type":"service_config","id":"{}"}}"#, sc.id), + LiveDebuggingData::ServiceConfiguration(sc) => { + format!(r#"{{"type":"service_config","id":"{}"}}"#, sc.id) + } }; entries.push((config_id.clone(), value)); } @@ -626,7 +698,9 @@ pub extern "C" fn ddog_global_log_probe_limiter_inc(remote_config: &RemoteConfig .active .get(&remote_config.live_debugger.config_id) { - if let Some(LiveDebuggingData::ServiceConfiguration(config)) = boxed.0.downcast::() { + if let Some(LiveDebuggingData::ServiceConfiguration(config)) = + boxed.0.downcast::() + { boxed.1.inc(config.sampling_snapshots_per_second) } else { true @@ -650,13 +724,18 @@ pub extern "C" fn ddog_remote_configs_service_env_change( tags: &libdd_common_ffi::Vec, process_tags: &libdd_common_ffi::Vec, ) -> bool { - let new_target = Target { - service: service.to_utf8_lossy().to_string(), - env: env.to_utf8_lossy().to_string(), - app_version: version.to_utf8_lossy().to_string(), - tags: tags.as_slice().to_vec(), - process_tags: process_tags.as_slice().to_vec(), - }; + let service = service.to_utf8_lossy().into_owned(); + let new_target = Target::new( + service.clone(), + env.to_utf8_lossy().into_owned(), + version.to_utf8_lossy().into_owned(), + tags.as_slice().iter().map(ToString::to_string).collect(), + process_tags + .as_slice() + .iter() + .map(ToString::to_string) + .collect(), + ); if let Some(target) = remote_config.manager.get_target() { if **target == new_target { @@ -664,6 +743,7 @@ pub extern "C" fn ddog_remote_configs_service_env_change( } } + remote_config.service = service; remote_config.manager.track_target(&Arc::new(new_target)); // Caller must call ddog_process_remote_configs if true. // We don't call it here to allow the caller delaying the call as necessary. @@ -732,7 +812,9 @@ pub extern "C" fn ddog_set_dynamic_instrumentation_enabled( } else { // Reinstall all probes in `active`, keyed by config_id (like apply/remove). for (config_id, boxed) in remote_config.live_debugger.active.iter() { - if let Some(LiveDebuggingData::Probe(probe)) = boxed.0.downcast::() { + if let Some(LiveDebuggingData::Probe(probe)) = + boxed.0.downcast::() + { let hook_id = (callbacks.set_probe)(probe.into(), &boxed.1); if hook_id >= 0 { remote_config @@ -794,12 +876,7 @@ pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>( probe: &'a Probe, timestamp: u64, ) -> MaybeError { - let service = Cow::Borrowed( - remote_config_state - .manager - .get_target() - .map_or("", |t| t.service.as_str()), - ); + let service = Cow::Borrowed(remote_config_state.service.as_str()); let mut payload = ddog_debugger_diagnostics_create_unboxed( probe, service, diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 14c3b0c6c8c..de6df3cb550 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -442,11 +442,6 @@ ddog_MaybeError ddog_sidecar_set_test_session_token(struct ddog_SidecarTransport void ddog_sidecar_reconnect(struct ddog_SidecarTransport **transport, struct ddog_SidecarTransport *(*factory)(void)); -/** - * Return the path of the crashtracker unix domain socket. - */ -ddog_CharSlice ddog_sidecar_get_crashtracker_unix_socket_path(void); - /** * Gets an agent info reader. */ diff --git a/components-rs/sidecar.rs b/components-rs/sidecar.rs index bd02688c1bb..761a42825c4 100644 --- a/components-rs/sidecar.rs +++ b/components-rs/sidecar.rs @@ -1,29 +1,36 @@ -use std::ffi::{c_char, CStr, OsStr}; -use std::ops::DerefMut; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt; -use lazy_static::{lazy_static, LazyStatic}; -use tracing::warn; -use std::sync::Mutex; -use std::time::Duration; +use datadog_ipc::rate_limiter::{AnyLimiter, ShmLimiterMemory}; use datadog_sidecar::config::{self, AppSecConfig, LogMethod}; use datadog_sidecar::service::blocking::{acquire_exception_hash_rate_limiter, SidecarTransport}; -use libdd_common::rate_limiter::{Limiter, LocalLimiter}; -use datadog_ipc::rate_limiter::{AnyLimiter, ShmLimiterMemory}; use datadog_sidecar::service::exception_hash_rate_limiter::ExceptionHashRateLimiter; use datadog_sidecar::tracer::shm_limiter_path; +use lazy_static::{lazy_static, LazyStatic}; +use libdd_common::rate_limiter::{Limiter, LocalLimiter}; use libdd_common::Endpoint; use libdd_common_ffi::slice::AsBytes; -use libdd_common_ffi::{CharSlice, self as ffi, MaybeError}; +use libdd_common_ffi::{self as ffi, CharSlice, MaybeError}; use libdd_telemetry_ffi::try_c; #[cfg(windows)] use spawn_worker::{get_trampoline_target_data, LibDependency}; +use std::ffi::{c_char, CStr, OsStr}; +use std::ops::DerefMut; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(target_os = "macos")] +use std::sync::atomic::{AtomicI32, Ordering}; +use std::sync::Mutex; +use std::time::Duration; +use tracing::warn; #[cfg(php_shared_build)] fn run_sidecar(mut cfg: config::Config) -> anyhow::Result { #[cfg(target_os = "linux")] - if std::env::var_os("DD_SIDECAR_DISABLE_DIRECT_EXEC").map(|s| s.is_empty()).unwrap_or(true) - && std::env::var_os("DD_SPAWN_WORKER_USE_EXEC").map(|s| s.is_empty()).unwrap_or(true) { + if std::env::var_os("DD_SIDECAR_DISABLE_DIRECT_EXEC") + .map(|s| s.is_empty()) + .unwrap_or(true) + && std::env::var_os("DD_SPAWN_WORKER_USE_EXEC") + .map(|s| s.is_empty()) + .unwrap_or(true) + { cfg.spawn_without_trampoline = true; } datadog_sidecar::start_or_connect_to_sidecar(cfg) @@ -41,7 +48,8 @@ pub static mut DDOG_PHP_FUNCTION: *const u8 = std::ptr::null(); #[cfg(windows)] fn run_sidecar(mut cfg: config::Config) -> anyhow::Result { let php_dll = get_trampoline_target_data(unsafe { DDOG_PHP_FUNCTION })?; - cfg.library_dependencies.push(LibDependency::Path(php_dll.into())); + cfg.library_dependencies + .push(LibDependency::Path(php_dll.into())); datadog_sidecar::start_or_connect_to_sidecar(cfg) } @@ -49,6 +57,83 @@ lazy_static! { static ref APPSEC_CONFIG: Mutex> = Mutex::new(None); } +#[cfg(target_os = "macos")] +static CRASHTRACKER_SIDECAR_FD: AtomicI32 = AtomicI32::new(-1); + +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[no_mangle] +pub extern "C" fn datadog_crasht_init_without_receiver( + config: libdd_crashtracker_ffi::Config<'_>, + metadata: libdd_crashtracker_ffi::Metadata<'_>, + sidecar_master_pid: u32, +) -> libdd_common_ffi::VoidResult { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + init_crashtracker_without_receiver(config, metadata, sidecar_master_pid) + })) + .map_or_else( + |panic| { + libdd_common_ffi::utils::handle_panic_error( + panic, + "datadog_crasht_init_without_receiver", + ) + .into() + }, + Into::into, + ) +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn init_crashtracker_without_receiver( + config: libdd_crashtracker_ffi::Config<'_>, + metadata: libdd_crashtracker_ffi::Metadata<'_>, + sidecar_master_pid: u32, +) -> anyhow::Result<()> { + let mut config: libdd_crashtracker::CrashtrackerConfiguration = config.try_into()?; + _ = datadog_sidecar::crashtracker::crashtracker_receiver_request_bytes(); + + #[cfg(target_os = "linux")] + { + let path = datadog_sidecar::crashtracker::crashtracker_ipc_socket_path( + sidecar_master_pid, + config::FromEnv::ipc_mode(), + ); + config.set_unix_socket_path(path.to_string_lossy().into_owned()); + config + .set_unix_socket_connector(datadog_sidecar::crashtracker::connect_to_sidecar_receiver); + } + + #[cfg(target_os = "macos")] + { + let _ = sidecar_master_pid; + config.set_unix_socket_path("sidecar".to_owned()); + config.set_unix_socket_connector(connect_to_sidecar_crashtracker_receiver); + } + + libdd_crashtracker::init( + config, + libdd_crashtracker::CrashtrackerReceiverConfig::default(), + metadata.try_into()?, + )?; + Ok(()) +} + +#[cfg(target_os = "macos")] +fn connect_to_sidecar_crashtracker_receiver(_unix_socket_path: &str) -> std::os::fd::RawFd { + let fd = CRASHTRACKER_SIDECAR_FD.load(Ordering::Acquire); + if fd < 0 { + return -1; + } + + let request = datadog_sidecar::crashtracker::crashtracker_receiver_request_bytes(); + // SAFETY: fd is the registered sidecar connection and request points to immutable static bytes. + let written = unsafe { libc::send(fd, request.as_ptr().cast(), request.len(), 0) }; + if written == request.len() as isize { + fd + } else { + -1 + } +} + // must be called prior to ddog_sidecar_connect #[no_mangle] pub extern "C" fn ddog_sidecar_enable_appsec( @@ -91,6 +176,12 @@ pub extern "C" fn ddog_sidecar_enable_appsec( fn sidecar_connect(cfg: config::Config) -> anyhow::Result> { let mut stream = Box::new(run_sidecar(cfg)?); + #[cfg(any(target_os = "linux", target_os = "macos"))] + if let Ok(pid) = stream.peer_pid() { + libdd_crashtracker::set_expected_receiver_pid(pid as i32); + } + #[cfg(target_os = "macos")] + CRASHTRACKER_SIDECAR_FD.store(stream.as_raw_fd(), Ordering::Release); // Generally the Send buffer ought to be big enough for instantaneous transmission _ = stream.set_write_timeout(Some(Duration::from_millis(100))); _ = stream.set_read_timeout(Some(Duration::from_secs(10))); @@ -134,12 +225,13 @@ pub extern "C" fn ddog_sidecar_connect_php( } } #[cfg(windows)] - let log_level = log_level.to_utf8_lossy().as_ref().into(); + let log_level = log_level.to_utf8_lossy().as_ref().into(); #[cfg(not(windows))] - let log_level = OsStr::from_bytes(log_level.as_bytes()).into(); - cfg.child_env.insert(OsStr::new("DD_TRACE_LOG_LEVEL").into(), log_level); + let log_level = OsStr::from_bytes(log_level.as_bytes()).into(); + cfg.child_env + .insert(OsStr::new("DD_TRACE_LOG_LEVEL").into(), log_level); } - + cfg.pipe_buffer_size = backpressure_bytes as usize; let reconnect_fn = on_reconnect.map(|on_reconnect| { @@ -150,7 +242,7 @@ pub extern "C" fn ddog_sidecar_connect_php( Some(transport) }) as Box _> }); - + let mut stream = try_c!(sidecar_connect(cfg)); stream.reconnect_fn = reconnect_fn; let _ = stream.set_backpressure(backpressure_bytes as usize, backpressure_queue); @@ -173,17 +265,23 @@ pub extern "C" fn datadog_sidecar_reconnect( }); } - lazy_static! { - pub static ref SHM_LIMITER: Option> = ShmLimiterMemory::open(&shm_limiter_path()).map_or_else(|e| { - warn!("Attempt to use the SHM_LIMITER failed: {e:?}"); - None - }, Some); - - pub static ref EXCEPTION_HASH_LIMITER: Option = ExceptionHashRateLimiter::open().map_or_else(|e| { - warn!("Attempt to use the EXCEPTION_HASH_LIMITER failed: {e:?}"); - None - }, Some); + pub static ref SHM_LIMITER: Option> = + ShmLimiterMemory::open(&shm_limiter_path()).map_or_else( + |e| { + warn!("Attempt to use the SHM_LIMITER failed: {e:?}"); + None + }, + Some + ); + pub static ref EXCEPTION_HASH_LIMITER: Option = + ExceptionHashRateLimiter::open().map_or_else( + |e| { + warn!("Attempt to use the EXCEPTION_HASH_LIMITER failed: {e:?}"); + None + }, + Some + ); } pub struct MaybeShmLimiter(Option); @@ -215,12 +313,20 @@ pub extern "C" fn ddog_shm_limiter_inc(limiter: &MaybeShmLimiter, limit: u32) -> } #[no_mangle] -pub extern "C" fn ddog_exception_hash_limiter_inc(connection: &mut SidecarTransport, hash: u64, granularity_seconds: u32) -> bool { +pub extern "C" fn ddog_exception_hash_limiter_inc( + connection: &mut SidecarTransport, + hash: u64, + granularity_seconds: u32, +) -> bool { if let Some(limiter) = &*EXCEPTION_HASH_LIMITER { if let Some(limiter) = limiter.find(hash) { return limiter.inc(); } } - let _ = acquire_exception_hash_rate_limiter(connection, hash, Duration::from_secs(granularity_seconds as u64)); + let _ = acquire_exception_hash_rate_limiter( + connection, + hash, + Duration::from_secs(granularity_seconds as u64), + ); true } diff --git a/components-rs/stats.rs b/components-rs/stats.rs index e2e831c48e9..72cac24b11e 100644 --- a/components-rs/stats.rs +++ b/components-rs/stats.rs @@ -10,8 +10,9 @@ use crate::trace_filter; use datadog_ipc::shm_stats::{OwnedShmSpanInput, ShmSpanConcentrator, ShmSpanInput, MAX_PEER_TAGS}; use datadog_sidecar::service::blocking::{add_span_to_concentrator, SidecarTransport}; -use libdd_trace_stats::span_concentrator::FixedAggregationKey; use libdd_common_ffi::slice::{AsBytes, CharSlice}; +use libdd_trace_protobuf::pb; +use libdd_trace_stats::span_concentrator::FixedAggregationKey; use std::collections::HashMap; use std::ffi::{c_char, c_void}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -64,7 +65,6 @@ pub struct PhpSpanStats<'a> { /// Value of the `_dd.svc_src` meta tag; empty slice when absent. pub service_source: CharSlice<'a>, - /// gRPC meta values in order: rpc.grpc.status_code, grpc.code, rpc.grpc.status.code, /// grpc.status.code. Empty slice = absent. pub grpc_meta: [CharSlice<'a>; PHP_GRPC_KEY_COUNT], @@ -101,7 +101,11 @@ fn extract_grpc_status_code(span: &PhpSpanStats<'_>) -> Option { .iter() .find_map(|m| { let s = char_slice_str(*m); - if s.is_empty() { None } else { s.parse::().ok() } + if s.is_empty() { + None + } else { + s.parse::().ok() + } }) .or_else(|| { span.grpc_metrics @@ -114,7 +118,11 @@ fn extract_grpc_status_code(span: &PhpSpanStats<'_>) -> Option { #[inline] fn extract_http_endpoint<'a>(span: &'a PhpSpanStats<'a>) -> &'a str { let ep = char_slice_str(span.http_endpoint); - if !ep.is_empty() { ep } else { char_slice_str(span.http_route) } + if !ep.is_empty() { + ep + } else { + char_slice_str(span.http_route) + } } /// Decode the raw peer-tags pointer into a slice. @@ -148,7 +156,11 @@ fn build_fixed_key<'a>(span: &'a PhpSpanStats<'a>) -> FixedAggregationKey<&'a st http_endpoint: extract_http_endpoint(span), http_status_code: extract_http_status_code(span), is_synthetics_request: is_synthetics_request(span), - is_trace_root: span.is_trace_root, + is_trace_root: if span.is_trace_root { + pb::Trilean::True + } else { + pb::Trilean::False + }, grpc_status_code: extract_grpc_status_code(span), service_source: char_slice_str(span.service_source), } @@ -226,7 +238,8 @@ impl SpanConcentrator { } } -static SPAN_CONCENTRATORS: LazyLock>> = LazyLock::new(|| RwLock::default()); +static SPAN_CONCENTRATORS: LazyLock>> = + LazyLock::new(|| RwLock::default()); /// Set to true once `apply_concentrator_config` has been called at least once, /// i.e. the sidecar has received and applied the agent's /info response. @@ -238,10 +251,14 @@ static AGENT_INFO_RECEIVED: AtomicBool = AtomicBool::new(false); static STATS_COMPUTATION_READY: AtomicBool = AtomicBool::new(false); fn agent_version_ge(version: Option<&str>, req_major: u32, req_minor: u32, req_patch: u32) -> bool { - let Some(v) = version else { return false; }; + let Some(v) = version else { + return false; + }; let v = v.split('-').next().unwrap_or(v); // strip agent version suffixes let mut parts = v.split('.').filter_map(|p| p.parse::().ok()); - let (Some(major), Some(minor), Some(patch)) = (parts.next(), parts.next(), parts.next()) else { return false; }; + let (Some(major), Some(minor), Some(patch)) = (parts.next(), parts.next(), parts.next()) else { + return false; + }; (major, minor, patch) >= (req_major, req_minor, req_patch) } @@ -287,7 +304,11 @@ pub(crate) fn apply_concentrator_config( version: Option<&str>, ) { let compiled = trace_filter::compile_trace_filter( - &tags_require, &tags_reject, ®ex_require, ®ex_reject, &ignore_resources, + &tags_require, + &tags_reject, + ®ex_require, + ®ex_reject, + &ignore_resources, ); trace_filter::set_trace_filter(compiled); AGENT_INFO_RECEIVED.store(true, Ordering::Release); @@ -354,7 +375,11 @@ pub unsafe extern "C" fn ddog_span_concentrator_with( let refresh = wg.get(&map_key).map_or(true, |c| c.needs_refresh()); if refresh { wg.remove(&map_key); - let path = datadog_sidecar::service::stats_flusher::env_stats_shm_path(&env_key, &version_key, &service_key); + let path = datadog_sidecar::service::stats_flusher::env_stats_shm_path( + &env_key, + &version_key, + &service_key, + ); let (shm, has_shm) = match ShmSpanConcentrator::open(path.as_c_str()) { Ok(s) => (Some(s), true), Err(e) => { @@ -472,7 +497,12 @@ unsafe fn php_span_to_owned_input(span: &PhpSpanStats<'_>) -> OwnedShmSpanInput let peer_tags = peer_tags_slice(span) .iter() .take(MAX_PEER_TAGS) - .map(|pt| (char_slice_str(pt.key).to_owned(), char_slice_str(pt.value).to_owned())) + .map(|pt| { + ( + char_slice_str(pt.key).to_owned(), + char_slice_str(pt.value).to_owned(), + ) + }) .collect(); let fixed = build_fixed_key(span); @@ -521,4 +551,3 @@ pub unsafe extern "C" fn ddog_sidecar_add_php_span_to_concentrator( trace!("Failed to send span to concentrator via IPC: {e}"); } } - diff --git a/components-rs/telemetry.h b/components-rs/telemetry.h index eec7db11e79..0e818ddc0fc 100644 --- a/components-rs/telemetry.h +++ b/components-rs/telemetry.h @@ -52,7 +52,7 @@ ddog_MaybeError ddog_telemetry_builder_with_config(struct ddog_TelemetryWorkerBu * * handle should be a non null pointer to a null pointer */ ddog_MaybeError ddog_telemetry_builder_run(struct ddog_TelemetryWorkerBuilder *builder, - struct ddog_TelemetryWorkerHandle **out_handle); + ddog_TelemetryWorkerHandle **out_handle); /** * Builds the telemetry worker and return a handle to it. The worker will only process and send @@ -63,13 +63,23 @@ ddog_MaybeError ddog_telemetry_builder_run(struct ddog_TelemetryWorkerBuilder *b * * handle should be a non null pointer to a null pointer */ ddog_MaybeError ddog_telemetry_builder_run_metric_logs(struct ddog_TelemetryWorkerBuilder *builder, - struct ddog_TelemetryWorkerHandle **out_handle); + ddog_TelemetryWorkerHandle **out_handle); +/** + * Sets the telemetry endpoint from its component parts. + * + * * `api_key` / `test_token`: ignored when empty. + * * `timeout_ms`: pass 0 to keep the existing/default timeout. + */ ddog_MaybeError ddog_telemetry_builder_with_endpoint_config_endpoint(struct ddog_TelemetryWorkerBuilder *telemetry_builder, - const struct ddog_Endpoint *endpoint); + ddog_CharSlice url, + ddog_CharSlice api_key, + uint64_t timeout_ms, + ddog_CharSlice test_token, + bool use_system_resolver); /** - * Sets a property from it's string value. + * Sets the endpoint property from its component parts. * * Available properties: * @@ -77,10 +87,14 @@ ddog_MaybeError ddog_telemetry_builder_with_endpoint_config_endpoint(struct ddog */ ddog_MaybeError ddog_telemetry_builder_with_property_endpoint(struct ddog_TelemetryWorkerBuilder *telemetry_builder, enum ddog_TelemetryWorkerBuilderEndpointProperty _property, - const struct ddog_Endpoint *endpoint); + ddog_CharSlice url, + ddog_CharSlice api_key, + uint64_t timeout_ms, + ddog_CharSlice test_token, + bool use_system_resolver); /** - * Sets a property from it's string value. + * Sets a named endpoint property from its component parts. * * Available properties: * @@ -88,7 +102,11 @@ ddog_MaybeError ddog_telemetry_builder_with_property_endpoint(struct ddog_Teleme */ ddog_MaybeError ddog_telemetry_builder_with_endpoint_named_property(struct ddog_TelemetryWorkerBuilder *telemetry_builder, ddog_CharSlice property, - const struct ddog_Endpoint *endpoint); + ddog_CharSlice url, + ddog_CharSlice api_key, + uint64_t timeout_ms, + ddog_CharSlice test_token, + bool use_system_resolver); ddog_MaybeError ddog_telemetry_builder_with_str_application_service_version(struct ddog_TelemetryWorkerBuilder *telemetry_builder, ddog_CharSlice param); @@ -239,11 +257,11 @@ ddog_MaybeError ddog_telemetry_builder_with_bool_named_property(struct ddog_Tele ddog_CharSlice property, bool param); -ddog_MaybeError ddog_telemetry_handle_add_dependency(const struct ddog_TelemetryWorkerHandle *handle, +ddog_MaybeError ddog_telemetry_handle_add_dependency(const ddog_TelemetryWorkerHandle *handle, ddog_CharSlice dependency_name, ddog_CharSlice dependency_version); -ddog_MaybeError ddog_telemetry_handle_add_integration(const struct ddog_TelemetryWorkerHandle *handle, +ddog_MaybeError ddog_telemetry_handle_add_integration(const ddog_TelemetryWorkerHandle *handle, ddog_CharSlice dependency_name, ddog_CharSlice dependency_version, bool enabled, @@ -256,33 +274,33 @@ ddog_MaybeError ddog_telemetry_handle_add_integration(const struct ddog_Telemetr * * stack_trace: stack trace associated with the log. If no stack trace is available, an empty * string should be passed */ -ddog_MaybeError ddog_telemetry_handle_add_log(const struct ddog_TelemetryWorkerHandle *handle, +ddog_MaybeError ddog_telemetry_handle_add_log(const ddog_TelemetryWorkerHandle *handle, ddog_CharSlice indentifier, ddog_CharSlice message, enum ddog_LogLevel level, ddog_CharSlice stack_trace); -ddog_MaybeError ddog_telemetry_handle_start(const struct ddog_TelemetryWorkerHandle *handle); +ddog_MaybeError ddog_telemetry_handle_start(const ddog_TelemetryWorkerHandle *handle); -struct ddog_TelemetryWorkerHandle *ddog_telemetry_handle_clone(const struct ddog_TelemetryWorkerHandle *handle); +ddog_TelemetryWorkerHandle *ddog_telemetry_handle_clone(const ddog_TelemetryWorkerHandle *handle); -ddog_MaybeError ddog_telemetry_handle_stop(const struct ddog_TelemetryWorkerHandle *handle); +ddog_MaybeError ddog_telemetry_handle_stop(const ddog_TelemetryWorkerHandle *handle); /** * * compatible: should be false if the metric is language specific, true otherwise */ -struct ddog_ContextKey ddog_telemetry_handle_register_metric_context(const struct ddog_TelemetryWorkerHandle *handle, +struct ddog_ContextKey ddog_telemetry_handle_register_metric_context(const ddog_TelemetryWorkerHandle *handle, ddog_CharSlice name, enum ddog_MetricType metric_type, struct ddog_Vec_Tag tags, bool common, enum ddog_MetricNamespace namespace_); -ddog_MaybeError ddog_telemetry_handle_add_point(const struct ddog_TelemetryWorkerHandle *handle, +ddog_MaybeError ddog_telemetry_handle_add_point(const ddog_TelemetryWorkerHandle *handle, const struct ddog_ContextKey *context_key, double value); -ddog_MaybeError ddog_telemetry_handle_add_point_with_tags(const struct ddog_TelemetryWorkerHandle *handle, +ddog_MaybeError ddog_telemetry_handle_add_point_with_tags(const ddog_TelemetryWorkerHandle *handle, const struct ddog_ContextKey *context_key, double value, struct ddog_Vec_Tag extra_tags); @@ -290,18 +308,18 @@ ddog_MaybeError ddog_telemetry_handle_add_point_with_tags(const struct ddog_Tele /** * This function takes ownership of the handle. It should not be used after calling it */ -void ddog_telemetry_handle_wait_for_shutdown(struct ddog_TelemetryWorkerHandle *handle); +void ddog_telemetry_handle_wait_for_shutdown(ddog_TelemetryWorkerHandle *handle); /** * This function takes ownership of the handle. It should not be used after calling it */ -void ddog_telemetry_handle_wait_for_shutdown_ms(struct ddog_TelemetryWorkerHandle *handle, +void ddog_telemetry_handle_wait_for_shutdown_ms(ddog_TelemetryWorkerHandle *handle, uint64_t wait_for_ms); /** * Drops the handle without waiting for shutdown. The worker will continue running in the * background until it exits by itself */ -void ddog_telemetry_handle_drop(struct ddog_TelemetryWorkerHandle *handle); +void ddog_telemetry_handle_drop(ddog_TelemetryWorkerHandle *handle); #endif /* DDOG_TELEMETRY_H */ diff --git a/components/context-discovery/context_discovery.c b/components/context-discovery/context_discovery.c new file mode 100644 index 00000000000..0741117099b --- /dev/null +++ b/components/context-discovery/context_discovery.c @@ -0,0 +1,67 @@ +#include "context_discovery.h" + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +typedef ddog_php_process_ctx_mapping (*ddog_php_process_ctx_fn)(void); +typedef void **(*ddog_php_thread_ctx_fn)(void); + +static void *ddog_php_context_symbol(void *module_handle, const char *name) { +#ifdef _WIN32 + return module_handle ? (void *)GetProcAddress((HMODULE)module_handle, name) : NULL; +#else + return module_handle ? dlsym(module_handle, name) : NULL; +#endif +} + +void *ddog_php_context_discovery_resolve_tls(void *symbol) { +#ifdef __APPLE__ + typedef struct { + void *(*thunk)(void *descriptor); + unsigned long key; + unsigned long offset; + } ddog_php_tls_descriptor; + + ddog_php_tls_descriptor *descriptor = symbol; + return descriptor && descriptor->thunk ? descriptor->thunk(descriptor) : NULL; +#else + return symbol; +#endif +} + +void ddog_php_context_discovery_reset(ddog_php_context_discovery *discovery) { + if (discovery) { + memset(discovery, 0, sizeof(*discovery)); + } +} + +bool ddog_php_context_discovery_resolve_ddtrace(ddog_php_context_discovery *discovery, void *module_handle) { + if (!discovery || !module_handle) { + return false; + } + discovery->process_ctx = (ddog_php_process_ctx_fn)(uintptr_t)ddog_php_context_symbol(module_handle, "ddog_process_ctx_v1"); + discovery->thread_ctx = (ddog_php_thread_ctx_fn)(uintptr_t)ddog_php_context_symbol(module_handle, "ddog_thread_ctx_v1"); + return discovery->process_ctx || discovery->thread_ctx; +} + +ddog_php_process_ctx_mapping ddog_php_context_discovery_process_mapping(const ddog_php_context_discovery *discovery) { + ddog_php_process_ctx_mapping unavailable = {0}; + return discovery && discovery->process_ctx ? discovery->process_ctx() : unavailable; +} + +void **ddog_php_context_discovery_thread_slot(const ddog_php_context_discovery *discovery) { + return discovery && discovery->thread_ctx ? discovery->thread_ctx() : NULL; +} + +void **ddog_php_context_discovery_otel_thread_slot(void) { +#if defined(__linux__) || defined(__APPLE__) + return (void **)ddog_php_context_discovery_resolve_tls(dlsym(RTLD_DEFAULT, "otel_thread_ctx_v1")); +#else + return NULL; +#endif +} diff --git a/components/context-discovery/context_discovery.h b/components/context-discovery/context_discovery.h new file mode 100644 index 00000000000..e68c7df9100 --- /dev/null +++ b/components/context-discovery/context_discovery.h @@ -0,0 +1,25 @@ +#ifndef DDOG_PHP_CONTEXT_DISCOVERY_H +#define DDOG_PHP_CONTEXT_DISCOVERY_H + +#include +#include +#include + +typedef struct { + const uint8_t *address; + uintptr_t length; +} ddog_php_process_ctx_mapping; + +typedef struct { + ddog_php_process_ctx_mapping (*process_ctx)(void); + void **(*thread_ctx)(void); +} ddog_php_context_discovery; + +void ddog_php_context_discovery_reset(ddog_php_context_discovery *discovery); +bool ddog_php_context_discovery_resolve_ddtrace(ddog_php_context_discovery *discovery, void *module_handle); +ddog_php_process_ctx_mapping ddog_php_context_discovery_process_mapping(const ddog_php_context_discovery *discovery); +void **ddog_php_context_discovery_thread_slot(const ddog_php_context_discovery *discovery); +void *ddog_php_context_discovery_resolve_tls(void *symbol); +void **ddog_php_context_discovery_otel_thread_slot(void); + +#endif diff --git a/config.m4 b/config.m4 index aa69d309a43..0bc79e7e5c8 100644 --- a/config.m4 +++ b/config.m4 @@ -224,6 +224,7 @@ if test "$PHP_DDTRACE" != "no"; then tracer/live_debugger.c \ tracer/limiter/limiter.c \ tracer/memory_limit.c \ + tracer/otel_context.c \ tracer/tracer_otel_config.c \ tracer/priority_sampling/priority_sampling.c \ tracer/profiling.c \ diff --git a/config.w32 b/config.w32 index 5ff5b2e9d70..fbb75db7d11 100644 --- a/config.w32 +++ b/config.w32 @@ -66,6 +66,7 @@ if (PHP_DDTRACE != 'no') { DDTRACE_TRACER_SOURCES += " ip_extraction.c"; DDTRACE_TRACER_SOURCES += " live_debugger.c"; DDTRACE_TRACER_SOURCES += " memory_limit.c"; + DDTRACE_TRACER_SOURCES += " otel_context.c"; DDTRACE_TRACER_SOURCES += " tracer_otel_config.c"; DDTRACE_TRACER_SOURCES += " profiling.c"; DDTRACE_TRACER_SOURCES += " random.c"; diff --git a/datadog-windows.sym b/datadog-windows.sym index a0d58995495..6a9de8a3fbb 100644 --- a/datadog-windows.sym +++ b/datadog-windows.sym @@ -2,3 +2,5 @@ ddog_setup_crashtracking OutOfProcessExceptionEventCallback OutOfProcessExceptionEventSignatureCallback OutOfProcessExceptionEventDebuggerLaunchCallback +ddog_process_ctx_v1 +ddog_thread_ctx_v1 diff --git a/datadog.sym b/datadog.sym index 510519066f2..5ef53280baa 100644 --- a/datadog.sym +++ b/datadog.sym @@ -1,8 +1,10 @@ ddtrace_close_all_spans_and_flush datadog_get_formatted_session_id -ddtrace_get_profiling_context ddtrace_get_root_span datadog_process_tags_get_serialized +ddog_process_ctx_v1 +ddog_thread_ctx_v1 +otel_thread_ctx_v1 datadog_get_sidecar_queue_id ddtrace_get_priority_sampling_on_span_zobj ddtrace_set_priority_sampling_on_span_zobj diff --git a/ext/datadog.c b/ext/datadog.c index fc2cd4622c4..00445fda67f 100644 --- a/ext/datadog.c +++ b/ext/datadog.c @@ -9,6 +9,7 @@ #include "configuration.h" #include "excluded_modules.h" +#include "ffi_utils.h" #include "agent_info.h" #include "logging.h" #include "phpinfo.h" @@ -267,6 +268,10 @@ static PHP_GINIT_FUNCTION(datadog) { ZEND_TSRMLS_CACHE_UPDATE(); #endif php_datadog_init_globals(datadog_globals); + datadog_globals->otel_process_context = datadog_otel_process_context_new(); + if (!datadog_globals->otel_process_context) { + LOG(ERROR, "Failed to allocate the OTel process context; context sharing is disabled"); + } #if ZTS datadog_thread_ginit(); #endif @@ -350,6 +355,10 @@ static PHP_GSHUTDOWN_FUNCTION(datadog) { #if ZTS datadog_thread_gshutdown(); #endif + if (datadog_globals->otel_process_context) { + datadog_otel_process_context_drop(datadog_globals->otel_process_context); + datadog_globals->otel_process_context = NULL; + } if (datadog_globals->remote_config_state) { ddog_shutdown_remote_config(datadog_globals->remote_config_state); } @@ -566,6 +575,18 @@ static void dd_rinit_once(void) { static pthread_once_t dd_rinit_once_control = PTHREAD_ONCE_INIT; +typedef struct { + const uint8_t *address; + uintptr_t length; +} ddog_process_ctx_mapping; + +DATADOG_PUBLIC ddog_process_ctx_mapping ddog_process_ctx_v1(void) { + ddog_process_ctx_mapping mapping = {0}; + datadog_otel_process_context_mapping( + DATADOG_G(otel_process_context), &mapping.address, &mapping.length); + return mapping; +} + static PHP_RINIT_FUNCTION(datadog) { UNUSED(module_number, type); @@ -581,6 +602,22 @@ static PHP_RINIT_FUNCTION(datadog) { // Things that should only run on the first RINIT after each minit. pthread_once(&dd_rinit_once_control, dd_rinit_once); + // Module globals are thread-local in ZTS builds, so each thread owns and publishes its fixed + // mapping. In NTS this updates the single process mapping on each request. + if (DATADOG_G(otel_process_context)) { + zend_string *process_tags = datadog_process_tags_get_serialized(); + bool published = datadog_otel_process_context_publish( + DATADOG_G(otel_process_context), + dd_zend_string_to_CharSlice(get_DD_HOSTNAME()), + dd_zend_string_to_CharSlice(get_DD_SERVICE()), + dd_zend_string_to_CharSlice(get_DD_ENV()), + dd_zend_string_to_CharSlice(get_DD_VERSION()), + dd_zend_string_to_CharSlice(process_tags)); + if (!published) { + LOG(ERROR, "Failed to publish the OTel process context; tracing will continue without context sharing"); + } + } + datadog_log_rinit(PG(error_log)); datadog_agent_info_rinit(); diff --git a/ext/datadog.h b/ext/datadog.h index c6f9fb8835e..6906551e76d 100644 --- a/ext/datadog.h +++ b/ext/datadog.h @@ -65,6 +65,7 @@ ZEND_BEGIN_MODULE_GLOBALS(datadog) zend_string *last_env_name; zend_string *last_version; ddog_Vec_Tag active_global_tags; + void *otel_process_context; bool request_initialized; ddog_SidecarActionsBuffer *telemetry_buffer; diff --git a/ext/signals.c b/ext/signals.c index 7def8a44e8c..80702419eb0 100644 --- a/ext/signals.c +++ b/ext/signals.c @@ -53,7 +53,6 @@ // true globals; only modify in MINIT/MSHUTDOWN static stack_t dd_altstack; static struct sigaction dd_sigsegv_sigaction; -static char crashtracker_socket_path[100] = {0}; static char *dd_signal_async_stack; static size_t dd_signal_async_stack_size; @@ -243,26 +242,16 @@ typedef struct { static void dd_crasht_do_init(ddog_crasht_EndpointConfig endpoint_config, void *userdata) { dd_crasht_init_args *args = (dd_crasht_init_args *)userdata; args->config.endpoint = endpoint_config; + uint32_t sidecar_master_pid = datadog_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD + ? (uint32_t)datadog_sidecar_master_pid + : 0; dd_crashtracker_check_result( - ddog_crasht_init_without_receiver(args->config, args->metadata), + datadog_crasht_init_without_receiver(args->config, args->metadata, sidecar_master_pid), "Cannot initialize CrashTracker" ); } static void dd_init_crashtracker() { - ddog_CharSlice socket_path = ddog_sidecar_get_crashtracker_unix_socket_path(); - if (socket_path.len > sizeof(crashtracker_socket_path) - 1) { - LOG(ERROR, "Cannot initialize CrashTracker : the socket path is too long."); - free((void *) socket_path.ptr); - return; - } - - // Copy the string to a global buffer to avoid a use-after-free error - memcpy(crashtracker_socket_path, socket_path.ptr, socket_path.len); - crashtracker_socket_path[socket_path.len] = '\0'; - free((void *) socket_path.ptr); - socket_path.ptr = crashtracker_socket_path; - if (!datadog_endpoint) { return; } @@ -274,7 +263,6 @@ static void dd_init_crashtracker() { .config = { .timeout_ms = 5000, .resolve_frames = DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, - .optional_unix_socket_filename = socket_path, .additional_files = {0}, .collect_all_threads = true, .max_threads = 0, // uses libdatadog default, which is 256 diff --git a/libdatadog b/libdatadog index 6a6d4a535e9..5278b8450eb 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 6a6d4a535e9875a7b012ce3f00eb6929649c3fb5 +Subproject commit 5278b8450ebd3fdf12e4a3dd808d02bbfb350672 diff --git a/profiling/Cargo.toml b/profiling/Cargo.toml index 37f9dea8dd8..7a8cac3c10b 100644 --- a/profiling/Cargo.toml +++ b/profiling/Cargo.toml @@ -25,6 +25,7 @@ http = { version = "1.4" } libdd-alloc = { path = "../libdatadog/libdd-alloc" } libdd-profiling = { path = "../libdatadog/libdd-profiling" } libdd-common = { path = "../libdatadog/libdd-common" } +libdd-library-config = { path = "../libdatadog/libdd-library-config", default-features = false, features = ["process-context-reader"] } libdd-library-config-ffi = { path = "../libdatadog/libdd-library-config-ffi" } env_logger = { version = "0.11", default-features = false } libc = "0.2" diff --git a/profiling/build.rs b/profiling/build.rs index 140662e6997..46c35a72381 100644 --- a/profiling/build.rs +++ b/profiling/build.rs @@ -111,6 +111,8 @@ fn build_zend_php_ffis( ) { println!("cargo:rerun-if-changed=src/php_ffi.h"); println!("cargo:rerun-if-changed=src/php_ffi.c"); + println!("cargo:rerun-if-changed=../components/context-discovery/context_discovery.c"); + println!("cargo:rerun-if-changed=../components/context-discovery/context_discovery.h"); println!("cargo:rerun-if-changed=../ext/handlers_api.c"); println!("cargo:rerun-if-changed=../ext/handlers_api.h"); @@ -142,7 +144,11 @@ fn build_zend_php_ffis( prefix = prefix.trim() ); - let files = ["src/php_ffi.c", "../ext/handlers_api.c"]; + let files = [ + "src/php_ffi.c", + "../ext/handlers_api.c", + "../components/context-discovery/context_discovery.c", + ]; let post_startup_cb = if post_startup_cb { "1" } else { "0" }; let preload = if preload { "1" } else { "0" }; let fibers = if fibers { "1" } else { "0" }; @@ -274,6 +280,9 @@ fn generate_bindings(php_config_includes: &str, fibers: bool, zend_error_observe .raw_line("pub type zend_vm_opcode_handler_func_t = *const ::std::ffi::c_void;") // Block a few of functions that we'll provide defs for manually .blocklist_item("datadog_php_profiling_vm_interrupt_addr") + .blocklist_item("ddog_php_prof_otel_thread_ctx_ginit") + .blocklist_item("ddog_php_prof_otel_thread_ctx_rinit") + .blocklist_item("datadog_php_profiling_context_api_name") // I had to block these for some reason *shrug* .blocklist_item("FP_INFINITE") .blocklist_item("FP_INT_DOWNWARD") diff --git a/profiling/src/bindings/mod.rs b/profiling/src/bindings/mod.rs index 220be2cd123..50bc5b9691b 100644 --- a/profiling/src/bindings/mod.rs +++ b/profiling/src/bindings/mod.rs @@ -340,6 +340,21 @@ extern "C" { /// Must be called from a PHP thread during a request. pub fn datadog_php_profiling_vm_interrupt_addr() -> *const AtomicBool; + /// Initializes per-thread profiler FFI state. + /// # Safety + /// Must be called from a PHP thread during GINIT. + #[cfg(any(target_os = "linux", target_os = "macos"))] + pub fn ddog_php_prof_otel_thread_ctx_ginit(); + + /// Verifies per-thread profiler FFI state. + /// # Safety + /// Must be called from a PHP thread during a request. + #[cfg(any(target_os = "linux", target_os = "macos"))] + pub fn ddog_php_prof_otel_thread_ctx_rinit() -> bool; + + /// Returns the profiling context API selected for this request. + pub fn datadog_php_profiling_context_api_name() -> ZaiStr<'static>; + /// Registers the extension. Note that it's kept in a zend_llist and gets /// pemalloc'd + memcpy'd into place. The engine says this is a mutable /// pointer, but in practice it's const. diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index e327b25ff5c..25dfb6f1b87 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -580,6 +580,27 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { // values to the ones in the configuration. let system_settings = SystemSettings::get(); + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + // SAFETY: we are in rinit on a PHP thread. + if !unsafe { zend::ddog_php_prof_otel_thread_ctx_rinit() } { + error!("failed to initialize profiler OTel thread context state"); + return ZendResult::Failure; + } + } + + // macOS has no discoverable Process Context mapping. Linux deliberately uses the self reader + // instead, so its process context matches the OTel TLS symbol chosen by the dynamic linker. + #[cfg(target_os = "macos")] + let globals = unsafe { &mut *module_globals::get_profiler_globals() }; + #[cfg(target_os = "macos")] + unsafe { + zend::ddog_php_prof_otel_process_ctx_rinit( + &mut globals.otel_process_context_base, + &mut globals.otel_process_context_len, + ); + } + // initialize the thread local storage and cache some items let result = REQUEST_LOCALS.try_with_borrow_mut(|locals| { // SAFETY: we are in rinit on a PHP thread. @@ -663,6 +684,13 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { let once = unsafe { &*ptr::addr_of!(RINIT_ONCE) }; once.call_once(|| { if system_settings.profiling_enabled { + // SAFETY: this returns a view of a static string owned by php_ffi.c. + let context_api = unsafe { bindings::datadog_php_profiling_context_api_name() }; + info!( + "Profiling context API selected: {}.", + context_api.to_string_lossy() + ); + // SAFETY: sapi_module is initialized by rinit and shouldn't be // modified at this point (safe to read values). let sapi_module = unsafe { &*ptr::addr_of!(zend::sapi_module) }; diff --git a/profiling/src/module_globals.rs b/profiling/src/module_globals.rs index 85c938e5355..970687d51a1 100644 --- a/profiling/src/module_globals.rs +++ b/profiling/src/module_globals.rs @@ -13,6 +13,8 @@ pub struct ProfilerGlobals { /// Wrapped in `Cell` to prevent torn reads/writes when allocation hooks /// are called re-entrantly during `rinit()`/`rshutdown()`. pub zend_mm_state: Cell, + pub otel_process_context_base: *const u8, + pub otel_process_context_len: usize, } /// We need TSRM to call into GINIT and GSHUTDOWN to observe spawning and @@ -29,6 +31,8 @@ pub static mut GLOBALS_ID: i32 = 0; #[cfg(not(php_zts))] pub static mut GLOBALS: ProfilerGlobals = ProfilerGlobals { zend_mm_state: Cell::new(ZendMMState::new()), + otel_process_context_base: ptr::null(), + otel_process_context_len: 0, }; #[cfg(php_zts)] @@ -85,12 +89,20 @@ pub unsafe extern "C" fn ginit(_globals_ptr: *mut c_void) { #[cfg(php_zts)] crate::timeline::timeline_ginit(); - // Initialize ZendMMState in PHP globals for ZTS builds. For NTS builds, - // this was already done in its const initializer. + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + // SAFETY: this is called by PHP's module globals ctor for the current PHP thread. + unsafe { crate::bindings::ddog_php_prof_otel_thread_ctx_ginit() }; + } + + // Initialize PHP globals for ZTS builds. For NTS builds, the static + // globals were already initialized in their const initializer. #[cfg(php_zts)] { let globals = _globals_ptr.cast::(); (*globals).zend_mm_state = Cell::new(ZendMMState::new()); + (*globals).otel_process_context_base = ptr::null(); + (*globals).otel_process_context_len = 0; } // SAFETY: this is called in thread ginit as expected, and no other places. diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index 1e906f3cbd3..1813aa9275e 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -1,31 +1,169 @@ #include "php_ffi.h" -#include #include #include #include #include #include "SAPI.h" +#include "components/context-discovery/context_discovery.h" -#if CFG_STACK_WALKING_TESTS +#if CFG_STACK_WALKING_TESTS || defined(__linux__) || defined(__APPLE__) #include // for dlsym #endif +#if defined(__linux__) || defined(__APPLE__) +#include +#include +#endif const char *datadog_extension_build_id(void) { return ZEND_EXTENSION_BUILD_ID; } const char *datadog_module_build_id(void) { return ZEND_MODULE_BUILD_ID; } uint8_t *datadog_runtime_id = NULL; +static const zai_str datadog_php_profiling_context_api_none = ZAI_STRL("none"); +#if defined(__linux__) || defined(__APPLE__) +static const zai_str ddog_php_prof_context_api_otel = ZAI_STRL("otel_thread_ctx_v1"); +#endif -static void locate_datadog_runtime_id(const zend_extension *extension) { - datadog_runtime_id = DL_FETCH_SYMBOL(extension->handle, "datadog_runtime_id"); +static ddtrace_profiling_context datadog_php_profiling_get_context(void); + +static ddog_php_context_discovery ddog_php_prof_context_discovery = {0}; + +#if defined(__linux__) || defined(__APPLE__) +typedef struct ddog_php_prof_otel_thread_context_record { + _Atomic(uint8_t) trace_id[16]; + _Atomic(uint8_t) span_id[8]; + _Atomic(uint8_t) valid; + uint8_t reserved; + _Atomic(uint16_t) attrs_data_size; + _Atomic(uint8_t) attrs_data[DDOG_PHP_PROF_OTEL_ATTRS_DATA_SIZE]; +} ddog_php_prof_otel_thread_context_record; + +_Static_assert(sizeof(ddog_php_prof_otel_thread_context_record) == 640, + "unexpected OTel thread context record size"); +_Static_assert(_Alignof(ddog_php_prof_otel_thread_context_record) == 2, + "unexpected OTel thread context record alignment"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, trace_id) == 0, + "unexpected OTel thread context trace_id offset"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, span_id) == 16, + "unexpected OTel thread context span_id offset"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, valid) == 24, + "unexpected OTel thread context valid offset"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, reserved) == 25, + "unexpected OTel thread context reserved offset"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, attrs_data_size) == 26, + "unexpected OTel thread context attrs_data_size offset"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, attrs_data_size) % _Alignof(uint16_t) == 0, + "unexpected OTel thread context attrs_data_size alignment"); +_Static_assert(offsetof(ddog_php_prof_otel_thread_context_record, attrs_data) == 28, + "unexpected OTel thread context attrs_data offset"); + +static __thread void **ddog_php_prof_otel_thread_ctx_slot = NULL; +static atomic_bool ddog_php_prof_otel_thread_ctx_symbol_available = false; + +static uint64_t ddog_php_prof_read_u64_be(const uint8_t src[8]) { + uint64_t be_value; + memcpy(&be_value, src, sizeof(be_value)); + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return __builtin_bswap64(be_value); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + return be_value; +#else +#error "Unsupported byte order" +#endif +} + +bool ddog_php_prof_read_otel_context(ddog_php_prof_otel_context *context) { + if (!ddog_php_prof_otel_thread_ctx_slot) { + return false; + } + + ddog_php_prof_otel_thread_context_record *record = + (ddog_php_prof_otel_thread_context_record *)*ddog_php_prof_otel_thread_ctx_slot; + if (!record || atomic_load_explicit(&record->valid, memory_order_relaxed) != 1) { + return false; + } + atomic_signal_fence(memory_order_acquire); + + uint8_t span_id[8]; + for (size_t i = 0; i < sizeof(span_id); ++i) { + span_id[i] = atomic_load_explicit(&record->span_id[i], memory_order_relaxed); + } + context->span_id = ddog_php_prof_read_u64_be(span_id); + context->attrs_data_size = atomic_load_explicit(&record->attrs_data_size, memory_order_relaxed); + if (context->attrs_data_size > DDOG_PHP_PROF_OTEL_ATTRS_DATA_SIZE) { + return false; + } + for (size_t i = 0; i < context->attrs_data_size; ++i) { + context->attrs_data[i] = atomic_load_explicit(&record->attrs_data[i], memory_order_relaxed); + } + atomic_signal_fence(memory_order_acquire); + if (atomic_load_explicit(&record->valid, memory_order_relaxed) != 1) { + return false; + } + + return true; +} + +static ddtrace_profiling_context ddog_php_prof_read_otel_profiling_context(void) { + ddtrace_profiling_context context = {0, 0}; + ddog_php_prof_otel_context otel_context; + if (!ddog_php_prof_read_otel_context(&otel_context)) { + return context; + } + + context.span_id = otel_context.span_id; + + return context; +} + +static void *ddog_php_prof_find_otel_thread_ctx_symbol(void) { +#ifdef __linux__ + return ddog_php_context_discovery_otel_thread_slot(); +#else + return ddog_php_context_discovery_thread_slot(&ddog_php_prof_context_discovery); +#endif } -static void locate_ddtrace_get_profiling_context(const zend_extension *extension) { - ddtrace_profiling_context (*get_profiling)(void) = - DL_FETCH_SYMBOL(extension->handle, "ddtrace_get_profiling_context"); - if (EXPECTED(get_profiling)) { - datadog_php_profiling_get_profiling_context = get_profiling; +static void ddog_php_prof_init_otel_thread_ctx_slot(void) { + if (ddog_php_prof_otel_thread_ctx_slot) { + return; } + + ddog_php_prof_otel_thread_ctx_slot = + (void **)ddog_php_prof_find_otel_thread_ctx_symbol(); + if (ddog_php_prof_otel_thread_ctx_slot) { + atomic_store_explicit( + &ddog_php_prof_otel_thread_ctx_symbol_available, + true, + memory_order_relaxed + ); + } +} + +static bool ddog_php_prof_otel_thread_ctx_slot_is_valid(void) { + if (!atomic_load_explicit( + &ddog_php_prof_otel_thread_ctx_symbol_available, + memory_order_relaxed + )) { + return true; + } + + if (ddog_php_prof_otel_thread_ctx_slot) { + return true; + } + + return false; +} +#else +bool ddog_php_prof_read_otel_context(ddog_php_prof_otel_context *context) { + (void) context; + return false; +} +#endif + +static void locate_datadog_runtime_id(const zend_extension *extension) { + datadog_runtime_id = DL_FETCH_SYMBOL(extension->handle, "datadog_runtime_id"); } static void locate_datadog_process_tags_get_serialized(const zend_extension *extension) { @@ -36,12 +174,13 @@ static void locate_datadog_process_tags_get_serialized(const zend_extension *ext } } -static bool is_ddtrace_extension(const zend_extension *ext) { - return ext && ext->name && strcmp(ext->name, "ddtrace") == 0; +static void locate_datadog_otel_process_context(const zend_extension *extension) { + ddog_php_context_discovery_resolve_ddtrace( + &ddog_php_prof_context_discovery, extension->handle); } -static ddtrace_profiling_context noop_get_profiling_context(void) { - return (ddtrace_profiling_context){0, 0}; +static bool is_ddtrace_extension(const zend_extension *ext) { + return ext && ext->name && strcmp(ext->name, "ddtrace") == 0; } static zend_string *noop_get_process_tags_serialized(void) { @@ -134,6 +273,10 @@ static post_startup_cb_result ddog_php_prof_post_startup_cb(void) { } } +#if defined(__linux__) || defined(__APPLE__) + ddog_php_prof_init_otel_thread_ctx_slot(); +#endif + _is_post_startup = true; return SUCCESS; @@ -155,7 +298,7 @@ void datadog_php_profiling_startup(zend_extension *extension) { _ignore_run_time_cache = strcmp(sapi_module.name, "cli") == 0; #endif - datadog_php_profiling_get_profiling_context = noop_get_profiling_context; + datadog_php_profiling_get_profiling_context = datadog_php_profiling_get_context; datadog_php_profiling_get_process_tags_serialized = noop_get_process_tags_serialized; /* Due to the optional dependency on ddtrace, the profiling module will be @@ -166,9 +309,9 @@ void datadog_php_profiling_startup(zend_extension *extension) { for (const zend_llist_element *item = list->head; item; item = item->next) { const zend_extension *maybe_ddtrace = (zend_extension *)item->data; if (maybe_ddtrace != extension && is_ddtrace_extension(maybe_ddtrace)) { - locate_ddtrace_get_profiling_context(maybe_ddtrace); locate_datadog_runtime_id(maybe_ddtrace); locate_datadog_process_tags_get_serialized(maybe_ddtrace); + locate_datadog_otel_process_context(maybe_ddtrace); break; } } @@ -181,14 +324,56 @@ void datadog_php_profiling_startup(zend_extension *extension) { #endif } +#if defined(__linux__) || defined(__APPLE__) +void ddog_php_prof_otel_thread_ctx_ginit(void) { + ddog_php_prof_init_otel_thread_ctx_slot(); +} + +bool ddog_php_prof_otel_thread_ctx_rinit(void) { + return ddog_php_prof_otel_thread_ctx_slot_is_valid(); +} + + +#endif + +bool ddog_php_prof_otel_process_ctx_rinit(const uint8_t **mapping_base, size_t *mapping_len) { + if (!mapping_base || !mapping_len) { + return false; + } + *mapping_base = NULL; + *mapping_len = 0; + ddog_php_process_ctx_mapping mapping = + ddog_php_context_discovery_process_mapping(&ddog_php_prof_context_discovery); + *mapping_base = mapping.address; + *mapping_len = mapping.length; + return mapping.address && mapping.length; +} + +zai_str datadog_php_profiling_context_api_name(void) { +#if defined(__linux__) || defined(__APPLE__) + if (ddog_php_prof_otel_thread_ctx_slot) { + return ddog_php_prof_context_api_otel; + } +#endif + return datadog_php_profiling_context_api_none; +} + void *datadog_php_profiling_vm_interrupt_addr(void) { return &EG(vm_interrupt); } zend_module_entry *datadog_get_module_entry(const char *str, uintptr_t len) { return zend_hash_str_find_ptr(&module_registry, str, len); } +static ddtrace_profiling_context datadog_php_profiling_get_context(void) { +#if defined(__linux__) || defined(__APPLE__) + return ddog_php_prof_read_otel_profiling_context(); +#else + return (ddtrace_profiling_context){0, 0}; +#endif +} + ddtrace_profiling_context (*datadog_php_profiling_get_profiling_context)(void) = - noop_get_profiling_context; + datadog_php_profiling_get_context; zend_string *(*datadog_php_profiling_get_process_tags_serialized)(void) = noop_get_process_tags_serialized; @@ -814,25 +999,10 @@ bool ddog_php_prof_is_parallel_thread() { return false; } - void **tls_ptr; -#ifdef __APPLE__ - // On macOS TLS variables accessed via dlsym return a descriptor structure containing a - // function pointer (thunk) that must be called to get the actual thread-local address. - // see https://github.com/apple-oss-distributions/dyld/blob/637911768f664e38e7e50b4fbf17e303e14fdc01/libdyld/ThreadLocalVariables.h#L110-L115 - typedef struct { - void* (*thunk)(void* desc); - unsigned long key; - unsigned long offset; - } tls_descriptor; - - tls_descriptor *desc = (tls_descriptor*)tls_symbol; - - if (desc->thunk == NULL) { - return false; - } - - tls_ptr = (void**)desc->thunk(desc); -#else + // On Darwin the shared helper resolves the descriptor. On Linux (musl and glibc), dlsym() + // resolves STT_TLS through __tls_get_addr() and the helper returns the pointer unchanged. + void **tls_ptr = (void **)ddog_php_context_discovery_resolve_tls(tls_symbol); +#ifndef __APPLE__ // Linux (musl and glibc) are nice to us, `dlsym()` detects that this symbol is a STT_TLS // (Symbol Table Type Thread-Local Storage) and calls `__tls_get_addr()` on it. // @@ -843,7 +1013,6 @@ bool ddog_php_prof_is_parallel_thread() { // // So in the end it just returns a pointer to the correct TLS variable for `this` thread we are // in, which makes it an easy pointer deref to get the value. - tls_ptr = (void**)tls_symbol; #endif if (tls_ptr == NULL) { diff --git a/profiling/src/php_ffi.h b/profiling/src/php_ffi.h index 558c3de4413..ed743ec1288 100644 --- a/profiling/src/php_ffi.h +++ b/profiling/src/php_ffi.h @@ -20,6 +20,8 @@ #include #endif +#define DDOG_PHP_PROF_OTEL_ATTRS_DATA_SIZE 612 + // Needed for `zend_observer_error_register` starting from PHP 8 #if CFG_ZEND_ERROR_OBSERVER // defined by build.rs #include @@ -73,18 +75,22 @@ zend_module_entry *datadog_get_module_entry(const char *str, uintptr_t len); void *datadog_php_profiling_vm_interrupt_addr(void); /** - * For Code Hotspots, we need the tracer's local root span id and the current - * span id. This is a cross-product struct, so keep it in sync with tracer's - * version of this struct. - * todo: re-use the tracer's header? + * For Code Hotspots, we need the local root span id and the current span id. + * This is populated exclusively from the OTel Thread Context. */ typedef struct ddtrace_profiling_context_s { uint64_t local_root_span_id, span_id; } ddtrace_profiling_context; +typedef struct ddog_php_prof_otel_context_s { + uint64_t span_id; + uint16_t attrs_data_size; + uint8_t attrs_data[DDOG_PHP_PROF_OTEL_ATTRS_DATA_SIZE]; +} ddog_php_prof_otel_context; + /** - * A pointer to the tracer's ddtrace_get_profiling_context function if it was - * found, otherwise points to a function which just returns {0, 0}. + * A pointer to the profiling-context function. It reads the OTel Thread + * Context directly on supported platforms and otherwise returns {0, 0}. */ extern ddtrace_profiling_context (*datadog_php_profiling_get_profiling_context)(void); @@ -97,10 +103,40 @@ extern zend_string *(*datadog_php_profiling_get_process_tags_serialized)(void); /** * Called by this zend_extension's .startup handler. Does things that are * burdensome in Rust, like locating the ddtrace extension in the module - * registry and finding the ddtrace_get_profiling_context function. + * registry and resolving its OTel Process Context accessor. */ void datadog_php_profiling_startup(zend_extension *extension); +#if defined(__linux__) || defined(__APPLE__) +/** + * Called from the PHP module globals ctor to initialize per-thread profiler FFI + * state. + */ +void ddog_php_prof_otel_thread_ctx_ginit(void); + +/** + * Called by this zend_extension's .activate handler to verify per-thread + * profiler FFI state. Returns false if a provider was found for the process but + * not initialized for this PHP thread. + */ +bool ddog_php_prof_otel_thread_ctx_rinit(void); +#endif + +/** Refreshes the process-context mapping retained in the profiler module globals. */ +bool ddog_php_prof_otel_process_ctx_rinit(const uint8_t **mapping_base, size_t *mapping_len); + +/** + * Copies the current OTel thread context for Rust-side decoding. Returns false + * when the OTel TLS slot is unavailable, empty, or currently invalid. + */ +bool ddog_php_prof_read_otel_context(ddog_php_prof_otel_context *context); + +/** + * Returns the profiling context API selected for this request, or "none" when + * no provider was found. + */ +zai_str datadog_php_profiling_context_api_name(void); + /** * Used to hold information for overwriting the internal function handler * pointer in the Zend Engine. diff --git a/profiling/src/profiling/mod.rs b/profiling/src/profiling/mod.rs index 89c686bd189..810841a0b4e 100644 --- a/profiling/src/profiling/mod.rs +++ b/profiling/src/profiling/mod.rs @@ -19,8 +19,7 @@ use crate::bindings::ddog_php_prof_get_active_fiber_test as ddog_php_prof_get_ac use crate::allocation::ALLOCATION_PROFILING_INTERVAL; use crate::bindings::{ - datadog_php_profiling_get_process_tags_serialized, datadog_php_profiling_get_profiling_context, - zai_str_from_zstr, zend_execute_data, + datadog_php_profiling_get_process_tags_serialized, zai_str_from_zstr, zend_execute_data, }; use crate::config::SystemSettings; use crate::exception::EXCEPTION_PROFILING_INTERVAL; @@ -166,6 +165,224 @@ pub struct Label { pub value: LabelValue, } +#[derive(Debug, Clone, Default)] +struct ProfileTagOverrides { + service: Option, + env: Option, + version: Option, +} + +impl ProfileTagOverrides { + fn is_empty(&self) -> bool { + self.service.is_none() && self.env.is_none() && self.version.is_none() + } +} + +#[derive(Debug, Clone)] +struct SampleContext { + labels: Vec