diff --git a/Cargo.lock b/Cargo.lock index c7470584b8..c7b3b9a39b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,15 @@ dependencies = [ "serde", ] +[[package]] +name = "cadence" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7611a627d6f459659e63e007fb6a01733a4dca558779fe019f66579068779d79" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "cast" version = "0.3.0" @@ -807,6 +816,7 @@ dependencies = [ "arrayref", "bincode", "bytes", + "cadence", "chrono", "console-subscriber", "datadog-ipc", diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 022f7d9a5f..0617b4f9ba 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -118,7 +118,8 @@ bool ddog_sidecar_is_closed(ddog_SidecarTransport **transport); ddog_MaybeError ddog_sidecar_session_set_config(ddog_SidecarTransport **transport, ddog_CharSlice session_id, - const struct ddog_Endpoint *endpoint, + const struct ddog_Endpoint *agent_endpoint, + const struct ddog_Endpoint *dogstatsd_endpoint, uint64_t flush_interval_milliseconds, uintptr_t force_flush_size, uintptr_t force_drop_size, @@ -139,4 +140,34 @@ ddog_CharSlice ddog_sidecar_dump(ddog_SidecarTransport **transport); ddog_CharSlice ddog_sidecar_stats(ddog_SidecarTransport **transport); +ddog_MaybeError ddog_sidecar_dogstatsd_count(ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice metric, + int64_t value, + const struct ddog_Vec_Tag *tags); + +ddog_MaybeError ddog_sidecar_dogstatsd_distribution(ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice metric, + double value, + const struct ddog_Vec_Tag *tags); + +ddog_MaybeError ddog_sidecar_dogstatsd_gauge(ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice metric, + double value, + const struct ddog_Vec_Tag *tags); + +ddog_MaybeError ddog_sidecar_dogstatsd_histogram(ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice metric, + double value, + const struct ddog_Vec_Tag *tags); + +ddog_MaybeError ddog_sidecar_dogstatsd_set(ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_CharSlice metric, + int64_t value, + const struct ddog_Vec_Tag *tags); + #endif /* DDOG_SIDECAR_H */ diff --git a/config.m4 b/config.m4 index 82479dd7ed..7ee36d2477 100644 --- a/config.m4 +++ b/config.m4 @@ -144,6 +144,7 @@ if test "$PHP_DDTRACE" != "no"; then ext/configuration.c \ ext/ddshared.c \ ext/distributed_tracing_headers.c \ + ext/dogstatsd.c \ ext/dogstatsd_client.c \ ext/engine_api.c \ ext/engine_hooks.c \ diff --git a/config.w32 b/config.w32 index 4467e2b4b8..cb5bbf7d6f 100644 --- a/config.w32 +++ b/config.w32 @@ -15,7 +15,7 @@ if (PHP_DDTRACE != 'no') { var version = PHP_VERSION * 100 + PHP_MINOR_VERSION; - var DDTRACE_EXT_SOURCES = "arrays.c auto_flush.c autoload_php_files.c compat_string.c configuration.c distributed_tracing_headers.c ddshared.c engine_api.c engine_hooks.c excluded_modules.c handlers_api.c handlers_curl" + (version < 800 ? "_php7" : "") + ".c handlers_exception.c handlers_internal.c handlers_pcntl.c ip_extraction.c logging.c memory_limit.c profiling.c random.c serializer.c sidecar.c span.c startup_logging.c telemetry.c user_request.c"; + var DDTRACE_EXT_SOURCES = "arrays.c auto_flush.c autoload_php_files.c compat_string.c configuration.c distributed_tracing_headers.c ddshared.c dogstatsd.c engine_api.c engine_hooks.c excluded_modules.c handlers_api.c handlers_curl" + (version < 800 ? "_php7" : "") + ".c handlers_exception.c handlers_internal.c handlers_pcntl.c ip_extraction.c logging.c memory_limit.c profiling.c random.c serializer.c sidecar.c span.c startup_logging.c telemetry.c user_request.c"; if (version >= 800 && version < 802) { DDTRACE_EXT_SOURCES += " weakrefs.c"; } diff --git a/ext/auto_flush.c b/ext/auto_flush.c index 8ebcdc5728..bac5e9c515 100644 --- a/ext/auto_flush.c +++ b/ext/auto_flush.c @@ -140,8 +140,6 @@ DDTRACE_PUBLIC void ddtrace_close_all_spans_and_flush() ddtrace_flush_tracer(true, true); } -#define HOST_V6_FORMAT_STR "http://[%s]:%u" -#define HOST_V4_FORMAT_STR "http://%s:%u" #define DEFAULT_UDS_PATH "/var/run/datadog/apm.socket" char *ddtrace_agent_url(void) { diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 8243d4f565..26b4cf3e30 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1963,6 +1963,91 @@ PHP_FUNCTION(DDTrace_Internal_handle_fork) { dd_internal_handle_fork(); } +PHP_FUNCTION(DDTrace_dogstatsd_count) { + zend_string *metric; + zend_long value; + zval *tags = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(metric) + Z_PARAM_LONG(value) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(tags) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_sidecar_dogstatsd_count(metric, value, tags); + + RETURN_NULL(); +} + +PHP_FUNCTION(DDTrace_dogstatsd_distribution) { + zend_string *metric; + double value; + zval *tags = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(metric) + Z_PARAM_DOUBLE(value) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(tags) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_sidecar_dogstatsd_distribution(metric, value, tags); + + RETURN_NULL(); +} + +PHP_FUNCTION(DDTrace_dogstatsd_gauge) { + zend_string *metric; + double value; + zval *tags = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(metric) + Z_PARAM_DOUBLE(value) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(tags) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_sidecar_dogstatsd_gauge(metric, value, tags); + + RETURN_NULL(); +} + +PHP_FUNCTION(DDTrace_dogstatsd_histogram) { + zend_string *metric; + double value; + zval *tags = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(metric) + Z_PARAM_DOUBLE(value) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(tags) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_sidecar_dogstatsd_histogram(metric, value, tags); + + RETURN_NULL(); +} + +PHP_FUNCTION(DDTrace_dogstatsd_set) { + zend_string *metric; + zend_long value; + zval *tags = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(metric) + Z_PARAM_LONG(value) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(tags) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_sidecar_dogstatsd_set(metric, value, tags); + + RETURN_NULL(); +} + PHP_FUNCTION(dd_trace_send_traces_via_thread) { char *payload = NULL; ddtrace_zpplong_t num_traces = 0; diff --git a/ext/ddtrace.h b/ext/ddtrace.h index cda06ffc60..34ef639c59 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -159,4 +159,7 @@ extern TSRM_TLS void *ATTR_TLS_GLOBAL_DYNAMIC TSRMLS_CACHE; #include "random.h" +#define HOST_V6_FORMAT_STR "http://[%s]:%u" +#define HOST_V4_FORMAT_STR "http://%s:%u" + #endif // DDTRACE_H diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index c4d96ee327..a8f5c1c870 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -632,6 +632,51 @@ function flush(): void {} * @param list{\CurlHandle, SpanData}[] $array An array which will be populated with curl handles and spans. */ function curl_multi_exec_get_request_spans(&$array): void {} + + /** + * Update a DogStatsD counter + * + * @param string $metric The metric name + * @param int $value The metric value + * @param array $tags A list of tags associated to the metric + */ + function dogstatsd_count(string $metric, int $value, array $tags = []): void {} + + /** + * Update a DogStatsD distribution + * + * @param string $metric The metric name + * @param float $value The metric value + * @param array $tags A list of tags associated to the metric + */ + function dogstatsd_distribution(string $metric, float $value, array $tags = []): void {} + + /** + * Update a DogStatsD gauge + * + * @param string $metric The metric name + * @param float $value The metric value + * @param array $tags A list of tags associated to the metric + */ + function dogstatsd_gauge(string $metric, float $value, array $tags = []): void {} + + /** + * Update a DogStatsD histogram + * + * @param string $metric The metric name + * @param float $value The metric value + * @param array $tags A list of tags associated to the metric + */ + function dogstatsd_histogram(string $metric, float $value, array $tags = []): void {} + + /** + * Update a DogStatsD set + * + * @param string $metric The metric name + * @param int $value The metric value + * @param array $tags A list of tags associated to the metric + */ + function dogstatsd_set(string $metric, int $value, array $tags = []): void {} } namespace DDTrace\System { diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 5f1a2a37ab..54b54153db 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: cbd454866b5b3f34bee057c9a35d7807c6e718ca */ + * Stub hash: 3a83516da1c795bbe41abd106fcc3e069bf3d946 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_trace_method, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, className, IS_STRING, 0) @@ -128,6 +128,24 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_curl_multi_exec_get_requ ZEND_ARG_INFO(1, array) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_dogstatsd_count, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, metric, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, tags, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_dogstatsd_distribution, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, metric, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_DOUBLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, tags, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() + +#define arginfo_DDTrace_dogstatsd_gauge arginfo_DDTrace_dogstatsd_distribution + +#define arginfo_DDTrace_dogstatsd_histogram arginfo_DDTrace_dogstatsd_distribution + +#define arginfo_DDTrace_dogstatsd_set arginfo_DDTrace_dogstatsd_count + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_System_container_id, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -302,6 +320,11 @@ ZEND_FUNCTION(DDTrace_current_context); ZEND_FUNCTION(DDTrace_set_distributed_tracing_context); ZEND_FUNCTION(DDTrace_flush); ZEND_FUNCTION(DDTrace_curl_multi_exec_get_request_spans); +ZEND_FUNCTION(DDTrace_dogstatsd_count); +ZEND_FUNCTION(DDTrace_dogstatsd_distribution); +ZEND_FUNCTION(DDTrace_dogstatsd_gauge); +ZEND_FUNCTION(DDTrace_dogstatsd_histogram); +ZEND_FUNCTION(DDTrace_dogstatsd_set); ZEND_FUNCTION(DDTrace_System_container_id); ZEND_FUNCTION(DDTrace_Config_integration_analytics_enabled); ZEND_FUNCTION(DDTrace_Config_integration_analytics_sample_rate); @@ -377,6 +400,11 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "set_distributed_tracing_context"), zif_DDTrace_set_distributed_tracing_context, arginfo_DDTrace_set_distributed_tracing_context, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "flush"), zif_DDTrace_flush, arginfo_DDTrace_flush, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "curl_multi_exec_get_request_spans"), zif_DDTrace_curl_multi_exec_get_request_spans, arginfo_DDTrace_curl_multi_exec_get_request_spans, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "dogstatsd_count"), zif_DDTrace_dogstatsd_count, arginfo_DDTrace_dogstatsd_count, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "dogstatsd_distribution"), zif_DDTrace_dogstatsd_distribution, arginfo_DDTrace_dogstatsd_distribution, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "dogstatsd_gauge"), zif_DDTrace_dogstatsd_gauge, arginfo_DDTrace_dogstatsd_gauge, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "dogstatsd_histogram"), zif_DDTrace_dogstatsd_histogram, arginfo_DDTrace_dogstatsd_histogram, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "dogstatsd_set"), zif_DDTrace_dogstatsd_set, arginfo_DDTrace_dogstatsd_set, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\System", "container_id"), zif_DDTrace_System_container_id, arginfo_DDTrace_System_container_id, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Config", "integration_analytics_enabled"), zif_DDTrace_Config_integration_analytics_enabled, arginfo_DDTrace_Config_integration_analytics_enabled, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Config", "integration_analytics_sample_rate"), zif_DDTrace_Config_integration_analytics_sample_rate, arginfo_DDTrace_Config_integration_analytics_sample_rate, 0, NULL, NULL) diff --git a/ext/dogstatsd.c b/ext/dogstatsd.c new file mode 100644 index 0000000000..b85c34f8d2 --- /dev/null +++ b/ext/dogstatsd.c @@ -0,0 +1,44 @@ +#include "dogstatsd.h" + +#include "configuration.h" +#include "ddtrace.h" + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); + +#define DEFAULT_UDS_PATH "/var/run/datadog/dsd.socket" + +char *ddtrace_dogstatsd_url(void) { + zend_string *url = get_DD_DOGSTATSD_URL(); + if (ZSTR_LEN(url) > 0) { + return zend_strndup(ZSTR_VAL(url), ZSTR_LEN(url) + 1); + } + + zend_string *hostname = get_global_DD_AGENT_HOST(); + if (ZSTR_LEN(hostname) > 7 && strncmp(ZSTR_VAL(hostname), "unix://", 7) == 0) { + return zend_strndup(ZSTR_VAL(hostname), ZSTR_LEN(hostname)); + } + + if (ZSTR_LEN(hostname) > 0) { + bool isIPv6 = memchr(ZSTR_VAL(hostname), ':', ZSTR_LEN(hostname)); + + int port = atoi(ZSTR_VAL(get_DD_DOGSTATSD_PORT())); + if (port <= 0 || port > 65535) { + port = 8125; + } + char *formatted_url; + asprintf(&formatted_url, isIPv6 ? HOST_V6_FORMAT_STR : HOST_V4_FORMAT_STR, ZSTR_VAL(hostname), (uint32_t)port); + return formatted_url; + } + + if (access(DEFAULT_UDS_PATH, F_OK) == SUCCESS) { + return zend_strndup(ZEND_STRL("unix://" DEFAULT_UDS_PATH)); + } + + int64_t port = get_global_DD_TRACE_AGENT_PORT(); + if (port <= 0 || port > 65535) { + port = 8125; + } + char *formatted_url; + asprintf(&formatted_url, HOST_V4_FORMAT_STR, "localhost", (uint32_t)port); + return formatted_url; +} diff --git a/ext/dogstatsd.h b/ext/dogstatsd.h new file mode 100644 index 0000000000..2e310d67ed --- /dev/null +++ b/ext/dogstatsd.h @@ -0,0 +1,6 @@ +#ifndef DDTRACE_DOGSTATSD_H +#define DDTRACE_DOGSTATSD_H + +char *ddtrace_dogstatsd_url(void); + +#endif // DDTRACE_DOGSTATSD_H diff --git a/ext/sidecar.c b/ext/sidecar.c index d4cea67507..17d91a2709 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -1,7 +1,10 @@ #include "ddtrace.h" #include "auto_flush.h" +#include "compat_string.h" #include "configuration.h" +#include "dogstatsd.h" #include "logging.h" +#include #include #include "sidecar.h" #include "telemetry.h" @@ -20,16 +23,23 @@ static void ddtrace_set_sidecar_globals(void) { } static bool dd_sidecar_connection_init(void) { + ddog_Endpoint *dogstatsd_endpoint; if (get_global_DD_TRACE_AGENTLESS() && ZSTR_LEN(get_global_DD_API_KEY())) { ddtrace_endpoint = ddog_endpoint_from_api_key(dd_zend_string_to_CharSlice(get_global_DD_API_KEY())); + dogstatsd_endpoint = ddog_endpoint_from_api_key(dd_zend_string_to_CharSlice(get_global_DD_API_KEY()));; } else { char *agent_url = ddtrace_agent_url(); ddtrace_endpoint = ddog_endpoint_from_url((ddog_CharSlice) {.ptr = agent_url, .len = strlen(agent_url)}); free(agent_url); + + char *dogstatsd_url = ddtrace_dogstatsd_url(); + dogstatsd_endpoint = ddog_endpoint_from_url((ddog_CharSlice) {.ptr = dogstatsd_url, .len = strlen(dogstatsd_url)}); + free(dogstatsd_url); } if (!ddtrace_endpoint) { ddtrace_sidecar = NULL; + ddog_endpoint_drop(dogstatsd_endpoint); return false; } @@ -44,6 +54,7 @@ static bool dd_sidecar_connection_init(void) { } if (!ddtrace_ffi_try("Failed connecting to the sidecar", ddog_sidecar_connect_php(&ddtrace_sidecar, logpath, dd_zend_string_to_CharSlice(get_global_DD_TRACE_LOG_LEVEL()), get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()))) { + ddog_endpoint_drop(dogstatsd_endpoint); ddog_endpoint_drop(ddtrace_endpoint); ddtrace_endpoint = NULL; ddtrace_sidecar = NULL; @@ -60,13 +71,15 @@ static bool dd_sidecar_connection_init(void) { } ddog_CharSlice session_id = (ddog_CharSlice) {.ptr = (char *) dd_sidecar_formatted_session_id, .len = sizeof(dd_sidecar_formatted_session_id)}; - ddog_sidecar_session_set_config(&ddtrace_sidecar, session_id, ddtrace_endpoint, + ddog_sidecar_session_set_config(&ddtrace_sidecar, session_id, ddtrace_endpoint, dogstatsd_endpoint, get_global_DD_TRACE_AGENT_FLUSH_INTERVAL(), get_global_DD_TRACE_AGENT_STACK_INITIAL_SIZE(), get_global_DD_TRACE_AGENT_STACK_BACKLOG() * get_global_DD_TRACE_AGENT_MAX_PAYLOAD_SIZE(), get_global_DD_TRACE_DEBUG() ? DDOG_CHARSLICE_C("debug") : dd_zend_string_to_CharSlice(get_global_DD_TRACE_LOG_LEVEL()), (ddog_CharSlice){ .ptr = logpath, .len = strlen(logpath) }); + ddog_endpoint_drop(dogstatsd_endpoint); + return true; } @@ -101,3 +114,102 @@ void ddtrace_reset_sidecar_globals(void) { ddtrace_set_sidecar_globals(); } } + +static inline void ddtrace_sidecar_dogstatsd_push_tag(ddog_Vec_Tag *vec, ddog_CharSlice key, ddog_CharSlice value) { + ddog_Vec_Tag_PushResult tag_result = ddog_Vec_Tag_push(vec, key, value); + if (tag_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) { + zend_string *msg = dd_CharSlice_to_zend_string(ddog_Error_message(&tag_result.err)); + LOG(WARN, "Failed to push DogStatsD tag: %s", ZSTR_VAL(msg)); + ddog_Error_drop(&tag_result.err); + zend_string_release(msg); + } +} + +static void ddtrace_sidecar_dogstatsd_push_tags(ddog_Vec_Tag *vec, zval *tags) { + // Global tags (https://github.com/DataDog/php-datadogstatsd/blob/0efdd1c38f6d3dd407efbb899ad1fd2e5cd18085/src/DogStatsd.php#L113-L125) + zend_string *env = get_DD_ENV(); + if (ZSTR_LEN(env) > 0) { + ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("env"), dd_zend_string_to_CharSlice(env)); + } + zend_string *service = get_DD_SERVICE(); + if (ZSTR_LEN(service) > 0) { + ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("service"), dd_zend_string_to_CharSlice(service)); + } + zend_string *version = get_DD_VERSION(); + if (ZSTR_LEN(version) > 0) { + ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("version"), dd_zend_string_to_CharSlice(version)); + } + + // Specific tags + if (!tags || Z_TYPE_P(tags) != IS_ARRAY) { + return; + } + + zend_string *key; + zval *tag_val; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(tags), key, tag_val) { + if (!key) { + continue; + } + zval value_str; + ddtrace_convert_to_string(&value_str, tag_val); + ddtrace_sidecar_dogstatsd_push_tag(vec, dd_zend_string_to_CharSlice(key), dd_zend_string_to_CharSlice(Z_STR(value_str))); + zend_string_release(Z_STR(value_str)); + } + ZEND_HASH_FOREACH_END(); +} + +void ddtrace_sidecar_dogstatsd_count(zend_string *metric, zend_long value, zval *tags) { + if (!ddtrace_sidecar) { + return; + } + + ddog_Vec_Tag vec = ddog_Vec_Tag_new(); + ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); + ddog_sidecar_dogstatsd_count(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddog_Vec_Tag_drop(vec); +} + +void ddtrace_sidecar_dogstatsd_distribution(zend_string *metric, double value, zval *tags) { + if (!ddtrace_sidecar) { + return; + } + + ddog_Vec_Tag vec = ddog_Vec_Tag_new(); + ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); + ddog_sidecar_dogstatsd_distribution(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddog_Vec_Tag_drop(vec); +} + +void ddtrace_sidecar_dogstatsd_gauge(zend_string *metric, double value, zval *tags) { + if (!ddtrace_sidecar) { + return; + } + + ddog_Vec_Tag vec = ddog_Vec_Tag_new(); + ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); + ddog_sidecar_dogstatsd_gauge(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddog_Vec_Tag_drop(vec); +} + +void ddtrace_sidecar_dogstatsd_histogram(zend_string *metric, double value, zval *tags) { + if (!ddtrace_sidecar) { + return; + } + + ddog_Vec_Tag vec = ddog_Vec_Tag_new(); + ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); + ddog_sidecar_dogstatsd_histogram(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddog_Vec_Tag_drop(vec); +} + +void ddtrace_sidecar_dogstatsd_set(zend_string *metric, zend_long value, zval *tags) { + if (!ddtrace_sidecar) { + return; + } + + ddog_Vec_Tag vec = ddog_Vec_Tag_new(); + ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); + ddog_sidecar_dogstatsd_set(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddog_Vec_Tag_drop(vec); +} diff --git a/ext/sidecar.h b/ext/sidecar.h index 2dc5df7051..4ccf350357 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -10,6 +10,12 @@ void ddtrace_sidecar_ensure_active(void); void ddtrace_sidecar_shutdown(void); void ddtrace_reset_sidecar_globals(void); +void ddtrace_sidecar_dogstatsd_count(zend_string *metric, zend_long value, zval *tags); +void ddtrace_sidecar_dogstatsd_distribution(zend_string *metric, double value, zval *tags); +void ddtrace_sidecar_dogstatsd_gauge(zend_string *metric, double value, zval *tags); +void ddtrace_sidecar_dogstatsd_histogram(zend_string *metric, double value, zval *tags); +void ddtrace_sidecar_dogstatsd_set(zend_string *metric, zend_long value, zval *tags); + static inline ddog_CharSlice dd_zend_string_to_CharSlice(zend_string *str) { return (ddog_CharSlice){ .len = str->len, .ptr = str->val }; } @@ -18,6 +24,10 @@ static inline ddog_CharSlice dd_zai_string_to_CharSlice(zai_string str) { return (ddog_CharSlice){ .len = str.len, .ptr = str.ptr }; } +static inline zend_string *dd_CharSlice_to_zend_string(ddog_CharSlice str) { + return zend_string_init(str.ptr, str.len, 0); +} + static inline bool ddtrace_ffi_try(const char *msg, ddog_MaybeError maybe_error) { if (maybe_error.tag == DDOG_OPTION_ERROR_SOME_ERROR) { ddog_CharSlice error = ddog_Error_message(&maybe_error.some); diff --git a/libdatadog b/libdatadog index e4c1985a6b..0730f67866 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit e4c1985a6b7701b38848984177c2957613c5f14e +Subproject commit 0730f678661a5f22972fc043b28b67c1a7b2cc3d diff --git a/tests/ext/dogstatsd/disabled_if_agentless.phpt b/tests/ext/dogstatsd/disabled_if_agentless.phpt new file mode 100644 index 0000000000..cca82df2e3 --- /dev/null +++ b/tests/ext/dogstatsd/disabled_if_agentless.phpt @@ -0,0 +1,65 @@ +--TEST-- +DogStatsD is disabled in agentlessmode +--SKIPIF-- + +--ENV-- +DD_TRACE_AGENTLESS=true +DD_API_KEY=1234 +DD_DOGSTATSD_URL=unix:///tmp/ddtrace-test-disabled_if_agentless.socket +--FILE-- +socket = socket_create(AF_UNIX, SOCK_DGRAM, 0))) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Couldn't create socket: [$errorcode] $errormsg\n"); + } + + if (!socket_bind($this->socket, $path)) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Could not bind socket : [$errorcode] $errormsg\n"); + } + } + + public function dump($iter = 100, $usleep = 100) { + $buf = ''; + for ($i = 0; $i < $iter; ++$i) { + usleep($usleep); + $r = socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port); + if ($buf) { + echo $buf."\n"; + $buf = ''; + } + } + } + + public function close() { + socket_close($this->socket); + } +} + +$server = new UDSServer('/tmp/ddtrace-test-disabled_if_agentless.socket'); + +\DDTrace\dogstatsd_count("simple-counter", 42, ['foo' => 'bar', 'bar' => true]); +\DDTrace\dogstatsd_gauge("gogogadget", 21.4); +\DDTrace\dogstatsd_histogram("my_histo", 22.22, ['histo' => 'gram']); +\DDTrace\dogstatsd_distribution("my_disti", 22.22, ['distri' => 'bution']); +\DDTrace\dogstatsd_set("set", 7, ['set' => '7']); + +$server->dump(); +$server->close(); + +echo "end\n"; + +?> +--EXPECT-- +end +--CLEAN-- + diff --git a/tests/ext/dogstatsd/metrics_over_udp.phpt b/tests/ext/dogstatsd/metrics_over_udp.phpt new file mode 100644 index 0000000000..953efd1cb0 --- /dev/null +++ b/tests/ext/dogstatsd/metrics_over_udp.phpt @@ -0,0 +1,61 @@ +--TEST-- +Send DogStatsD metrics over a UDP socket +--SKIPIF-- + +--ENV-- +DD_DOGSTATSD_URL=http://127.0.0.1:9876 +--FILE-- +socket = socket_create(AF_INET, SOCK_DGRAM, 0))) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Couldn't create socket: [$errorcode] $errormsg\n"); + } + + if (!socket_bind($this->socket, $addr, $port)) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Could not bind socket : [$errorcode] $errormsg\n"); + } + } + + public function dump($iter = 100, $usleep = 100) { + $buf = ''; + for ($i = 0; $i < $iter; ++$i) { + usleep($usleep); + $r = socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port); + if ($buf) { + echo $buf."\n"; + $buf = ''; + } + } + } + + public function close() { + socket_close($this->socket); + } +} + +$server = new UDPServer('127.0.0.1', 9876); + +\DDTrace\dogstatsd_count("simple-counter", 42, ['foo' => 'bar', 'bar' => true]); +\DDTrace\dogstatsd_gauge("gogogadget", 21.4); +\DDTrace\dogstatsd_histogram("my_histo", 22.22, ['histo' => 'gram']); +\DDTrace\dogstatsd_distribution("my_disti", 22.22, ['distri' => 'bution']); +\DDTrace\dogstatsd_set("set", 7, ['set' => '7']); + +$server->dump(); +$server->close(); + +?> +--EXPECT-- +simple-counter:42|c|#foo:bar,bar:true +gogogadget:21.4|g +my_histo:22.22|h|#histo:gram +my_disti:22.22|d|#distri:bution +set:7|s|#set:7 diff --git a/tests/ext/dogstatsd/metrics_over_uds.phpt b/tests/ext/dogstatsd/metrics_over_uds.phpt new file mode 100644 index 0000000000..778ef4d61d --- /dev/null +++ b/tests/ext/dogstatsd/metrics_over_uds.phpt @@ -0,0 +1,76 @@ +--TEST-- +Send DogStatsD metrics over an Unix Domain Socket +--SKIPIF-- + +--ENV-- +DD_DOGSTATSD_URL=unix:///tmp/ddtrace-test-metrics_over_uds.socket +DD_SERVICE=test-app +DD_ENV=test +DD_VERSION=1.12 +--FILE-- +socket = socket_create(AF_UNIX, SOCK_DGRAM, 0))) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Couldn't create socket: [$errorcode] $errormsg\n"); + } + + if (!socket_bind($this->socket, $path)) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Could not bind socket : [$errorcode] $errormsg\n"); + } + + // On the CI, when this test is ran using "pecl run-tests" with sudo + // the Unix socket is owned by root while the sidecar process is ran as another user + chmod($path, 0777); + } + + public function dump($iter = 100, $usleep = 100) { + $buf = ''; + for ($i = 0; $i < $iter; ++$i) { + usleep($usleep); + $r = socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port); + if ($buf) { + echo $buf."\n"; + $buf = ''; + } + } + } + + public function close() { + socket_close($this->socket); + } +} + +$server = new UDSServer('/tmp/ddtrace-test-metrics_over_uds.socket'); + +\DDTrace\dogstatsd_count("simple-counter", 42, ['foo' => 'bar', 'bar' => true]); +\DDTrace\dogstatsd_gauge("gogogadget", 21.4); +\DDTrace\dogstatsd_histogram("my_histo", 22.22, ['histo' => 'gram']); +\DDTrace\dogstatsd_distribution("my_disti", 22.22, ['distri' => 'bution']); +\DDTrace\dogstatsd_set("set", 7, ['set' => '7']); + +$server->dump(); +$server->close(); + +?> +--EXPECT-- +simple-counter:42|c|#env:test,service:test-app,version:1.12,foo:bar,bar:true +gogogadget:21.4|g|#env:test,service:test-app,version:1.12 +my_histo:22.22|h|#env:test,service:test-app,version:1.12,histo:gram +my_disti:22.22|d|#env:test,service:test-app,version:1.12,distri:bution +set:7|s|#env:test,service:test-app,version:1.12,set:7 +--CLEAN-- +