diff --git a/config.m4 b/config.m4 index cda06f43..10a86bfd 100644 --- a/config.m4 +++ b/config.m4 @@ -86,7 +86,7 @@ if test "$PHP_VALKEY_GLIDE" != "no"; then esac PHP_NEW_EXTENSION(valkey_glide, - valkey_glide.c valkey_glide_cluster.c cluster_scan_cursor.c command_response.c logger.c valkey_glide_commands.c valkey_glide_commands_2.c valkey_glide_commands_3.c valkey_glide_core_commands.c valkey_glide_core_common.c valkey_glide_expire_commands.c valkey_glide_geo_commands.c valkey_glide_geo_common.c valkey_glide_hash_common.c valkey_glide_list_common.c valkey_glide_s_common.c valkey_glide_str_commands.c valkey_glide_x_commands.c valkey_glide_x_common.c valkey_glide_z.c valkey_glide_z_common.c valkey_z_php_methods.c src/command_request.pb-c.c src/connection_request.pb-c.c src/response.pb-c.c src/client_constructor_mock.c, + valkey_glide.c valkey_glide_cluster.c cluster_scan_cursor.c command_response.c logger.c valkey_glide_commands.c valkey_glide_commands_2.c valkey_glide_commands_3.c valkey_glide_core_commands.c valkey_glide_core_common.c valkey_glide_expire_commands.c valkey_glide_geo_commands.c valkey_glide_geo_common.c valkey_glide_hash_common.c valkey_glide_list_common.c valkey_glide_s_common.c valkey_glide_str_commands.c valkey_glide_x_commands.c valkey_glide_x_common.c valkey_glide_z.c valkey_glide_z_common.c valkey_z_php_methods.c valkey_glide_otel.c valkey_glide_otel_commands.c src/command_request.pb-c.c src/connection_request.pb-c.c src/response.pb-c.c src/client_constructor_mock.c, $ext_shared,, $VALKEY_GLIDE_SHARED_LIBADD) dnl Add FFI library only for macOS (keep Mac working as before) diff --git a/examples/otel_example.php b/examples/otel_example.php new file mode 100644 index 00000000..63818aed --- /dev/null +++ b/examples/otel_example.php @@ -0,0 +1,76 @@ + [ + 'endpoint' => 'grpc://localhost:4317', // OTEL collector endpoint + 'sample_percentage' => 10 // Sample 10% of requests (default is 1%) + ], + 'metrics' => [ + 'endpoint' => 'grpc://localhost:4317' // OTEL collector endpoint + ], + 'flush_interval_ms' => 5000 // Flush every 5 seconds (default) + ]; + + // Create ValkeyGlide client with OTEL configuration + $client = new ValkeyGlide( + addresses: [ + ['host' => 'localhost', 'port' => 6379] + ], + use_tls: false, + credentials: null, + read_from: ValkeyGlide::READ_FROM_PRIMARY, + request_timeout: null, + reconnect_strategy: null, + database_id: 0, + client_name: 'otel-example-client', + client_az: null, + advanced_config: [ + 'connection_timeout' => 5000, + 'otel' => $otelConfig // Add OTEL configuration + ] + ); + + echo "ValkeyGlide client created with OpenTelemetry support" . PHP_EOL; + echo "- Sample percentage: 10% (higher than default 1% for demo)" . PHP_EOL; + echo "- Flush interval: 5000ms (default)" . PHP_EOL; + echo "- Traces endpoint: grpc://localhost:4317" . PHP_EOL; + echo "- Metrics endpoint: grpc://localhost:4317" . PHP_EOL . PHP_EOL; + + // Perform some operations that will be traced + $client->set('otel:test:key1', 'value1'); + echo "SET operation completed" . PHP_EOL; + + $value = $client->get('otel:test:key1'); + echo "GET operation completed: $value" . PHP_EOL; + + $client->set('otel:test:key2', 'value2'); + $client->set('otel:test:key3', 'value3'); + + // Batch operations will also be traced + $results = $client->mget(['otel:test:key1', 'otel:test:key2', 'otel:test:key3']); + echo "MGET operation completed: " . json_encode($results) . PHP_EOL; + + // Cleanup + $client->del(['otel:test:key1', 'otel:test:key2', 'otel:test:key3']); + echo "Cleanup completed" . PHP_EOL; + + $client->close(); + echo "Client closed" . PHP_EOL; + +} catch (Exception $e) { + echo "Error: " . $e->getMessage() . PHP_EOL; + exit(1); +} + +echo PHP_EOL . "OpenTelemetry example completed successfully!" . PHP_EOL; +echo "Check your OTEL collector for traces and metrics." . PHP_EOL; +echo PHP_EOL . "Note: OTEL can only be initialized once per process." . PHP_EOL; +echo "If you need to change configuration, restart the process." . PHP_EOL; +?> diff --git a/package.xml b/package.xml index bf796ad6..6e2597de 100644 --- a/package.xml +++ b/package.xml @@ -85,6 +85,10 @@ Requirements: + + + + diff --git a/tests/ValkeyGlideClusterFeaturesTest.php b/tests/ValkeyGlideClusterFeaturesTest.php index 6ee2e6ce..a28cb9e2 100644 --- a/tests/ValkeyGlideClusterFeaturesTest.php +++ b/tests/ValkeyGlideClusterFeaturesTest.php @@ -755,4 +755,48 @@ public function testClusterClientCreateDeleteLoop() echo "WARNING: Significant memory growth detected: " . round($memoryGrowth / 1024 / 1024, 2) . " MB\n"; } } + + public function testOtelClusterConfiguration() + { + // Test that cluster client works with OpenTelemetry configuration + $otelConfig = [ + 'traces' => [ + 'endpoint' => 'file:///tmp/valkey-cluster-traces.json', + 'sample_percentage' => 10 + ], + 'metrics' => [ + 'endpoint' => 'file:///tmp/valkey-cluster-metrics.json' + ] + ]; + + // Create cluster client with OpenTelemetry configuration + $client = new ValkeyGlideCluster( + addresses: [ + ['host' => 'localhost', 'port' => 7001], + ['host' => 'localhost', 'port' => 7002], + ['host' => 'localhost', 'port' => 7003] + ], + use_tls: false, + credentials: null, + read_from: null, + request_timeout: null, + reconnect_strategy: null, + client_name: 'otel-cluster-test', + periodic_checks: null, + client_az: null, + advanced_config: [ + 'otel' => $otelConfig + ] + ); + + // Verify cluster client works with OpenTelemetry configured + $client->set('otel:cluster:test', 'value'); + $value = $client->get('otel:cluster:test'); + $this->assertEquals('value', $value, "GET should return the set value with OpenTelemetry"); + + $deleteResult = $client->del('otel:cluster:test'); + $this->assertGreaterThan(0, $deleteResult, "DEL should delete the key with OpenTelemetry"); + + $client->close(); + } } diff --git a/tests/ValkeyGlideFeaturesTest.php b/tests/ValkeyGlideFeaturesTest.php index 7a6142bd..50adbcb4 100644 --- a/tests/ValkeyGlideFeaturesTest.php +++ b/tests/ValkeyGlideFeaturesTest.php @@ -895,4 +895,80 @@ public function testClientCreateDeleteLoop() echo "WARNING: Significant memory growth detected: " . round($memoryGrowth / 1024 / 1024, 2) . " MB\n"; } } + + public function testOtelConfiguration() + { + // Test that client works with OpenTelemetry configuration + $otelConfig = [ + 'traces' => [ + 'endpoint' => 'file:///tmp/valkey_glide_traces.json', + 'sample_percentage' => 10 + ], + 'metrics' => [ + 'endpoint' => 'file:///tmp/valkey_glide_metrics.json' + ] + ]; + + try { + // Create client with OpenTelemetry configuration + $client = new ValkeyGlide( + addresses: [ + ['host' => 'localhost', 'port' => 6379] + ], + use_tls: false, + credentials: null, + read_from: null, + request_timeout: null, + reconnect_strategy: null, + client_name: 'otel-test-client', + periodic_checks: null, + advanced_config: [ + 'otel' => $otelConfig + ] + ); + + // Verify client works with OpenTelemetry configured + $client->set('otel:test', 'value'); + $value = $client->get('otel:test'); + $this->assertEquals('value', $value, "GET should return the set value with OpenTelemetry"); + + $deleteResult = $client->del('otel:test'); + $this->assertGreaterThan(0, $deleteResult, "DEL should delete the key with OpenTelemetry"); + + $client->close(); + } catch (Exception $e) { + // OpenTelemetry configuration should not cause client construction to fail + $this->fail("OpenTelemetry configuration caused client construction to fail: " . $e->getMessage()); + } + } + + public function testOtelWithoutConfiguration() + { + // Test that client works normally without OpenTelemetry configuration + try { + $client = new ValkeyGlide( + addresses: [ + ['host' => 'localhost', 'port' => 6379] + ], + use_tls: false, + credentials: null, + read_from: null, + request_timeout: null, + reconnect_strategy: null, + client_name: 'no-otel-test' + ); + + // Operations should work normally without OpenTelemetry + $client->set('no:otel:test', 'value'); + $value = $client->get('no:otel:test'); + $this->assertEquals('value', $value, "GET should return the set value"); + + $deleteResult = $client->del('no:otel:test'); + $this->assertGreaterThan(0, $deleteResult, "DEL should delete the key"); + + $client->close(); + } catch (Exception $e) { + $this->fail("Client without OpenTelemetry should work normally: " . $e->getMessage()); + } + } } diff --git a/valgrind.supp b/valgrind.supp index 4c335e86..3403a8e1 100644 --- a/valgrind.supp +++ b/valgrind.supp @@ -326,6 +326,30 @@ fun:*aws_lc_rs*rand*fill* } +# OpenTelemetry SDK resource detector allocations +{ + opentelemetry_resource_from_detectors + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:*hashbrown*raw*RawTableInner*fallible_with_capacity* + fun:*hashbrown*raw*RawTable*reserve_rehash* + fun:*hashbrown*map*HashMap*insert* + fun:*opentelemetry_sdk*resource*Resource*from_detectors* +} + +# Thread-local storage for OpenTelemetry threads +{ + opentelemetry_thread_local_storage + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls + fun:allocate_stack +} + # Rust raw_vec finish_grow with corrupted stack { rust_raw_vec_finish_grow_corrupted diff --git a/valkey_glide.c b/valkey_glide.c index 57becb23..0016c81f 100644 --- a/valkey_glide.c +++ b/valkey_glide.c @@ -14,6 +14,7 @@ #include "valkey_glide_cluster_arginfo.h" // Include generated arginfo header #include "valkey_glide_commands_common.h" #include "valkey_glide_hash_common.h" +#include "valkey_glide_otel.h" // Include OTEL support /* Enum support includes - must be BEFORE arginfo includes */ #if PHP_VERSION_ID >= 80100 @@ -389,6 +390,16 @@ void valkey_glide_build_client_config_base(valkey_glide_php_common_constructor_p } else { config->advanced_config->tls_config = NULL; } + + /* Check for OTEL config */ + zval* otel_config_val = zend_hash_str_find(advanced_ht, "otel", sizeof("otel") - 1); + if (otel_config_val && Z_TYPE_P(otel_config_val) == IS_ARRAY) { + VALKEY_LOG_DEBUG("otel_config", "Processing OTEL configuration from advanced_config"); + if (!valkey_glide_otel_init(otel_config_val)) { + VALKEY_LOG_WARN("otel_config", + "Failed to initialize OTEL, continuing without tracing"); + } + } } else { config->advanced_config = NULL; } @@ -453,7 +464,6 @@ PHP_MINIT_FUNCTION(valkey_glide) { return SUCCESS; } - zend_module_entry valkey_glide_module_entry = {STANDARD_MODULE_HEADER, "valkey_glide", ext_functions, diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index c2a294c1..42c4290f 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -316,7 +316,10 @@ class ValkeyGlide * @param string|null $client_name Client name identifier. * @param string|null $client_az Client availability zone. * @param array|null $advanced_config Advanced configuration ['connection_timeout' => 5000, - * 'tls_config' => ['use_insecure_tls' => false]]. + * 'tls_config' => ['use_insecure_tls' => false], + * 'otel' => ['traces' => ['endpoint' => 'grpc://localhost:4317', 'sample_percentage' => 1], + * 'metrics' => ['endpoint' => 'grpc://localhost:4317'], + * 'flush_interval_ms' => 5000]]. * connection_timeout is in milliseconds. * @param bool|null $lazy_connect Whether to use lazy connection. */ diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index c59787ad..9812bf7f 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -197,6 +197,9 @@ class ValkeyGlideCluster * - 'tls_config' => ['use_insecure_tls' => false] * - 'refresh_topology_from_initial_nodes' => false (default: false) * When true, topology updates use only initial nodes instead of internal cluster view. + * - 'otel' => ['traces' => ['endpoint' => 'grpc://localhost:4317', 'sample_percentage' => 1], + * 'metrics' => ['endpoint' => 'grpc://localhost:4317'], + * 'flush_interval_ms' => 5000] * @param bool|null $lazy_connect Whether to use lazy connection. * @param int|null $database_id Index of the logical database to connect to. Must be non-negative * and within the range supported by the server configuration. diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index 3b8bc211..8e4ea26f 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -21,6 +21,7 @@ #include #include "logger.h" +#include "valkey_glide_otel.h" #include "valkey_glide_z_common.h" /* ==================================================================== @@ -50,11 +51,15 @@ int execute_core_command(valkey_glide_object* valkey_glide, return 0; } + /* Create OTEL span for tracing */ + uint64_t span_ptr = valkey_glide_create_span(args->cmd_type); + /* Log command execution entry */ VALKEY_LOG_DEBUG_FMT("command_execution", - "Entering command execution - Command type: %d, Batch mode: %s", + "Entering command execution - Command type: %d, Batch mode: %s, Span: %lu", args->cmd_type, - valkey_glide->is_in_batch_mode ? "yes" : "no"); + valkey_glide->is_in_batch_mode ? "yes" : "no", + (unsigned long) span_ptr); uintptr_t* cmd_args = NULL; unsigned long* cmd_args_len = NULL; @@ -73,6 +78,7 @@ int execute_core_command(valkey_glide_object* valkey_glide, if (arg_count < 0) { VALKEY_LOG_ERROR("execute_core_command", "Failed to prepare command arguments"); + valkey_glide_drop_span(span_ptr); efree(result_ptr); return 0; } @@ -94,6 +100,7 @@ int execute_core_command(valkey_glide_object* valkey_glide, processor); free_core_args(cmd_args, cmd_args_len, allocated_strings, allocated_count); + valkey_glide_drop_span(span_ptr); if (res == 0) { VALKEY_LOG_WARN_FMT("batch_execution", "Failed to buffer command for batch - command type: %d", @@ -110,16 +117,17 @@ int execute_core_command(valkey_glide_object* valkey_glide, if (args->has_route && args->route_param) { /* Cluster mode with routing */ VALKEY_LOG_DEBUG("command_execution", "Using cluster routing"); - result = execute_command_with_route(args->glide_client, - args->cmd_type, - arg_count, - cmd_args, - cmd_args_len, - args->route_param); + result = execute_command_with_route_and_span(args->glide_client, + args->cmd_type, + arg_count, + cmd_args, + cmd_args_len, + args->route_param, + span_ptr); } else { /* Non-cluster mode or no routing */ - result = - execute_command(args->glide_client, args->cmd_type, arg_count, cmd_args, cmd_args_len); + result = execute_command_with_span( + args->glide_client, args->cmd_type, arg_count, cmd_args, cmd_args_len, span_ptr); } debug_print_command_result(result); @@ -146,6 +154,7 @@ int execute_core_command(valkey_glide_object* valkey_glide, /* Cleanup */ free_core_args(cmd_args, cmd_args_len, allocated_strings, allocated_count); + valkey_glide_drop_span(span_ptr); return res; } diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index f45db8cd..e26b65a7 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -19,6 +19,7 @@ #include "command_response.h" #include "valkey_glide_commands_common.h" +#include "valkey_glide_otel_commands.h" /* ==================================================================== * CORE COMMAND ARGUMENT STRUCTURES diff --git a/valkey_glide_otel.c b/valkey_glide_otel.c new file mode 100644 index 00000000..217ddf7a --- /dev/null +++ b/valkey_glide_otel.c @@ -0,0 +1,286 @@ +/* + +----------------------------------------------------------------------+ + +----------------------------------------------------------------------+ + | Copyright (c) 2023-2025 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "valkey_glide_otel.h" + +#include +#include + +#include "logger.h" + +/* Global OTEL configuration */ +valkey_glide_otel_config_t g_otel_config = {0}; +static bool g_otel_initialized = false; + +/** + * Initialize OpenTelemetry with the given configuration + * Can only be initialized once per process + */ +int valkey_glide_otel_init(zval* config_array) { + if (g_otel_initialized) { + VALKEY_LOG_WARN("otel_init", + "OpenTelemetry already initialized, ignoring subsequent calls"); + return 1; /* Success - already initialized */ + } + + if (!config_array || Z_TYPE_P(config_array) != IS_ARRAY) { + VALKEY_LOG_DEBUG("otel_init", "No OTEL configuration provided"); + return 1; /* Success - OTEL is optional */ + } + + /* Parse configuration */ + if (!parse_otel_config_array(config_array, &g_otel_config)) { + VALKEY_LOG_ERROR("otel_init", "Failed to parse OTEL configuration"); + cleanup_otel_config(&g_otel_config); + return 0; + } + + /* Initialize OTEL with Rust FFI */ + const char* error = init_open_telemetry(g_otel_config.config); + if (error) { + VALKEY_LOG_ERROR_FMT("otel_init", "Failed to initialize OTEL: %s", error); + free_c_string((char*) error); + cleanup_otel_config(&g_otel_config); + return 0; + } + + g_otel_config.enabled = true; + g_otel_initialized = true; + VALKEY_LOG_INFO("otel_init", "OpenTelemetry initialized successfully"); + return 1; +} + +/** + * Shutdown OpenTelemetry + */ +void valkey_glide_otel_shutdown(void) { + if (g_otel_config.enabled) { + g_otel_config.enabled = false; + g_otel_initialized = false; + VALKEY_LOG_INFO("otel_shutdown", "OpenTelemetry shutdown complete"); + } +} + +/** + * Create a span for command tracing + */ +uint64_t valkey_glide_create_span(enum RequestType request_type) { + if (!g_otel_config.enabled) { + return 0; + } + return create_otel_span(request_type); +} + +/** + * Drop a span + */ +void valkey_glide_drop_span(uint64_t span_ptr) { + if (span_ptr != 0) { + drop_otel_span(span_ptr); + } +} + +/** + * Parse OTEL configuration from PHP array + */ +int parse_otel_config_array(zval* config_array, valkey_glide_otel_config_t* otel_config) { + HashTable* ht = Z_ARRVAL_P(config_array); + + /* Allocate main config */ + otel_config->config = ecalloc(1, sizeof(struct OpenTelemetryConfig)); + + /* Set default flush interval */ + otel_config->config->has_flush_interval_ms = true; + otel_config->config->flush_interval_ms = 5000; + + /* Parse traces configuration */ + zval* traces_val = zend_hash_str_find(ht, "traces", sizeof("traces") - 1); + if (traces_val && Z_TYPE_P(traces_val) == IS_ARRAY) { + otel_config->traces_config = ecalloc(1, sizeof(struct OpenTelemetryTracesConfig)); + HashTable* traces_ht = Z_ARRVAL_P(traces_val); + + /* Parse endpoint */ + zval* endpoint_val = zend_hash_str_find(traces_ht, "endpoint", sizeof("endpoint") - 1); + if (endpoint_val && Z_TYPE_P(endpoint_val) == IS_STRING) { + otel_config->traces_config->endpoint = estrdup(Z_STRVAL_P(endpoint_val)); + } else { + VALKEY_LOG_ERROR("otel_config", + "Traces endpoint is required when traces config is provided"); + cleanup_otel_config(otel_config); + zend_throw_exception(get_valkey_glide_exception_ce(), + "Traces endpoint is required when traces config is provided", + 0); + return 0; + } + + /* Parse sample_percentage with default of 1% */ + zval* sample_val = + zend_hash_str_find(traces_ht, "sample_percentage", sizeof("sample_percentage") - 1); + if (sample_val && Z_TYPE_P(sample_val) == IS_LONG) { + long sample_pct = Z_LVAL_P(sample_val); + if (sample_pct < 0 || sample_pct > 100) { + VALKEY_LOG_ERROR("otel_config", "Sample percentage must be between 0 and 100"); + cleanup_otel_config(otel_config); + zend_throw_exception(get_valkey_glide_exception_ce(), + "Sample percentage must be between 0 and 100", + 0); + return 0; + } + otel_config->traces_config->has_sample_percentage = true; + otel_config->traces_config->sample_percentage = (uint32_t) sample_pct; + } else { + /* Default to 1% */ + otel_config->traces_config->has_sample_percentage = true; + otel_config->traces_config->sample_percentage = 1; + } + + otel_config->config->traces = otel_config->traces_config; + } + + /* Parse metrics configuration */ + zval* metrics_val = zend_hash_str_find(ht, "metrics", sizeof("metrics") - 1); + if (metrics_val && Z_TYPE_P(metrics_val) == IS_ARRAY) { + otel_config->metrics_config = ecalloc(1, sizeof(struct OpenTelemetryMetricsConfig)); + HashTable* metrics_ht = Z_ARRVAL_P(metrics_val); + + /* Parse endpoint */ + zval* endpoint_val = zend_hash_str_find(metrics_ht, "endpoint", sizeof("endpoint") - 1); + if (endpoint_val && Z_TYPE_P(endpoint_val) == IS_STRING) { + otel_config->metrics_config->endpoint = estrdup(Z_STRVAL_P(endpoint_val)); + } else { + VALKEY_LOG_ERROR("otel_config", + "Metrics endpoint is required when metrics config is provided"); + cleanup_otel_config(otel_config); + zend_throw_exception(get_valkey_glide_exception_ce(), + "Metrics endpoint is required when metrics config is provided", + 0); + return 0; + } + + otel_config->config->metrics = otel_config->metrics_config; + } + + /* Parse flush_interval_ms (override default if provided) */ + zval* flush_val = zend_hash_str_find(ht, "flush_interval_ms", sizeof("flush_interval_ms") - 1); + if (flush_val && Z_TYPE_P(flush_val) == IS_LONG) { + long flush_ms = Z_LVAL_P(flush_val); + if (flush_ms <= 0) { + VALKEY_LOG_ERROR("otel_config", "Flush interval must be a positive integer"); + cleanup_otel_config(otel_config); + zend_throw_exception( + get_valkey_glide_exception_ce(), "Flush interval must be a positive integer", 0); + return 0; + } + otel_config->config->flush_interval_ms = (uint32_t) flush_ms; + } + + /* Validate at least one of traces or metrics is configured */ + if (!otel_config->config->traces && !otel_config->config->metrics) { + VALKEY_LOG_ERROR("otel_config", "At least one of traces or metrics must be configured"); + cleanup_otel_config(otel_config); + zend_throw_exception(get_valkey_glide_exception_ce(), + "At least one of traces or metrics must be configured", + 0); + return 0; + } + + return 1; +} + +/** + * Cleanup OTEL configuration + */ +void cleanup_otel_config(valkey_glide_otel_config_t* otel_config) { + if (otel_config->traces_config) { + if (otel_config->traces_config->endpoint) { + efree((void*) otel_config->traces_config->endpoint); + otel_config->traces_config->endpoint = NULL; + } + efree(otel_config->traces_config); + otel_config->traces_config = NULL; + } + + if (otel_config->metrics_config) { + if (otel_config->metrics_config->endpoint) { + efree((void*) otel_config->metrics_config->endpoint); + otel_config->metrics_config->endpoint = NULL; + } + efree(otel_config->metrics_config); + otel_config->metrics_config = NULL; + } + + if (otel_config->config) { + /* Reset pointers to avoid double-free */ + otel_config->config->traces = NULL; + otel_config->config->metrics = NULL; + efree(otel_config->config); + otel_config->config = NULL; + } + + otel_config->enabled = false; + otel_config->current_sample_percentage = 0; +} + +/* Public API function implementations */ + +bool valkey_glide_otel_is_initialized(void) { + return g_otel_config.enabled; +} + +int32_t valkey_glide_otel_get_sample_percentage(void) { + if (!g_otel_config.enabled || !g_otel_config.traces_config) { + return -1; // Not initialized or no traces config + } + return (int32_t) g_otel_config.traces_config->sample_percentage; +} + +bool valkey_glide_otel_set_sample_percentage(int32_t percentage) { + if (!g_otel_config.enabled || !g_otel_config.traces_config) { + return false; // Not initialized or no traces config + } + + if (percentage < 0 || percentage > 100) { + return false; // Invalid percentage + } + + g_otel_config.traces_config->sample_percentage = (uint32_t) percentage; + return true; +} + +bool valkey_glide_otel_should_sample(void) { + int32_t percentage = valkey_glide_otel_get_sample_percentage(); + return percentage > 0 && (percentage == 100 || (rand() % 100) < percentage); +} + +uint64_t valkey_glide_otel_create_named_span(const char* name) { + if (!g_otel_config.enabled || !name || strlen(name) == 0) { + return 0; // Not initialized or invalid name + } + + if (strlen(name) > 256) { + return 0; // Name too long + } + + return create_named_otel_span(name); +} + +bool valkey_glide_otel_end_span(uint64_t span_ptr) { + if (span_ptr == 0) { + return true; // Safe no-op for zero pointer + } + + drop_otel_span(span_ptr); + return true; +} diff --git a/valkey_glide_otel.h b/valkey_glide_otel.h new file mode 100644 index 00000000..1e0c7ca4 --- /dev/null +++ b/valkey_glide_otel.h @@ -0,0 +1,51 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) 2023-2025 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef VALKEY_GLIDE_OTEL_H +#define VALKEY_GLIDE_OTEL_H + +#include "include/glide_bindings.h" +#include "php.h" + +/* OTEL configuration structure for PHP */ +typedef struct { + bool enabled; + struct OpenTelemetryConfig* config; + struct OpenTelemetryTracesConfig* traces_config; + struct OpenTelemetryMetricsConfig* metrics_config; + int32_t current_sample_percentage; +} valkey_glide_otel_config_t; + +/* Global OTEL configuration */ +extern valkey_glide_otel_config_t g_otel_config; + +/* Function declarations */ +int valkey_glide_otel_init(zval* config_array); +void valkey_glide_otel_shutdown(void); +uint64_t valkey_glide_create_span(enum RequestType request_type); +void valkey_glide_drop_span(uint64_t span_ptr); + +/* Public API functions */ +bool valkey_glide_otel_is_initialized(void); +int32_t valkey_glide_otel_get_sample_percentage(void); +bool valkey_glide_otel_set_sample_percentage(int32_t percentage); +bool valkey_glide_otel_should_sample(void); +uint64_t valkey_glide_otel_create_named_span(const char* name); +bool valkey_glide_otel_end_span(uint64_t span_ptr); + +/* Helper functions */ +int parse_otel_config_array(zval* config_array, valkey_glide_otel_config_t* otel_config); +void cleanup_otel_config(valkey_glide_otel_config_t* otel_config); + +#endif /* VALKEY_GLIDE_OTEL_H */ diff --git a/valkey_glide_otel_commands.c b/valkey_glide_otel_commands.c new file mode 100644 index 00000000..57b0462f --- /dev/null +++ b/valkey_glide_otel_commands.c @@ -0,0 +1,61 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) 2023-2025 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "valkey_glide_otel_commands.h" + +#include "command_response.h" +#include "logger.h" + +/** + * Execute command with OTEL span support + */ +CommandResult* execute_command_with_span(const void* client_adapter_ptr, + enum RequestType command_type, + unsigned long arg_count, + const uintptr_t* args, + const unsigned long* args_len, + uint64_t span_ptr) { + VALKEY_LOG_DEBUG_FMT( + "otel_command", "Executing command with span: %lu", (unsigned long) span_ptr); + + /* Use the FFI command function with span support */ + return command((void*) client_adapter_ptr, + (uintptr_t) client_adapter_ptr, /* request_id */ + command_type, + arg_count, + args, + args_len, + NULL, /* route_bytes */ + 0, /* route_bytes_len */ + span_ptr); +} + +/** + * Execute command with routing and OTEL span support + */ +CommandResult* execute_command_with_route_and_span(const void* client_adapter_ptr, + enum RequestType command_type, + unsigned long arg_count, + const uintptr_t* args, + const unsigned long* args_len, + zval* route, + uint64_t span_ptr) { + VALKEY_LOG_DEBUG_FMT( + "otel_command", "Executing routed command with span: %lu", (unsigned long) span_ptr); + + /* For now, use the existing execute_command_with_route function */ + /* TODO: Add proper route serialization and span support */ + return execute_command_with_route( + (void*) client_adapter_ptr, command_type, arg_count, args, args_len, route); +} diff --git a/valkey_glide_otel_commands.h b/valkey_glide_otel_commands.h new file mode 100644 index 00000000..0b8fc440 --- /dev/null +++ b/valkey_glide_otel_commands.h @@ -0,0 +1,37 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) 2023-2025 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef VALKEY_GLIDE_OTEL_COMMANDS_H +#define VALKEY_GLIDE_OTEL_COMMANDS_H + +#include "command_response.h" +#include "include/glide_bindings.h" + +/* Command execution wrappers with OTEL span support */ +CommandResult* execute_command_with_span(const void* client_adapter_ptr, + enum RequestType command_type, + unsigned long arg_count, + const uintptr_t* args, + const unsigned long* args_len, + uint64_t span_ptr); + +CommandResult* execute_command_with_route_and_span(const void* client_adapter_ptr, + enum RequestType command_type, + unsigned long arg_count, + const uintptr_t* args, + const unsigned long* args_len, + zval* route, + uint64_t span_ptr); + +#endif /* VALKEY_GLIDE_OTEL_COMMANDS_H */