Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0422f98
perf(config): cache sys getenv
morrisonlevi Feb 27, 2026
5f302b9
perf(config): avoid interim string copies
morrisonlevi Feb 27, 2026
5fc7a16
fix: zero name len test
morrisonlevi Feb 28, 2026
f2e55d2
fix: missed a getenv -> getenv_ex conversion
morrisonlevi Mar 10, 2026
e3b0e13
perf: skip getenv cache refresh on CLI
morrisonlevi Mar 10, 2026
fe8e371
test: sapi precedence and cached sys env
morrisonlevi Mar 10, 2026
7291ccb
test: better output on zval str failure
morrisonlevi Mar 10, 2026
9206658
test: fix headers/build for zai (hopefully)
morrisonlevi Mar 10, 2026
e6935d9
test: fix sapi_getenv mock
morrisonlevi Mar 10, 2026
834f21f
test: try to fix sapi env priority
morrisonlevi Mar 11, 2026
be40766
refactor(env): replace buffer-based getenv with zai_option_str API
morrisonlevi Mar 13, 2026
faa345e
fix config priority, SAPI env, and dynamic config update
morrisonlevi Mar 17, 2026
c680176
Merge branch 'master' into levi/cache-getenv
morrisonlevi Mar 17, 2026
e8a037f
Merge branch 'master' into levi/cache-getenv
morrisonlevi Mar 18, 2026
a62b503
Merge branch 'master' into levi/cache-getenv
morrisonlevi Mar 18, 2026
1babfc8
fix: make 2nd ext in zai config more realistic
morrisonlevi Mar 18, 2026
3a5fe10
build: remove C++ std to see if it fixes 7.0 build issue
morrisonlevi Mar 19, 2026
197c0ae
build: specify C++14 specifically in the properties
morrisonlevi Mar 19, 2026
8d4f54a
docs: fix outdated docs
morrisonlevi Mar 19, 2026
5970886
Merge branch 'master' into levi/cache-getenv
morrisonlevi Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 47 additions & 43 deletions ext/otel_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ static void report_otel_cfg_telemetry_invalid(const char *otel_cfg, const char *
}
}

static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) {
if (zai_getenv_ex(str, buf, pre_rinit) == ZAI_ENV_SUCCESS) {
return true;
}
static bool get_otel_value(zai_str str, zai_env_buffer *buf, bool pre_rinit) {
if (!pre_rinit && zai_sapi_getenv(str, buf) == ZAI_ENV_SUCCESS) return true;
zai_option_str sys = zai_sys_getenv(str);
if (zai_option_str_is_some(sys)) { buf->ptr = sys.ptr; buf->len = sys.len; return true; }

zval *cfg = cfg_get_entry(str.ptr, str.len);
if (cfg) {
if (Z_TYPE_P(cfg) == IS_ARRAY) {
zval *val;
char *off = buf.ptr;
char *off = buf->ptr;
ZEND_HASH_FOREACH_VAL(Z_ARR_P(cfg), val) {
if (Z_TYPE_P(val) == IS_STRING) {
if (off - buf.ptr + Z_STRLEN_P(val) + 2 >= ZAI_ENV_MAX_BUFSIZ) {
if (off - buf->ptr + Z_STRLEN_P(val) + 2 >= ZAI_ENV_MAX_BUFSIZ) {
return false;
}
if (off != buf.ptr) {
if (off != buf->ptr) {
*off++ = ',';
}
memcpy(off, Z_STRVAL_P(val), Z_STRLEN_P(val));
Expand All @@ -46,29 +46,30 @@ static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) {
} else if (Z_STRLEN_P(cfg) == 0 || Z_STRLEN_P(cfg) + 1 >= ZAI_ENV_MAX_BUFSIZ) {
return false;
} else {
memcpy(buf.ptr, Z_STRVAL_P(cfg), Z_STRLEN_P(cfg) + 1);
memcpy(buf->ptr, Z_STRVAL_P(cfg), Z_STRLEN_P(cfg) + 1);
}
return true;
}

return false;
}

static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int len, zai_env_buffer buf, bool pre_rinit) {
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) {
static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int len, zai_env_buffer *buf, bool pre_rinit) {
ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ);
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), &local, pre_rinit)) {
return false;
}

for (char *cur = buf.ptr, *key_start = cur; *cur; ++cur) {
for (char *cur = local.ptr, *key_start = cur; *cur; ++cur) {
if (*cur == '=') {
char *key_end = cur++;
while (*cur && *cur != ',') {
++cur;
}
if (key_end - key_start == len && memcmp(key_start, tag, len) == 0 && key_end[1]) {
size_t vallen = cur - (key_end + 1);
memcpy(buf.ptr, key_end + 1, vallen);
buf.ptr[vallen] = 0;
memcpy(buf->ptr, key_end + 1, vallen);
buf->ptr[vallen] = 0;
return true;
}
key_start = cur-- + 1;
Expand All @@ -78,92 +79,95 @@ static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int l
return false;
}

bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer *buf, bool pre_rinit) {
return ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("deployment.environment"), buf, pre_rinit);
}

bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer *buf, bool pre_rinit) {
return ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("service.version"), buf, pre_rinit);
}

