diff --git a/Cargo.lock b/Cargo.lock index 912cec23bd8..ba2f26ff501 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2709,6 +2709,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "libdd-capabilities" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "http", + "thiserror 1.0.69", +] + +[[package]] +name = "libdd-capabilities-impl" +version = "0.1.0" +dependencies = [ + "bytes", + "http", + "libdd-capabilities", + "libdd-common", +] + [[package]] name = "libdd-common" version = "3.0.2" @@ -2730,6 +2750,7 @@ dependencies = [ "hyper-util", "indexmap 2.12.1", "libc 0.2.177", + "libdd-capabilities", "maplit", "mime", "multer", @@ -2740,7 +2761,6 @@ dependencies = [ "reqwest", "rustls", "rustls-native-certs", - "rusty-fork", "serde", "static_assertions", "tempfile", @@ -2833,9 +2853,12 @@ dependencies = [ "clap", "criterion", "either", + "getrandom 0.2.15", "http", "http-body-util", "httpmock", + "libdd-capabilities", + "libdd-capabilities-impl", "libdd-common", "libdd-ddsketch", "libdd-dogstatsd-client", @@ -2941,6 +2964,7 @@ dependencies = [ "bolero", "byteorder", "bytes", + "cc", "chrono", "criterion", "crossbeam-channel", @@ -2992,6 +3016,7 @@ version = "4.0.0" dependencies = [ "anyhow", "base64 0.22.1", + "bytes", "futures", "hashbrown 0.15.2", "http", @@ -3089,12 +3114,15 @@ dependencies = [ "criterion", "flate2", "futures", + "getrandom 0.2.15", "http", "http-body", "http-body-util", "httpmock", "hyper", "indexmap 2.12.1", + "libdd-capabilities", + "libdd-capabilities-impl", "libdd-common", "libdd-tinybytes", "libdd-trace-normalization", @@ -6017,6 +6045,7 @@ checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom 0.2.15", "serde", + "wasm-bindgen", ] [[package]] diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs index f72779abe9c..94aac8b2352 100644 --- a/components-rs/remote_config.rs +++ b/components-rs/remote_config.rs @@ -60,13 +60,33 @@ type VecRemoteConfigCapabilities = libdd_common_ffi::Vec, +} + #[derive(Default)] struct DynamicConfig { - active_config_path: Option, - configs: Vec, + active_configs: HashMap, + merged_configs: Vec, old_config_values: HashMap>, } +fn compute_merged_configs(active_configs: &HashMap) -> Vec { + let mut sorted: Vec<_> = active_configs.values().collect(); + sorted.sort_by_key(|c| c.priority); + let mut seen = HashSet::new(); + let mut merged = vec![]; + for entry in sorted { + for config in &entry.configs { + if seen.insert(mem::discriminant(config)) { + merged.push(config.clone()); + } + } + } + merged +} + pub struct RemoteConfigState { manager: RemoteConfigManager, live_debugger: LiveDebuggerState, @@ -109,6 +129,7 @@ pub unsafe extern "C" fn ddog_init_remote_config( DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingLogsInjection); DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingSampleRate); DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingSampleRules); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingMulticonfig); DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmFeatures); DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::AsmAutoUserInstrumMode); @@ -254,8 +275,8 @@ fn remove_old_configs(remote_config: &mut RemoteConfigState) { for (name, val) in remote_config.dynamic_config.old_config_values.drain() { reset_old_config(name.as_str(), val); } - remote_config.dynamic_config.old_config_values.clear(); - remote_config.dynamic_config.active_config_path = None; + remote_config.dynamic_config.active_configs.clear(); + remote_config.dynamic_config.merged_configs.clear(); } fn insert_new_configs( @@ -338,14 +359,17 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt apply_config(rc_ref, debugger, limiter); } RemoteConfigData::DynamicConfig(config_data) => { + let priority = config_data.priority(); let configs: Vec = config_data.lib_config.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); insert_new_configs( &mut remote_config.dynamic_config.old_config_values, - &mut remote_config.dynamic_config.configs, - configs, + &mut remote_config.dynamic_config.merged_configs, + merged, ); - remote_config.dynamic_config.active_config_path = Some(value.config_id); } } RemoteConfigData::Ignored(_) => (), @@ -360,8 +384,17 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt } } RemoteConfigProduct::ApmTracing => { - if Some(path.config_id) == remote_config.dynamic_config.active_config_path { - remove_old_configs(remote_config); + 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); + insert_new_configs( + &mut remote_config.dynamic_config.old_config_values, + &mut remote_config.dynamic_config.merged_configs, + merged, + ); + } } } _ => (), @@ -529,7 +562,7 @@ pub unsafe extern "C" fn ddog_remote_config_alter_dynamic_config( { let mut ret = false; let config_name = config.to_utf8_lossy(); - for config in remote_config.dynamic_config.configs.iter() { + for config in remote_config.dynamic_config.merged_configs.iter() { let name = map_config_name(config); if name == config_name.as_ref() { let val = map_config_value(config); @@ -559,6 +592,8 @@ pub unsafe extern "C" fn ddog_setup_remote_config( pub extern "C" fn ddog_rshutdown_remote_config(remote_config: &mut RemoteConfigState) { remote_config.live_debugger.spans_map.clear(); remote_config.dynamic_config.old_config_values.clear(); + remote_config.dynamic_config.active_configs.clear(); + remote_config.dynamic_config.merged_configs.clear(); remote_config.manager.unload_configs(&[ RemoteConfigProduct::ApmTracing, RemoteConfigProduct::LiveDebugger, diff --git a/ext/remote_config.c b/ext/remote_config.c index d5288dc1df5..d3fe8bde31e 100644 --- a/ext/remote_config.c +++ b/ext/remote_config.c @@ -99,7 +99,10 @@ static zend_string *dd_dynamic_configuration_update(ddog_CharSlice config, zend_ } } else { ZEND_ASSERT(mode == DDOG_DYNAMIC_CONFIG_UPDATE_MODE_WRITE); + ddog_RemoteConfigState *saved = DDTRACE_G(remote_config_state); + DDTRACE_G(remote_config_state) = NULL; zend_alter_ini_entry(name, value, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); + DDTRACE_G(remote_config_state) = saved; zend_string_release(value); } zend_string_release(name); diff --git a/libdatadog b/libdatadog index bfdbeae3e32..3a4a5443a75 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit bfdbeae3e32dabf8949a945c421e47c81b098040 +Subproject commit 3a4a5443a75df3b08a41daab1ea4ffe82accd40c diff --git a/tests/ext/remote_config/dynamic_config_multiconfig.phpt b/tests/ext/remote_config/dynamic_config_multiconfig.phpt new file mode 100644 index 00000000000..df3ee6080bc --- /dev/null +++ b/tests/ext/remote_config/dynamic_config_multiconfig.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test dynamic config multiconfig priority merging +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=remote-config/dynamic_config_multiconfig +--FILE-- + 0.3, + "log_injection_enabled" => true, +]); +$specific_path = put_dynamic_config_file([ + "tracing_sample_rate" => 0.7, +]); + +sleep(20); // signal interrupts interrupt the sleep(). + +// Specific config wins for sample_rate; org-level provides log_injection. +print "After both configs:\n"; +var_dump(ini_get("datadog.trace.sample_rate")); +var_dump(ini_get("datadog.logs_injection")); + +del_rc_file($specific_path); + +sleep(20); // signal interrupts interrupt the sleep(). + +// Only org-level remains: sample_rate falls back to 0.3. +print "After removing specific config:\n"; +var_dump(ini_get("datadog.trace.sample_rate")); +var_dump(ini_get("datadog.logs_injection")); + +?> +--CLEAN-- + +--EXPECT-- +After both configs: +string(3) "0.7" +string(1) "1" +After removing specific config: +string(3) "0.3" +string(1) "1" diff --git a/tests/ext/remote_config/remote_config.inc b/tests/ext/remote_config/remote_config.inc index 4aa1a326dfd..97e79da7270 100644 --- a/tests/ext/remote_config/remote_config.inc +++ b/tests/ext/remote_config/remote_config.inc @@ -56,3 +56,18 @@ function put_dynamic_config_file($configs, $service = null, $env = null) { put_rc_file($path, $data, $service); return $path; } + +function put_wildcard_dynamic_config_file($configs, $service = "*", $env = "*") { + $json = [ + "action" => "enable", + "service_target" => [ + "service" => $service, + "env" => $env, + ], + "lib_config" => $configs, + ]; + $data = json_encode($json); + $path = "datadog/2/APM_TRACING/" . sha1($data) . "/config"; + put_rc_file($path, $data); + return $path; +}