diff --git a/Makefile b/Makefile index dd630354e..5cccaf721 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ GCOV ?= gcov SHELL = /bin/bash GCOVR ?= gcovr GIT ?= git +# make sure go toolchain defined in daemon/go.mod is always used +GOTOOLCHAIN=auto include make/config.mk include make/vendor.mk @@ -165,9 +167,17 @@ agent-valgrind: agent/Makefile # Configure the target directory for go install export GOBIN=$(CURDIR)/bin +.PHONY: daemon-golang-verify +daemon-golang-verify: + @golang_in_binary=$$(go version -m bin/daemon | awk '/^bin\/daemon/ {print $$2;}') \ + && golang_from_toolchain=$$(awk '/^toolchain/ {print $$2;}' daemon/go.mod) \ + && [ "$$golang_in_binary" = "$$golang_from_toolchain" ] && echo "daemon built using: $$golang_from_toolchain" \ + || { echo "ERROR: daemon built using go: $$golang_in_binary, required: $$golang_from_toolchain"; exit 1; } + .PHONY: daemon daemon: $(MAKE) -C daemon + $(MAKE) daemon-golang-verify .PHONY: daemon_race daemon_race: diff --git a/VERSION b/VERSION index 4044f9086..77903b35f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -12.0.0 +12.1.0 diff --git a/agent/fw_cakephp.c b/agent/fw_cakephp.c index 572f6f23f..590f7c8f1 100644 --- a/agent/fw_cakephp.c +++ b/agent/fw_cakephp.c @@ -173,10 +173,12 @@ void nr_cakephp_enable(TSRMLS_D) { nr_php_wrap_user_function( NR_PSTR("Cake\\Controller\\Controller::invokeAction"), nr_cakephp_name_the_wt_4); - nr_php_wrap_user_function( - NR_PSTR( - "Cake\\Error\\Middleware\\ErrorHandlerMiddleware::handleException"), - nr_cakephp_error_handler_wrapper); + if (!NRINI(ignore_framework_error_exception_handler)) { + nr_php_wrap_user_function( + NR_PSTR( + "Cake\\Error\\Middleware\\ErrorHandlerMiddleware::handleException"), + nr_cakephp_error_handler_wrapper); + } nr_txn_suggest_package_supportability_metric(NRPRG(txn), PHP_PACKAGE_NAME, PHP_PACKAGE_VERSION_UNKNOWN); } diff --git a/agent/fw_drupal8.c b/agent/fw_drupal8.c index 59c1c5bc8..a6af1c196 100644 --- a/agent/fw_drupal8.c +++ b/agent/fw_drupal8.c @@ -898,14 +898,16 @@ void nr_drupal8_enable(TSRMLS_D) { /* * Log exceptions without further handling. */ - nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\ExceptionLoggingSubscriber::onException"), - nr_drupal_exception); + if (!NRINI(ignore_framework_error_exception_handler)) { + nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\ExceptionLoggingSubscriber::onException"), + nr_drupal_exception); - /* - * Last-chance handler for exceptions: the final exception subscriber. - */ - nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\FinalExceptionSubscriber::onException"), - nr_drupal_exception); + /* + * Last-chance handler for exceptions: the final exception subscriber. + */ + nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\FinalExceptionSubscriber::onException"), + nr_drupal_exception); + } // clang-format on /* diff --git a/agent/fw_laravel.c b/agent/fw_laravel.c index dc409ec10..1a5fe1678 100644 --- a/agent/fw_laravel.c +++ b/agent/fw_laravel.c @@ -865,19 +865,21 @@ NR_PHP_WRAPPER(nr_laravel5_application_boot) { * to sensibly name transactions when an exception is thrown during routing * and also to record the error. */ - exception_handler = nr_php_call_offsetGet( - this_var, "Illuminate\\Contracts\\Debug\\ExceptionHandler" TSRMLS_CC); - if (nr_php_is_zval_valid_object(exception_handler)) { - nr_laravel_add_callback_method(Z_OBJCE_P(exception_handler), - NR_PSTR("render"), - nr_laravel5_exception_render TSRMLS_CC); - - nr_laravel_add_callback_method(Z_OBJCE_P(exception_handler), - NR_PSTR("report"), - nr_laravel5_exception_report TSRMLS_CC); - } else { - nrl_verbosedebug(NRL_FRAMEWORK, "%s: cannot get exception handler", - __func__); + if (!NRINI(ignore_framework_error_exception_handler)) { + exception_handler = nr_php_call_offsetGet( + this_var, "Illuminate\\Contracts\\Debug\\ExceptionHandler" TSRMLS_CC); + if (nr_php_is_zval_valid_object(exception_handler)) { + nr_laravel_add_callback_method(Z_OBJCE_P(exception_handler), + NR_PSTR("render"), + nr_laravel5_exception_render TSRMLS_CC); + + nr_laravel_add_callback_method(Z_OBJCE_P(exception_handler), + NR_PSTR("report"), + nr_laravel5_exception_report TSRMLS_CC); + } else { + nrl_verbosedebug(NRL_FRAMEWORK, "%s: cannot get exception handler", + __func__); + } } end: diff --git a/agent/fw_lumen.c b/agent/fw_lumen.c index 79f89ebc3..2cae28dbf 100644 --- a/agent/fw_lumen.c +++ b/agent/fw_lumen.c @@ -218,16 +218,18 @@ void nr_lumen_enable(TSRMLS_D) { nr_php_wrap_user_function( NR_PSTR("Laravel\\Lumen\\Application::handleFoundRoute"), nr_lumen_handle_found_route TSRMLS_CC); + if (!NRINI(ignore_framework_error_exception_handler)) { #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ && !defined OVERWRITE_ZEND_EXECUTE_DATA - nr_php_wrap_user_function_before_after_clean( - NR_PSTR("Laravel\\Lumen\\Application::sendExceptionToHandler"), - nr_lumen_exception, NULL, NULL); + nr_php_wrap_user_function_before_after_clean( + NR_PSTR("Laravel\\Lumen\\Application::sendExceptionToHandler"), + nr_lumen_exception, NULL, NULL); #else - nr_php_wrap_user_function( - NR_PSTR("Laravel\\Lumen\\Application::sendExceptionToHandler"), - nr_lumen_exception TSRMLS_CC); + nr_php_wrap_user_function( + NR_PSTR("Laravel\\Lumen\\Application::sendExceptionToHandler"), + nr_lumen_exception TSRMLS_CC); #endif + } if (NRINI(vulnerability_management_package_detection_enabled)) { nr_txn_add_php_package(NRPRG(txn), PHP_PACKAGE_NAME, diff --git a/agent/fw_symfony4.c b/agent/fw_symfony4.c index 8592186ac..a9f3c06d8 100644 --- a/agent/fw_symfony4.c +++ b/agent/fw_symfony4.c @@ -250,19 +250,21 @@ void nr_symfony4_enable(TSRMLS_D) { * nr_txn_record_error and pass the exception message. Now we get errors in * the error analytics page. */ - nr_php_wrap_user_function( - NR_PSTR("Symfony\\Component\\HttpKernel\\" - "EventListener\\ExceptionListener::onKernelException"), - nr_symfony4_exception TSRMLS_CC); + if (!NRINI(ignore_framework_error_exception_handler)) { + nr_php_wrap_user_function( + NR_PSTR("Symfony\\Component\\HttpKernel\\" + "EventListener\\ExceptionListener::onKernelException"), + nr_symfony4_exception TSRMLS_CC); - /* - * In Symfony 5 listener that catch errors was changed to ErrorListener, - * we try to hook into it - */ - nr_php_wrap_user_function( - NR_PSTR("Symfony\\Component\\HttpKernel\\" - "EventListener\\ErrorListener::onKernelException"), - nr_symfony4_exception TSRMLS_CC); + /* + * In Symfony 5 listener that catch errors was changed to ErrorListener, + * we try to hook into it + */ + nr_php_wrap_user_function( + NR_PSTR("Symfony\\Component\\HttpKernel\\" + "EventListener\\ErrorListener::onKernelException"), + nr_symfony4_exception TSRMLS_CC); + } /* * Listen for Symfony commands so we can name those appropriately. diff --git a/agent/fw_yii.c b/agent/fw_yii.c index 0b1af7c96..a6eede3e8 100644 --- a/agent/fw_yii.c +++ b/agent/fw_yii.c @@ -219,8 +219,10 @@ void nr_yii2_enable(TSRMLS_D) { * allowing default PHP error handler to be intercepted by the NewRelic agent * implementation. */ - nr_php_wrap_user_function(NR_PSTR("yii\\base\\ErrorHandler::logException"), - nr_yii2_error_handler_wrapper TSRMLS_CC); + if (!NRINI(ignore_framework_error_exception_handler)) { + nr_php_wrap_user_function(NR_PSTR("yii\\base\\ErrorHandler::logException"), + nr_yii2_error_handler_wrapper TSRMLS_CC); + } #endif if (NRINI(vulnerability_management_package_detection_enabled)) { diff --git a/agent/php_mysqli.c b/agent/php_mysqli.c index 24a321b02..a6d3c8e76 100644 --- a/agent/php_mysqli.c +++ b/agent/php_mysqli.c @@ -688,3 +688,28 @@ const char* nr_php_mysqli_strip_persistent_prefix(const char* host) { return host; } + +void nr_php_mysqli_rshutdown() { + /* + * This frees mysqli metadata stored in the transaction. + * + * `mysqli_queries` contains duplicates of zvals. If + * `nr_php_txn_end` is called from the post-deactivate callback, request + * shutdown functions have already been called; and the Zend VM has already + * forcefully freed all dangling zvals that are not referenced by the global + * scope (regardless of their reference count), thus leaving the zvals stored + * in the mysqli_queries metadata in an "undefined" state. Consequently, + * freeing the zvals in `nr_php_txn_end` at this stage can result in undefined + * behavior. + * + * Calling this function during the RSHUTDOWN phase ensures that the zvals in + * `mysqli_queries` are cleaned up before Zend winds down the VM and + * forcefully frees zvals. + * + * If `nr_php_txn_end` is called outside the post-deactivate callback, + * it frees `mysqli_queries` by itself. + */ + if (nrlikely(NRPRG(txn))) { + nr_hashmap_destroy(&NRTXNGLOBAL(mysqli_queries)); + } +} diff --git a/agent/php_mysqli.h b/agent/php_mysqli.h index 03d42cf57..ca99d98a1 100644 --- a/agent/php_mysqli.h +++ b/agent/php_mysqli.h @@ -163,4 +163,10 @@ extern nr_datastore_instance_t* nr_php_mysqli_retrieve_datastore_instance( extern void nr_php_mysqli_remove_datastore_instance( const zval* mysqli_obj TSRMLS_DC); +/* + * Purpose : Frees reference incremented, transaction global zvals + * that must be cleaned up prior to postdeactivate + */ +extern void nr_php_mysqli_rshutdown(); + #endif /* PHP_MYSQLI_HDR */ diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 9683bad28..e59de32b6 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -238,6 +238,9 @@ nrinibool_t nrinibool_t ignore_user_exception_handler; /* newrelic.error_collector.ignore_user_exception_handler */ +nrinibool_t + ignore_framework_error_exception_handler; /* newrelic.error_collector.ignore_framework_error_excpetion_handler + */ nriniint_t ignore_errors; /* newrelic.error_collector.ignore_errors */ nrinistr_t ignore_exceptions; /* newrelic.error_collector.ignore_exceptions */ nrinibool_t @@ -491,6 +494,16 @@ nrinibool_t span_events_enabled; /* newrelic.span_events_enabled */ nriniuint_t span_events_max_samples_stored; /* newrelic.span_events.max_samples_stored */ + +nrinistr_t dt_remote_parent_sampled; /* newrelic.distributed_tracing.sampler.remote_parent_sampled */ +nrinistr_t + dt_remote_parent_not_sampled; /* newrelic.distributed_tracing.sampler.remote_parent_not_sampled */ +/* decoding of newrelic.distributed_tracing.sampler.remote_parent_sampled and + * newrelic.distributed_tracing.sampler.remote_parent_not_sampled. + */ +nr_upstream_parent_sampling_control_t dt_sampler_parent_sampled; +nr_upstream_parent_sampling_control_t dt_sampler_parent_not_sampled; + nrinistr_t trace_observer_host; /* newrelic.infinite_tracing.trace_observer.host */ nriniuint_t diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 37da5a820..7cd574328 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -1970,6 +1970,55 @@ static PHP_INI_MH(nr_wordpress_hooks_options_mh) { return SUCCESS; } +static PHP_INI_MH(nr_dt_sampler_remote_parent_mh) { + nrinistr_t* p; + + char* base = (char*)mh_arg2; + p = (nrinistr_t*)(base + (size_t)mh_arg1); + bool parent_sampled = false; + + (void)mh_arg3; + NR_UNUSED_TSRMLS; + + p->where = 0; + + if (0 == nr_strcmp(ZEND_STRING_VALUE(entry->name), + "newrelic.distributed_tracing.sampler.remote_parent_sampled")) { + parent_sampled = true; + } + + if (0 == nr_strcmp(NEW_VALUE, "default")) { + if (parent_sampled) { + NRPRG(dt_sampler_parent_sampled) = DEFAULT; + } else { + NRPRG(dt_sampler_parent_not_sampled) = DEFAULT; + } + } else if (0 == nr_strcmp(NEW_VALUE, "always_on")) { + if (parent_sampled) { + NRPRG(dt_sampler_parent_sampled) = ALWAYS_KEEP; + } else { + NRPRG(dt_sampler_parent_not_sampled) = ALWAYS_KEEP; + } + } else if (0 == nr_strcmp(NEW_VALUE, "always_off")) { + if (parent_sampled) { + NRPRG(dt_sampler_parent_sampled) = ALWAYS_DROP; + } else { + NRPRG(dt_sampler_parent_not_sampled) = ALWAYS_DROP; + } + } else { + nrl_warning(NRL_INIT, "Invalid %s value \"%s\"; using \"%s\" instead.", + ZEND_STRING_VALUE(entry->name), NEW_VALUE, + DEFAULT_WORDPRESS_HOOKS_OPTIONS); + /* This will cause PHP to call the handler again with default value */ + return FAILURE; + } + + p->where = stage; + p->value = NEW_VALUE; + + return SUCCESS; +} + /* * Now for the actual INI entry table. Please note there are two types of INI * entry specification used. @@ -2587,6 +2636,14 @@ STD_PHP_INI_ENTRY_EX("newrelic.error_collector.ignore_user_exception_handler", zend_newrelic_globals, newrelic_globals, nr_yes_no_dh) +STD_PHP_INI_ENTRY_EX("newrelic.error_collector.ignore_framework_error_exception_handler", + "0", + NR_PHP_REQUEST, + nr_boolean_mh, + ignore_framework_error_exception_handler, + zend_newrelic_globals, + newrelic_globals, + nr_yes_no_dh) STD_PHP_INI_ENTRY_EX("newrelic.error_collector.ignore_errors", "", NR_PHP_REQUEST, @@ -2930,6 +2987,24 @@ STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing_exclude_newrelic_header", newrelic_globals, 0) + +STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing.sampler.remote_parent_sampled", + "default", + NR_PHP_REQUEST, + nr_dt_sampler_remote_parent_mh, + dt_remote_parent_sampled, + zend_newrelic_globals, + newrelic_globals, + 0) +STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing.sampler.remote_parent_not_sampled", + "default", + NR_PHP_REQUEST, + nr_dt_sampler_remote_parent_mh, + dt_remote_parent_not_sampled, + zend_newrelic_globals, + newrelic_globals, + 0) + /* * This setting is not documented and affects the length of the interally used * trace id. This INI setting should not be modified unless requested by diff --git a/agent/php_observer.c b/agent/php_observer.c index 01f84b08f..fe03a6e70 100644 --- a/agent/php_observer.c +++ b/agent/php_observer.c @@ -84,6 +84,10 @@ static zend_observer_fcall_handlers nr_php_fcall_register_handlers( return handlers; } + if (0 == nr_php_recording()) { + return handlers; + } + if (nrunlikely(NR_PHP_PROCESS_GLOBALS(special_flags).show_executes)) { nr_php_show_exec("observe", execute_data, NULL); } diff --git a/agent/php_pdo.c b/agent/php_pdo.c index d850ffa82..18d01be3b 100644 --- a/agent/php_pdo.c +++ b/agent/php_pdo.c @@ -638,3 +638,28 @@ zval* nr_php_pdo_disable_persistence(const zval* options TSRMLS_DC) { nr_php_zval_free(&persistent); return result; } + +void nr_php_pdo_rshutdown() { + /* + * This frees pdo metadata stored in the transaction. + * + * `pdo_link_options` contains duplicates of zvals. If + * `nr_php_txn_end` is called from the post-deactivate callback, request + * shutdown functions have already been called; and the Zend VM has already + * forcefully freed all dangling zvals that are not referenced by the global + * scope (regardless of their reference count), thus leaving the zvals stored + * in the pdo_link_options metadata in an "undefined" state. Consequently, + * freeing the zvals in `nr_php_txn_end` at this stage can result in undefined + * behavior. + * + * Calling this function during the RSHUTDOWN phase ensures that the zvals in + * `pdo_link_options` are cleaned up before Zend winds down the VM and + * forcefully frees zvals. + * + * If `nr_php_txn_end` is called outside the post-deactivate callback, + * it frees `pdo_link_options` by itself. + */ + if (nrlikely(NRPRG(txn))) { + nr_hashmap_destroy(&NRTXNGLOBAL(pdo_link_options)); + } +} diff --git a/agent/php_pdo.h b/agent/php_pdo.h index e6e4d54d4..d5b6d6cbb 100644 --- a/agent/php_pdo.h +++ b/agent/php_pdo.h @@ -161,4 +161,9 @@ extern nr_status_t nr_php_pdo_parse_data_source( extern void nr_php_pdo_free_data_sources(struct pdo_data_src_parser* parsed, size_t nparams); +/* + * Purpose : Frees reference incremented, transaction global zvals + * that must be cleaned up prior to postdeactivate + */ +extern void nr_php_pdo_rshutdown(); #endif diff --git a/agent/php_rshutdown.c b/agent/php_rshutdown.c index a4165ed7a..7716324d7 100644 --- a/agent/php_rshutdown.c +++ b/agent/php_rshutdown.c @@ -12,6 +12,8 @@ #include "php_globals.h" #include "php_user_instrument.h" #include "php_wrapper.h" +#include "php_mysqli.h" +#include "php_pdo.h" #include "util_logging.h" #include "lib_guzzle4.h" @@ -49,6 +51,8 @@ PHP_RSHUTDOWN_FUNCTION(newrelic) { nr_guzzle4_rshutdown(TSRMLS_C); nr_curl_rshutdown(TSRMLS_C); + nr_php_pdo_rshutdown(); + nr_php_mysqli_rshutdown(); nrl_verbosedebug(NRL_INIT, "RSHUTDOWN processing done"); diff --git a/agent/php_txn.c b/agent/php_txn.c index 800cfb7f2..bd5f373b4 100644 --- a/agent/php_txn.c +++ b/agent/php_txn.c @@ -942,6 +942,10 @@ nr_status_t nr_php_txn_begin(const char* appnames, = is_cli ? NRINI(tt_max_segments_cli) : NRINI(tt_max_segments_web); opts.span_queue_batch_size = NRINI(agent_span_queue_size); opts.span_queue_batch_timeout = NRINI(agent_span_queue_timeout); + opts.dt_sampler_parent_sampled + = NRPRG(dt_sampler_parent_sampled); + opts.dt_sampler_parent_not_sampled + = NRPRG(dt_sampler_parent_not_sampled); opts.logging_enabled = NRINI(logging_enabled); opts.log_decorating_enabled = NRINI(log_decorating_enabled); opts.log_forwarding_enabled = NRINI(log_forwarding_enabled); @@ -1187,9 +1191,6 @@ static void nr_php_txn_do_shutdown(nrtxn_t* txn TSRMLS_DC) { * cannot be configured into the browser client config. */ nr_php_capture_request_parameters(txn TSRMLS_CC); - - nr_hashmap_destroy(&NRTXNGLOBAL(mysqli_queries)); - nr_hashmap_destroy(&NRTXNGLOBAL(pdo_link_options)); } void nr_php_txn_shutdown(TSRMLS_D) { @@ -1356,6 +1357,9 @@ nr_status_t nr_php_txn_end(int ignoretxn, int in_post_deactivate TSRMLS_DC) { nr_hashmap_destroy(&NRTXNGLOBAL(curl_multi_metadata)); nr_mysqli_metadata_destroy(&NRTXNGLOBAL(mysqli_links)); + nr_hashmap_destroy(&NRTXNGLOBAL(mysqli_queries)); + + nr_hashmap_destroy(&NRTXNGLOBAL(pdo_link_options)); return NR_SUCCESS; } diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index 4c199f00b..eb81d86c8 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -432,6 +432,22 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; ;newrelic.error_collector.ignore_user_exception_handler = false +; Setting: newrelic.error_collector.ignore_framework_error_exception_handler +; Type : boolean +; Scope : per-directory +; Default: false +; Info : If enabled, the New Relic error collector will ignore any errors or +; exceptions that are handled by a framework error or exception +; handler. +; +; This is useful if you only want to see exceptions that are re- +; thrown by the framework or if you are inserting your own +; handling logic into the exception handling stack that will +; filter errors/exceptions and manually call the New Relic API +; when instrumentation is desired. +; +;newrelic.error_collector.ignore_framework_error_exception_handler = false + ; Setting: newrelic.error_collector.ignore_exceptions ; Type: string ; Scope: per-directory @@ -911,6 +927,36 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; ;newrelic.distributed_tracing_exclude_newrelic_header = false +; Setting: newrelic.distributed_tracing.sampler.remote_parent_sampled +; Type : string +; Scope : per-directory +; Default: "default" +; Info : This option defines how the agent should handle sampling spans when +; their parent span from an upstream entity was sampled. For example, +; setting remote_parent_sampled: always_on means the agent will sample +; anything if the upstream entity sampled the parent. +; The possible values are: +; - "default": Use New Relic's standard sampling rules. +; - "always_on": Always sample spans whose upstream parent was sampled. +; - "always_off": Always skip sampling spans whose upstream parent was sampled. +; +;newrelic.distributed_tracing.sampler.remote_parent_sampled = "default" + +; Setting: newrelic.distributed_tracing.sampler.remote_parent_not_sampled +; Type : string +; Scope : per-directory +; Default: "default" +; Info : This option defines how the agent should handle sampling spans when +; their parent span from an upstream entity was not sampled. For example, +; setting remote_parent_not_sampled: always_off means the agent will not +; try to sample anything if the upstream entity did not sample the parent. +; The possible values are: +; - "default": Use New Relic's standard sampling rules. +; - "always_on": Always sample spans whose upstream parent was not sampled. +; - "always_off": Always skip sampling spans whose upstream parent was not sampled. +; +;newrelic.distributed_tracing.sampler.remote_parent_sampled = "default" + ; Setting: newrelic.span_events_enabled ; Type : boolean ; Scope : per-directory diff --git a/axiom/nr_distributed_trace.c b/axiom/nr_distributed_trace.c index 25fcd27d5..9da90fb53 100644 --- a/axiom/nr_distributed_trace.c +++ b/axiom/nr_distributed_trace.c @@ -1298,3 +1298,43 @@ char* nr_distributed_trace_create_w3c_traceparent_header(const char* trace_id, return trace_parent_header; } + +void nr_distributed_trace_handle_inbound_w3c_sampled_flag( + nr_distributed_trace_t* dt, + const nrobj_t* trace_headers, + nr_upstream_parent_sampling_control_t remote_parent_sampled, + nr_upstream_parent_sampling_control_t remote_parent_not_sampled) { + const nrobj_t* traceparent = NULL; + int sampled = 0; + nr_status_t parse_err = NR_FAILURE; + if (DEFAULT != remote_parent_sampled || DEFAULT != remote_parent_not_sampled) { + traceparent = nro_get_hash_value(trace_headers, "traceparent", &parse_err); + if (nrunlikely(NULL == traceparent || NR_SUCCESS != parse_err)) { + return; + } + sampled = nro_get_hash_int(traceparent, "trace_flags", &parse_err); + if (nrunlikely(NR_SUCCESS != parse_err)) { + return; + } + /* The final bit of the trace_flags indicates the sampling decision */ + if (sampled & 0x01) { + if (DEFAULT != remote_parent_sampled) { + if (ALWAYS_KEEP == remote_parent_sampled) { + dt->sampled = true; + dt->priority = 2; + } else { + dt->sampled = false; + } + } + } else { + if (DEFAULT != remote_parent_not_sampled) { + if (ALWAYS_DROP == remote_parent_not_sampled) { + dt->sampled = false; + } else { + dt->sampled = true; + dt->priority = 2; + } + } + } + } +} diff --git a/axiom/nr_distributed_trace.h b/axiom/nr_distributed_trace.h index f59d7ae24..2b2e5b45e 100644 --- a/axiom/nr_distributed_trace.h +++ b/axiom/nr_distributed_trace.h @@ -60,6 +60,16 @@ static const char NR_DISTRIBUTED_TRACE_W3C_TRACECONTEXT_ACCEPT_EXCEPTION[] typedef struct _nr_distributed_trace_t nr_distributed_trace_t; typedef struct _nr_distributed_trace_payload_t nr_distributed_trace_payload_t; +/* + * Control options for how to respect other-vendor upstream sampling + * decisions. + */ +typedef enum { + DEFAULT, + ALWAYS_KEEP, + ALWAYS_DROP +} nr_upstream_parent_sampling_control_t; + /* * Purpose : Creates/allocates a new distributed tracing metadata struct * instance. It's the responsibility of the caller to @@ -409,4 +419,18 @@ bool nr_distributed_trace_accept_inbound_w3c_payload( const char* transport_type, const char** error); +/* + * Purpose : Handle upstream w3c sampled flag according to settings + * + * Params : 1. The distributed trace object + * 2. W3C trace headers objet + * 3. Setting for if the upstream trace is sampled + * 4. Setting for if the upstream trace is not sampled + */ +void nr_distributed_trace_handle_inbound_w3c_sampled_flag( + nr_distributed_trace_t* dt, + const nrobj_t* trace_headers, + nr_upstream_parent_sampling_control_t remote_parent_sampled, + nr_upstream_parent_sampling_control_t remote_parent_not_sampled); + #endif /* NR_DISTRIBUTED_TRACE_HDR */ diff --git a/axiom/nr_txn.c b/axiom/nr_txn.c index 60e1ea5ee..744f9e077 100644 --- a/axiom/nr_txn.c +++ b/axiom/nr_txn.c @@ -2972,6 +2972,14 @@ static bool nr_txn_accept_w3c_trace_context_headers( nr_distributed_trace_accept_inbound_w3c_payload( txn->distributed_trace, trace_headers, transport_type, &error_metrics); + /* Depending on the user's INI settings, we may or may not want to + * consider the traceparent's sampled field */ + nr_distributed_trace_handle_inbound_w3c_sampled_flag( + txn->distributed_trace, + trace_headers, + txn->options.dt_sampler_parent_sampled, + txn->options.dt_sampler_parent_not_sampled); + if (error_metrics) { nr_txn_force_single_count(txn, error_metrics); } diff --git a/axiom/nr_txn.h b/axiom/nr_txn.h index 9a29fd878..2a1f3a569 100644 --- a/axiom/nr_txn.h +++ b/axiom/nr_txn.h @@ -100,6 +100,14 @@ typedef struct _nrtxnopt_t { headers in favor of only W3C trace context headers */ + nr_upstream_parent_sampling_control_t + dt_sampler_parent_sampled; /* how to sample spans when non- + New Relic upstream did sample. + */ + nr_upstream_parent_sampling_control_t + dt_sampler_parent_not_sampled; /* how to sample spans when non- + New Relic upstream didn't sample. + */ int span_events_enabled; /* Whether span events are enabled */ size_t span_events_max_samples_stored; /* The maximum number of span events per diff --git a/axiom/nr_version.c b/axiom/nr_version.c index bc4c7d6c4..0a376f0bd 100644 --- a/axiom/nr_version.c +++ b/axiom/nr_version.c @@ -23,7 +23,6 @@ /* * Current version naming scheme is gemstones * - * jasmine 08Mar2023 (10.7) * kalmia 27Mar2023 (10.8) * lilac 05Apr2023 (10.9) * marigold 30May2023 (10.10) @@ -50,8 +49,9 @@ * hiddenite 21Apr2025 (11.8) * indicolite 13May2025 (11.9) * jade 25Jun2025 (11.10) + * kernite 11Aug2025 (12.0) */ -#define NR_CODENAME "kernite" +#define NR_CODENAME "lizardite" const char* nr_version(void) { return NR_STR2(NR_VERSION); diff --git a/axiom/tests/test_txn.c b/axiom/tests/test_txn.c index 493213206..ab2c999ff 100644 --- a/axiom/tests/test_txn.c +++ b/axiom/tests/test_txn.c @@ -6299,6 +6299,181 @@ static void test_txn_accept_distributed_trace_payload_metrics(void) { nrm_table_destroy(&txn.unscoped_metrics); } +static void test_txn_accept_distributed_trace_payload_w3c_sample_flags(void) { + nrtxn_t txn = {0}; + nr_hashmap_t* headers; + bool rv = true; + char* TRACEPARENT_SAMPLED = "00-87b1c9a429205b25e5b687d890d4821f-7d3efb1b173fecfa-01"; + char* TRACEPARENT_NOT_SAMPLED = "00-87b1c9a429205b25e5b687d890d4821f-7d3efb1b173fecfa-00"; + char* TRACESTATE_SAMPLED = "123@nr=0-2-account-app-span-transaction-1-1.1273-1529445826000"; + char* TRACESTATE_NOT_SAMPLED = "123@nr=0-2-account-app-span-transaction-0-1.1273-1529445826000"; + nrtime_t payload_timestamp_ms = 1529445826000; + nrtime_t txn_timestamp_us = 15214458260000 * NR_TIME_DIVISOR_MS; + nrtime_t delta_timestamp_us = nr_time_duration( + (payload_timestamp_ms * NR_TIME_DIVISOR_MS), txn_timestamp_us); + + tlib_fail_if_int64_t_equal("Zero duration", 0, delta_timestamp_us); + + nr_memset(&txn, 0, sizeof(nrtxn_t)); + txn.app_connect_reply = nro_new_hash(); + txn.unscoped_metrics = nrm_table_create(0); + nro_set_hash_string(txn.app_connect_reply, "trusted_account_key", "123"); + // default value for padding can be used for this test, because other + // test (test_distributed_trace_create_trace_parent_header) already + // covers using trace_id of different lengths when w3c header is created. + txn.options.distributed_tracing_pad_trace_id = false; + txn.options.distributed_tracing_enabled = true; + +#define TEST_TXN_ACCEPT_DT_PAYLOAD_RESET \ + txn.distributed_trace->inbound.set = 0; \ + nrm_table_destroy(&txn.unscoped_metrics); \ + txn.unscoped_metrics = nrm_table_create(0); \ + txn.options.dt_sampler_parent_not_sampled = DEFAULT; \ + txn.options.dt_sampler_parent_sampled = DEFAULT; + + headers = nr_hashmap_create(NULL); + + /* + * Test : DT traceparent sampled flag INI settings + */ + // 1: upstream not sampled, agent discard + txn.options.dt_sampler_parent_not_sampled = ALWAYS_DROP; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_NOT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_false("Sampled should be set to false", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + // 2: upstream not sampled, agent keep + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_not_sampled = ALWAYS_KEEP; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_NOT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_NOT_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_true("Sampled should be set to true", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_double_equal("Priority should be set to max", 2.0, + txn.distributed_trace->priority); + + // 3: upstream not sampled, agent default (keep) + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_not_sampled = DEFAULT; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_NOT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_true("Sampled should be set to true", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + // 4: upstream not sampled, agent default (toss) + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_not_sampled = DEFAULT; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_NOT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_NOT_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_false("Sampled should be set to false", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + // 5: upstream sampled, agent discard + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_sampled = ALWAYS_DROP; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_false("Sampled should be set to false", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + // 6: upstream sampled, agent keep + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_sampled = ALWAYS_KEEP; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_NOT_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_true("Sampled should be set to true", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_double_equal("Priority should be set to max", 2.0, + txn.distributed_trace->priority); + + // 7: upstream sampled, agent default (keep) + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_sampled = DEFAULT; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_true("Sampled should be set to true", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + // 8: upstream sampled, agent default (toss) + TEST_TXN_ACCEPT_DT_PAYLOAD_RESET + txn.options.dt_sampler_parent_sampled = DEFAULT; + nr_distributed_trace_destroy(&txn.distributed_trace); + txn.distributed_trace = nr_distributed_trace_create(); + nr_hashmap_update(headers, NR_PSTR("traceparent"), TRACEPARENT_SAMPLED); + nr_hashmap_update(headers, NR_PSTR("tracestate"), TRACESTATE_NOT_SAMPLED); + rv = nr_txn_accept_distributed_trace_payload(&txn, headers, "HTTP"); + tlib_pass_if_true("The header should be accepted", rv, "Return value = %d", + (int)rv); + tlib_pass_if_false("Sampled should be set to false", + txn.distributed_trace->sampled, "sampled flag = %d", + (int)txn.distributed_trace->sampled); + tlib_pass_if_true("Priority should not be overwritten", + 2.0 != txn.distributed_trace->priority, + "priority is 2.0"); + + nr_txn_destroy_fields(&txn); + nr_hashmap_destroy(&headers); +#undef TEST_TXN_ACCEPT_DT_PAYLOAD_RESET +} + static void test_txn_accept_distributed_trace_payload_w3c(void) { nrtxn_t txn = {0}; nr_hashmap_t* headers; @@ -6780,6 +6955,7 @@ static void test_txn_accept_distributed_trace_payload_w3c(void) { nr_txn_destroy_fields(&txn); nr_hashmap_destroy(&headers); +#undef TEST_TXN_ACCEPT_DT_PAYLOAD_RESET } static void test_txn_accept_distributed_trace_payload_w3c_and_nr(void) { @@ -8912,6 +9088,7 @@ void test_main(void* p NRUNUSED) { test_create_w3c_traceparent_header(); test_create_w3c_tracestate_header(); test_txn_accept_distributed_trace_payload_w3c(); + test_txn_accept_distributed_trace_payload_w3c_sample_flags(); test_txn_accept_distributed_trace_payload_w3c_and_nr(); test_span_queue(); test_segment_record_error(); diff --git a/daemon/go.mod b/daemon/go.mod index a913dbd87..2548fbb62 100644 --- a/daemon/go.mod +++ b/daemon/go.mod @@ -1,7 +1,7 @@ module github.com/newrelic/newrelic-php-agent/daemon go 1.24.0 -toolchain go1.24.4 +toolchain go1.25.1 require ( github.com/golang/protobuf v1.5.3 diff --git a/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled.php b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled.php new file mode 100644 index 000000000..073195757 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled.php @@ -0,0 +1,76 @@ + "00-74be672b84ddc4e4b28be285632bbc0a-27ddd2d8890283b4-00", + 'tracestate' => "123@nr=0-0-1349956-41346604-27ddd2d8890283b4-b28be285632bbc0a-0-1.1273-1569367663277" +); + +newrelic_accept_distributed_trace_headers($payload); + +$outbound_headers = array('Accept-Language' => 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '01', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '1', 'tracestate sampled flag ok'); +tap_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled_non_newrelic.php b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled_non_newrelic.php new file mode 100644 index 000000000..f70d0ec10 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_not_sampled_non_newrelic.php @@ -0,0 +1,84 @@ + 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '01', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '1', 'tracestate sampled flag ok'); +tap_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled.php b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled.php new file mode 100644 index 000000000..9ca578f80 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled.php @@ -0,0 +1,78 @@ + "00-74be672b84ddc4e4b28be285632bbc0a-27ddd2d8890283b4-01", + 'tracestate' => "123@nr=0-0-1349956-41346604-27ddd2d8890283b4-b28be285632bbc0a-0-1.1273-1569367663277" +); + +newrelic_accept_distributed_trace_headers($payload); + +$outbound_headers = array('Accept-Language' => 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '01', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '1', 'tracestate sampled flag ok'); +tap_equal($tracestate[7], '2.000000', 'tracestate priority ok'); + diff --git a/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled_non_newrelic.php b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled_non_newrelic.php new file mode 100644 index 000000000..a5057abd5 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_keep_headers_sampled_non_newrelic.php @@ -0,0 +1,84 @@ + 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '01', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '1', 'tracestate sampled flag ok'); +tap_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled.php b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled.php new file mode 100644 index 000000000..7506e964d --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled.php @@ -0,0 +1,47 @@ + "00-74be672b84ddc4e4b28be285632bbc0a-27ddd2d8890283b4-00", + 'tracestate' => "123@nr=0-0-1349956-41346604-27ddd2d8890283b4-b28be285632bbc0a-1-1.1273-1569367663277" +); + +newrelic_accept_distributed_trace_headers($payload); + +$outbound_headers = array('Accept-Language' => 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '00', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '0', 'tracestate sampled flag ok'); +tap_not_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled_non_newrelic.php b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled_non_newrelic.php new file mode 100644 index 000000000..5f591c647 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_not_sampled_non_newrelic.php @@ -0,0 +1,46 @@ + 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '00', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '0', 'tracestate sampled flag ok'); +tap_not_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled.php b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled.php new file mode 100644 index 000000000..e77487fe5 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled.php @@ -0,0 +1,46 @@ + "00-74be672b84ddc4e4b28be285632bbc0a-27ddd2d8890283b4-01", + 'tracestate' => "123@nr=0-0-1349956-41346604-27ddd2d8890283b4-b28be285632bbc0a-1-1.1273-1569367663277" +); + +newrelic_accept_distributed_trace_headers($payload); + +$outbound_headers = array('Accept-Language' => 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '00', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '0', 'tracestate sampled flag ok'); +tap_not_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled_non_newrelic.php b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled_non_newrelic.php new file mode 100644 index 000000000..eaf95b690 --- /dev/null +++ b/tests/integration/distributed_tracing/w3c/test_force_toss_headers_sampled_non_newrelic.php @@ -0,0 +1,46 @@ + 'en-US,en;q=0.5'); +tap_assert(newrelic_insert_distributed_trace_headers($outbound_headers), 'insert function succeeded'); +$traceparent = explode('-', $outbound_headers['traceparent']); +$tracestate = explode('-', explode('=', $outbound_headers['tracestate'])[1]); + +tap_equal($traceparent[3], '00', 'traceparent sampled flag ok'); +tap_equal($tracestate[6], '0', 'tracestate sampled flag ok'); +tap_not_equal($tracestate[7], '2.000000', 'tracestate priority ok'); diff --git a/tests/integration/external/curl_multi_exec/test_txn_restart_ignore.php b/tests/integration/external/curl_multi_exec/test_txn_restart_ignore.php new file mode 100644 index 000000000..c7ada36a5 --- /dev/null +++ b/tests/integration/external/curl_multi_exec/test_txn_restart_ignore.php @@ -0,0 +1,96 @@ + 0); + + curl_close($ch1); + curl_close($ch2); + curl_multi_close($mh); + + tap_ok("end of function reached without crash", true); +} + +test_txn_restart(); diff --git a/tests/integration/external/guzzle7/test_txn_ignore.php b/tests/integration/external/guzzle7/test_txn_ignore.php new file mode 100644 index 000000000..169785545 --- /dev/null +++ b/tests/integration/external/guzzle7/test_txn_ignore.php @@ -0,0 +1,98 @@ + +get($url); + echo $response->getBody(); + + $response = $client->get($url, [ + 'headers' => [ + 'zip' => 'zap']]); + echo $response->getBody(); + + $response = $client->get($url, [ + 'headers' => [ + 'zip' => 'zap', + CUSTOMER_HEADER => 'zap']]); + echo $response->getBody(); +} + +run_test(); + +newrelic_end_transaction(true); +newrelic_start_transaction(ini_get("newrelic.appname")); + +run_test(); + +newrelic_end_transaction(); diff --git a/tests/integration/mysqli/test_ignore_txn.php b/tests/integration/mysqli/test_ignore_txn.php new file mode 100644 index 000000000..6081ca027 --- /dev/null +++ b/tests/integration/mysqli/test_ignore_txn.php @@ -0,0 +1,105 @@ +", + "?? SQL ID", + "SELECT TABLE_NAME FROM information_schema.tables WHERE table_name=?", + "Datastore/statement/MySQL/tables/select", + 1, + "?? total time", + "?? min time", + "?? max time", + { + "backtrace": [ + " in mysqli_stmt_execute called at __FILE__ (??)", + " in test_prepare called at __FILE__ (??)" + ] + } + ] + ] +] +*/ + +/*EXPECT_TRACED_ERRORS +null +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/config.php'); + +function test_prepare($link, $query, $types = null, $params = null) +{ + $stmt = mysqli_prepare($link, $query); + if (FALSE === $stmt) { + echo mysqli_error($link) . "\n"; + return; + } + + if ($types && $params) { + $args = array($stmt, $types); + foreach ($params as $param) { + $args[] = &$param; + } + call_user_func_array('mysqli_stmt_bind_param', $args); + } + + if (FALSE === mysqli_stmt_execute($stmt)) { + echo mysqli_stmt_error($stmt) . "\n"; + return; + } + + if (FALSE === mysqli_stmt_bind_result($stmt, $value)) { + echo mysqli_stmt_error($stmt) . "\n"; + return; + } + + while (mysqli_stmt_fetch($stmt)) { + echo $value . "\n"; + } + + mysqli_stmt_close($stmt); +} + +$link = new mysqli($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWD, $MYSQL_DB, $MYSQL_PORT, $MYSQL_SOCKET); +if (mysqli_connect_errno()) { + echo mysqli_connect_error() . "\n"; + exit(1); +} + +test_prepare($link, "SELECT TABLE_NAME FROM information_schema.tables WHERE table_name='STATISTICS' AND ? = ?", 'ii', array(1, 1)); + +newrelic_end_transaction(true); +newrelic_start_transaction(ini_get("newrelic.appname")); + +test_prepare($link, "SELECT TABLE_NAME FROM information_schema.tables WHERE table_name='STATISTICS'"); + +mysqli_close($link); +newrelic_end_transaction(); diff --git a/tests/integration/pdo/test_txn_ignore.php b/tests/integration/pdo/test_txn_ignore.php new file mode 100644 index 000000000..7e5a16cfc --- /dev/null +++ b/tests/integration/pdo/test_txn_ignore.php @@ -0,0 +1,102 @@ +", + "?? SQL id", + "select * from information_schema.tables where table_name = ? limit ?;", + "Datastore/statement/MySQL/tables/select", + 1, + "?? total time", + "?? min time", + "?? max time", + { + "backtrace": [ + " in PDOStatement::execute called at __FILE__ (??)", + " in test_prepared_statement called at __FILE__ (??)" + ], + "explain_plan": [ + [ + "id", + "select_type", + "table", + "type", + "possible_keys", + "key", + "key_len", + "ref", + "rows", + "Extra" + ], + [ + [ + 1, + "SIMPLE", + "tables", + "ALL", + null, + "TABLE_NAME", + null, + null, + "??", + "??" + ] + ] + ] + } + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/tap.php'); +require_once(realpath (dirname ( __FILE__ )) . '/pdo.inc'); + +function test_prepared_statement() { + global $PDO_MYSQL_DSN, $MYSQL_USER, $MYSQL_PASSWD; + + $conn = new PDO($PDO_MYSQL_DSN, $MYSQL_USER, $MYSQL_PASSWD); + $stmt = $conn->prepare('select * from information_schema.tables where table_name = ? limit 1;'); + $stmt->bindValue(1, "missing"); + tap_assert($stmt->execute(), 'execute prepared statement with a param'); +} + +test_prepared_statement(); + +newrelic_end_transaction(true); +newrelic_start_transaction(ini_get("newrelic.appname")); + +test_prepared_statement(); + +newrelic_end_transaction();