bool ddtrace_conf_otel_service_name(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_service_name(zai_env_buffer *buf, bool pre_rinit) {
return get_otel_value((zai_str)ZAI_STRL("OTEL_SERVICE_NAME"), buf, pre_rinit)
|| ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("service.name"), buf, pre_rinit);
}

bool ddtrace_conf_otel_log_level(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_log_level(zai_env_buffer *buf, bool pre_rinit) {
return get_otel_value((zai_str)ZAI_STRL("OTEL_LOG_LEVEL"), buf, pre_rinit);
}

bool ddtrace_conf_otel_propagators(zai_env_buffer buf, bool pre_rinit) {
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_PROPAGATORS"), buf, pre_rinit)) {
bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit) {
ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ);
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_PROPAGATORS"), &local, pre_rinit)) {
return false;
}
char *off = (char *)zend_memnstr(buf.ptr, ZEND_STRL("b3"), buf.ptr + strlen(buf.ptr));
if (off && (!off[strlen("b3")] || off[strlen("b3")] == ',') && strlen(buf.ptr) < buf.len - 100) {
memmove(off + strlen("b3 single header"), off + strlen("b3"), buf.ptr + strlen(buf.ptr) - (off + strlen("b3")) + 1);
memcpy(buf->ptr, local.ptr, strlen(local.ptr) + 1);
char *off = (char *)zend_memnstr(buf->ptr, ZEND_STRL("b3"), buf->ptr + strlen(buf->ptr));
if (off && (!off[strlen("b3")] || off[strlen("b3")] == ',') && strlen(buf->ptr) < buf->len - 100) {
memmove(off + strlen("b3 single header"), off + strlen("b3"), buf->ptr + strlen(buf->ptr) - (off + strlen("b3")) + 1);
memcpy(off, "b3 single header", strlen("b3 single header"));
}
return true;
}

bool ddtrace_conf_otel_sample_rate(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_sample_rate(zai_env_buffer *buf, bool pre_rinit) {
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_SAMPLER"), buf, pre_rinit)) {
return false;
}

if (strcmp(buf.ptr, "always_on") == 0 || strcmp(buf.ptr, "parentbased_always_on") == 0) {
memcpy(buf.ptr, ZEND_STRS("1"));
if (strcmp(buf->ptr, "always_on") == 0 || strcmp(buf->ptr, "parentbased_always_on") == 0) {
buf->ptr = "1"; buf->len = 1;
return true;
}
if (strcmp(buf.ptr, "always_off") == 0 || strcmp(buf.ptr, "parentbased_always_off") == 0) {
memcpy(buf.ptr, ZEND_STRS("0"));
if (strcmp(buf->ptr, "always_off") == 0 || strcmp(buf->ptr, "parentbased_always_off") == 0) {
buf->ptr = "0"; buf->len = 1;
return true;
}
if (strcmp(buf.ptr, "traceidratio") == 0 || strcmp(buf.ptr, "parentbased_traceidratio") == 0) {
if (strcmp(buf->ptr, "traceidratio") == 0 || strcmp(buf->ptr, "parentbased_traceidratio") == 0) {
if (get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_SAMPLER_ARG"), buf, pre_rinit)) {
return true;
}
LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER is %s, but is missing OTEL_TRACES_SAMPLER_ARG", buf.ptr);
LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER is %s, but is missing OTEL_TRACES_SAMPLER_ARG", buf->ptr);
} else {
LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER has invalid value: %s", buf.ptr);
LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER has invalid value: %s", buf->ptr);
}
report_otel_cfg_telemetry_invalid("otel_traces_sampler", "dd_trace_sample_rate", pre_rinit);
return false;
}

bool ddtrace_conf_otel_traces_exporter(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_traces_exporter(zai_env_buffer *buf, bool pre_rinit) {
if (get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_EXPORTER"), buf, pre_rinit)) {
if (strcmp(buf.ptr, "none") == 0) {
memcpy(buf.ptr, ZEND_STRS("0"));
if (strcmp(buf->ptr, "none") == 0) {
buf->ptr = "0"; buf->len = 1;
return true;
}
LOG_ONCE(WARN, "OTEL_TRACES_EXPORTER has invalid value: %s", buf.ptr);
LOG_ONCE(WARN, "OTEL_TRACES_EXPORTER has invalid value: %s", buf->ptr);
report_otel_cfg_telemetry_invalid("otel_traces_exporter", "dd_trace_enabled", pre_rinit);
}
return false;
}

bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer buf, bool pre_rinit) {
bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit) {
if (get_otel_value((zai_str)ZAI_STRL("OTEL_METRICS_EXPORTER"), buf, pre_rinit)) {
if (strcmp(buf.ptr, "none") == 0) {
memcpy(buf.ptr, ZEND_STRS("0"));
if (strcmp(buf->ptr, "none") == 0) {
buf->ptr = "0"; buf->len = 1;
return true;
}
LOG_ONCE(WARN, "OTEL_METRICS_EXPORTER has invalid value: %s", buf.ptr);
LOG_ONCE(WARN, "OTEL_METRICS_EXPORTER has invalid value: %s", buf->ptr);
report_otel_cfg_telemetry_invalid("otel_metrics_exporter", "dd_integration_metrics_enabled", pre_rinit);
}
return false;
}

bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rinit) {
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) {
bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit) {
ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ);
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), &local, pre_rinit)) {
return false;
}

char *out = buf.ptr;
char *out = buf->ptr;
int tags = 0;
for (char *cur = buf.ptr, *key_start = cur; *cur; ++cur) {
for (char *cur = local.ptr, *key_start = cur; *cur; ++cur) {
if (*cur == '=') {
char *key = key_start, *key_end = cur++;
while (*cur && *cur != ',') {
Expand All @@ -190,7 +194,7 @@ bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rin
--cur;
}
}
if (out != buf.ptr) {
if (out != buf->ptr) {
--out;
}
*out = 0;
Expand Down
18 changes: 9 additions & 9 deletions ext/otel_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

#include <env/env.h>

bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_service_name(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_log_level(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_propagators(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_sample_rate(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_traces_exporter(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_service_name(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_log_level(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_sample_rate(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_traces_exporter(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit);

#endif // DD_OTEL_CONFIG_H
74 changes: 62 additions & 12 deletions zend_abstract_interface/config/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <assert.h>
#include <json/json.h>
#include <main/php.h>
#include <main/SAPI.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -12,11 +13,34 @@ HashTable zai_config_name_map = {0};
uint16_t zai_config_memoized_entries_count = 0;
zai_config_memoized_entry zai_config_memoized_entries[ZAI_CONFIG_ENTRIES_COUNT_MAX];

static bool zai_config_get_env_value(zai_str name, zai_env_buffer buf) {
// TODO Handle other return codes
// We want to explicitly allow pre-RINIT access to env vars here. So that callers can have an early view at config.
// But in general allmost all configurations shall only be accessed after first RINIT. (the trivial getter will
return zai_getenv_ex(name, buf, true) == ZAI_ENV_SUCCESS;
// Indexed [config_id][name_index]; NULL means not set.
static char *zai_config_cached_sys_env[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX];

const char *zai_config_sys_env_cached(zai_config_id id, uint8_t name_index) {
return zai_config_cached_sys_env[id][name_index];
}

static void zai_config_cache_sys_env(void) {
for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) {
zai_config_memoized_entry *m = &zai_config_memoized_entries[i];
for (uint8_t n = 0; n < m->names_count; n++) {
zai_str name = ZAI_STR_NEW(m->names[n].ptr, m->names[n].len);
zai_option_str val = zai_sys_getenv(name);
zai_config_cached_sys_env[i][n] = zai_option_str_is_some(val)
? pestrdup(val.ptr, 1) : NULL;
}
}
}

static void zai_config_clear_sys_env_cache(void) {
for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) {
for (uint8_t n = 0; n < zai_config_memoized_entries[i].names_count; n++) {
if (zai_config_cached_sys_env[i][n]) {
pefree(zai_config_cached_sys_env[i][n], 1);
zai_config_cached_sys_env[i][n] = NULL;
}
}
}
}

static inline void zai_config_process_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, zai_option_str *value) {
Expand All @@ -31,7 +55,7 @@ static inline void zai_config_process_env(zai_config_memoized_entry *memoized, z
}
}

static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id) {
static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id, bool in_request) {
// TODO Use less buffer space
// TODO Make a more generic zai_string_buffer
ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ);
Expand All @@ -48,18 +72,34 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z
name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE;
memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id);
break;
} else if (zai_config_get_env_value(name, buf)) {
zai_config_process_env(memoized, buf, &value);
break;
} else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) {
} else {
// SAPI env (e.g. Apache SetEnv) takes priority over the sys env
// cache and must be checked here at first RINIT, not only in
// zai_config_ini_rinit. Code that runs between first_time_rinit
// and zai_config_ini_rinit--such as the signal handler setup that
// reads DD_TRACE_HEALTH_METRICS_ENABLED--relies on SAPI-provided
// values being present in the decoded config.
if (in_request && zai_sapi_getenv(name, &buf) == ZAI_ENV_SUCCESS) {
zai_config_process_env(memoized, buf, &value);
break;
}
const char *cached = zai_config_sys_env_cached(id, name_index);
if (cached) {
buf.ptr = (char *)cached;
buf.len = strlen(cached);
zai_config_process_env(memoized, buf, &value);
break;
}
}
if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) {
strcpy(buf.ptr, ZSTR_VAL(entry->value));
zai_config_process_env(memoized, buf, &value);
name_index = ZAI_CONFIG_ORIGIN_LOCAL_STABLE;
memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id);
break;
}
}
if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(buf, true)) {
if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(&buf, true)) {
zai_config_process_env(memoized, buf, &value);
name_index = ZAI_CONFIG_ORIGIN_MODIFIED;
}
Expand Down Expand Up @@ -153,6 +193,7 @@ bool zai_config_minit(zai_config_entry entries[], size_t entries_count, zai_conf
#if PHP_VERSION_ID >= 70300 && PHP_VERSION_ID < 70400
zai_persistent_new_interned_string = zend_new_interned_string;
#endif
zai_config_cache_sys_env();
return true;
}

Expand All @@ -164,11 +205,13 @@ static void zai_config_dtor_memoized_zvals(void) {

void zai_config_mshutdown(void) {
zai_config_dtor_memoized_zvals();
zai_config_memoized_entries_count = 0;
if (zai_config_name_map.nTableSize) {
zend_hash_destroy(&zai_config_name_map);
}
zai_config_ini_mshutdown();
zai_config_stable_file_mshutdown();
zai_config_clear_sys_env_cache();
}

void zai_config_runtime_config_ctor(void);
Expand Down Expand Up @@ -237,9 +280,16 @@ void zai_config_first_time_rinit(bool in_request) {
(void)in_request;
#endif

// Non-CLI SAPIs (CGI/FPM/mod_php) may inject env vars before the first
// request, so refresh the cache to pick them up.
if (in_request && strcmp(sapi_module.name, "cli") != 0) {
zai_config_clear_sys_env_cache();
zai_config_cache_sys_env();
}

for (uint16_t i = 0; i < zai_config_memoized_entries_count; i++) {
zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i];
zai_config_find_and_set_value(memoized, i);
zai_config_find_and_set_value(memoized, i, in_request);
#if PHP_VERSION_ID >= 70300
zai_config_intern_zval(&memoized->decoded_value);
#else
Expand Down
3 changes: 3 additions & 0 deletions zend_abstract_interface/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ zval *zai_config_get_value(zai_config_id id);

bool zai_config_get_id_by_name(zai_str name, zai_config_id *id);

// Returns NULL if not set; persistent allocation, must not be freed.
const char *zai_config_sys_env_cached(zai_config_id id, uint8_t name_index);

// Adds name to name<->id mapping. Id may be present multiple times.
void zai_config_register_config_id(zai_config_name *name, zai_config_id id);

Expand Down
Loading
Loading